Skip to content

Commit

Permalink
Makes properties optional on Subscriptions (#958)
Browse files Browse the repository at this point in the history
* Fix subscriptions properties

* Rename and changeset

* Update for new API

* Update export

* Fix build

* Update

* Fixed TS Doc
  • Loading branch information
nihalbhatnagar authored Nov 18, 2024
1 parent bff7366 commit 11a05cc
Show file tree
Hide file tree
Showing 13 changed files with 101 additions and 63 deletions.
6 changes: 6 additions & 0 deletions .changeset/dull-cherries-pay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@osdk/client": patch
"@osdk/api": patch
---

Updated subscribe api to make requesting properties optional
20 changes: 16 additions & 4 deletions etc/api.report.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -582,16 +582,28 @@ export interface ObjectSet<Q extends ObjectOrInterfaceDefinition = any, _UNUSED
readonly fetchOneWithErrors: Q extends ObjectTypeDefinition ? <L extends PropertyKeys<Q>, R extends boolean, S extends false | "throw" = NullabilityAdherence.Default>(primaryKey: PrimaryKeyType<Q>, options?: SelectArg<Q, L, R, S>) => Promise<Result<Osdk.Instance<Q, ExtractOptions<R, S>, L>>> : never;
readonly intersect: (...objectSets: ReadonlyArray<CompileTimeMetadata<Q>["objectSet"]>) => this;
readonly pivotTo: <L extends LinkNames<Q>>(type: L) => CompileTimeMetadata<LinkedType<Q, L>>["objectSet"];
// Warning: (ae-forgotten-export) The symbol "EXPERIMENTAL_ObjectSetListener" needs to be exported by the entry point index.d.ts
//
// (undocumented)
readonly subscribe: <const P extends PropertyKeys<Q>>(properties: Array<P>, listener: EXPERIMENTAL_ObjectSetListener<Q, P>) => {
readonly subscribe: <const P extends PropertyKeys<Q>>(listener: ObjectSetListener<Q, P>, opts?: ObjectSetListenerOptions<Q, P>) => {
unsubscribe: () => void;
};
readonly subtract: (...objectSets: ReadonlyArray<CompileTimeMetadata<Q>["objectSet"]>) => this;
readonly union: (...objectSets: ReadonlyArray<CompileTimeMetadata<Q>["objectSet"]>) => this;
}

// @public (undocumented)
export interface ObjectSetListener<O extends ObjectOrInterfaceDefinition, P extends PropertyKeys<O> = PropertyKeys<O>> {
// Warning: (ae-forgotten-export) The symbol "ObjectUpdate" needs to be exported by the entry point index.d.ts
onChange?: (objectUpdate: ObjectUpdate<O, P>) => void;
onError?: (errors: Array<any>) => void;
onOutOfDate?: () => void;
onSuccessfulSubscription?: () => void;
}

// @public
export interface ObjectSetListenerOptions<O extends ObjectOrInterfaceDefinition, P extends PropertyKeys<O> = PropertyKeys<O>> {
// (undocumented)
properties?: Array<P>;
}

// @public (undocumented)
export interface ObjectSetQueryDataType<T_Target extends ObjectTypeDefinition = never> extends BaseQueryDataTypeDefinition<"objectSet"> {
// (undocumented)
Expand Down
9 changes: 6 additions & 3 deletions packages/api/src/experimental/subscribe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,23 @@
*/

import type { ObjectSet } from "../objectSet/ObjectSet.js";
import type {
ObjectSetListener,
ObjectSetListenerOptions,
} from "../objectSet/ObjectSetListener.js";
import type {
ObjectOrInterfaceDefinition,
PropertyKeys,
} from "../ontology/ObjectOrInterface.js";
import type { EXPERIMENTAL_ObjectSetListener } from "../public/unstable.js";
import type { Experiment } from "./Experiment.js";

type subscribeFn = <
Q extends ObjectOrInterfaceDefinition,
const P extends PropertyKeys<Q>,
>(
objectSet: ObjectSet<Q>,
properties: Array<P>,
listener: EXPERIMENTAL_ObjectSetListener<Q, P>,
listener: ObjectSetListener<Q, P>,
opts?: ObjectSetListenerOptions<Q, P>,
) => { unsubscribe: () => void };

export const __EXPERIMENTAL__NOT_SUPPORTED_YET_subscribe: Experiment<
Expand Down
2 changes: 2 additions & 0 deletions packages/api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ export { isOk } from "./object/Result.js";
export type { Result } from "./object/Result.js";
export type { BaseObjectSet } from "./objectSet/BaseObjectSet.js";
export type { ObjectSet } from "./objectSet/ObjectSet.js";
export type { ObjectSetListener } from "./objectSet/ObjectSetListener.js";
export type { ObjectSetListenerOptions } from "./objectSet/ObjectSetListener.js";
export type {
ActionDefinition,
ActionMetadata,
Expand Down
15 changes: 12 additions & 3 deletions packages/api/src/objectSet/ObjectSet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@ import type { ExtractOptions, Osdk } from "../OsdkObjectFrom.js";
import type { PageResult } from "../PageResult.js";
import type { LinkedType, LinkNames } from "../util/LinkUtils.js";
import type { BaseObjectSet } from "./BaseObjectSet.js";
import type { EXPERIMENTAL_ObjectSetListener } from "./EXPERIMENTAL_ObjectSetListener.js";
import type {
ObjectSetListener,
ObjectSetListenerOptions,
} from "./ObjectSetListener.js";

export interface MinimalObjectSet<Q extends ObjectOrInterfaceDefinition>
extends BaseObjectSet<Q>
Expand Down Expand Up @@ -239,10 +242,16 @@ export interface ObjectSet<
>
: never;

/**
* Request updates when the objects in an object set are added, updated, or removed.
* @param listener - The handlers to be executed during the lifecycle of the subscription.
* @param opts - Options to modify what properties are returned on subscription updates.
* @returns an object containing a function to unsubscribe.
*/
readonly subscribe: <
const P extends PropertyKeys<Q>,
>(
properties: Array<P>,
listener: EXPERIMENTAL_ObjectSetListener<Q, P>,
listener: ObjectSetListener<Q, P>,
opts?: ObjectSetListenerOptions<Q, P>,
) => { unsubscribe: () => void };
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import type {
} from "../ontology/ObjectOrInterface.js";
import type { Osdk } from "../OsdkObjectFrom.js";

export interface EXPERIMENTAL_ObjectSetListener<
export interface ObjectSetListener<
O extends ObjectOrInterfaceDefinition,
P extends PropertyKeys<O> = PropertyKeys<O>,
> {
Expand Down Expand Up @@ -55,3 +55,16 @@ type ObjectUpdate<
object: Osdk.Instance<O, never, P>;
state: "ADDED_OR_UPDATED" | "REMOVED";
};

/**
* Options for subscribing to an ObjectSet.
*
* properties - The properties to request a subscription for. Requesting specific properties limits the possible properties
* that can be returned from the subscription. If not provided, all properties will be requested and potentially be returned on updates.
*/
export interface ObjectSetListenerOptions<
O extends ObjectOrInterfaceDefinition,
P extends PropertyKeys<O> = PropertyKeys<O>,
> {
properties?: Array<P>;
}
1 change: 0 additions & 1 deletion packages/api/src/public/unstable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,4 @@ export { __EXPERIMENTAL__NOT_SUPPORTED_YET__preexistingObjectSet } from "../expe
export { __EXPERIMENTAL__NOT_SUPPORTED_YET_subscribe } from "../experimental/subscribe.js";

export type { EXPERIMENTAL_BulkLinkResult } from "../objectSet/BulkLinkResult.js";
export type { EXPERIMENTAL_ObjectSetListener } from "../objectSet/EXPERIMENTAL_ObjectSetListener.js";
export type { MinimalObjectSet } from "../objectSet/ObjectSet.js";
9 changes: 5 additions & 4 deletions packages/client/src/createClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@ import type {
InterfaceDefinition,
ObjectOrInterfaceDefinition,
ObjectSet,
ObjectSetListener,
ObjectSetListenerOptions,
ObjectTypeDefinition,
PropertyKeys,
QueryDefinition,
} from "@osdk/api";
import type {
Experiment,
EXPERIMENTAL_ObjectSetListener,
ExperimentFns,
MinimalObjectSet,
} from "@osdk/api/unstable";
Expand Down Expand Up @@ -186,16 +187,16 @@ export function createClientInternal(
const P extends PropertyKeys<Q>,
>(
objectSet: ObjectSet<Q>,
properties: Array<P>,
listener: EXPERIMENTAL_ObjectSetListener<Q, P>,
listener: ObjectSetListener<Q, P>,
opts?: ObjectSetListenerOptions<Q, P>,
) => {
const pendingSubscribe = ObjectSetListenerWebsocket.getInstance(
clientCtx,
).subscribe(
objectSet.$objectSetInternals?.def!,
getWireObjectSet(objectSet),
listener,
properties,
opts?.properties,
);

return { unsubscribe: async () => (await pendingSubscribe)() };
Expand Down
50 changes: 29 additions & 21 deletions packages/client/src/objectSet/ObjectSetListenerWebsocket.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@
* limitations under the License.
*/

import type { ObjectOrInterfaceDefinition, PropertyKeys } from "@osdk/api";
import type { EXPERIMENTAL_ObjectSetListener as ObjectSetListener } from "@osdk/api/unstable";
import type {
ObjectOrInterfaceDefinition,
ObjectSetListener,
PropertyKeys,
} from "@osdk/api";
import { $ontologyRid, Employee } from "@osdk/client.test.ontology";
import type {
ObjectSetStreamSubscribeRequests,
Expand Down Expand Up @@ -209,13 +212,17 @@ describe("ObjectSetListenerWebsocket", async () => {
expect(ws.send).not.toHaveBeenCalled();
});

it("currently requests regular object properties", () => {
it("correctly requests regular object properties", () => {
expect(subReq1.requests[0].propertySet).toEqual([
"employeeId",
"fullName",
"office",
"startDate",
"employeeStatus",
]);
});

it("currently requests reference backed properties", () => {
it("correctly requests reference backed properties", () => {
expect(subReq1.requests[0].referenceSet).toEqual(["employeeLocation"]);
});

Expand Down Expand Up @@ -310,23 +317,20 @@ describe("ObjectSetListenerWebsocket", async () => {
let unsubscribe2: () => void;
let subReq2: ObjectSetStreamSubscribeRequests;
beforeEach(async () => {
[unsubscribe2, subReq2] = await Promise.all([
client.subscribe(
{
type: "object",
apiName: "Employee",
},
{
type: "base",
objectType: Employee.apiName,
},
listener,
["employeeStatus"],
),
unsubscribe2 = await client.subscribe(
{
type: "object",
apiName: "Employee",
},
{
type: "base",
objectType: Employee.apiName,
},
listener,
["employeeId"],
);

expectSingleSubscribeMessage(ws),
]);
rootLogger.fatal({ subReq2 });
subReq2 = await expectSingleSubscribeMessage(ws);

respondSuccessToSubscribe(ws, subReq2);
});
Expand All @@ -335,6 +339,11 @@ describe("ObjectSetListenerWebsocket", async () => {
unsubscribe2();
});

it("only requests requested properties", () => {
expect(subReq2.requests[1].propertySet).toEqual(["employeeId"]);
expect(subReq2.requests[1].referenceSet).toEqual([]);
});

it("does not trigger an out of date ", () => {
expect(listener.onOutOfDate).not.toHaveBeenCalled();
});
Expand Down Expand Up @@ -490,7 +499,6 @@ async function subscribeAndExpectWebSocket(
objectType: Employee.apiName,
},
listener,
["employeeId", "employeeLocation"],
),
]);

Expand Down
11 changes: 6 additions & 5 deletions packages/client/src/objectSet/ObjectSetListenerWebsocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,10 @@

import type {
ObjectOrInterfaceDefinition,
ObjectSetListener,
Osdk,
PropertyKeys,
} from "@osdk/api";
import type {
__EXPERIMENTAL__NOT_SUPPORTED_YET_subscribe,
EXPERIMENTAL_ObjectSetListener as ObjectSetListener,
} from "@osdk/api/unstable";
import type {
ObjectSet,
ObjectSetStreamSubscribeRequest,
Expand Down Expand Up @@ -168,7 +165,7 @@ export class ObjectSetListenerWebsocket {
objectType: ObjectOrInterfaceDefinition,
objectSet: ObjectSet,
listener: ObjectSetListener<Q, P>,
properties: Array<P>,
properties: Array<P> = [],
): Promise<() => void> {
if (process.env.TARGET !== "browser") {
// Node 18 does not expose 'crypto' on globalThis, so we need to do it ourselves. This
Expand All @@ -180,6 +177,10 @@ export class ObjectSetListenerWebsocket {
objectType.apiName,
);

if (properties.length === 0) {
properties = Object.keys(objDef.properties) as Array<P>;
}

const objectProperties = properties.filter((p) =>
objDef.properties[p].type !== "geotimeSeriesReference"
);
Expand Down
4 changes: 2 additions & 2 deletions packages/client/src/objectSet/createObjectSet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,16 +226,16 @@ export function createObjectSet<Q extends ObjectOrInterfaceDefinition>(
: undefined) as ObjectSet<Q>["fetchOneWithErrors"],

subscribe: (
properties,
listener,
opts,
) => {
const pendingSubscribe = ObjectSetListenerWebsocket.getInstance(
clientCtx,
).subscribe(
objectType,
objectSet,
listener,
properties,
opts?.properties,
);

return { unsubscribe: async () => (await pendingSubscribe)() };
Expand Down
13 changes: 2 additions & 11 deletions packages/e2e.sandbox.catchall/src/runSubscriptionsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,11 @@ export async function runSubscriptionsTest() {
let counter = 0;
const subscription = client(OsdkTestObject)
.subscribe(
[
"primaryKey_",
"stringProperty",
],
{
onChange(object) {
console.log(
"Object with primaryKey ",
object.object.primaryKey_,
object.object.$primaryKey,
" changed stringProperty to ",
object.object.stringProperty,
);
Expand Down Expand Up @@ -68,18 +64,13 @@ export async function runSubscriptionsTest() {
});
},
},
{ properties: ["stringProperty"] },
);

const mtaBusSubscription = dsClient(
__EXPERIMENTAL__NOT_SUPPORTED_YET_subscribe,
).subscribe(
dsClient(MtaBus),
[
"nextStopId",
"positionId",
"routeId",
"vehicleId",
],
{
onChange(object) {
if (object.object.positionId != null) {
Expand Down
9 changes: 1 addition & 8 deletions packages/e2e.sandbox.todoapp/src/useTodos.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { ObjectTypeDefinition, Osdk } from "@osdk/api";
import { __EXPERIMENTAL__NOT_SUPPORTED_YET_subscribe } from "@osdk/api/unstable";
import { ActionValidationError } from "@osdk/client";
import { useCallback, useEffect } from "react";
import type { KeyedMutator } from "swr";
Expand Down Expand Up @@ -167,14 +166,8 @@ function updateOne<T extends { $primaryKey: Q }, Q>(
/** disabling for now */
export function useSubscribe(mutate: KeyedMutator<SimpleTodo[]>) {
useEffect(() => {
const subscription = $(__EXPERIMENTAL__NOT_SUPPORTED_YET_subscribe)
const subscription = $(MyOsdk.Todo)
.subscribe(
$(MyOsdk.Todo),
[
"id",
"isComplete",
"title",
],
{
onChange(objectUpdate) {
// index incoming objects by apiName and then by pk value
Expand Down

0 comments on commit 11a05cc

Please sign in to comment.