@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.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
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
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
|
|
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
|
}
|