@osdk/client 2.1.0-beta.29 → 2.1.0-beta.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,14 +2,14 @@
2
2
 
3
3
  var chunkTCHGLBKJ_cjs = require('./chunk-TCHGLBKJ.cjs');
4
4
  var shared_net_errors = require('@osdk/shared.net.errors');
5
- var WebSocket = require('isomorphic-ws');
6
- var invariant = require('tiny-invariant');
5
+ var invariant5 = require('tiny-invariant');
7
6
  var api = require('@osdk/api');
7
+ var WebSocket = require('isomorphic-ws');
8
8
 
9
9
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
10
10
 
11
+ var invariant5__default = /*#__PURE__*/_interopDefault(invariant5);
11
12
  var WebSocket__default = /*#__PURE__*/_interopDefault(WebSocket);
12
- var invariant__default = /*#__PURE__*/_interopDefault(invariant);
13
13
 
14
14
  // src/Client.ts
15
15
  var additionalContext = Symbol("additionalContext");
@@ -60,831 +60,831 @@ async function fetchSingleWithErrors(client, objectType, args, objectSet) {
60
60
  };
61
61
  }
62
62
  }
63
- var MINIMUM_RECONNECT_DELAY_MS = 5 * 1e3;
64
- function doNothing() {
63
+ function extractNamespace(fqApiName) {
64
+ const last = fqApiName.lastIndexOf(".");
65
+ if (last === -1) return [undefined, fqApiName];
66
+ return [fqApiName.slice(0, last), fqApiName.slice(last + 1)];
65
67
  }
66
- function fillOutListener({
67
- onChange = doNothing,
68
- onError = doNothing,
69
- onOutOfDate = doNothing,
70
- onSuccessfulSubscription = doNothing
71
- }) {
68
+ function modernToLegacyWhereClause(whereClause, objectOrInterface) {
69
+ if ("$and" in whereClause) {
70
+ return {
71
+ type: "and",
72
+ value: whereClause.$and.map((clause) => modernToLegacyWhereClause(clause, objectOrInterface))
73
+ };
74
+ } else if ("$or" in whereClause) {
75
+ return {
76
+ type: "or",
77
+ value: whereClause.$or.map((clause) => modernToLegacyWhereClause(clause, objectOrInterface))
78
+ };
79
+ } else if ("$not" in whereClause) {
80
+ return {
81
+ type: "not",
82
+ value: modernToLegacyWhereClause(whereClause.$not, objectOrInterface)
83
+ };
84
+ }
85
+ const parts = Object.entries(whereClause);
86
+ if (parts.length === 1) {
87
+ return handleWherePair(parts[0], objectOrInterface);
88
+ }
72
89
  return {
73
- onChange,
74
- onError,
75
- onOutOfDate,
76
- onSuccessfulSubscription
90
+ type: "and",
91
+ value: parts.map((v) => handleWherePair(v, objectOrInterface))
77
92
  };
78
93
  }
79
- function isReady(sub) {
80
- return sub.isReady != null;
81
- }
82
- function subscriptionIsDone(sub) {
83
- return sub.status === "done" || sub.status === "error";
94
+ function makeGeoFilterBbox(bbox, filterType, propertyIdentifier, field) {
95
+ return {
96
+ type: filterType === "$within" ? "withinBoundingBox" : "intersectsBoundingBox",
97
+ /**
98
+ * This is a bit ugly, but did this so that propertyIdentifier only shows up in the return object if its defined,
99
+ * this makes it so we don't need to go update our entire test bed either to include a field which may change in near future.
100
+ * Once we solidify that this is the way forward, I can remove field and clean this up
101
+ */
102
+ ...propertyIdentifier != null && {
103
+ propertyIdentifier
104
+ },
105
+ field,
106
+ value: {
107
+ topLeft: {
108
+ type: "Point",
109
+ coordinates: [bbox[0], bbox[3]]
110
+ },
111
+ bottomRight: {
112
+ type: "Point",
113
+ coordinates: [bbox[2], bbox[1]]
114
+ }
115
+ }
116
+ };
84
117
  }
85
- var ObjectSetListenerWebsocket = class _ObjectSetListenerWebsocket {
86
- static #instances = /* @__PURE__ */ new WeakMap();
87
- // FIXME
88
- static getInstance(client) {
89
- let instance = _ObjectSetListenerWebsocket.#instances.get(client.clientCacheKey);
90
- if (instance == null) {
91
- instance = new _ObjectSetListenerWebsocket(client);
92
- _ObjectSetListenerWebsocket.#instances.set(client.clientCacheKey, instance);
118
+ function makeGeoFilterPolygon(coordinates, filterType, propertyIdentifier, field) {
119
+ return {
120
+ type: filterType,
121
+ ...propertyIdentifier != null && {
122
+ propertyIdentifier
123
+ },
124
+ field,
125
+ value: {
126
+ type: "Polygon",
127
+ coordinates
93
128
  }
94
- return instance;
129
+ };
130
+ }
131
+ function handleWherePair([fieldName, filter], objectOrInterface, structFieldSelector) {
132
+ !(filter != null) ? process.env.NODE_ENV !== "production" ? invariant5__default.default(false, "Defined key values are only allowed when they are not undefined.") : invariant5__default.default(false) : undefined;
133
+ const propertyIdentifier = structFieldSelector != null ? {
134
+ type: "structField",
135
+ ...structFieldSelector,
136
+ propertyApiName: fullyQualifyPropName(structFieldSelector.propertyApiName, objectOrInterface)
137
+ } : undefined;
138
+ const field = structFieldSelector == null ? fullyQualifyPropName(fieldName, objectOrInterface) : undefined;
139
+ if (typeof filter === "string" || typeof filter === "number" || typeof filter === "boolean") {
140
+ return {
141
+ type: "eq",
142
+ ...propertyIdentifier != null && {
143
+ propertyIdentifier
144
+ },
145
+ field,
146
+ value: filter
147
+ };
95
148
  }
96
- #ws;
97
- #lastWsConnect = 0;
98
- #client;
99
- #logger;
100
- /**
101
- * map of requestId to all active subscriptions at the time of the request
102
- */
103
- #pendingSubscriptions = /* @__PURE__ */ new Map();
104
- /**
105
- * Map of subscriptionId to Subscription. Note: the subscriptionId may be
106
- * temporary and not the actual subscriptionId from the server.
107
- */
108
- #subscriptions = /* @__PURE__ */ new Map();
109
- #endedSubscriptions = /* @__PURE__ */ new Set();
110
- #maybeDisconnectTimeout;
111
- // DO NOT CONSTRUCT DIRECTLY. ONLY EXPOSED AS A TESTING SEAM
112
- constructor(client, {
113
- minimumReconnectDelayMs = MINIMUM_RECONNECT_DELAY_MS
114
- } = {}) {
115
- this.MINIMUM_RECONNECT_DELAY_MS = minimumReconnectDelayMs;
116
- this.#client = client;
117
- this.#logger = client.logger?.child({}, {
118
- msgPrefix: "<OSW> "
149
+ const keysOfFilter = Object.keys(filter);
150
+ const hasDollarSign = keysOfFilter.some((key) => key.startsWith("$"));
151
+ !(!hasDollarSign || keysOfFilter.length === 1) ? process.env.NODE_ENV !== "production" ? invariant5__default.default(false, "WhereClause Filter with multiple clauses isn't allowed") : invariant5__default.default(false) : undefined;
152
+ if (!hasDollarSign) {
153
+ const structFilter = Object.entries(filter);
154
+ !(structFilter.length === 1) ? process.env.NODE_ENV !== "production" ? invariant5__default.default(false, "Cannot filter on more than one struct field in the same clause, need to use an and clause") : invariant5__default.default(false) : undefined;
155
+ const structFieldApiName = keysOfFilter[0];
156
+ return handleWherePair(Object.entries(filter)[0], objectOrInterface, {
157
+ propertyApiName: fieldName,
158
+ structFieldApiName
119
159
  });
120
- !(client.baseUrl.startsWith("https://") || client.baseUrl.startsWith("http://")) ? process.env.NODE_ENV !== "production" ? invariant__default.default(false, "Stack must be a URL") : invariant__default.default(false) : undefined;
121
160
  }
122
- async subscribe(objectType, objectSet, listener, properties = []) {
123
- const objOrInterfaceDef = objectType.type === "object" ? await this.#client.ontologyProvider.getObjectDefinition(objectType.apiName) : await this.#client.ontologyProvider.getInterfaceDefinition(objectType.apiName);
124
- let objectProperties = [];
125
- let referenceProperties = [];
126
- if (properties.length === 0) {
127
- properties = Object.keys(objOrInterfaceDef.properties);
128
- }
129
- objectProperties = properties.filter((p) => objOrInterfaceDef.properties[p].type !== "geotimeSeriesReference");
130
- referenceProperties = properties.filter((p) => objOrInterfaceDef.properties[p].type === "geotimeSeriesReference");
131
- const sub = {
132
- listener: fillOutListener(listener),
133
- objectSet,
134
- primaryKeyPropertyName: objOrInterfaceDef.type === "interface" ? undefined : objOrInterfaceDef.primaryKeyApiName,
135
- requestedProperties: objectProperties,
136
- requestedReferenceProperties: referenceProperties,
137
- status: "preparing",
138
- // Since we don't have a real subscription id yet but we need to keep
139
- // track of this reference, we can just use a random uuid.
140
- subscriptionId: `TMP-${nextUuid()}}`,
141
- interfaceApiName: objOrInterfaceDef.type === "object" ? undefined : objOrInterfaceDef.apiName
142
- };
143
- this.#subscriptions.set(sub.subscriptionId, sub);
144
- void this.#initiateSubscribe(sub);
145
- return () => {
146
- this.#unsubscribe(sub);
161
+ const firstKey = keysOfFilter[0];
162
+ !(filter[firstKey] != null) ? process.env.NODE_ENV !== "production" ? invariant5__default.default(false) : invariant5__default.default(false) : undefined;
163
+ if (firstKey === "$ne") {
164
+ return {
165
+ type: "not",
166
+ value: {
167
+ type: "eq",
168
+ ...propertyIdentifier != null && {
169
+ propertyIdentifier
170
+ },
171
+ field,
172
+ value: filter[firstKey]
173
+ }
147
174
  };
148
175
  }
149
- /**
150
- * Called at least once for every subscription.
151
- *
152
- * - Resets pending expiry
153
- * - Recreates temporary object set
154
- * - Triggers a full subscribe message
155
- *
156
- * @returns
157
- */
158
- async #initiateSubscribe(sub) {
159
- if (process.env.NODE_ENV !== "production") {
160
- this.#logger?.trace("#initiateSubscribe()");
161
- }
162
- try {
163
- await this.#ensureWebsocket();
164
- if (subscriptionIsDone(sub)) {
165
- return;
166
- }
167
- sub.isReady = true;
168
- if (this.#ws?.readyState === WebSocket__default.default.OPEN) {
169
- this.#sendSubscribeMessage();
170
- }
171
- } catch (error) {
172
- this.#logger?.error(error, "Error in #initiateSubscribe");
173
- this.#tryCatchOnError(sub, true, error);
176
+ if (firstKey === "$within") {
177
+ const withinBody = filter[firstKey];
178
+ if (Array.isArray(withinBody)) {
179
+ return makeGeoFilterBbox(withinBody, firstKey, propertyIdentifier, field);
180
+ } else if ("$bbox" in withinBody && withinBody.$bbox != null) {
181
+ return makeGeoFilterBbox(withinBody.$bbox, firstKey, propertyIdentifier, field);
182
+ } else if ("$distance" in withinBody && "$of" in withinBody && withinBody.$distance != null && withinBody.$of != null) {
183
+ return {
184
+ type: "withinDistanceOf",
185
+ ...propertyIdentifier != null && {
186
+ propertyIdentifier
187
+ },
188
+ field,
189
+ value: {
190
+ center: Array.isArray(withinBody.$of) ? {
191
+ type: "Point",
192
+ coordinates: withinBody.$of
193
+ } : withinBody.$of,
194
+ distance: {
195
+ value: withinBody.$distance[0],
196
+ unit: api.DistanceUnitMapping[withinBody.$distance[1]]
197
+ }
198
+ }
199
+ };
200
+ } else {
201
+ const coordinates = "$polygon" in withinBody ? withinBody.$polygon : withinBody.coordinates;
202
+ return makeGeoFilterPolygon(coordinates, "withinPolygon", propertyIdentifier, fieldName);
174
203
  }
175
204
  }
176
- #sendSubscribeMessage() {
177
- if (process.env.NODE_ENV !== "production") {
178
- this.#logger?.trace("#sendSubscribeMessage()");
205
+ if (firstKey === "$intersects") {
206
+ const intersectsBody = filter[firstKey];
207
+ if (Array.isArray(intersectsBody)) {
208
+ return makeGeoFilterBbox(intersectsBody, firstKey, propertyIdentifier, field);
209
+ } else if ("$bbox" in intersectsBody && intersectsBody.$bbox != null) {
210
+ return makeGeoFilterBbox(intersectsBody.$bbox, firstKey, propertyIdentifier, field);
211
+ } else {
212
+ const coordinates = "$polygon" in intersectsBody ? intersectsBody.$polygon : intersectsBody.coordinates;
213
+ return makeGeoFilterPolygon(coordinates, "intersectsPolygon", propertyIdentifier, field);
179
214
  }
180
- const readySubs = [...this.#subscriptions.values()].filter(isReady);
181
- const id = nextUuid();
182
- this.#pendingSubscriptions.set(id, readySubs);
183
- const subscribe = {
184
- id,
185
- requests: readySubs.map(({
186
- objectSet,
187
- requestedProperties,
188
- requestedReferenceProperties,
189
- interfaceApiName
190
- }) => {
191
- return {
192
- objectSet,
193
- propertySet: requestedProperties,
194
- referenceSet: requestedReferenceProperties
195
- };
196
- })
197
- };
198
- if (process.env.NODE_ENV !== "production") {
199
- this.#logger?.trace({
200
- payload: subscribe
201
- }, "sending subscribe message");
202
- }
203
- this.#ws?.send(JSON.stringify(subscribe));
204
215
  }
205
- #unsubscribe(sub, newStatus = "done") {
206
- if (subscriptionIsDone(sub)) {
207
- return;
208
- }
209
- sub.status = newStatus;
210
- sub.listener = fillOutListener({});
211
- this.#subscriptions.delete(sub.subscriptionId);
212
- this.#endedSubscriptions.add(sub.subscriptionId);
213
- this.#sendSubscribeMessage();
214
- if (this.#maybeDisconnectTimeout) {
215
- clearTimeout(this.#maybeDisconnectTimeout);
216
- }
217
- this.#maybeDisconnectTimeout = setTimeout(
218
- () => {
219
- this.#maybeDisconnectTimeout = undefined;
220
- if (this.#subscriptions.size === 0) {
221
- this.#cycleWebsocket();
222
- }
216
+ if (firstKey === "$containsAllTerms" || firstKey === "$containsAnyTerm") {
217
+ return {
218
+ type: firstKey.substring(1),
219
+ ...propertyIdentifier != null && {
220
+ propertyIdentifier
223
221
  },
224
- 15e3
225
- /* ms */
226
- );
222
+ field,
223
+ value: typeof filter[firstKey] === "string" ? filter[firstKey] : filter[firstKey]["term"],
224
+ fuzzy: typeof filter[firstKey] === "string" ? false : filter[firstKey]["fuzzySearch"] ?? false
225
+ };
227
226
  }
228
- async #ensureWebsocket() {
229
- if (this.#ws == null) {
230
- const {
231
- baseUrl,
232
- tokenProvider
233
- } = this.#client;
234
- const url = constructWebsocketUrl(baseUrl, await this.#client.ontologyRid);
235
- const token = await tokenProvider();
236
- if (this.#ws == null) {
237
- const nextConnectTime = (this.#lastWsConnect ?? 0) + this.MINIMUM_RECONNECT_DELAY_MS;
238
- if (nextConnectTime > Date.now()) {
239
- await new Promise((resolve) => {
240
- setTimeout(resolve, nextConnectTime - Date.now());
241
- });
242
- }
243
- this.#lastWsConnect = Date.now();
244
- if (this.#ws == null) {
245
- if (process.env.NODE_ENV !== "production") {
246
- this.#logger?.trace("Creating websocket");
247
- }
248
- this.#ws = new WebSocket__default.default(url, [`Bearer-${token}`]);
249
- this.#ws.addEventListener("close", this.#onClose);
250
- this.#ws.addEventListener("message", this.#onMessage);
251
- this.#ws.addEventListener("open", this.#onOpen);
252
- }
253
- }
254
- if (this.#ws.readyState === WebSocket__default.default.CONNECTING) {
255
- const ws = this.#ws;
256
- return new Promise((resolve, reject) => {
257
- function cleanup() {
258
- ws.removeEventListener("open", open);
259
- ws.removeEventListener("error", error);
260
- ws.removeEventListener("close", cleanup);
261
- }
262
- function open() {
263
- cleanup();
264
- resolve();
265
- }
266
- function error(evt) {
267
- cleanup();
268
- reject(evt);
227
+ return {
228
+ type: firstKey.substring(1),
229
+ ...propertyIdentifier != null && {
230
+ propertyIdentifier
231
+ },
232
+ field,
233
+ value: filter[firstKey]
234
+ };
235
+ }
236
+ function fullyQualifyPropName(fieldName, objectOrInterface) {
237
+ if (objectOrInterface.type === "interface") {
238
+ const [objApiNamespace] = extractNamespace(objectOrInterface.apiName);
239
+ const [fieldApiNamespace, fieldShortName] = extractNamespace(fieldName);
240
+ return fieldApiNamespace == null && objApiNamespace != null ? `${objApiNamespace}.${fieldShortName}` : fieldName;
241
+ }
242
+ return fieldName;
243
+ }
244
+
245
+ // src/derivedProperties/createWithPropertiesObjectSet.ts
246
+ function createWithPropertiesObjectSet(objectType, objectSet, definitionMap) {
247
+ const base = {
248
+ pivotTo: (link) => {
249
+ return createWithPropertiesObjectSet(objectType, {
250
+ type: "searchAround",
251
+ objectSet,
252
+ link
253
+ }, definitionMap);
254
+ },
255
+ where: (clause) => {
256
+ return createWithPropertiesObjectSet(objectType, {
257
+ type: "filter",
258
+ objectSet,
259
+ where: modernToLegacyWhereClause(clause, objectType)
260
+ }, definitionMap);
261
+ },
262
+ aggregate: (aggregation, opt) => {
263
+ const splitAggregation = aggregation.split(":");
264
+ !(splitAggregation.length === 2 || splitAggregation[0] === "$count") ? process.env.NODE_ENV !== "production" ? invariant5__default.default(false, "Invalid aggregation format") : invariant5__default.default(false) : undefined;
265
+ const [aggregationPropertyName, aggregationOperation] = splitAggregation;
266
+ let aggregationOperationDefinition;
267
+ switch (aggregationOperation) {
268
+ case "sum":
269
+ case "avg":
270
+ case "min":
271
+ case "max":
272
+ case "exactDistinct":
273
+ case "approximateDistinct":
274
+ aggregationOperationDefinition = {
275
+ type: aggregationOperation,
276
+ selectedPropertyApiName: aggregationPropertyName
277
+ };
278
+ break;
279
+ case "approximatePercentile":
280
+ aggregationOperationDefinition = {
281
+ type: "approximatePercentile",
282
+ selectedPropertyApiName: aggregationPropertyName,
283
+ approximatePercentile: opt?.percentile ?? 0.5
284
+ };
285
+ break;
286
+ case "collectSet":
287
+ case "collectList":
288
+ aggregationOperationDefinition = {
289
+ type: aggregationOperation,
290
+ selectedPropertyApiName: aggregationPropertyName,
291
+ limit: opt?.limit ?? 100
292
+ };
293
+ break;
294
+ case undefined:
295
+ if (aggregationPropertyName === "$count") {
296
+ aggregationOperationDefinition = {
297
+ type: "count"
298
+ };
299
+ break;
269
300
  }
270
- ws.addEventListener("open", open);
271
- ws.addEventListener("error", error);
272
- ws.addEventListener("close", cleanup);
273
- });
301
+ default:
302
+ process.env.NODE_ENV !== "production" ? invariant5__default.default(false, "Invalid aggregation operation " + aggregationOperation) : invariant5__default.default(false) ;
274
303
  }
304
+ const selectorResult = {
305
+ type: {}
306
+ };
307
+ definitionMap.set(selectorResult, {
308
+ type: "selection",
309
+ objectSet,
310
+ operation: aggregationOperationDefinition
311
+ });
312
+ return selectorResult;
313
+ },
314
+ selectProperty: (name) => {
315
+ const selectorResult = {
316
+ type: {}
317
+ };
318
+ definitionMap.set(selectorResult, {
319
+ type: "selection",
320
+ objectSet,
321
+ operation: {
322
+ type: "get",
323
+ selectedPropertyApiName: name
324
+ }
325
+ });
326
+ return selectorResult;
275
327
  }
276
- }
277
- #onOpen = () => {
278
- this.#sendSubscribeMessage();
279
328
  };
280
- #onMessage = async (message) => {
281
- const data = JSON.parse(message.data.toString());
282
- if (process.env.NODE_ENV !== "production") {
283
- this.#logger?.trace({
284
- payload: data
285
- }, "received message from ws");
329
+ return base;
330
+ }
331
+ function legacyToModernSingleAggregationResult(entry) {
332
+ return entry.metrics.reduce((accumulator, curValue) => {
333
+ const parts = curValue.name.split(".");
334
+ if (parts[0] === "count") {
335
+ return accumulator;
286
336
  }
287
- switch (data.type) {
288
- case "objectSetChanged":
289
- return void this.#handleMessage_objectSetChanged(data);
290
- case "refreshObjectSet":
291
- return void this.#handleMessage_refreshObjectSet(data);
292
- case "subscribeResponses":
293
- return void this.#handleMessage_subscribeResponses(data);
294
- case "subscriptionClosed": {
295
- return void this.#handleMessage_subscriptionClosed(data);
296
- }
297
- default:
298
- process.env.NODE_ENV !== "production" ? invariant__default.default(false, "Unexpected message type") : invariant__default.default(false) ;
337
+ !(parts.length === 2) ? process.env.NODE_ENV !== "production" ? invariant5__default.default(false, "assumed we were getting a `${key}.${type}`") : invariant5__default.default(false) : undefined;
338
+ const property = parts[0];
339
+ const metricType = parts[1];
340
+ if (!(property in accumulator)) {
341
+ accumulator[property] = {};
299
342
  }
300
- };
301
- #handleMessage_objectSetChanged = async (payload) => {
302
- const sub = this.#subscriptions.get(payload.id);
303
- if (sub == null) return;
304
- const objectUpdates = payload.updates.filter((update) => update.type === "object");
305
- const referenceUpdates = payload.updates.filter((update) => update.type === "reference");
306
- const osdkObjectsWithReferenceUpdates = await Promise.all(referenceUpdates.map(async (o) => {
307
- const osdkObjectArray = await this.#client.objectFactory2(this.#client, [{
308
- __apiName: o.objectType,
309
- __primaryKey: sub.primaryKeyPropertyName != null ? o.primaryKey[sub.primaryKeyPropertyName] : undefined,
310
- ...o.primaryKey,
311
- [o.property]: o.value
312
- }], sub.interfaceApiName, false, undefined, false, await this.#fetchInterfaceMapping(o.objectType, sub.interfaceApiName));
313
- const singleOsdkObject = osdkObjectArray[0] ?? undefined;
314
- return singleOsdkObject != null ? {
315
- object: singleOsdkObject,
316
- state: "ADDED_OR_UPDATED"
317
- } : undefined;
318
- }));
319
- for (const osdkObject of osdkObjectsWithReferenceUpdates) {
320
- if (osdkObject != null) {
321
- try {
322
- sub.listener.onChange?.(osdkObject);
323
- } catch (error) {
324
- this.#logger?.error(error, "Error in onChange callback");
325
- this.#tryCatchOnError(sub, false, error);
326
- }
327
- }
343
+ accumulator[property][metricType] = curValue.value;
344
+ return accumulator;
345
+ }, {});
346
+ }
347
+
348
+ // src/internal/conversions/modernToLegacyAggregationClause.ts
349
+ var directionFieldMap = (dir) => dir === "asc" ? "ASC" : dir === "desc" ? "DESC" : undefined;
350
+ function modernToLegacyAggregationClause(select) {
351
+ return Object.entries(select).flatMap(([propAndMetric, aggregationType]) => {
352
+ if (propAndMetric === "$count") {
353
+ return {
354
+ type: "count",
355
+ name: "count",
356
+ direction: directionFieldMap(aggregationType)
357
+ };
328
358
  }
329
- const osdkObjects = await Promise.all(objectUpdates.map(async (o) => {
330
- const keysToDelete = Object.keys(o.object).filter((key) => sub.requestedReferenceProperties.includes(key));
331
- for (const key of keysToDelete) {
332
- delete o.object[key];
333
- }
334
- const osdkObjectArray = await this.#client.objectFactory2(this.#client, [o.object], sub.interfaceApiName, false, undefined, false, await this.#fetchInterfaceMapping(o.object.__apiName, sub.interfaceApiName));
335
- const singleOsdkObject = osdkObjectArray[0] ?? undefined;
336
- return singleOsdkObject != null ? {
337
- object: singleOsdkObject,
338
- state: o.state
339
- } : undefined;
340
- }));
341
- for (const osdkObject of osdkObjects) {
342
- if (osdkObject != null) {
343
- try {
344
- sub.listener.onChange?.(osdkObject);
345
- } catch (error) {
346
- this.#logger?.error(error, "Error in onChange callback");
347
- this.#tryCatchOnError(sub, false, error);
348
- }
359
+ const colonPos = propAndMetric.lastIndexOf(":");
360
+ const property = propAndMetric.slice(0, colonPos);
361
+ const metric = propAndMetric.slice(colonPos + 1);
362
+ return [{
363
+ type: metric,
364
+ name: `${property}.${metric}`,
365
+ direction: directionFieldMap(aggregationType),
366
+ field: property
367
+ }];
368
+ });
369
+ }
370
+ function modernToLegacyGroupByClause(groupByClause) {
371
+ if (!groupByClause) return [];
372
+ return Object.entries(groupByClause).flatMap(([field, type]) => {
373
+ if (type === "exact") {
374
+ return [{
375
+ type,
376
+ field
377
+ }];
378
+ } else if ("$exactWithLimit" in type) {
379
+ {
380
+ return [{
381
+ type: "exact",
382
+ field,
383
+ maxGroupCount: type.$exactWithLimit
384
+ }];
349
385
  }
350
- }
386
+ } else if ("$fixedWidth" in type) {
387
+ return [{
388
+ type: "fixedWidth",
389
+ field,
390
+ fixedWidth: type.$fixedWidth
391
+ }];
392
+ } else if ("$ranges" in type) {
393
+ return [{
394
+ type: "ranges",
395
+ field,
396
+ ranges: type.$ranges.map((range) => convertRange(range))
397
+ }];
398
+ } else if ("$duration" in type) {
399
+ return [{
400
+ type: "duration",
401
+ field,
402
+ value: type.$duration[0],
403
+ unit: api.DurationMapping[type.$duration[1]]
404
+ }];
405
+ } else return [];
406
+ });
407
+ }
408
+ function convertRange(range) {
409
+ return {
410
+ startValue: range[0],
411
+ endValue: range[1]
351
412
  };
352
- async #fetchInterfaceMapping(objectTypeApiName, interfaceApiName) {
353
- if (interfaceApiName == null) return {};
354
- const interfaceMap = (await this.#client.ontologyProvider.getObjectDefinition(objectTypeApiName)).interfaceMap;
413
+ }
414
+
415
+ // src/object/aggregate.ts
416
+ async function aggregate(clientCtx, objectType, objectSet = chunkTCHGLBKJ_cjs.resolveBaseObjectSetType(objectType), req) {
417
+ chunkTCHGLBKJ_cjs.resolveBaseObjectSetType(objectType);
418
+ const body = {
419
+ aggregation: modernToLegacyAggregationClause(req.$select),
420
+ groupBy: [],
421
+ where: undefined
422
+ };
423
+ if (req.$groupBy) {
424
+ body.groupBy = modernToLegacyGroupByClause(req.$groupBy);
425
+ }
426
+ const result = await chunkTCHGLBKJ_cjs.OntologyObjectSet_exports.aggregate(chunkTCHGLBKJ_cjs.addUserAgentAndRequestContextHeaders(clientCtx, objectType), await clientCtx.ontologyRid, {
427
+ objectSet,
428
+ groupBy: body.groupBy,
429
+ aggregation: body.aggregation
430
+ });
431
+ if (!req.$groupBy) {
432
+ !(result.data.length === 1) ? process.env.NODE_ENV !== "production" ? invariant5__default.default(false, "no group by clause should mean only one data result") : invariant5__default.default(false) : undefined;
355
433
  return {
356
- [interfaceApiName]: {
357
- [objectTypeApiName]: interfaceMap[interfaceApiName]
358
- }
434
+ ...aggregationToCountResult(result.data[0]),
435
+ ...legacyToModernSingleAggregationResult(result.data[0])
359
436
  };
360
437
  }
361
- #handleMessage_refreshObjectSet = (payload) => {
362
- const sub = this.#subscriptions.get(payload.id);
363
- !sub ? process.env.NODE_ENV !== "production" ? invariant__default.default(false, `Expected subscription id ${payload.id}`) : invariant__default.default(false) : undefined;
364
- try {
365
- sub.listener.onOutOfDate();
366
- } catch (error) {
367
- this.#logger?.error(error, "Error in onOutOfDate callback");
368
- this.#tryCatchOnError(sub, false, error);
438
+ const ret = result.data.map((entry) => {
439
+ return {
440
+ $group: entry.group,
441
+ ...aggregationToCountResult(entry),
442
+ ...legacyToModernSingleAggregationResult(entry)
443
+ };
444
+ });
445
+ return ret;
446
+ }
447
+ function aggregationToCountResult(entry) {
448
+ for (const aggregateResult of entry.metrics) {
449
+ if (aggregateResult.name === "count") {
450
+ return {
451
+ $count: aggregateResult.value
452
+ };
369
453
  }
454
+ }
455
+ }
456
+
457
+ // src/util/augmentRequestContext.ts
458
+ var augmentRequestContext = (client, augment) => ({
459
+ ...client,
460
+ requestContext: {
461
+ ...client.requestContext,
462
+ ...augment(client.requestContext)
463
+ }
464
+ });
465
+
466
+ // src/util/WireObjectSet.ts
467
+ var WIRE_OBJECT_SET_TYPES = /* @__PURE__ */ new Set(["base", "filter", "intersect", "reference", "searchAround", "static", "subtract", "union"]);
468
+ function isWireObjectSet(o) {
469
+ return o != null && typeof o === "object" && WIRE_OBJECT_SET_TYPES.has(o.type);
470
+ }
471
+ var MINIMUM_RECONNECT_DELAY_MS = 5 * 1e3;
472
+ function doNothing() {
473
+ }
474
+ function fillOutListener({
475
+ onChange = doNothing,
476
+ onError = doNothing,
477
+ onOutOfDate = doNothing,
478
+ onSuccessfulSubscription = doNothing
479
+ }) {
480
+ return {
481
+ onChange,
482
+ onError,
483
+ onOutOfDate,
484
+ onSuccessfulSubscription
370
485
  };
371
- #handleMessage_subscribeResponses = (payload) => {
372
- const {
373
- id,
374
- responses
375
- } = payload;
376
- const subs = this.#pendingSubscriptions.get(id);
377
- !subs ? process.env.NODE_ENV !== "production" ? invariant__default.default(false, `should have a pending subscription for ${id}`) : invariant__default.default(false) : undefined;
378
- this.#pendingSubscriptions.delete(id);
379
- for (let i = 0; i < responses.length; i++) {
380
- const sub = subs[i];
381
- const response = responses[i];
382
- switch (response.type) {
383
- case "error":
384
- this.#tryCatchOnError(sub, true, response.errors);
385
- this.#unsubscribe(sub, "error");
386
- break;
387
- case "qos":
388
- this.#cycleWebsocket();
389
- break;
390
- case "success":
391
- const shouldFireOutOfDate = sub.status === "expired" || sub.status === "reconnecting";
392
- if (process.env.NODE_ENV !== "production") {
393
- this.#logger?.trace({
394
- shouldFireOutOfDate
395
- }, "success");
396
- }
397
- sub.status = "subscribed";
398
- if (sub.subscriptionId !== response.id) {
399
- this.#subscriptions.delete(sub.subscriptionId);
400
- sub.subscriptionId = response.id;
401
- this.#subscriptions.set(sub.subscriptionId, sub);
402
- }
403
- try {
404
- if (shouldFireOutOfDate) sub.listener.onOutOfDate();
405
- else sub.listener.onSuccessfulSubscription();
406
- } catch (error) {
407
- this.#logger?.error(error, "Error in onOutOfDate or onSuccessfulSubscription callback");
408
- this.#tryCatchOnError(sub, false, error);
409
- }
410
- break;
411
- default:
412
- this.#tryCatchOnError(sub, true, response);
413
- }
486
+ }
487
+ function isReady(sub) {
488
+ return sub.isReady != null;
489
+ }
490
+ function subscriptionIsDone(sub) {
491
+ return sub.status === "done" || sub.status === "error";
492
+ }
493
+ var ObjectSetListenerWebsocket = class _ObjectSetListenerWebsocket {
494
+ static #instances = /* @__PURE__ */ new WeakMap();
495
+ // FIXME
496
+ static getInstance(client) {
497
+ let instance = _ObjectSetListenerWebsocket.#instances.get(client.clientCacheKey);
498
+ if (instance == null) {
499
+ instance = new _ObjectSetListenerWebsocket(client);
500
+ _ObjectSetListenerWebsocket.#instances.set(client.clientCacheKey, instance);
501
+ }
502
+ return instance;
503
+ }
504
+ #ws;
505
+ #lastWsConnect = 0;
506
+ #client;
507
+ #logger;
508
+ /**
509
+ * map of requestId to all active subscriptions at the time of the request
510
+ */
511
+ #pendingSubscriptions = /* @__PURE__ */ new Map();
512
+ /**
513
+ * Map of subscriptionId to Subscription. Note: the subscriptionId may be
514
+ * temporary and not the actual subscriptionId from the server.
515
+ */
516
+ #subscriptions = /* @__PURE__ */ new Map();
517
+ #endedSubscriptions = /* @__PURE__ */ new Set();
518
+ #maybeDisconnectTimeout;
519
+ // DO NOT CONSTRUCT DIRECTLY. ONLY EXPOSED AS A TESTING SEAM
520
+ constructor(client, {
521
+ minimumReconnectDelayMs = MINIMUM_RECONNECT_DELAY_MS
522
+ } = {}) {
523
+ this.MINIMUM_RECONNECT_DELAY_MS = minimumReconnectDelayMs;
524
+ this.#client = client;
525
+ this.#logger = client.logger?.child({}, {
526
+ msgPrefix: "<OSW> "
527
+ });
528
+ !(client.baseUrl.startsWith("https://") || client.baseUrl.startsWith("http://")) ? process.env.NODE_ENV !== "production" ? invariant5__default.default(false, "Stack must be a URL") : invariant5__default.default(false) : undefined;
529
+ }
530
+ async subscribe(objectType, objectSet, listener, properties = []) {
531
+ const objOrInterfaceDef = objectType.type === "object" ? await this.#client.ontologyProvider.getObjectDefinition(objectType.apiName) : await this.#client.ontologyProvider.getInterfaceDefinition(objectType.apiName);
532
+ let objectProperties = [];
533
+ let referenceProperties = [];
534
+ if (properties.length === 0) {
535
+ properties = Object.keys(objOrInterfaceDef.properties);
414
536
  }
415
- };
416
- #handleMessage_subscriptionClosed(payload) {
417
- const sub = this.#subscriptions.get(payload.id);
418
- if (sub == null && this.#endedSubscriptions.has(payload.id)) return;
419
- !sub ? process.env.NODE_ENV !== "production" ? invariant__default.default(false, `Expected subscription id ${payload.id}`) : invariant__default.default(false) : undefined;
420
- this.#tryCatchOnError(sub, true, payload.cause);
421
- this.#unsubscribe(sub, "error");
537
+ objectProperties = properties.filter((p) => objOrInterfaceDef.properties[p].type !== "geotimeSeriesReference");
538
+ referenceProperties = properties.filter((p) => objOrInterfaceDef.properties[p].type === "geotimeSeriesReference");
539
+ const sub = {
540
+ listener: fillOutListener(listener),
541
+ objectSet,
542
+ primaryKeyPropertyName: objOrInterfaceDef.type === "interface" ? undefined : objOrInterfaceDef.primaryKeyApiName,
543
+ requestedProperties: objectProperties,
544
+ requestedReferenceProperties: referenceProperties,
545
+ status: "preparing",
546
+ // Since we don't have a real subscription id yet but we need to keep
547
+ // track of this reference, we can just use a random uuid.
548
+ subscriptionId: `TMP-${nextUuid()}}`,
549
+ interfaceApiName: objOrInterfaceDef.type === "object" ? undefined : objOrInterfaceDef.apiName
550
+ };
551
+ this.#subscriptions.set(sub.subscriptionId, sub);
552
+ void this.#initiateSubscribe(sub);
553
+ return () => {
554
+ this.#unsubscribe(sub);
555
+ };
422
556
  }
423
- #onClose = (event) => {
557
+ /**
558
+ * Called at least once for every subscription.
559
+ *
560
+ * - Resets pending expiry
561
+ * - Recreates temporary object set
562
+ * - Triggers a full subscribe message
563
+ *
564
+ * @returns
565
+ */
566
+ async #initiateSubscribe(sub) {
424
567
  if (process.env.NODE_ENV !== "production") {
425
- this.#logger?.trace({
426
- event
427
- }, "Received close event from ws", event);
428
- }
429
- this.#cycleWebsocket();
430
- };
431
- #cycleWebsocket = () => {
432
- if (this.#ws) {
433
- this.#ws.removeEventListener("open", this.#onOpen);
434
- this.#ws.removeEventListener("message", this.#onMessage);
435
- this.#ws.removeEventListener("close", this.#onClose);
436
- if (this.#ws.readyState !== WebSocket__default.default.CLOSING && this.#ws.readyState !== WebSocket__default.default.CLOSED) {
437
- this.#ws.close();
438
- }
439
- this.#ws = undefined;
568
+ this.#logger?.trace("#initiateSubscribe()");
440
569
  }
441
- if (this.#subscriptions.size > 0) {
442
- if (process.env.NODE_ENV !== "production") {
443
- for (const s of this.#subscriptions.values()) {
444
- !(s.status !== "done" && s.status !== "error") ? process.env.NODE_ENV !== "production" ? invariant__default.default(false, "should not have done/error subscriptions still") : invariant__default.default(false) : undefined;
445
- }
570
+ try {
571
+ await this.#ensureWebsocket();
572
+ if (subscriptionIsDone(sub)) {
573
+ return;
446
574
  }
447
- for (const s of this.#subscriptions.values()) {
448
- if (s.status === "subscribed") s.status = "reconnecting";
575
+ sub.isReady = true;
576
+ if (this.#ws?.readyState === WebSocket__default.default.OPEN) {
577
+ this.#sendSubscribeMessage();
449
578
  }
450
- void this.#ensureWebsocket();
579
+ } catch (error) {
580
+ this.#logger?.error(error, "Error in #initiateSubscribe");
581
+ this.#tryCatchOnError(sub, true, error);
451
582
  }
452
- };
453
- #tryCatchOnError = (sub, subscriptionClosed, error) => {
454
- try {
455
- sub.listener.onError({
456
- subscriptionClosed,
457
- error
458
- });
459
- } catch (onErrorError) {
460
- console.error(`Error encountered in an onError callback for an OSDK subscription`, onErrorError);
461
- console.error(`This onError call was triggered by an error in another callback`, error);
462
- console.error(`The subscription has been closed.`, error);
463
- if (!subscriptionClosed) {
464
- this.#logger?.error(error, "Error in onError callback");
465
- this.#unsubscribe(sub, "error");
466
- this.#tryCatchOnError(sub, true, onErrorError);
467
- }
583
+ }
584
+ #sendSubscribeMessage() {
585
+ if (process.env.NODE_ENV !== "production") {
586
+ this.#logger?.trace("#sendSubscribeMessage()");
468
587
  }
469
- };
470
- };
471
- function constructWebsocketUrl(baseUrl, ontologyRid) {
472
- const base = new URL(baseUrl);
473
- const url = new URL(`api/v2/ontologySubscriptions/ontologies/${ontologyRid}/streamSubscriptions`, base);
474
- url.protocol = url.protocol.replace("https", "wss");
475
- return url;
476
- }
477
- var uuidCounter = 0;
478
- function nextUuid() {
479
- return `00000000-0000-0000-0000-${(uuidCounter++).toString().padStart(12, "0")}`;
480
- }
481
- function extractNamespace(fqApiName) {
482
- const last = fqApiName.lastIndexOf(".");
483
- if (last === -1) return [undefined, fqApiName];
484
- return [fqApiName.slice(0, last), fqApiName.slice(last + 1)];
485
- }
486
- function modernToLegacyWhereClause(whereClause, objectOrInterface) {
487
- if ("$and" in whereClause) {
488
- return {
489
- type: "and",
490
- value: whereClause.$and.map((clause) => modernToLegacyWhereClause(clause, objectOrInterface))
491
- };
492
- } else if ("$or" in whereClause) {
493
- return {
494
- type: "or",
495
- value: whereClause.$or.map((clause) => modernToLegacyWhereClause(clause, objectOrInterface))
496
- };
497
- } else if ("$not" in whereClause) {
498
- return {
499
- type: "not",
500
- value: modernToLegacyWhereClause(whereClause.$not, objectOrInterface)
588
+ const readySubs = [...this.#subscriptions.values()].filter(isReady);
589
+ const id = nextUuid();
590
+ this.#pendingSubscriptions.set(id, readySubs);
591
+ const subscribe = {
592
+ id,
593
+ requests: readySubs.map(({
594
+ objectSet,
595
+ requestedProperties,
596
+ requestedReferenceProperties,
597
+ interfaceApiName
598
+ }) => {
599
+ return {
600
+ objectSet,
601
+ propertySet: requestedProperties,
602
+ referenceSet: requestedReferenceProperties
603
+ };
604
+ })
501
605
  };
606
+ if (process.env.NODE_ENV !== "production") {
607
+ this.#logger?.trace({
608
+ payload: subscribe
609
+ }, "sending subscribe message");
610
+ }
611
+ this.#ws?.send(JSON.stringify(subscribe));
502
612
  }
503
- const parts = Object.entries(whereClause);
504
- if (parts.length === 1) {
505
- return handleWherePair(parts[0], objectOrInterface);
506
- }
507
- return {
508
- type: "and",
509
- value: parts.map((v) => handleWherePair(v, objectOrInterface))
510
- };
511
- }
512
- function makeGeoFilterBbox(bbox, filterType, propertyIdentifier, field) {
513
- return {
514
- type: filterType === "$within" ? "withinBoundingBox" : "intersectsBoundingBox",
515
- /**
516
- * This is a bit ugly, but did this so that propertyIdentifier only shows up in the return object if its defined,
517
- * this makes it so we don't need to go update our entire test bed either to include a field which may change in near future.
518
- * Once we solidify that this is the way forward, I can remove field and clean this up
519
- */
520
- ...propertyIdentifier != null && {
521
- propertyIdentifier
522
- },
523
- field,
524
- value: {
525
- topLeft: {
526
- type: "Point",
527
- coordinates: [bbox[0], bbox[3]]
613
+ #unsubscribe(sub, newStatus = "done") {
614
+ if (subscriptionIsDone(sub)) {
615
+ return;
616
+ }
617
+ sub.status = newStatus;
618
+ sub.listener = fillOutListener({});
619
+ this.#subscriptions.delete(sub.subscriptionId);
620
+ this.#endedSubscriptions.add(sub.subscriptionId);
621
+ this.#sendSubscribeMessage();
622
+ if (this.#maybeDisconnectTimeout) {
623
+ clearTimeout(this.#maybeDisconnectTimeout);
624
+ }
625
+ this.#maybeDisconnectTimeout = setTimeout(
626
+ () => {
627
+ this.#maybeDisconnectTimeout = undefined;
628
+ if (this.#subscriptions.size === 0) {
629
+ this.#cycleWebsocket();
630
+ }
528
631
  },
529
- bottomRight: {
530
- type: "Point",
531
- coordinates: [bbox[2], bbox[1]]
632
+ 15e3
633
+ /* ms */
634
+ );
635
+ }
636
+ async #ensureWebsocket() {
637
+ if (this.#ws == null) {
638
+ const {
639
+ baseUrl,
640
+ tokenProvider
641
+ } = this.#client;
642
+ const url = constructWebsocketUrl(baseUrl, await this.#client.ontologyRid);
643
+ const token = await tokenProvider();
644
+ if (this.#ws == null) {
645
+ const nextConnectTime = (this.#lastWsConnect ?? 0) + this.MINIMUM_RECONNECT_DELAY_MS;
646
+ if (nextConnectTime > Date.now()) {
647
+ await new Promise((resolve) => {
648
+ setTimeout(resolve, nextConnectTime - Date.now());
649
+ });
650
+ }
651
+ this.#lastWsConnect = Date.now();
652
+ if (this.#ws == null) {
653
+ if (process.env.NODE_ENV !== "production") {
654
+ this.#logger?.trace("Creating websocket");
655
+ }
656
+ this.#ws = new WebSocket__default.default(url, [`Bearer-${token}`]);
657
+ this.#ws.addEventListener("close", this.#onClose);
658
+ this.#ws.addEventListener("message", this.#onMessage);
659
+ this.#ws.addEventListener("open", this.#onOpen);
660
+ }
661
+ }
662
+ if (this.#ws.readyState === WebSocket__default.default.CONNECTING) {
663
+ const ws = this.#ws;
664
+ return new Promise((resolve, reject) => {
665
+ function cleanup() {
666
+ ws.removeEventListener("open", open);
667
+ ws.removeEventListener("error", error);
668
+ ws.removeEventListener("close", cleanup);
669
+ }
670
+ function open() {
671
+ cleanup();
672
+ resolve();
673
+ }
674
+ function error(evt) {
675
+ cleanup();
676
+ reject(evt);
677
+ }
678
+ ws.addEventListener("open", open);
679
+ ws.addEventListener("error", error);
680
+ ws.addEventListener("close", cleanup);
681
+ });
532
682
  }
533
683
  }
684
+ }
685
+ #onOpen = () => {
686
+ this.#sendSubscribeMessage();
534
687
  };
535
- }
536
- function makeGeoFilterPolygon(coordinates, filterType, propertyIdentifier, field) {
537
- return {
538
- type: filterType,
539
- ...propertyIdentifier != null && {
540
- propertyIdentifier
541
- },
542
- field,
543
- value: {
544
- type: "Polygon",
545
- coordinates
688
+ #onMessage = async (message) => {
689
+ const data = JSON.parse(message.data.toString());
690
+ if (process.env.NODE_ENV !== "production") {
691
+ this.#logger?.trace({
692
+ payload: data
693
+ }, "received message from ws");
546
694
  }
547
- };
548
- }
549
- function handleWherePair([fieldName, filter], objectOrInterface, structFieldSelector) {
550
- !(filter != null) ? process.env.NODE_ENV !== "production" ? invariant__default.default(false, "Defined key values are only allowed when they are not undefined.") : invariant__default.default(false) : undefined;
551
- const propertyIdentifier = structFieldSelector != null ? {
552
- type: "structField",
553
- ...structFieldSelector,
554
- propertyApiName: fullyQualifyPropName(structFieldSelector.propertyApiName, objectOrInterface)
555
- } : undefined;
556
- const field = structFieldSelector == null ? fullyQualifyPropName(fieldName, objectOrInterface) : undefined;
557
- if (typeof filter === "string" || typeof filter === "number" || typeof filter === "boolean") {
558
- return {
559
- type: "eq",
560
- ...propertyIdentifier != null && {
561
- propertyIdentifier
562
- },
563
- field,
564
- value: filter
565
- };
566
- }
567
- const keysOfFilter = Object.keys(filter);
568
- const hasDollarSign = keysOfFilter.some((key) => key.startsWith("$"));
569
- !(!hasDollarSign || keysOfFilter.length === 1) ? process.env.NODE_ENV !== "production" ? invariant__default.default(false, "WhereClause Filter with multiple clauses isn't allowed") : invariant__default.default(false) : undefined;
570
- if (!hasDollarSign) {
571
- const structFilter = Object.entries(filter);
572
- !(structFilter.length === 1) ? process.env.NODE_ENV !== "production" ? invariant__default.default(false, "Cannot filter on more than one struct field in the same clause, need to use an and clause") : invariant__default.default(false) : undefined;
573
- const structFieldApiName = keysOfFilter[0];
574
- return handleWherePair(Object.entries(filter)[0], objectOrInterface, {
575
- propertyApiName: fieldName,
576
- structFieldApiName
577
- });
578
- }
579
- const firstKey = keysOfFilter[0];
580
- !(filter[firstKey] != null) ? process.env.NODE_ENV !== "production" ? invariant__default.default(false) : invariant__default.default(false) : undefined;
581
- if (firstKey === "$ne") {
582
- return {
583
- type: "not",
584
- value: {
585
- type: "eq",
586
- ...propertyIdentifier != null && {
587
- propertyIdentifier
588
- },
589
- field,
590
- value: filter[firstKey]
695
+ switch (data.type) {
696
+ case "objectSetChanged":
697
+ return void this.#handleMessage_objectSetChanged(data);
698
+ case "refreshObjectSet":
699
+ return void this.#handleMessage_refreshObjectSet(data);
700
+ case "subscribeResponses":
701
+ return void this.#handleMessage_subscribeResponses(data);
702
+ case "subscriptionClosed": {
703
+ return void this.#handleMessage_subscriptionClosed(data);
591
704
  }
592
- };
593
- }
594
- if (firstKey === "$within") {
595
- const withinBody = filter[firstKey];
596
- if (Array.isArray(withinBody)) {
597
- return makeGeoFilterBbox(withinBody, firstKey, propertyIdentifier, field);
598
- } else if ("$bbox" in withinBody && withinBody.$bbox != null) {
599
- return makeGeoFilterBbox(withinBody.$bbox, firstKey, propertyIdentifier, field);
600
- } else if ("$distance" in withinBody && "$of" in withinBody && withinBody.$distance != null && withinBody.$of != null) {
601
- return {
602
- type: "withinDistanceOf",
603
- ...propertyIdentifier != null && {
604
- propertyIdentifier
605
- },
606
- field,
607
- value: {
608
- center: Array.isArray(withinBody.$of) ? {
609
- type: "Point",
610
- coordinates: withinBody.$of
611
- } : withinBody.$of,
612
- distance: {
613
- value: withinBody.$distance[0],
614
- unit: api.DistanceUnitMapping[withinBody.$distance[1]]
615
- }
705
+ default:
706
+ process.env.NODE_ENV !== "production" ? invariant5__default.default(false, "Unexpected message type") : invariant5__default.default(false) ;
707
+ }
708
+ };
709
+ #handleMessage_objectSetChanged = async (payload) => {
710
+ const sub = this.#subscriptions.get(payload.id);
711
+ if (sub == null) return;
712
+ const objectUpdates = payload.updates.filter((update) => update.type === "object");
713
+ const referenceUpdates = payload.updates.filter((update) => update.type === "reference");
714
+ const osdkObjectsWithReferenceUpdates = await Promise.all(referenceUpdates.map(async (o) => {
715
+ const osdkObjectArray = await this.#client.objectFactory2(this.#client, [{
716
+ __apiName: o.objectType,
717
+ __primaryKey: sub.primaryKeyPropertyName != null ? o.primaryKey[sub.primaryKeyPropertyName] : undefined,
718
+ ...o.primaryKey,
719
+ [o.property]: o.value
720
+ }], sub.interfaceApiName, false, undefined, false, await this.#fetchInterfaceMapping(o.objectType, sub.interfaceApiName));
721
+ const singleOsdkObject = osdkObjectArray[0] ?? undefined;
722
+ return singleOsdkObject != null ? {
723
+ object: singleOsdkObject,
724
+ state: "ADDED_OR_UPDATED"
725
+ } : undefined;
726
+ }));
727
+ for (const osdkObject of osdkObjectsWithReferenceUpdates) {
728
+ if (osdkObject != null) {
729
+ try {
730
+ sub.listener.onChange?.(osdkObject);
731
+ } catch (error) {
732
+ this.#logger?.error(error, "Error in onChange callback");
733
+ this.#tryCatchOnError(sub, false, error);
616
734
  }
617
- };
618
- } else {
619
- const coordinates = "$polygon" in withinBody ? withinBody.$polygon : withinBody.coordinates;
620
- return makeGeoFilterPolygon(coordinates, "withinPolygon", propertyIdentifier, fieldName);
735
+ }
621
736
  }
622
- }
623
- if (firstKey === "$intersects") {
624
- const intersectsBody = filter[firstKey];
625
- if (Array.isArray(intersectsBody)) {
626
- return makeGeoFilterBbox(intersectsBody, firstKey, propertyIdentifier, field);
627
- } else if ("$bbox" in intersectsBody && intersectsBody.$bbox != null) {
628
- return makeGeoFilterBbox(intersectsBody.$bbox, firstKey, propertyIdentifier, field);
629
- } else {
630
- const coordinates = "$polygon" in intersectsBody ? intersectsBody.$polygon : intersectsBody.coordinates;
631
- return makeGeoFilterPolygon(coordinates, "intersectsPolygon", propertyIdentifier, field);
737
+ const osdkObjects = await Promise.all(objectUpdates.map(async (o) => {
738
+ const keysToDelete = Object.keys(o.object).filter((key) => sub.requestedReferenceProperties.includes(key));
739
+ for (const key of keysToDelete) {
740
+ delete o.object[key];
741
+ }
742
+ const osdkObjectArray = await this.#client.objectFactory2(this.#client, [o.object], sub.interfaceApiName, false, undefined, false, await this.#fetchInterfaceMapping(o.object.__apiName, sub.interfaceApiName));
743
+ const singleOsdkObject = osdkObjectArray[0] ?? undefined;
744
+ return singleOsdkObject != null ? {
745
+ object: singleOsdkObject,
746
+ state: o.state
747
+ } : undefined;
748
+ }));
749
+ for (const osdkObject of osdkObjects) {
750
+ if (osdkObject != null) {
751
+ try {
752
+ sub.listener.onChange?.(osdkObject);
753
+ } catch (error) {
754
+ this.#logger?.error(error, "Error in onChange callback");
755
+ this.#tryCatchOnError(sub, false, error);
756
+ }
757
+ }
632
758
  }
633
- }
634
- if (firstKey === "$containsAllTerms" || firstKey === "$containsAnyTerm") {
759
+ };
760
+ async #fetchInterfaceMapping(objectTypeApiName, interfaceApiName) {
761
+ if (interfaceApiName == null) return {};
762
+ const interfaceMap = (await this.#client.ontologyProvider.getObjectDefinition(objectTypeApiName)).interfaceMap;
635
763
  return {
636
- type: firstKey.substring(1),
637
- ...propertyIdentifier != null && {
638
- propertyIdentifier
639
- },
640
- field,
641
- value: typeof filter[firstKey] === "string" ? filter[firstKey] : filter[firstKey]["term"],
642
- fuzzy: typeof filter[firstKey] === "string" ? false : filter[firstKey]["fuzzySearch"] ?? false
764
+ [interfaceApiName]: {
765
+ [objectTypeApiName]: interfaceMap[interfaceApiName]
766
+ }
643
767
  };
644
768
  }
645
- return {
646
- type: firstKey.substring(1),
647
- ...propertyIdentifier != null && {
648
- propertyIdentifier
649
- },
650
- field,
651
- value: filter[firstKey]
769
+ #handleMessage_refreshObjectSet = (payload) => {
770
+ const sub = this.#subscriptions.get(payload.id);
771
+ !sub ? process.env.NODE_ENV !== "production" ? invariant5__default.default(false, `Expected subscription id ${payload.id}`) : invariant5__default.default(false) : undefined;
772
+ try {
773
+ sub.listener.onOutOfDate();
774
+ } catch (error) {
775
+ this.#logger?.error(error, "Error in onOutOfDate callback");
776
+ this.#tryCatchOnError(sub, false, error);
777
+ }
652
778
  };
653
- }
654
- function fullyQualifyPropName(fieldName, objectOrInterface) {
655
- if (objectOrInterface.type === "interface") {
656
- const [objApiNamespace] = extractNamespace(objectOrInterface.apiName);
657
- const [fieldApiNamespace, fieldShortName] = extractNamespace(fieldName);
658
- return fieldApiNamespace == null && objApiNamespace != null ? `${objApiNamespace}.${fieldShortName}` : fieldName;
659
- }
660
- return fieldName;
661
- }
662
-
663
- // src/derivedProperties/createWithPropertiesObjectSet.ts
664
- function createWithPropertiesObjectSet(objectType, objectSet, definitionMap) {
665
- const base = {
666
- pivotTo: (link) => {
667
- return createWithPropertiesObjectSet(objectType, {
668
- type: "searchAround",
669
- objectSet,
670
- link
671
- }, definitionMap);
672
- },
673
- where: (clause) => {
674
- return createWithPropertiesObjectSet(objectType, {
675
- type: "filter",
676
- objectSet,
677
- where: modernToLegacyWhereClause(clause, objectType)
678
- }, definitionMap);
679
- },
680
- aggregate: (aggregation, opt) => {
681
- const splitAggregation = aggregation.split(":");
682
- !(splitAggregation.length === 2 || splitAggregation[0] === "$count") ? process.env.NODE_ENV !== "production" ? invariant__default.default(false, "Invalid aggregation format") : invariant__default.default(false) : undefined;
683
- const [aggregationPropertyName, aggregationOperation] = splitAggregation;
684
- let aggregationOperationDefinition;
685
- switch (aggregationOperation) {
686
- case "sum":
687
- case "avg":
688
- case "min":
689
- case "max":
690
- case "exactDistinct":
691
- case "approximateDistinct":
692
- aggregationOperationDefinition = {
693
- type: aggregationOperation,
694
- selectedPropertyApiName: aggregationPropertyName
695
- };
696
- break;
697
- case "approximatePercentile":
698
- aggregationOperationDefinition = {
699
- type: "approximatePercentile",
700
- selectedPropertyApiName: aggregationPropertyName,
701
- approximatePercentile: opt?.percentile ?? 0.5
702
- };
779
+ #handleMessage_subscribeResponses = (payload) => {
780
+ const {
781
+ id,
782
+ responses
783
+ } = payload;
784
+ const subs = this.#pendingSubscriptions.get(id);
785
+ !subs ? process.env.NODE_ENV !== "production" ? invariant5__default.default(false, `should have a pending subscription for ${id}`) : invariant5__default.default(false) : undefined;
786
+ this.#pendingSubscriptions.delete(id);
787
+ for (let i = 0; i < responses.length; i++) {
788
+ const sub = subs[i];
789
+ const response = responses[i];
790
+ switch (response.type) {
791
+ case "error":
792
+ this.#tryCatchOnError(sub, true, response.errors);
793
+ this.#unsubscribe(sub, "error");
703
794
  break;
704
- case "collectSet":
705
- case "collectList":
706
- aggregationOperationDefinition = {
707
- type: aggregationOperation,
708
- selectedPropertyApiName: aggregationPropertyName,
709
- limit: opt?.limit ?? 100
710
- };
795
+ case "qos":
796
+ this.#cycleWebsocket();
711
797
  break;
712
- case undefined:
713
- if (aggregationPropertyName === "$count") {
714
- aggregationOperationDefinition = {
715
- type: "count"
716
- };
717
- break;
798
+ case "success":
799
+ const shouldFireOutOfDate = sub.status === "expired" || sub.status === "reconnecting";
800
+ if (process.env.NODE_ENV !== "production") {
801
+ this.#logger?.trace({
802
+ shouldFireOutOfDate
803
+ }, "success");
804
+ }
805
+ sub.status = "subscribed";
806
+ if (sub.subscriptionId !== response.id) {
807
+ this.#subscriptions.delete(sub.subscriptionId);
808
+ sub.subscriptionId = response.id;
809
+ this.#subscriptions.set(sub.subscriptionId, sub);
810
+ }
811
+ try {
812
+ if (shouldFireOutOfDate) sub.listener.onOutOfDate();
813
+ else sub.listener.onSuccessfulSubscription();
814
+ } catch (error) {
815
+ this.#logger?.error(error, "Error in onOutOfDate or onSuccessfulSubscription callback");
816
+ this.#tryCatchOnError(sub, false, error);
718
817
  }
818
+ break;
719
819
  default:
720
- process.env.NODE_ENV !== "production" ? invariant__default.default(false, "Invalid aggregation operation " + aggregationOperation) : invariant__default.default(false) ;
820
+ this.#tryCatchOnError(sub, true, response);
721
821
  }
722
- const selectorResult = {
723
- type: {}
724
- };
725
- definitionMap.set(selectorResult, {
726
- type: "selection",
727
- objectSet,
728
- operation: aggregationOperationDefinition
729
- });
730
- return selectorResult;
731
- },
732
- selectProperty: (name) => {
733
- const selectorResult = {
734
- type: {}
735
- };
736
- definitionMap.set(selectorResult, {
737
- type: "selection",
738
- objectSet,
739
- operation: {
740
- type: "get",
741
- selectedPropertyApiName: name
742
- }
743
- });
744
- return selectorResult;
745
822
  }
746
823
  };
747
- return base;
748
- }
749
- function legacyToModernSingleAggregationResult(entry) {
750
- return entry.metrics.reduce((accumulator, curValue) => {
751
- const parts = curValue.name.split(".");
752
- if (parts[0] === "count") {
753
- return accumulator;
754
- }
755
- !(parts.length === 2) ? process.env.NODE_ENV !== "production" ? invariant__default.default(false, "assumed we were getting a `${key}.${type}`") : invariant__default.default(false) : undefined;
756
- const property = parts[0];
757
- const metricType = parts[1];
758
- if (!(property in accumulator)) {
759
- accumulator[property] = {};
824
+ #handleMessage_subscriptionClosed(payload) {
825
+ const sub = this.#subscriptions.get(payload.id);
826
+ if (sub == null && this.#endedSubscriptions.has(payload.id)) return;
827
+ !sub ? process.env.NODE_ENV !== "production" ? invariant5__default.default(false, `Expected subscription id ${payload.id}`) : invariant5__default.default(false) : undefined;
828
+ this.#tryCatchOnError(sub, true, payload.cause);
829
+ this.#unsubscribe(sub, "error");
830
+ }
831
+ #onClose = (event) => {
832
+ if (process.env.NODE_ENV !== "production") {
833
+ this.#logger?.trace({
834
+ event
835
+ }, "Received close event from ws", event);
760
836
  }
761
- accumulator[property][metricType] = curValue.value;
762
- return accumulator;
763
- }, {});
764
- }
765
-
766
- // src/internal/conversions/modernToLegacyAggregationClause.ts
767
- var directionFieldMap = (dir) => dir === "asc" ? "ASC" : dir === "desc" ? "DESC" : undefined;
768
- function modernToLegacyAggregationClause(select) {
769
- return Object.entries(select).flatMap(([propAndMetric, aggregationType]) => {
770
- if (propAndMetric === "$count") {
771
- return {
772
- type: "count",
773
- name: "count",
774
- direction: directionFieldMap(aggregationType)
775
- };
837
+ this.#cycleWebsocket();
838
+ };
839
+ #cycleWebsocket = () => {
840
+ if (this.#ws) {
841
+ this.#ws.removeEventListener("open", this.#onOpen);
842
+ this.#ws.removeEventListener("message", this.#onMessage);
843
+ this.#ws.removeEventListener("close", this.#onClose);
844
+ if (this.#ws.readyState !== WebSocket__default.default.CLOSING && this.#ws.readyState !== WebSocket__default.default.CLOSED) {
845
+ this.#ws.close();
846
+ }
847
+ this.#ws = undefined;
776
848
  }
777
- const colonPos = propAndMetric.lastIndexOf(":");
778
- const property = propAndMetric.slice(0, colonPos);
779
- const metric = propAndMetric.slice(colonPos + 1);
780
- return [{
781
- type: metric,
782
- name: `${property}.${metric}`,
783
- direction: directionFieldMap(aggregationType),
784
- field: property
785
- }];
786
- });
787
- }
788
- function modernToLegacyGroupByClause(groupByClause) {
789
- if (!groupByClause) return [];
790
- return Object.entries(groupByClause).flatMap(([field, type]) => {
791
- if (type === "exact") {
792
- return [{
793
- type,
794
- field
795
- }];
796
- } else if ("$exactWithLimit" in type) {
797
- {
798
- return [{
799
- type: "exact",
800
- field,
801
- maxGroupCount: type.$exactWithLimit
802
- }];
849
+ if (this.#subscriptions.size > 0) {
850
+ if (process.env.NODE_ENV !== "production") {
851
+ for (const s of this.#subscriptions.values()) {
852
+ !(s.status !== "done" && s.status !== "error") ? process.env.NODE_ENV !== "production" ? invariant5__default.default(false, "should not have done/error subscriptions still") : invariant5__default.default(false) : undefined;
853
+ }
803
854
  }
804
- } else if ("$fixedWidth" in type) {
805
- return [{
806
- type: "fixedWidth",
807
- field,
808
- fixedWidth: type.$fixedWidth
809
- }];
810
- } else if ("$ranges" in type) {
811
- return [{
812
- type: "ranges",
813
- field,
814
- ranges: type.$ranges.map((range) => convertRange(range))
815
- }];
816
- } else if ("$duration" in type) {
817
- return [{
818
- type: "duration",
819
- field,
820
- value: type.$duration[0],
821
- unit: api.DurationMapping[type.$duration[1]]
822
- }];
823
- } else return [];
824
- });
825
- }
826
- function convertRange(range) {
827
- return {
828
- startValue: range[0],
829
- endValue: range[1]
830
- };
831
- }
832
-
833
- // src/object/aggregate.ts
834
- async function aggregate(clientCtx, objectType, objectSet = chunkTCHGLBKJ_cjs.resolveBaseObjectSetType(objectType), req) {
835
- chunkTCHGLBKJ_cjs.resolveBaseObjectSetType(objectType);
836
- const body = {
837
- aggregation: modernToLegacyAggregationClause(req.$select),
838
- groupBy: [],
839
- where: undefined
855
+ for (const s of this.#subscriptions.values()) {
856
+ if (s.status === "subscribed") s.status = "reconnecting";
857
+ }
858
+ void this.#ensureWebsocket();
859
+ }
840
860
  };
841
- if (req.$groupBy) {
842
- body.groupBy = modernToLegacyGroupByClause(req.$groupBy);
843
- }
844
- const result = await chunkTCHGLBKJ_cjs.OntologyObjectSet_exports.aggregate(chunkTCHGLBKJ_cjs.addUserAgentAndRequestContextHeaders(clientCtx, objectType), await clientCtx.ontologyRid, {
845
- objectSet,
846
- groupBy: body.groupBy,
847
- aggregation: body.aggregation
848
- });
849
- if (!req.$groupBy) {
850
- !(result.data.length === 1) ? process.env.NODE_ENV !== "production" ? invariant__default.default(false, "no group by clause should mean only one data result") : invariant__default.default(false) : undefined;
851
- return {
852
- ...aggregationToCountResult(result.data[0]),
853
- ...legacyToModernSingleAggregationResult(result.data[0])
854
- };
855
- }
856
- const ret = result.data.map((entry) => {
857
- return {
858
- $group: entry.group,
859
- ...aggregationToCountResult(entry),
860
- ...legacyToModernSingleAggregationResult(entry)
861
- };
862
- });
863
- return ret;
864
- }
865
- function aggregationToCountResult(entry) {
866
- for (const aggregateResult of entry.metrics) {
867
- if (aggregateResult.name === "count") {
868
- return {
869
- $count: aggregateResult.value
870
- };
861
+ #tryCatchOnError = (sub, subscriptionClosed, error) => {
862
+ try {
863
+ sub.listener.onError({
864
+ subscriptionClosed,
865
+ error
866
+ });
867
+ } catch (onErrorError) {
868
+ console.error(`Error encountered in an onError callback for an OSDK subscription`, onErrorError);
869
+ console.error(`This onError call was triggered by an error in another callback`, error);
870
+ console.error(`The subscription has been closed.`, error);
871
+ if (!subscriptionClosed) {
872
+ this.#logger?.error(error, "Error in onError callback");
873
+ this.#unsubscribe(sub, "error");
874
+ this.#tryCatchOnError(sub, true, onErrorError);
875
+ }
871
876
  }
872
- }
877
+ };
878
+ };
879
+ function constructWebsocketUrl(baseUrl, ontologyRid) {
880
+ const base = new URL(baseUrl);
881
+ const url = new URL(`api/v2/ontologySubscriptions/ontologies/${ontologyRid}/streamSubscriptions`, base);
882
+ url.protocol = url.protocol.replace("https", "wss");
883
+ return url;
873
884
  }
874
-
875
- // src/util/augmentRequestContext.ts
876
- var augmentRequestContext = (client, augment) => ({
877
- ...client,
878
- requestContext: {
879
- ...client.requestContext,
880
- ...augment(client.requestContext)
881
- }
882
- });
883
-
884
- // src/util/WireObjectSet.ts
885
- var WIRE_OBJECT_SET_TYPES = /* @__PURE__ */ new Set(["base", "filter", "intersect", "reference", "searchAround", "static", "subtract", "union"]);
886
- function isWireObjectSet(o) {
887
- return o != null && typeof o === "object" && WIRE_OBJECT_SET_TYPES.has(o.type);
885
+ var uuidCounter = 0;
886
+ function nextUuid() {
887
+ return `00000000-0000-0000-0000-${(uuidCounter++).toString().padStart(12, "0")}`;
888
888
  }
889
889
 
890
890
  // src/objectSet/createObjectSet.ts
@@ -1013,7 +1013,6 @@ async function createWithPk(clientCtx, objectType, objectSet, primaryKey) {
1013
1013
  return withPk;
1014
1014
  }
1015
1015
 
1016
- exports.ObjectSetListenerWebsocket = ObjectSetListenerWebsocket;
1017
1016
  exports.additionalContext = additionalContext;
1018
1017
  exports.augmentRequestContext = augmentRequestContext;
1019
1018
  exports.createObjectSet = createObjectSet;
@@ -1025,5 +1024,5 @@ exports.hydrateAttachmentFromRid = hydrateAttachmentFromRid;
1025
1024
  exports.hydrateAttachmentFromRidInternal = hydrateAttachmentFromRidInternal;
1026
1025
  exports.isObjectSet = isObjectSet;
1027
1026
  exports.isWireObjectSet = isWireObjectSet;
1028
- //# sourceMappingURL=chunk-JIK7S65P.cjs.map
1029
- //# sourceMappingURL=chunk-JIK7S65P.cjs.map
1027
+ //# sourceMappingURL=chunk-R4DO4ZKB.cjs.map
1028
+ //# sourceMappingURL=chunk-R4DO4ZKB.cjs.map