@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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +16 -0
- package/dist/index.d.mts +201 -3
- package/dist/index.d.ts +201 -3
- package/dist/index.js +338 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +338 -7
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -9
- package/src/client.feed.test.ts +273 -0
- package/src/client.msw.test.ts +4 -2
- package/src/client.test.ts +179 -0
- package/src/index.ts +425 -17
package/dist/index.js
CHANGED
|
@@ -27,6 +27,7 @@ __export(index_exports, {
|
|
|
27
27
|
createQuery: () => createQuery
|
|
28
28
|
});
|
|
29
29
|
module.exports = __toCommonJS(index_exports);
|
|
30
|
+
var import_data = require("@objectstack/spec/data");
|
|
30
31
|
var import_core = require("@objectstack/core");
|
|
31
32
|
|
|
32
33
|
// src/query-builder.ts
|
|
@@ -614,12 +615,80 @@ var ObjectStackClient = class {
|
|
|
614
615
|
const res = await this.fetch(`${this.baseUrl}${route}/files/${fileId}/url`);
|
|
615
616
|
const data = await res.json();
|
|
616
617
|
return data.url;
|
|
618
|
+
},
|
|
619
|
+
/**
|
|
620
|
+
* Get a presigned URL for direct-to-cloud upload
|
|
621
|
+
*/
|
|
622
|
+
getPresignedUrl: async (req) => {
|
|
623
|
+
const route = this.getRoute("storage");
|
|
624
|
+
const res = await this.fetch(`${this.baseUrl}${route}/upload/presigned`, {
|
|
625
|
+
method: "POST",
|
|
626
|
+
body: JSON.stringify(req)
|
|
627
|
+
});
|
|
628
|
+
return res.json();
|
|
629
|
+
},
|
|
630
|
+
/**
|
|
631
|
+
* Initiate a chunked (multipart) upload session
|
|
632
|
+
*/
|
|
633
|
+
initChunkedUpload: async (req) => {
|
|
634
|
+
const route = this.getRoute("storage");
|
|
635
|
+
const res = await this.fetch(`${this.baseUrl}${route}/upload/chunked`, {
|
|
636
|
+
method: "POST",
|
|
637
|
+
body: JSON.stringify(req)
|
|
638
|
+
});
|
|
639
|
+
return res.json();
|
|
640
|
+
},
|
|
641
|
+
/**
|
|
642
|
+
* Upload a single chunk/part of a multipart upload
|
|
643
|
+
*/
|
|
644
|
+
uploadPart: async (uploadId, chunkIndex, resumeToken, data) => {
|
|
645
|
+
const route = this.getRoute("storage");
|
|
646
|
+
const res = await this.fetch(`${this.baseUrl}${route}/upload/chunked/${uploadId}/chunk/${chunkIndex}`, {
|
|
647
|
+
method: "PUT",
|
|
648
|
+
headers: { "x-resume-token": resumeToken },
|
|
649
|
+
body: data
|
|
650
|
+
});
|
|
651
|
+
return res.json();
|
|
652
|
+
},
|
|
653
|
+
/**
|
|
654
|
+
* Complete a chunked upload by assembling all parts
|
|
655
|
+
*/
|
|
656
|
+
completeChunkedUpload: async (req) => {
|
|
657
|
+
const route = this.getRoute("storage");
|
|
658
|
+
const res = await this.fetch(`${this.baseUrl}${route}/upload/chunked/${req.uploadId}/complete`, {
|
|
659
|
+
method: "POST",
|
|
660
|
+
body: JSON.stringify(req)
|
|
661
|
+
});
|
|
662
|
+
return res.json();
|
|
663
|
+
},
|
|
664
|
+
/**
|
|
665
|
+
* Resume an interrupted chunked upload.
|
|
666
|
+
* Fetches current progress, then uploads remaining chunks and completes.
|
|
667
|
+
*/
|
|
668
|
+
resumeUpload: async (uploadId, file, chunkSize, resumeToken) => {
|
|
669
|
+
const route = this.getRoute("storage");
|
|
670
|
+
const progressRes = await this.fetch(`${this.baseUrl}${route}/upload/chunked/${uploadId}/progress`);
|
|
671
|
+
const progress = await progressRes.json();
|
|
672
|
+
const { totalChunks, uploadedChunks } = progress.data;
|
|
673
|
+
const parts = [];
|
|
674
|
+
const fileBuffer = file instanceof ArrayBuffer ? file : await file.arrayBuffer();
|
|
675
|
+
for (let i = uploadedChunks; i < totalChunks; i++) {
|
|
676
|
+
const start = i * chunkSize;
|
|
677
|
+
const end = Math.min(start + chunkSize, fileBuffer.byteLength);
|
|
678
|
+
const chunk = new Blob([fileBuffer.slice(start, end)]);
|
|
679
|
+
const chunkRes = await this.storage.uploadPart(uploadId, i, resumeToken, chunk);
|
|
680
|
+
parts.push({ chunkIndex: i, eTag: chunkRes.data.eTag });
|
|
681
|
+
}
|
|
682
|
+
return this.storage.completeChunkedUpload({ uploadId, parts });
|
|
617
683
|
}
|
|
618
684
|
};
|
|
619
685
|
/**
|
|
620
686
|
* Automation Services
|
|
621
687
|
*/
|
|
622
688
|
this.automation = {
|
|
689
|
+
/**
|
|
690
|
+
* Trigger a named automation flow (legacy endpoint)
|
|
691
|
+
*/
|
|
623
692
|
trigger: async (triggerName, payload) => {
|
|
624
693
|
const route = this.getRoute("automation");
|
|
625
694
|
const res = await this.fetch(`${this.baseUrl}${route}/trigger/${triggerName}`, {
|
|
@@ -627,6 +696,90 @@ var ObjectStackClient = class {
|
|
|
627
696
|
body: JSON.stringify(payload)
|
|
628
697
|
});
|
|
629
698
|
return res.json();
|
|
699
|
+
},
|
|
700
|
+
/**
|
|
701
|
+
* List all registered automation flows
|
|
702
|
+
*/
|
|
703
|
+
list: async () => {
|
|
704
|
+
const route = this.getRoute("automation");
|
|
705
|
+
const res = await this.fetch(`${this.baseUrl}${route}`);
|
|
706
|
+
return this.unwrapResponse(res);
|
|
707
|
+
},
|
|
708
|
+
/**
|
|
709
|
+
* Get a flow definition by name
|
|
710
|
+
*/
|
|
711
|
+
get: async (name) => {
|
|
712
|
+
const route = this.getRoute("automation");
|
|
713
|
+
const res = await this.fetch(`${this.baseUrl}${route}/${name}`);
|
|
714
|
+
return this.unwrapResponse(res);
|
|
715
|
+
},
|
|
716
|
+
/**
|
|
717
|
+
* Create (register) a new flow
|
|
718
|
+
*/
|
|
719
|
+
create: async (name, definition) => {
|
|
720
|
+
const route = this.getRoute("automation");
|
|
721
|
+
const res = await this.fetch(`${this.baseUrl}${route}`, {
|
|
722
|
+
method: "POST",
|
|
723
|
+
body: JSON.stringify({ name, ...definition })
|
|
724
|
+
});
|
|
725
|
+
return this.unwrapResponse(res);
|
|
726
|
+
},
|
|
727
|
+
/**
|
|
728
|
+
* Update an existing flow
|
|
729
|
+
*/
|
|
730
|
+
update: async (name, definition) => {
|
|
731
|
+
const route = this.getRoute("automation");
|
|
732
|
+
const res = await this.fetch(`${this.baseUrl}${route}/${name}`, {
|
|
733
|
+
method: "PUT",
|
|
734
|
+
body: JSON.stringify({ definition })
|
|
735
|
+
});
|
|
736
|
+
return this.unwrapResponse(res);
|
|
737
|
+
},
|
|
738
|
+
/**
|
|
739
|
+
* Delete (unregister) a flow
|
|
740
|
+
*/
|
|
741
|
+
delete: async (name) => {
|
|
742
|
+
const route = this.getRoute("automation");
|
|
743
|
+
const res = await this.fetch(`${this.baseUrl}${route}/${name}`, {
|
|
744
|
+
method: "DELETE"
|
|
745
|
+
});
|
|
746
|
+
return this.unwrapResponse(res);
|
|
747
|
+
},
|
|
748
|
+
/**
|
|
749
|
+
* Enable or disable a flow
|
|
750
|
+
*/
|
|
751
|
+
toggle: async (name, enabled) => {
|
|
752
|
+
const route = this.getRoute("automation");
|
|
753
|
+
const res = await this.fetch(`${this.baseUrl}${route}/${name}/toggle`, {
|
|
754
|
+
method: "POST",
|
|
755
|
+
body: JSON.stringify({ enabled })
|
|
756
|
+
});
|
|
757
|
+
return this.unwrapResponse(res);
|
|
758
|
+
},
|
|
759
|
+
/**
|
|
760
|
+
* Execution run history
|
|
761
|
+
*/
|
|
762
|
+
runs: {
|
|
763
|
+
/**
|
|
764
|
+
* List execution runs for a flow
|
|
765
|
+
*/
|
|
766
|
+
list: async (flowName, options) => {
|
|
767
|
+
const route = this.getRoute("automation");
|
|
768
|
+
const params = new URLSearchParams();
|
|
769
|
+
if (options?.limit) params.set("limit", String(options.limit));
|
|
770
|
+
if (options?.cursor) params.set("cursor", options.cursor);
|
|
771
|
+
const qs = params.toString();
|
|
772
|
+
const res = await this.fetch(`${this.baseUrl}${route}/${flowName}/runs${qs ? `?${qs}` : ""}`);
|
|
773
|
+
return this.unwrapResponse(res);
|
|
774
|
+
},
|
|
775
|
+
/**
|
|
776
|
+
* Get a single execution run
|
|
777
|
+
*/
|
|
778
|
+
get: async (flowName, runId) => {
|
|
779
|
+
const route = this.getRoute("automation");
|
|
780
|
+
const res = await this.fetch(`${this.baseUrl}${route}/${flowName}/runs/${runId}`);
|
|
781
|
+
return this.unwrapResponse(res);
|
|
782
|
+
}
|
|
630
783
|
}
|
|
631
784
|
};
|
|
632
785
|
/**
|
|
@@ -1007,6 +1160,174 @@ var ObjectStackClient = class {
|
|
|
1007
1160
|
return this.unwrapResponse(res);
|
|
1008
1161
|
}
|
|
1009
1162
|
};
|
|
1163
|
+
/**
|
|
1164
|
+
* Feed / Chatter Services
|
|
1165
|
+
*
|
|
1166
|
+
* Provides access to the activity timeline (comments, field changes, tasks),
|
|
1167
|
+
* emoji reactions, pin/star, search, changelog, and record subscriptions.
|
|
1168
|
+
* Base path: /api/data/{object}/{recordId}/feed
|
|
1169
|
+
*/
|
|
1170
|
+
this.feed = {
|
|
1171
|
+
/**
|
|
1172
|
+
* List feed items for a record
|
|
1173
|
+
*/
|
|
1174
|
+
list: async (object, recordId, options) => {
|
|
1175
|
+
const route = this.getRoute("feed");
|
|
1176
|
+
const params = new URLSearchParams();
|
|
1177
|
+
if (options?.type) params.set("type", options.type);
|
|
1178
|
+
if (options?.limit) params.set("limit", String(options.limit));
|
|
1179
|
+
if (options?.cursor) params.set("cursor", options.cursor);
|
|
1180
|
+
const qs = params.toString();
|
|
1181
|
+
const res = await this.fetch(`${this.baseUrl}${route}/${encodeURIComponent(object)}/${encodeURIComponent(recordId)}/feed${qs ? `?${qs}` : ""}`);
|
|
1182
|
+
return this.unwrapResponse(res);
|
|
1183
|
+
},
|
|
1184
|
+
/**
|
|
1185
|
+
* Create a new feed item (comment, note, task, etc.)
|
|
1186
|
+
*/
|
|
1187
|
+
create: async (object, recordId, data) => {
|
|
1188
|
+
const route = this.getRoute("feed");
|
|
1189
|
+
const res = await this.fetch(`${this.baseUrl}${route}/${encodeURIComponent(object)}/${encodeURIComponent(recordId)}/feed`, {
|
|
1190
|
+
method: "POST",
|
|
1191
|
+
body: JSON.stringify(data)
|
|
1192
|
+
});
|
|
1193
|
+
return this.unwrapResponse(res);
|
|
1194
|
+
},
|
|
1195
|
+
/**
|
|
1196
|
+
* Update an existing feed item
|
|
1197
|
+
*/
|
|
1198
|
+
update: async (object, recordId, feedId, data) => {
|
|
1199
|
+
const route = this.getRoute("feed");
|
|
1200
|
+
const res = await this.fetch(`${this.baseUrl}${route}/${encodeURIComponent(object)}/${encodeURIComponent(recordId)}/feed/${encodeURIComponent(feedId)}`, {
|
|
1201
|
+
method: "PUT",
|
|
1202
|
+
body: JSON.stringify(data)
|
|
1203
|
+
});
|
|
1204
|
+
return this.unwrapResponse(res);
|
|
1205
|
+
},
|
|
1206
|
+
/**
|
|
1207
|
+
* Delete a feed item
|
|
1208
|
+
*/
|
|
1209
|
+
delete: async (object, recordId, feedId) => {
|
|
1210
|
+
const route = this.getRoute("feed");
|
|
1211
|
+
const res = await this.fetch(`${this.baseUrl}${route}/${encodeURIComponent(object)}/${encodeURIComponent(recordId)}/feed/${encodeURIComponent(feedId)}`, {
|
|
1212
|
+
method: "DELETE"
|
|
1213
|
+
});
|
|
1214
|
+
return this.unwrapResponse(res);
|
|
1215
|
+
},
|
|
1216
|
+
/**
|
|
1217
|
+
* Add an emoji reaction to a feed item
|
|
1218
|
+
*/
|
|
1219
|
+
addReaction: async (object, recordId, feedId, emoji) => {
|
|
1220
|
+
const route = this.getRoute("feed");
|
|
1221
|
+
const res = await this.fetch(`${this.baseUrl}${route}/${encodeURIComponent(object)}/${encodeURIComponent(recordId)}/feed/${encodeURIComponent(feedId)}/reactions`, {
|
|
1222
|
+
method: "POST",
|
|
1223
|
+
body: JSON.stringify({ emoji })
|
|
1224
|
+
});
|
|
1225
|
+
return this.unwrapResponse(res);
|
|
1226
|
+
},
|
|
1227
|
+
/**
|
|
1228
|
+
* Remove an emoji reaction from a feed item
|
|
1229
|
+
*/
|
|
1230
|
+
removeReaction: async (object, recordId, feedId, emoji) => {
|
|
1231
|
+
const route = this.getRoute("feed");
|
|
1232
|
+
const res = await this.fetch(`${this.baseUrl}${route}/${encodeURIComponent(object)}/${encodeURIComponent(recordId)}/feed/${encodeURIComponent(feedId)}/reactions/${encodeURIComponent(emoji)}`, {
|
|
1233
|
+
method: "DELETE"
|
|
1234
|
+
});
|
|
1235
|
+
return this.unwrapResponse(res);
|
|
1236
|
+
},
|
|
1237
|
+
/**
|
|
1238
|
+
* Pin a feed item to the top of the timeline
|
|
1239
|
+
*/
|
|
1240
|
+
pin: async (object, recordId, feedId) => {
|
|
1241
|
+
const route = this.getRoute("feed");
|
|
1242
|
+
const res = await this.fetch(`${this.baseUrl}${route}/${encodeURIComponent(object)}/${encodeURIComponent(recordId)}/feed/${encodeURIComponent(feedId)}/pin`, {
|
|
1243
|
+
method: "POST"
|
|
1244
|
+
});
|
|
1245
|
+
return this.unwrapResponse(res);
|
|
1246
|
+
},
|
|
1247
|
+
/**
|
|
1248
|
+
* Unpin a feed item
|
|
1249
|
+
*/
|
|
1250
|
+
unpin: async (object, recordId, feedId) => {
|
|
1251
|
+
const route = this.getRoute("feed");
|
|
1252
|
+
const res = await this.fetch(`${this.baseUrl}${route}/${encodeURIComponent(object)}/${encodeURIComponent(recordId)}/feed/${encodeURIComponent(feedId)}/pin`, {
|
|
1253
|
+
method: "DELETE"
|
|
1254
|
+
});
|
|
1255
|
+
return this.unwrapResponse(res);
|
|
1256
|
+
},
|
|
1257
|
+
/**
|
|
1258
|
+
* Star (bookmark) a feed item
|
|
1259
|
+
*/
|
|
1260
|
+
star: async (object, recordId, feedId) => {
|
|
1261
|
+
const route = this.getRoute("feed");
|
|
1262
|
+
const res = await this.fetch(`${this.baseUrl}${route}/${encodeURIComponent(object)}/${encodeURIComponent(recordId)}/feed/${encodeURIComponent(feedId)}/star`, {
|
|
1263
|
+
method: "POST"
|
|
1264
|
+
});
|
|
1265
|
+
return this.unwrapResponse(res);
|
|
1266
|
+
},
|
|
1267
|
+
/**
|
|
1268
|
+
* Unstar a feed item
|
|
1269
|
+
*/
|
|
1270
|
+
unstar: async (object, recordId, feedId) => {
|
|
1271
|
+
const route = this.getRoute("feed");
|
|
1272
|
+
const res = await this.fetch(`${this.baseUrl}${route}/${encodeURIComponent(object)}/${encodeURIComponent(recordId)}/feed/${encodeURIComponent(feedId)}/star`, {
|
|
1273
|
+
method: "DELETE"
|
|
1274
|
+
});
|
|
1275
|
+
return this.unwrapResponse(res);
|
|
1276
|
+
},
|
|
1277
|
+
/**
|
|
1278
|
+
* Search feed items
|
|
1279
|
+
*/
|
|
1280
|
+
search: async (object, recordId, query, options) => {
|
|
1281
|
+
const route = this.getRoute("feed");
|
|
1282
|
+
const params = new URLSearchParams();
|
|
1283
|
+
params.set("query", query);
|
|
1284
|
+
if (options?.type) params.set("type", options.type);
|
|
1285
|
+
if (options?.actorId) params.set("actorId", options.actorId);
|
|
1286
|
+
if (options?.dateFrom) params.set("dateFrom", options.dateFrom);
|
|
1287
|
+
if (options?.dateTo) params.set("dateTo", options.dateTo);
|
|
1288
|
+
if (options?.limit) params.set("limit", String(options.limit));
|
|
1289
|
+
if (options?.cursor) params.set("cursor", options.cursor);
|
|
1290
|
+
const res = await this.fetch(`${this.baseUrl}${route}/${encodeURIComponent(object)}/${encodeURIComponent(recordId)}/feed/search?${params.toString()}`);
|
|
1291
|
+
return this.unwrapResponse(res);
|
|
1292
|
+
},
|
|
1293
|
+
/**
|
|
1294
|
+
* Get field-level changelog for a record
|
|
1295
|
+
*/
|
|
1296
|
+
getChangelog: async (object, recordId, options) => {
|
|
1297
|
+
const route = this.getRoute("feed");
|
|
1298
|
+
const params = new URLSearchParams();
|
|
1299
|
+
if (options?.field) params.set("field", options.field);
|
|
1300
|
+
if (options?.actorId) params.set("actorId", options.actorId);
|
|
1301
|
+
if (options?.dateFrom) params.set("dateFrom", options.dateFrom);
|
|
1302
|
+
if (options?.dateTo) params.set("dateTo", options.dateTo);
|
|
1303
|
+
if (options?.limit) params.set("limit", String(options.limit));
|
|
1304
|
+
if (options?.cursor) params.set("cursor", options.cursor);
|
|
1305
|
+
const qs = params.toString();
|
|
1306
|
+
const res = await this.fetch(`${this.baseUrl}${route}/${encodeURIComponent(object)}/${encodeURIComponent(recordId)}/changelog${qs ? `?${qs}` : ""}`);
|
|
1307
|
+
return this.unwrapResponse(res);
|
|
1308
|
+
},
|
|
1309
|
+
/**
|
|
1310
|
+
* Subscribe to record notifications
|
|
1311
|
+
*/
|
|
1312
|
+
subscribe: async (object, recordId, options) => {
|
|
1313
|
+
const route = this.getRoute("feed");
|
|
1314
|
+
const res = await this.fetch(`${this.baseUrl}${route}/${encodeURIComponent(object)}/${encodeURIComponent(recordId)}/subscribe`, {
|
|
1315
|
+
method: "POST",
|
|
1316
|
+
body: JSON.stringify(options || {})
|
|
1317
|
+
});
|
|
1318
|
+
return this.unwrapResponse(res);
|
|
1319
|
+
},
|
|
1320
|
+
/**
|
|
1321
|
+
* Unsubscribe from record notifications
|
|
1322
|
+
*/
|
|
1323
|
+
unsubscribe: async (object, recordId) => {
|
|
1324
|
+
const route = this.getRoute("feed");
|
|
1325
|
+
const res = await this.fetch(`${this.baseUrl}${route}/${encodeURIComponent(object)}/${encodeURIComponent(recordId)}/subscribe`, {
|
|
1326
|
+
method: "DELETE"
|
|
1327
|
+
});
|
|
1328
|
+
return this.unwrapResponse(res);
|
|
1329
|
+
}
|
|
1330
|
+
};
|
|
1010
1331
|
/**
|
|
1011
1332
|
* Data Operations
|
|
1012
1333
|
*/
|
|
@@ -1039,11 +1360,12 @@ var ObjectStackClient = class {
|
|
|
1039
1360
|
if (options.select) {
|
|
1040
1361
|
queryParams.set("select", options.select.join(","));
|
|
1041
1362
|
}
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1363
|
+
const filterValue = options.filter ?? options.filters;
|
|
1364
|
+
if (filterValue) {
|
|
1365
|
+
if (this.isFilterAST(filterValue) || Array.isArray(filterValue)) {
|
|
1366
|
+
queryParams.set("filter", JSON.stringify(filterValue));
|
|
1367
|
+
} else if (typeof filterValue === "object" && filterValue !== null) {
|
|
1368
|
+
Object.entries(filterValue).forEach(([k, v]) => {
|
|
1047
1369
|
if (v !== void 0 && v !== null) {
|
|
1048
1370
|
queryParams.append(k, String(v));
|
|
1049
1371
|
}
|
|
@@ -1198,11 +1520,19 @@ var ObjectStackClient = class {
|
|
|
1198
1520
|
throw e;
|
|
1199
1521
|
}
|
|
1200
1522
|
}
|
|
1523
|
+
/**
|
|
1524
|
+
* Well-known capability flags discovered from the server.
|
|
1525
|
+
* Returns undefined if the client has not yet connected or the server
|
|
1526
|
+
* did not include capabilities in its discovery response.
|
|
1527
|
+
*/
|
|
1528
|
+
get capabilities() {
|
|
1529
|
+
return this.discoveryInfo?.capabilities;
|
|
1530
|
+
}
|
|
1201
1531
|
/**
|
|
1202
1532
|
* Private Helpers
|
|
1203
1533
|
*/
|
|
1204
1534
|
isFilterAST(filter) {
|
|
1205
|
-
return
|
|
1535
|
+
return (0, import_data.isFilterAST)(filter);
|
|
1206
1536
|
}
|
|
1207
1537
|
/**
|
|
1208
1538
|
* Unwrap the standard REST API response envelope.
|
|
@@ -1287,7 +1617,8 @@ var ObjectStackClient = class {
|
|
|
1287
1617
|
views: "/api/v1/ui/views",
|
|
1288
1618
|
notifications: "/api/v1/notifications",
|
|
1289
1619
|
ai: "/api/v1/ai",
|
|
1290
|
-
i18n: "/api/v1/i18n"
|
|
1620
|
+
i18n: "/api/v1/i18n",
|
|
1621
|
+
feed: "/api/v1/data"
|
|
1291
1622
|
};
|
|
1292
1623
|
return routeMap[type] || `/api/v1/${type}`;
|
|
1293
1624
|
}
|