diff --git a/.changeset/lucky-tomatoes-admire.md b/.changeset/lucky-tomatoes-admire.md new file mode 100644 index 000000000..321b21777 --- /dev/null +++ b/.changeset/lucky-tomatoes-admire.md @@ -0,0 +1,5 @@ +--- +"@osdk/client": minor +--- + +OSDK Client no longer uses javascript proxies for its objects. This results in a 13% increase in construction time but at 1kb per object reduction in memory diff --git a/packages/client/src/fetchMetadata.test.ts b/packages/client/src/fetchMetadata.test.ts index e1c208e66..b9ea52e28 100644 --- a/packages/client/src/fetchMetadata.test.ts +++ b/packages/client/src/fetchMetadata.test.ts @@ -105,6 +105,13 @@ describe("FetchMetadata", () => { "primaryKeyApiName": "employeeId", "primaryKeyType": "integer", "properties": { + "class": { + "description": "", + "displayName": undefined, + "multiplicity": false, + "nullable": true, + "type": "string", + }, "employeeId": { "description": undefined, "displayName": undefined, diff --git a/packages/client/src/object/convertWireToOsdkObjects.test.ts b/packages/client/src/object/convertWireToOsdkObjects.test.ts index aab19bd9b..5834d28a1 100644 --- a/packages/client/src/object/convertWireToOsdkObjects.test.ts +++ b/packages/client/src/object/convertWireToOsdkObjects.test.ts @@ -64,6 +64,7 @@ describe("convertWireToOsdkObjects", () => { expect(Object.keys(employee).sort()).toEqual([ "employeeId", + "$title", "fullName", "office", "class", @@ -227,128 +228,6 @@ describe("convertWireToOsdkObjects", () => { expect(prototypeBefore).not.toBe(prototypeAfter); }); - it("updates interface when underlying changes - old", async () => { - const clientCtx = createMinimalClient( - { ontologyRid: $ontologyRid }, - "https://stack.palantir.com", - async () => "myAccessToken", - ); - - let objectFromWire = { - __apiName: "Employee" as const, - __primaryKey: 0, - __title: "Steve", - fullName: "Steve", - employeeId: "5", - } satisfies OntologyObjectV2; - - const [obj] = (await convertWireToOsdkObjects( - clientCtx, - [objectFromWire], - undefined, - )) as unknown as Osdk[]; - - expect(obj.fullName).toEqual("Steve"); - expect(Object.keys(obj).sort()).toEqual([ - "$apiName", - "$objectType", - "$primaryKey", - "$title", - "employeeId", - "fullName", - ].sort()); - - const objAsFoo = obj.$as(FooInterface); - expect(objAsFoo).toMatchObject({ - fooSpt: obj.fullName, - $apiName: FooInterface.apiName, - $primaryKey: obj.$primaryKey, - $objectType: obj.$objectType, - $title: obj.$title, - }); - - console.log(obj); - console.log(objAsFoo); - - (obj as any).$updateInternalValues({ - fullName: "Bob", - }); - expect(obj.fullName).toEqual("Bob"); - expect(objAsFoo.fooSpt).toEqual(obj.fullName); - - expect(Object.keys(objAsFoo).sort()).toEqual([ - "$apiName", - "$objectType", - "$primaryKey", - "$title", - "fooSpt", - ].sort()); - - expect(obj).toBe(objAsFoo.$as(Employee)); - expect(objAsFoo).toBe(obj.$as(FooInterface)); - }); - - it("updates interface when underlying changes - new", async () => { - const clientCtx = createMinimalClient( - { ontologyRid: $ontologyRid }, - "https://stack.palantir.com", - async () => "myAccessToken", - ); - - let objectFromWire = { - __apiName: "Employee" as const, - __primaryKey: 0, - __title: "Steve", - fullName: "Steve", - employeeId: "5", - } satisfies OntologyObjectV2; - - const [obj] = (await convertWireToOsdkObjects2( - clientCtx, - [objectFromWire], - undefined, - )) as unknown as Osdk[]; - - expect(obj.fullName).toEqual("Steve"); - expect(Object.keys(obj).sort()).toEqual([ - "$apiName", - "$objectType", - "$primaryKey", - "$title", - "employeeId", - "fullName", - ].sort()); - - const objAsFoo = obj.$as(FooInterface); - expect(objAsFoo).toMatchObject({ - fooSpt: obj.fullName, - $apiName: FooInterface.apiName, - $primaryKey: obj.$primaryKey, - $objectType: obj.$objectType, - $title: obj.$title, - }); - - console.log(obj); - console.log(objAsFoo); - - (obj as any).$updateInternalValues({ - fullName: "Bob", - }); - expect(obj.fullName).toEqual("Bob"); - expect(objAsFoo.fooSpt).toEqual(obj.fullName); - - expect(Object.keys(objAsFoo).sort()).toEqual([ - "$apiName", - "$objectType", - "$primaryKey", - "$title", - "fooSpt", - ].sort()); - - expect(obj).toBe(objAsFoo.$as(Employee)); - expect(objAsFoo).toBe(obj.$as(FooInterface)); - }); - it("reconstitutes interfaces properly without rid - old", async () => { const clientCtx = createMinimalClient( { ontologyRid: $ontologyRid }, diff --git a/packages/client/src/object/convertWireToOsdkObjects/InterfaceHolder.ts b/packages/client/src/object/convertWireToOsdkObjects/InterfaceHolder.ts index 3dbe22a68..7cd325c6e 100644 --- a/packages/client/src/object/convertWireToOsdkObjects/InterfaceHolder.ts +++ b/packages/client/src/object/convertWireToOsdkObjects/InterfaceHolder.ts @@ -23,15 +23,9 @@ import type { import type { ObjectHolder } from "./ObjectHolder.js"; /** @internal */ -export interface InterfaceHolderOwnProps< +export interface InterfaceHolder< Q extends FetchedObjectTypeDefinition, > { [UnderlyingOsdkObject]: Osdk & ObjectHolder; [InterfaceDefRef]: InterfaceMetadata; } - -/** @internal */ -export interface InterfaceHolder< - Q extends FetchedObjectTypeDefinition, -> extends InterfaceHolderOwnProps { -} diff --git a/packages/client/src/object/convertWireToOsdkObjects/InternalSymbols.ts b/packages/client/src/object/convertWireToOsdkObjects/InternalSymbols.ts index aa671ec68..d9045fe37 100644 --- a/packages/client/src/object/convertWireToOsdkObjects/InternalSymbols.ts +++ b/packages/client/src/object/convertWireToOsdkObjects/InternalSymbols.ts @@ -31,22 +31,11 @@ export const InterfaceDefRef = Symbol( process.env.MODE !== "production" ? "InterfaceDefinition" : undefined, ); -/** A symbol for getting the raw data object off of the holder an osdk object proxy points to */ -/** @internal */ -export const RawObject = Symbol( - process.env.MODE !== "production" ? "RawObject" : undefined, -); - /** @internal */ export const ClientRef = Symbol( process.env.MODE !== "production" ? "ClientRef" : undefined, ); -/** @internal */ -export const CachedPropertiesRef = Symbol( - process.env.MODE !== "production" ? "CachedProperties" : undefined, -); - export interface HolderBase { [UnderlyingOsdkObject]: OsdkBase; [ObjectDefRef]?: T; diff --git a/packages/client/src/object/convertWireToOsdkObjects/ObjectHolder.ts b/packages/client/src/object/convertWireToOsdkObjects/ObjectHolder.ts index f29d43cbe..518f56a51 100644 --- a/packages/client/src/object/convertWireToOsdkObjects/ObjectHolder.ts +++ b/packages/client/src/object/convertWireToOsdkObjects/ObjectHolder.ts @@ -15,36 +15,21 @@ */ import type { Osdk } from "@osdk/api"; -import type { OntologyObjectV2 } from "@osdk/internal.foundry.core"; import type { MinimalClient } from "../../MinimalClientContext.js"; import type { FetchedObjectTypeDefinition } from "../../ontology/OntologyProvider.js"; import type { DollarAsFn } from "./getDollarAs.js"; import type { get$link } from "./getDollarLink.js"; import type { - CachedPropertiesRef, ClientRef, ObjectDefRef, - RawObject, UnderlyingOsdkObject, } from "./InternalSymbols.js"; /** @internal */ -export interface ObjectHolderPrototypeOwnProps { +export interface ObjectHolder { + readonly [UnderlyingOsdkObject]: Osdk; readonly [ObjectDefRef]: FetchedObjectTypeDefinition; readonly [ClientRef]: MinimalClient; readonly "$as": DollarAsFn; readonly "$link": ReturnType; - readonly "$updateInternalValues": (newValues: Record) => void; -} -/** @internal */ -export interface ObjectHolderOwnProperties { - [RawObject]: OntologyObjectV2; - [CachedPropertiesRef]: Map; -} - -/** @internal */ -export interface ObjectHolder - extends ObjectHolderPrototypeOwnProps, ObjectHolderOwnProperties -{ - [UnderlyingOsdkObject]: Osdk; } diff --git a/packages/client/src/object/convertWireToOsdkObjects/createOsdkInterface.test.ts b/packages/client/src/object/convertWireToOsdkObjects/createOsdkInterface.test.ts index 14ae7c362..df6ed28db 100644 --- a/packages/client/src/object/convertWireToOsdkObjects/createOsdkInterface.test.ts +++ b/packages/client/src/object/convertWireToOsdkObjects/createOsdkInterface.test.ts @@ -52,8 +52,7 @@ describe(createOsdkInterface, () => { status: "ACTIVE", } satisfies FetchedObjectTypeDefinition, }; - // underlying: Osdk & ObjectHolder, - // interfaceDef: InterfaceMetadata, + const iface = createOsdkInterface(underlying as any, { "apiName": "IFoo", displayName: "", @@ -114,8 +113,7 @@ describe(createOsdkInterface, () => { status: "ACTIVE", } satisfies FetchedObjectTypeDefinition, }; - // underlying: Osdk & ObjectHolder, - // interfaceDef: InterfaceMetadata, + const iface = createOsdkInterface(underlying as any, { "apiName": "a.IFoo", displayName: "", @@ -176,8 +174,7 @@ describe(createOsdkInterface, () => { status: "ACTIVE", } satisfies FetchedObjectTypeDefinition, }; - // underlying: Osdk & ObjectHolder, - // interfaceDef: InterfaceMetadata, + const iface = createOsdkInterface(underlying as any, { "apiName": "a.IFoo", displayName: "", diff --git a/packages/client/src/object/convertWireToOsdkObjects/createOsdkInterface.ts b/packages/client/src/object/convertWireToOsdkObjects/createOsdkInterface.ts index d7f2aa54b..dfbac30ef 100644 --- a/packages/client/src/object/convertWireToOsdkObjects/createOsdkInterface.ts +++ b/packages/client/src/object/convertWireToOsdkObjects/createOsdkInterface.ts @@ -14,14 +14,10 @@ * limitations under the License. */ -import type { InterfaceMetadata, Osdk, OsdkBase } from "@osdk/api"; +import type { InterfaceMetadata, Osdk } from "@osdk/api"; import { extractNamespace } from "../../internal/conversions/modernToLegacyWhereClause.js"; import type { FetchedObjectTypeDefinition } from "../../ontology/OntologyProvider.js"; -import { createSimpleCache } from "../SimpleCache.js"; -import type { - InterfaceHolder, - InterfaceHolderOwnProps, -} from "./InterfaceHolder.js"; +import type { InterfaceHolder } from "./InterfaceHolder.js"; import { InterfaceDefRef, ObjectDefRef, @@ -29,14 +25,6 @@ import { } from "./InternalSymbols.js"; import type { ObjectHolder } from "./ObjectHolder.js"; -const handlerCache = createSimpleCache< - InterfaceMetadata, - ProxyHandler & Osdk> ->( - new WeakMap(), - createInterfaceProxyHandler, -); - /** @internal */ export function createOsdkInterface< Q extends FetchedObjectTypeDefinition, @@ -44,121 +32,52 @@ export function createOsdkInterface< underlying: Osdk & ObjectHolder, interfaceDef: InterfaceMetadata, ) { - const interfaceHolder: InterfaceHolderOwnProps = { - [UnderlyingOsdkObject]: underlying, - [InterfaceDefRef]: interfaceDef, - }; - - const handler = handlerCache.get(interfaceDef); - - const proxy = new Proxy>( - interfaceHolder as unknown as OsdkBase, // the wrapper doesn't contain everything obviously. we proxy - handler, + const [objApiNamespace] = extractNamespace(interfaceDef.apiName); + + return Object.freeze( + Object.defineProperties({}, { + // first to minimize hidden classes + [UnderlyingOsdkObject]: { value: underlying }, + + "$apiName": { value: interfaceDef.apiName, enumerable: true }, + "$as": { + value: underlying.$as, + enumerable: false, + }, + "$objectType": { + value: underlying.$objectType, + enumerable: "$objectType" in underlying, + }, + "$primaryKey": { + value: underlying.$primaryKey, + enumerable: "$primaryKey" in underlying, + }, + "$title": { + value: underlying.$title, + enumerable: "$title" in underlying, + }, + "$rid": { + value: (underlying as any).$rid, + enumerable: "$rid" in underlying, + }, + + [InterfaceDefRef]: { value: interfaceDef }, + + ...Object.fromEntries( + Object.keys(interfaceDef.properties).map(p => { + const objDef = underlying[ObjectDefRef]; + + const [apiNamespace, apiName] = extractNamespace(p); + + const targetPropName = objDef + .interfaceMap![interfaceDef.apiName][p]; + + return [apiNamespace === objApiNamespace ? apiName : p, { + enumerable: targetPropName in underlying, + value: underlying[targetPropName as keyof typeof underlying], + }]; + }), + ), + }) as InterfaceHolder & Osdk, ); - return proxy; -} - -function createInterfaceProxyHandler( - newDef: InterfaceMetadata, -): ProxyHandler & Osdk> { - return { - getOwnPropertyDescriptor(target, p) { - const underlying = target[UnderlyingOsdkObject]; - const objDef = underlying[ObjectDefRef]; - - switch (p) { - case "$primaryKey": - case "$title": - case "$objectType": - case "$rid": - return underlying[p] != null - ? Reflect.getOwnPropertyDescriptor(underlying, p) - : undefined; - - case "$apiName": - return { - enumerable: true, - configurable: true, - value: target[InterfaceDefRef].apiName, - }; - } - - const [objApiNamespace] = extractNamespace(newDef.apiName); - if (objApiNamespace != null) { - const [apiNamespace, apiName] = extractNamespace(p as string); - if (apiNamespace == null) { - p = `${objApiNamespace}.${apiName}`; - } - } - - if (newDef.properties[p as string] != null) { - return { - enumerable: true, - configurable: true, - value: underlying[ - objDef.interfaceMap![newDef.apiName][p as string] as any - ], - }; - } - }, - - ownKeys(target) { - const underlying = target[UnderlyingOsdkObject]; - const [objApiNamespace] = extractNamespace(newDef.apiName); - let propNames = Object.keys(newDef.properties); - - if (objApiNamespace != null) { - propNames = propNames.map(p => { - const [apiNamespace, apiName] = extractNamespace(p as string); - if (apiNamespace === objApiNamespace) { - p = apiName; - } - return p; - }); - } - - return [ - "$apiName", - "$objectType", - "$primaryKey", - ...(underlying["$rid"] ? ["$rid"] : []), - "$title", - ...propNames, - ]; - }, - - set() { - return false; - }, - - get(target, p) { - const underlying = target[UnderlyingOsdkObject]; - switch (p) { - case InterfaceDefRef: - return newDef; - case "$apiName": - return newDef.apiName; - case "$as": - case UnderlyingOsdkObject: - case "$primaryKey": - case "$title": - case "$objectType": - case "$rid": - return underlying[p as string]; - } - - const [objApiNamespace] = extractNamespace(newDef.apiName); - if (objApiNamespace != null) { - const [apiNamespace, apiName] = extractNamespace(p as string); - if (apiNamespace == null) { - p = `${objApiNamespace}.${apiName}`; - } - } - - if (newDef.properties[p as string] != null) { - const objDef = target[UnderlyingOsdkObject][ObjectDefRef]; - return underlying[objDef.interfaceMap![newDef.apiName][p as string]]; - } - }, - }; } diff --git a/packages/client/src/object/convertWireToOsdkObjects/createOsdkObject.ts b/packages/client/src/object/convertWireToOsdkObjects/createOsdkObject.ts index f486f8d10..d585f292d 100644 --- a/packages/client/src/object/convertWireToOsdkObjects/createOsdkObject.ts +++ b/packages/client/src/object/convertWireToOsdkObjects/createOsdkObject.ts @@ -14,123 +14,52 @@ * limitations under the License. */ -import type { - GeotimeSeriesProperty, - ObjectTypeDefinition, - Osdk, - TimeSeriesPoint, -} from "@osdk/api"; +import type { ObjectTypeDefinition, Osdk } from "@osdk/api"; import type { OntologyObjectV2 } from "@osdk/internal.foundry.core"; -import { OntologiesV2 } from "@osdk/internal.foundry.ontologiesv2"; +import invariant from "tiny-invariant"; import { createAttachmentFromRid } from "../../createAttachmentFromRid.js"; import { GeotimeSeriesPropertyImpl } from "../../createGeotimeSeriesProperty.js"; import { TimeSeriesPropertyImpl } from "../../createTimeseriesProperty.js"; import type { MinimalClient } from "../../MinimalClientContext.js"; import type { FetchedObjectTypeDefinition } from "../../ontology/OntologyProvider.js"; -import { createClientCache } from "../Cache.js"; import { get$as } from "./getDollarAs.js"; import { get$link } from "./getDollarLink.js"; import { - CachedPropertiesRef, ClientRef, ObjectDefRef, - RawObject, UnderlyingOsdkObject, } from "./InternalSymbols.js"; -import type { - ObjectHolder, - ObjectHolderPrototypeOwnProps, -} from "./ObjectHolder.js"; -import type { PropertyDescriptorRecord } from "./PropertyDescriptorRecord.js"; +import type { ObjectHolder } from "./ObjectHolder.js"; -const objectPrototypeCache = createClientCache( - function(client, objectDef: FetchedObjectTypeDefinition) { - return Object.create( - null, - { - [ObjectDefRef]: { value: objectDef }, - [ClientRef]: { value: client }, - "$as": { value: get$as(objectDef) }, - "$link": { - get: function(this: ObjectHolder) { - return get$link(this); - }, - }, - "$updateInternalValues": { - value: function( - this: ObjectHolder, - newValues: Record, - ) { - this[RawObject] = Object.assign( - {}, - this[RawObject], - newValues, - ); - }, - }, - } satisfies PropertyDescriptorRecord, - ); - }, -); - -if (process.env.NODE_ENV !== "production") { - const installed = Symbol(); - const gw: any = typeof window === "undefined" ? global : window; - if (!(installed in gw)) { - gw[installed] = true; - - gw.devtoolsFormatters ??= []; - gw.devtoolsFormatters.push({ - header: function(object: any) { - const raw = object[RawObject]; - if (raw == null) return null; - - return [ - "div", - {}, +interface InternalOsdkInstance { + [ObjectDefRef]: FetchedObjectTypeDefinition; + [ClientRef]: MinimalClient; +} - `Osdk.Instance<${raw.$apiName}> { $primaryKey:`, - ["object", { object: raw.$primaryKey }], - `, $title:`, - ["object", { object: raw.$title }], - `, ... }`, - ]; - }, - hasBody: function(object: any) { - return object[RawObject] != null; - }, - body: function(object: any) { - const raw = object[RawObject]; - if (raw == null) return null; +const specialPropertyTypes = new Set( + [ + "attachment", + "geotimeSeriesReference", + "numericTimeseries", + "stringTimeseries", + "sensorTimeseries", + ], +); - return [ - "ol", - { - style: ` - list-style-type: none; - padding-left: 0; - margin-top: 0; - margin-left: 18px - `, - }, - ["li", {}, "$raw:", ["object", { object: raw }]], - // ...Object.keys(raw).map(key => { - // return [ - // "li", - // {}, - // [ - // "span", - // { style: "color: #888888" }, - // `${key}: `, - // ], - // ["object", { object: raw[key] }], // ["span", {}, `${key}: `]], - // ]; - // }), - ]; - }, - }); - } -} +// kept separate so we are not redefining these functions +// every time an object is created. +const basePropDefs = { + "$as": { + get: function(this: InternalOsdkInstance) { + return get$as(this[ObjectDefRef]); + }, + }, + "$link": { + get: function(this: InternalOsdkInstance & ObjectHolder) { + return get$link(this); + }, + }, +}; /** @internal */ export function createOsdkObject< @@ -140,50 +69,57 @@ export function createOsdkObject< objectDef: Q, rawObj: OntologyObjectV2, ): Osdk { - // We use multiple layers of prototypes to maximize reuse and also to keep - // [RawObject] out of `ownKeys`. This keeps the code in the proxy below simpler. - const objectHolderPrototype = Object.create( - objectPrototypeCache.get(client, objectDef), - { - [RawObject]: { - value: rawObj, - writable: true, // so we can allow updates - }, - [CachedPropertiesRef]: { - value: new Map(), - writable: true, - }, + // updates the object's "hidden class/map". + Object.defineProperties(rawObj, { + [UnderlyingOsdkObject]: { + enumerable: false, + value: rawObj, }, - ); - - // we separate the holder out so we can update - // the underlying data without having to return a new object - // we also need the holder so we can customize the console.log output - const holder: ObjectHolder = Object.create(objectHolderPrototype); + [ObjectDefRef]: { value: objectDef, enumerable: false }, + [ClientRef]: { value: client, enumerable: false }, + ...basePropDefs, + }); - const osdkObject: any = new Proxy(holder, { - ownKeys(target) { - return Reflect.ownKeys(target[RawObject]); - }, - get(target, p, receiver) { - switch (p) { - case UnderlyingOsdkObject: - // effectively point back to the proxy - return receiver; - } + // Assign the special values + for (const propKey of Object.keys(rawObj)) { + if ( + propKey in objectDef.properties + && specialPropertyTypes.has(objectDef.properties[propKey].type) + ) { + rawObj[propKey] = createSpecialProperty( + client, + objectDef, + rawObj as any, + propKey, + ); + } + } - if (p in target) return target[p as keyof typeof target]; + return Object.freeze(rawObj) as Osdk; +} - if (p in rawObj) { - const rawValue = target[RawObject][p as any]; - const propDef = objectDef.properties[p as any]; - if (propDef) { +function createSpecialProperty( + client: MinimalClient, + objectDef: FetchedObjectTypeDefinition, + rawObject: Osdk.Instance, + p: keyof typeof rawObject & string | symbol, +) { + const rawValue = rawObject[p as any]; + const propDef = objectDef.properties[p as any]; + if (process.env.NODE_ENV !== "production") { + invariant(propDef != null && specialPropertyTypes.has(propDef.type)); + } + { + { + { + { if (propDef.type === "attachment") { if (Array.isArray(rawValue)) { return rawValue.map(a => createAttachmentFromRid(client, a.rid)); } return createAttachmentFromRid(client, rawValue.rid); } + if ( propDef.type === "numericTimeseries" || propDef.type === "stringTimeseries" @@ -196,19 +132,16 @@ export function createOsdkObject< >( client, objectDef.apiName, - target[RawObject][objectDef.primaryKeyApiName as string], + rawObject[objectDef.primaryKeyApiName as string], p as string, ); } + if (propDef.type === "geotimeSeriesReference") { - const instance = target[CachedPropertiesRef].get(p); - if (instance != null) { - return instance; - } - const geotimeProp = new GeotimeSeriesPropertyImpl( + return new GeotimeSeriesPropertyImpl( client, objectDef.apiName, - target[RawObject][objectDef.primaryKeyApiName as string], + rawObject[objectDef.primaryKeyApiName as string], p as string, rawValue.type === "geotimeSeriesValue" ? { @@ -220,36 +153,9 @@ export function createOsdkObject< } : undefined, ); - target[CachedPropertiesRef].set(p, geotimeProp); - return geotimeProp; } } - return rawValue; } - - // we do not do any fall through to avoid unexpected behavior - }, - - set(target, p, newValue) { - // allow the prototype to update this value - if (p === RawObject) { - // symbol only exists internally so no one else can hit this - target[p as typeof RawObject] = newValue; - return true; - } - return false; - }, - - getOwnPropertyDescriptor(target, p) { - if (p === RawObject) { - return Reflect.getOwnPropertyDescriptor(target, p); - } - - if (target[RawObject][p as string] != null) { - return { configurable: true, enumerable: true }; - } - return undefined; - }, - }); - return osdkObject; + } + } } diff --git a/packages/client/src/object/convertWireToOsdkObjects/getDollarLink.ts b/packages/client/src/object/convertWireToOsdkObjects/getDollarLink.ts index 703d25216..c20f15184 100644 --- a/packages/client/src/object/convertWireToOsdkObjects/getDollarLink.ts +++ b/packages/client/src/object/convertWireToOsdkObjects/getDollarLink.ts @@ -23,77 +23,52 @@ import type { } from "@osdk/api"; import { getWireObjectSet } from "../../objectSet/createObjectSet.js"; import { fetchSingle, fetchSingleWithErrors } from "../fetchSingle.js"; -import { ClientRef, ObjectDefRef, RawObject } from "./InternalSymbols.js"; -import type { - ObjectHolder, - ObjectHolderOwnProperties, -} from "./ObjectHolder.js"; +import { + ClientRef, + ObjectDefRef, + UnderlyingOsdkObject, +} from "./InternalSymbols.js"; +import type { ObjectHolder } from "./ObjectHolder.js"; /** @internal */ export function get$link( holder: ObjectHolder, ): OsdkObjectLinksObject { - return new Proxy(holder, DollarLinkProxyHandler) as - & ObjectHolder - & OsdkObjectLinksObject; -} - -const DollarLinkProxyHandler: ProxyHandler> = { - get(target: ObjectHolder, p: string | symbol) { - const { - [ObjectDefRef]: objDef, - [ClientRef]: client, - [RawObject]: rawObj, - } = target; - const linkDef = objDef.links[p as string]; - if (linkDef == null) { - return; - } - const objectSet = - (client.objectSetFactory(objDef, client) as ObjectSet) - .where({ - [objDef.primaryKeyApiName]: rawObj.$primaryKey, - } as WhereClause) - .pivotTo(p as string); + const client = holder[ClientRef]; + const objDef = holder[ObjectDefRef]; + const rawObj = holder[UnderlyingOsdkObject]; + return Object.freeze(Object.fromEntries( + Object.keys(objDef.links).map( + (linkName) => { + const linkDef = objDef.links[linkName as keyof typeof objDef.links]; + const objectSet = + (client.objectSetFactory(objDef, client) as ObjectSet) + .where({ + [objDef.primaryKeyApiName]: rawObj.$primaryKey, + } as WhereClause) + .pivotTo(linkName); - if (!linkDef.multiplicity) { - return { - fetchOne: >(options?: A) => - fetchSingle( - client, - objDef, - options ?? {}, - getWireObjectSet(objectSet), - ), - fetchOneWithErrors: >(options?: A) => - fetchSingleWithErrors( - client, - objDef, - options ?? {}, - getWireObjectSet(objectSet), - ), - }; - } else { - return objectSet; - } - }, + const value = !linkDef.multiplicity + ? { + fetchOne: >(options?: A) => + fetchSingle( + client, + objDef, + options ?? {}, + getWireObjectSet(objectSet), + ), + fetchOneWithErrors: >(options?: A) => + fetchSingleWithErrors( + client, + objDef, + options ?? {}, + getWireObjectSet(objectSet), + ), + } + : objectSet; - ownKeys( - target: ObjectHolder, - ): ArrayLike { - return [...Object.keys(target[ObjectDefRef].links)]; - }, - - getOwnPropertyDescriptor( - target: ObjectHolder, - p: string | symbol, - ): PropertyDescriptor | undefined { - if (target[ObjectDefRef].links[p as any]) { - return { - enumerable: true, - configurable: true, // fixme - writable: false, - }; - } - }, -}; + return [linkName, value]; + }, + ), + )); +} diff --git a/packages/client/src/objectSet/ObjectSet.test.ts b/packages/client/src/objectSet/ObjectSet.test.ts index becabb943..14841b517 100644 --- a/packages/client/src/objectSet/ObjectSet.test.ts +++ b/packages/client/src/objectSet/ObjectSet.test.ts @@ -524,6 +524,7 @@ describe("ObjectSet", () => { expectTypeOf>() .toEqualTypeOf< + | "class" | "fullName" | "office" | "employeeId" diff --git a/packages/client/src/objectSet/ObjectSetListenerWebsocket.test.ts b/packages/client/src/objectSet/ObjectSetListenerWebsocket.test.ts index 2f6f99589..ff46c9e9f 100644 --- a/packages/client/src/objectSet/ObjectSetListenerWebsocket.test.ts +++ b/packages/client/src/objectSet/ObjectSetListenerWebsocket.test.ts @@ -219,6 +219,7 @@ describe("ObjectSetListenerWebsocket", async () => { "employeeId", "fullName", "office", + "class", "startDate", "employeeStatus", "employeeSensor", @@ -278,6 +279,8 @@ describe("ObjectSetListenerWebsocket", async () => { "object": { "$apiName": "Employee", "$objectType": "Employee", + "$primaryKey": undefined, + "$title": undefined, "employeeId": 1, }, "state": "ADDED_OR_UPDATED", @@ -297,6 +300,7 @@ describe("ObjectSetListenerWebsocket", async () => { "$apiName": "Employee", "$objectType": "Employee", "$primaryKey": "12345", + "$title": undefined, "employeeId": "12345", "employeeLocation": GeotimeSeriesPropertyImpl { "lastFetchedValue": { diff --git a/packages/foundry-sdk-generator/src/ontologyMetadata/__snapshots__/metadataResolver.test.ts.snap b/packages/foundry-sdk-generator/src/ontologyMetadata/__snapshots__/metadataResolver.test.ts.snap index da653a3b8..437e79a80 100644 --- a/packages/foundry-sdk-generator/src/ontologyMetadata/__snapshots__/metadataResolver.test.ts.snap +++ b/packages/foundry-sdk-generator/src/ontologyMetadata/__snapshots__/metadataResolver.test.ts.snap @@ -84,6 +84,13 @@ exports[`Load Ontologies Metadata > Loads object and action types using only spe "pluralDisplayName": "Employees", "primaryKey": "employeeId", "properties": { + "class": { + "dataType": { + "type": "string", + }, + "description": "", + "rid": "rid", + }, "employeeId": { "dataType": { "type": "integer", @@ -274,6 +281,13 @@ exports[`Load Ontologies Metadata > Loads object and action types without link t "pluralDisplayName": "Employees", "primaryKey": "employeeId", "properties": { + "class": { + "dataType": { + "type": "string", + }, + "description": "", + "rid": "rid", + }, "employeeId": { "dataType": { "type": "integer", diff --git a/packages/shared.test/src/stubs/objectTypeV2.ts b/packages/shared.test/src/stubs/objectTypeV2.ts index d4d5880ec..b0cd7480f 100644 --- a/packages/shared.test/src/stubs/objectTypeV2.ts +++ b/packages/shared.test/src/stubs/objectTypeV2.ts @@ -46,6 +46,13 @@ export const employeeObjectType: ObjectTypeV2 = { }, rid: "rid", }, + class: { + description: "", + dataType: { + type: "string", + }, + rid: "rid", + }, startDate: { description: "The date the employee was hired (most recently, if they were re-hired)",