@objectstack/client 3.0.8 → 3.0.10

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.
package/dist/index.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  // src/index.ts
2
+ import { isFilterAST } from "@objectstack/spec/data";
2
3
  import { createLogger } from "@objectstack/core";
3
4
 
4
5
  // src/query-builder.ts
@@ -586,12 +587,80 @@ var ObjectStackClient = class {
586
587
  const res = await this.fetch(`${this.baseUrl}${route}/files/${fileId}/url`);
587
588
  const data = await res.json();
588
589
  return data.url;
590
+ },
591
+ /**
592
+ * Get a presigned URL for direct-to-cloud upload
593
+ */
594
+ getPresignedUrl: async (req) => {
595
+ const route = this.getRoute("storage");
596
+ const res = await this.fetch(`${this.baseUrl}${route}/upload/presigned`, {
597
+ method: "POST",
598
+ body: JSON.stringify(req)
599
+ });
600
+ return res.json();
601
+ },
602
+ /**
603
+ * Initiate a chunked (multipart) upload session
604
+ */
605
+ initChunkedUpload: async (req) => {
606
+ const route = this.getRoute("storage");
607
+ const res = await this.fetch(`${this.baseUrl}${route}/upload/chunked`, {
608
+ method: "POST",
609
+ body: JSON.stringify(req)
610
+ });
611
+ return res.json();
612
+ },
613
+ /**
614
+ * Upload a single chunk/part of a multipart upload
615
+ */
616
+ uploadPart: async (uploadId, chunkIndex, resumeToken, data) => {
617
+ const route = this.getRoute("storage");
618
+ const res = await this.fetch(`${this.baseUrl}${route}/upload/chunked/${uploadId}/chunk/${chunkIndex}`, {
619
+ method: "PUT",
620
+ headers: { "x-resume-token": resumeToken },
621
+ body: data
622
+ });
623
+ return res.json();
624
+ },
625
+ /**
626
+ * Complete a chunked upload by assembling all parts
627
+ */
628
+ completeChunkedUpload: async (req) => {
629
+ const route = this.getRoute("storage");
630
+ const res = await this.fetch(`${this.baseUrl}${route}/upload/chunked/${req.uploadId}/complete`, {
631
+ method: "POST",
632
+ body: JSON.stringify(req)
633
+ });
634
+ return res.json();
635
+ },
636
+ /**
637
+ * Resume an interrupted chunked upload.
638
+ * Fetches current progress, then uploads remaining chunks and completes.
639
+ */
640
+ resumeUpload: async (uploadId, file, chunkSize, resumeToken) => {
641
+ const route = this.getRoute("storage");
642
+ const progressRes = await this.fetch(`${this.baseUrl}${route}/upload/chunked/${uploadId}/progress`);
643
+ const progress = await progressRes.json();
644
+ const { totalChunks, uploadedChunks } = progress.data;
645
+ const parts = [];
646
+ const fileBuffer = file instanceof ArrayBuffer ? file : await file.arrayBuffer();
647
+ for (let i = uploadedChunks; i < totalChunks; i++) {
648
+ const start = i * chunkSize;
649
+ const end = Math.min(start + chunkSize, fileBuffer.byteLength);
650
+ const chunk = new Blob([fileBuffer.slice(start, end)]);
651
+ const chunkRes = await this.storage.uploadPart(uploadId, i, resumeToken, chunk);
652
+ parts.push({ chunkIndex: i, eTag: chunkRes.data.eTag });
653
+ }
654
+ return this.storage.completeChunkedUpload({ uploadId, parts });
589
655
  }
590
656
  };
591
657
  /**
592
658
  * Automation Services
593
659
  */
594
660
  this.automation = {
661
+ /**
662
+ * Trigger a named automation flow (legacy endpoint)
663
+ */
595
664
  trigger: async (triggerName, payload) => {
596
665
  const route = this.getRoute("automation");
597
666
  const res = await this.fetch(`${this.baseUrl}${route}/trigger/${triggerName}`, {
@@ -599,6 +668,90 @@ var ObjectStackClient = class {
599
668
  body: JSON.stringify(payload)
600
669
  });
601
670
  return res.json();
671
+ },
672
+ /**
673
+ * List all registered automation flows
674
+ */
675
+ list: async () => {
676
+ const route = this.getRoute("automation");
677
+ const res = await this.fetch(`${this.baseUrl}${route}`);
678
+ return this.unwrapResponse(res);
679
+ },
680
+ /**
681
+ * Get a flow definition by name
682
+ */
683
+ get: async (name) => {
684
+ const route = this.getRoute("automation");
685
+ const res = await this.fetch(`${this.baseUrl}${route}/${name}`);
686
+ return this.unwrapResponse(res);
687
+ },
688
+ /**
689
+ * Create (register) a new flow
690
+ */
691
+ create: async (name, definition) => {
692
+ const route = this.getRoute("automation");
693
+ const res = await this.fetch(`${this.baseUrl}${route}`, {
694
+ method: "POST",
695
+ body: JSON.stringify({ name, ...definition })
696
+ });
697
+ return this.unwrapResponse(res);
698
+ },
699
+ /**
700
+ * Update an existing flow
701
+ */
702
+ update: async (name, definition) => {
703
+ const route = this.getRoute("automation");
704
+ const res = await this.fetch(`${this.baseUrl}${route}/${name}`, {
705
+ method: "PUT",
706
+ body: JSON.stringify({ definition })
707
+ });
708
+ return this.unwrapResponse(res);
709
+ },
710
+ /**
711
+ * Delete (unregister) a flow
712
+ */
713
+ delete: async (name) => {
714
+ const route = this.getRoute("automation");
715
+ const res = await this.fetch(`${this.baseUrl}${route}/${name}`, {
716
+ method: "DELETE"
717
+ });
718
+ return this.unwrapResponse(res);
719
+ },
720
+ /**
721
+ * Enable or disable a flow
722
+ */
723
+ toggle: async (name, enabled) => {
724
+ const route = this.getRoute("automation");
725
+ const res = await this.fetch(`${this.baseUrl}${route}/${name}/toggle`, {
726
+ method: "POST",
727
+ body: JSON.stringify({ enabled })
728
+ });
729
+ return this.unwrapResponse(res);
730
+ },
731
+ /**
732
+ * Execution run history
733
+ */
734
+ runs: {
735
+ /**
736
+ * List execution runs for a flow
737
+ */
738
+ list: async (flowName, options) => {
739
+ const route = this.getRoute("automation");
740
+ const params = new URLSearchParams();
741
+ if (options?.limit) params.set("limit", String(options.limit));
742
+ if (options?.cursor) params.set("cursor", options.cursor);
743
+ const qs = params.toString();
744
+ const res = await this.fetch(`${this.baseUrl}${route}/${flowName}/runs${qs ? `?${qs}` : ""}`);
745
+ return this.unwrapResponse(res);
746
+ },
747
+ /**
748
+ * Get a single execution run
749
+ */
750
+ get: async (flowName, runId) => {
751
+ const route = this.getRoute("automation");
752
+ const res = await this.fetch(`${this.baseUrl}${route}/${flowName}/runs/${runId}`);
753
+ return this.unwrapResponse(res);
754
+ }
602
755
  }
603
756
  };
604
757
  /**
@@ -979,6 +1132,174 @@ var ObjectStackClient = class {
979
1132
  return this.unwrapResponse(res);
980
1133
  }
981
1134
  };
1135
+ /**
1136
+ * Feed / Chatter Services
1137
+ *
1138
+ * Provides access to the activity timeline (comments, field changes, tasks),
1139
+ * emoji reactions, pin/star, search, changelog, and record subscriptions.
1140
+ * Base path: /api/data/{object}/{recordId}/feed
1141
+ */
1142
+ this.feed = {
1143
+ /**
1144
+ * List feed items for a record
1145
+ */
1146
+ list: async (object, recordId, options) => {
1147
+ const route = this.getRoute("feed");
1148
+ const params = new URLSearchParams();
1149
+ if (options?.type) params.set("type", options.type);
1150
+ if (options?.limit) params.set("limit", String(options.limit));
1151
+ if (options?.cursor) params.set("cursor", options.cursor);
1152
+ const qs = params.toString();
1153
+ const res = await this.fetch(`${this.baseUrl}${route}/${encodeURIComponent(object)}/${encodeURIComponent(recordId)}/feed${qs ? `?${qs}` : ""}`);
1154
+ return this.unwrapResponse(res);
1155
+ },
1156
+ /**
1157
+ * Create a new feed item (comment, note, task, etc.)
1158
+ */
1159
+ create: async (object, recordId, data) => {
1160
+ const route = this.getRoute("feed");
1161
+ const res = await this.fetch(`${this.baseUrl}${route}/${encodeURIComponent(object)}/${encodeURIComponent(recordId)}/feed`, {
1162
+ method: "POST",
1163
+ body: JSON.stringify(data)
1164
+ });
1165
+ return this.unwrapResponse(res);
1166
+ },
1167
+ /**
1168
+ * Update an existing feed item
1169
+ */
1170
+ update: async (object, recordId, feedId, data) => {
1171
+ const route = this.getRoute("feed");
1172
+ const res = await this.fetch(`${this.baseUrl}${route}/${encodeURIComponent(object)}/${encodeURIComponent(recordId)}/feed/${encodeURIComponent(feedId)}`, {
1173
+ method: "PUT",
1174
+ body: JSON.stringify(data)
1175
+ });
1176
+ return this.unwrapResponse(res);
1177
+ },
1178
+ /**
1179
+ * Delete a feed item
1180
+ */
1181
+ delete: async (object, recordId, feedId) => {
1182
+ const route = this.getRoute("feed");
1183
+ const res = await this.fetch(`${this.baseUrl}${route}/${encodeURIComponent(object)}/${encodeURIComponent(recordId)}/feed/${encodeURIComponent(feedId)}`, {
1184
+ method: "DELETE"
1185
+ });
1186
+ return this.unwrapResponse(res);
1187
+ },
1188
+ /**
1189
+ * Add an emoji reaction to a feed item
1190
+ */
1191
+ addReaction: async (object, recordId, feedId, emoji) => {
1192
+ const route = this.getRoute("feed");
1193
+ const res = await this.fetch(`${this.baseUrl}${route}/${encodeURIComponent(object)}/${encodeURIComponent(recordId)}/feed/${encodeURIComponent(feedId)}/reactions`, {
1194
+ method: "POST",
1195
+ body: JSON.stringify({ emoji })
1196
+ });
1197
+ return this.unwrapResponse(res);
1198
+ },
1199
+ /**
1200
+ * Remove an emoji reaction from a feed item
1201
+ */
1202
+ removeReaction: async (object, recordId, feedId, emoji) => {
1203
+ const route = this.getRoute("feed");
1204
+ const res = await this.fetch(`${this.baseUrl}${route}/${encodeURIComponent(object)}/${encodeURIComponent(recordId)}/feed/${encodeURIComponent(feedId)}/reactions/${encodeURIComponent(emoji)}`, {
1205
+ method: "DELETE"
1206
+ });
1207
+ return this.unwrapResponse(res);
1208
+ },
1209
+ /**
1210
+ * Pin a feed item to the top of the timeline
1211
+ */
1212
+ pin: async (object, recordId, feedId) => {
1213
+ const route = this.getRoute("feed");
1214
+ const res = await this.fetch(`${this.baseUrl}${route}/${encodeURIComponent(object)}/${encodeURIComponent(recordId)}/feed/${encodeURIComponent(feedId)}/pin`, {
1215
+ method: "POST"
1216
+ });
1217
+ return this.unwrapResponse(res);
1218
+ },
1219
+ /**
1220
+ * Unpin a feed item
1221
+ */
1222
+ unpin: async (object, recordId, feedId) => {
1223
+ const route = this.getRoute("feed");
1224
+ const res = await this.fetch(`${this.baseUrl}${route}/${encodeURIComponent(object)}/${encodeURIComponent(recordId)}/feed/${encodeURIComponent(feedId)}/pin`, {
1225
+ method: "DELETE"
1226
+ });
1227
+ return this.unwrapResponse(res);
1228
+ },
1229
+ /**
1230
+ * Star (bookmark) a feed item
1231
+ */
1232
+ star: async (object, recordId, feedId) => {
1233
+ const route = this.getRoute("feed");
1234
+ const res = await this.fetch(`${this.baseUrl}${route}/${encodeURIComponent(object)}/${encodeURIComponent(recordId)}/feed/${encodeURIComponent(feedId)}/star`, {
1235
+ method: "POST"
1236
+ });
1237
+ return this.unwrapResponse(res);
1238
+ },
1239
+ /**
1240
+ * Unstar a feed item
1241
+ */
1242
+ unstar: async (object, recordId, feedId) => {
1243
+ const route = this.getRoute("feed");
1244
+ const res = await this.fetch(`${this.baseUrl}${route}/${encodeURIComponent(object)}/${encodeURIComponent(recordId)}/feed/${encodeURIComponent(feedId)}/star`, {
1245
+ method: "DELETE"
1246
+ });
1247
+ return this.unwrapResponse(res);
1248
+ },
1249
+ /**
1250
+ * Search feed items
1251
+ */
1252
+ search: async (object, recordId, query, options) => {
1253
+ const route = this.getRoute("feed");
1254
+ const params = new URLSearchParams();
1255
+ params.set("query", query);
1256
+ if (options?.type) params.set("type", options.type);
1257
+ if (options?.actorId) params.set("actorId", options.actorId);
1258
+ if (options?.dateFrom) params.set("dateFrom", options.dateFrom);
1259
+ if (options?.dateTo) params.set("dateTo", options.dateTo);
1260
+ if (options?.limit) params.set("limit", String(options.limit));
1261
+ if (options?.cursor) params.set("cursor", options.cursor);
1262
+ const res = await this.fetch(`${this.baseUrl}${route}/${encodeURIComponent(object)}/${encodeURIComponent(recordId)}/feed/search?${params.toString()}`);
1263
+ return this.unwrapResponse(res);
1264
+ },
1265
+ /**
1266
+ * Get field-level changelog for a record
1267
+ */
1268
+ getChangelog: async (object, recordId, options) => {
1269
+ const route = this.getRoute("feed");
1270
+ const params = new URLSearchParams();
1271
+ if (options?.field) params.set("field", options.field);
1272
+ if (options?.actorId) params.set("actorId", options.actorId);
1273
+ if (options?.dateFrom) params.set("dateFrom", options.dateFrom);
1274
+ if (options?.dateTo) params.set("dateTo", options.dateTo);
1275
+ if (options?.limit) params.set("limit", String(options.limit));
1276
+ if (options?.cursor) params.set("cursor", options.cursor);
1277
+ const qs = params.toString();
1278
+ const res = await this.fetch(`${this.baseUrl}${route}/${encodeURIComponent(object)}/${encodeURIComponent(recordId)}/changelog${qs ? `?${qs}` : ""}`);
1279
+ return this.unwrapResponse(res);
1280
+ },
1281
+ /**
1282
+ * Subscribe to record notifications
1283
+ */
1284
+ subscribe: async (object, recordId, options) => {
1285
+ const route = this.getRoute("feed");
1286
+ const res = await this.fetch(`${this.baseUrl}${route}/${encodeURIComponent(object)}/${encodeURIComponent(recordId)}/subscribe`, {
1287
+ method: "POST",
1288
+ body: JSON.stringify(options || {})
1289
+ });
1290
+ return this.unwrapResponse(res);
1291
+ },
1292
+ /**
1293
+ * Unsubscribe from record notifications
1294
+ */
1295
+ unsubscribe: async (object, recordId) => {
1296
+ const route = this.getRoute("feed");
1297
+ const res = await this.fetch(`${this.baseUrl}${route}/${encodeURIComponent(object)}/${encodeURIComponent(recordId)}/subscribe`, {
1298
+ method: "DELETE"
1299
+ });
1300
+ return this.unwrapResponse(res);
1301
+ }
1302
+ };
982
1303
  /**
983
1304
  * Data Operations
984
1305
  */
@@ -1011,11 +1332,12 @@ var ObjectStackClient = class {
1011
1332
  if (options.select) {
1012
1333
  queryParams.set("select", options.select.join(","));
1013
1334
  }
1014
- if (options.filters) {
1015
- if (this.isFilterAST(options.filters)) {
1016
- queryParams.set("filters", JSON.stringify(options.filters));
1017
- } else {
1018
- Object.entries(options.filters).forEach(([k, v]) => {
1335
+ const filterValue = options.filter ?? options.filters;
1336
+ if (filterValue) {
1337
+ if (this.isFilterAST(filterValue) || Array.isArray(filterValue)) {
1338
+ queryParams.set("filter", JSON.stringify(filterValue));
1339
+ } else if (typeof filterValue === "object" && filterValue !== null) {
1340
+ Object.entries(filterValue).forEach(([k, v]) => {
1019
1341
  if (v !== void 0 && v !== null) {
1020
1342
  queryParams.append(k, String(v));
1021
1343
  }
@@ -1170,11 +1492,19 @@ var ObjectStackClient = class {
1170
1492
  throw e;
1171
1493
  }
1172
1494
  }
1495
+ /**
1496
+ * Well-known capability flags discovered from the server.
1497
+ * Returns undefined if the client has not yet connected or the server
1498
+ * did not include capabilities in its discovery response.
1499
+ */
1500
+ get capabilities() {
1501
+ return this.discoveryInfo?.capabilities;
1502
+ }
1173
1503
  /**
1174
1504
  * Private Helpers
1175
1505
  */
1176
1506
  isFilterAST(filter) {
1177
- return Array.isArray(filter);
1507
+ return isFilterAST(filter);
1178
1508
  }
1179
1509
  /**
1180
1510
  * Unwrap the standard REST API response envelope.
@@ -1259,7 +1589,8 @@ var ObjectStackClient = class {
1259
1589
  views: "/api/v1/ui/views",
1260
1590
  notifications: "/api/v1/notifications",
1261
1591
  ai: "/api/v1/ai",
1262
- i18n: "/api/v1/i18n"
1592
+ i18n: "/api/v1/i18n",
1593
+ feed: "/api/v1/data"
1263
1594
  };
1264
1595
  return routeMap[type] || `/api/v1/${type}`;
1265
1596
  }