@objectstack/client 2.0.0 → 2.0.2
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 +17 -0
- package/CLIENT_SERVER_INTEGRATION_TESTS.md +939 -0
- package/CLIENT_SPEC_COMPLIANCE.md +361 -0
- package/QUICK_REFERENCE.md +206 -0
- package/README.md +129 -0
- package/dist/index.d.mts +235 -27
- package/dist/index.d.ts +235 -27
- package/dist/index.js +483 -32
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +483 -32
- package/dist/index.mjs.map +1 -1
- package/package.json +12 -11
- package/src/client.test.ts +563 -2
- package/src/index.ts +540 -37
- package/src/query-builder.ts +75 -0
- package/tests/integration/01-discovery.test.ts +68 -0
- package/tests/integration/README.md +72 -0
- package/vitest.integration.config.ts +18 -0
package/dist/index.mjs
CHANGED
|
@@ -83,6 +83,41 @@ var FilterBuilder = class {
|
|
|
83
83
|
this.conditions.push([field, "is_not_null", null]);
|
|
84
84
|
return this;
|
|
85
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* BETWEEN filter: field BETWEEN min AND max
|
|
88
|
+
*/
|
|
89
|
+
between(field, min, max) {
|
|
90
|
+
this.conditions.push(["and", [field, ">=", min], [field, "<=", max]]);
|
|
91
|
+
return this;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* CONTAINS filter: field contains value (case-insensitive LIKE %value%)
|
|
95
|
+
*/
|
|
96
|
+
contains(field, value) {
|
|
97
|
+
this.conditions.push([field, "like", `%${value}%`]);
|
|
98
|
+
return this;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* STARTS WITH filter: field starts with value (LIKE value%)
|
|
102
|
+
*/
|
|
103
|
+
startsWith(field, value) {
|
|
104
|
+
this.conditions.push([field, "like", `${value}%`]);
|
|
105
|
+
return this;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* ENDS WITH filter: field ends with value (LIKE %value)
|
|
109
|
+
*/
|
|
110
|
+
endsWith(field, value) {
|
|
111
|
+
this.conditions.push([field, "like", `%${value}`]);
|
|
112
|
+
return this;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* EXISTS filter: field is not null (alias for isNotNull)
|
|
116
|
+
*/
|
|
117
|
+
exists(field) {
|
|
118
|
+
this.conditions.push([field, "is_not_null", null]);
|
|
119
|
+
return this;
|
|
120
|
+
}
|
|
86
121
|
/**
|
|
87
122
|
* Build the filter condition
|
|
88
123
|
*/
|
|
@@ -178,6 +213,37 @@ var QueryBuilder = class {
|
|
|
178
213
|
this.query.groupBy = fields;
|
|
179
214
|
return this;
|
|
180
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* Expand (eager-load) a related object with an optional sub-query
|
|
218
|
+
*/
|
|
219
|
+
expand(relation, subQuery) {
|
|
220
|
+
if (!this.query.expand) {
|
|
221
|
+
this.query.expand = {};
|
|
222
|
+
}
|
|
223
|
+
this.query.expand[relation] = subQuery || {};
|
|
224
|
+
return this;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Add full-text search
|
|
228
|
+
*/
|
|
229
|
+
search(query, options) {
|
|
230
|
+
this.query.search = { query, ...options };
|
|
231
|
+
return this;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Set cursor for keyset pagination
|
|
235
|
+
*/
|
|
236
|
+
cursor(cursor) {
|
|
237
|
+
this.query.cursor = cursor;
|
|
238
|
+
return this;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Enable SELECT DISTINCT
|
|
242
|
+
*/
|
|
243
|
+
distinct() {
|
|
244
|
+
this.query.distinct = true;
|
|
245
|
+
return this;
|
|
246
|
+
}
|
|
181
247
|
/**
|
|
182
248
|
* Build the final query AST
|
|
183
249
|
*/
|
|
@@ -335,36 +401,6 @@ var ObjectStackClient = class {
|
|
|
335
401
|
return res.json();
|
|
336
402
|
}
|
|
337
403
|
};
|
|
338
|
-
/**
|
|
339
|
-
* Hub Management Services
|
|
340
|
-
*/
|
|
341
|
-
this.hub = {
|
|
342
|
-
spaces: {
|
|
343
|
-
list: async () => {
|
|
344
|
-
const route = this.getRoute("hub");
|
|
345
|
-
const res = await this.fetch(`${this.baseUrl}${route}/spaces`);
|
|
346
|
-
return res.json();
|
|
347
|
-
},
|
|
348
|
-
create: async (payload) => {
|
|
349
|
-
const route = this.getRoute("hub");
|
|
350
|
-
const res = await this.fetch(`${this.baseUrl}${route}/spaces`, {
|
|
351
|
-
method: "POST",
|
|
352
|
-
body: JSON.stringify(payload)
|
|
353
|
-
});
|
|
354
|
-
return res.json();
|
|
355
|
-
}
|
|
356
|
-
},
|
|
357
|
-
plugins: {
|
|
358
|
-
install: async (pkg, version) => {
|
|
359
|
-
const route = this.getRoute("hub");
|
|
360
|
-
const res = await this.fetch(`${this.baseUrl}${route}/plugins/install`, {
|
|
361
|
-
method: "POST",
|
|
362
|
-
body: JSON.stringify({ pkg, version })
|
|
363
|
-
});
|
|
364
|
-
return res.json();
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
};
|
|
368
404
|
/**
|
|
369
405
|
* Package Management Services
|
|
370
406
|
*
|
|
@@ -475,6 +511,36 @@ var ObjectStackClient = class {
|
|
|
475
511
|
const route = this.getRoute("auth");
|
|
476
512
|
const res = await this.fetch(`${this.baseUrl}${route}/me`);
|
|
477
513
|
return res.json();
|
|
514
|
+
},
|
|
515
|
+
/**
|
|
516
|
+
* Register a new user account
|
|
517
|
+
*/
|
|
518
|
+
register: async (request) => {
|
|
519
|
+
const route = this.getRoute("auth");
|
|
520
|
+
const res = await this.fetch(`${this.baseUrl}${route}/register`, {
|
|
521
|
+
method: "POST",
|
|
522
|
+
body: JSON.stringify(request)
|
|
523
|
+
});
|
|
524
|
+
const data = await res.json();
|
|
525
|
+
if (data.data?.token) {
|
|
526
|
+
this.token = data.data.token;
|
|
527
|
+
}
|
|
528
|
+
return data;
|
|
529
|
+
},
|
|
530
|
+
/**
|
|
531
|
+
* Refresh an authentication token
|
|
532
|
+
*/
|
|
533
|
+
refreshToken: async (refreshToken) => {
|
|
534
|
+
const route = this.getRoute("auth");
|
|
535
|
+
const res = await this.fetch(`${this.baseUrl}${route}/refresh`, {
|
|
536
|
+
method: "POST",
|
|
537
|
+
body: JSON.stringify({ refreshToken })
|
|
538
|
+
});
|
|
539
|
+
const data = await res.json();
|
|
540
|
+
if (data.data?.token) {
|
|
541
|
+
this.token = data.data.token;
|
|
542
|
+
}
|
|
543
|
+
return data;
|
|
478
544
|
}
|
|
479
545
|
};
|
|
480
546
|
/**
|
|
@@ -531,6 +597,384 @@ var ObjectStackClient = class {
|
|
|
531
597
|
return res.json();
|
|
532
598
|
}
|
|
533
599
|
};
|
|
600
|
+
/**
|
|
601
|
+
* Permissions Services
|
|
602
|
+
*/
|
|
603
|
+
this.permissions = {
|
|
604
|
+
/**
|
|
605
|
+
* Check if current user has permission for an action on an object
|
|
606
|
+
*/
|
|
607
|
+
check: async (request) => {
|
|
608
|
+
const route = this.getRoute("permissions");
|
|
609
|
+
const res = await this.fetch(`${this.baseUrl}${route}/check`, {
|
|
610
|
+
method: "POST",
|
|
611
|
+
body: JSON.stringify(request)
|
|
612
|
+
});
|
|
613
|
+
return this.unwrapResponse(res);
|
|
614
|
+
},
|
|
615
|
+
/**
|
|
616
|
+
* Get all permissions for a specific object
|
|
617
|
+
*/
|
|
618
|
+
getObjectPermissions: async (object) => {
|
|
619
|
+
const route = this.getRoute("permissions");
|
|
620
|
+
const res = await this.fetch(`${this.baseUrl}${route}/permissions/${encodeURIComponent(object)}`);
|
|
621
|
+
return this.unwrapResponse(res);
|
|
622
|
+
},
|
|
623
|
+
/**
|
|
624
|
+
* Get effective permissions for the current user
|
|
625
|
+
*/
|
|
626
|
+
getEffectivePermissions: async () => {
|
|
627
|
+
const route = this.getRoute("permissions");
|
|
628
|
+
const res = await this.fetch(`${this.baseUrl}${route}/permissions/effective`);
|
|
629
|
+
return this.unwrapResponse(res);
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
/**
|
|
633
|
+
* Realtime Services
|
|
634
|
+
*/
|
|
635
|
+
this.realtime = {
|
|
636
|
+
/**
|
|
637
|
+
* Establish a realtime connection
|
|
638
|
+
*/
|
|
639
|
+
connect: async (request) => {
|
|
640
|
+
const route = this.getRoute("realtime");
|
|
641
|
+
const res = await this.fetch(`${this.baseUrl}${route}/connect`, {
|
|
642
|
+
method: "POST",
|
|
643
|
+
body: JSON.stringify(request || {})
|
|
644
|
+
});
|
|
645
|
+
return this.unwrapResponse(res);
|
|
646
|
+
},
|
|
647
|
+
/**
|
|
648
|
+
* Disconnect from realtime services
|
|
649
|
+
*/
|
|
650
|
+
disconnect: async () => {
|
|
651
|
+
const route = this.getRoute("realtime");
|
|
652
|
+
await this.fetch(`${this.baseUrl}${route}/disconnect`, {
|
|
653
|
+
method: "POST"
|
|
654
|
+
});
|
|
655
|
+
},
|
|
656
|
+
/**
|
|
657
|
+
* Subscribe to a channel
|
|
658
|
+
*/
|
|
659
|
+
subscribe: async (request) => {
|
|
660
|
+
const route = this.getRoute("realtime");
|
|
661
|
+
const res = await this.fetch(`${this.baseUrl}${route}/subscribe`, {
|
|
662
|
+
method: "POST",
|
|
663
|
+
body: JSON.stringify(request)
|
|
664
|
+
});
|
|
665
|
+
return this.unwrapResponse(res);
|
|
666
|
+
},
|
|
667
|
+
/**
|
|
668
|
+
* Unsubscribe from a channel
|
|
669
|
+
*/
|
|
670
|
+
unsubscribe: async (subscriptionId) => {
|
|
671
|
+
const route = this.getRoute("realtime");
|
|
672
|
+
await this.fetch(`${this.baseUrl}${route}/unsubscribe`, {
|
|
673
|
+
method: "POST",
|
|
674
|
+
body: JSON.stringify({ subscriptionId })
|
|
675
|
+
});
|
|
676
|
+
},
|
|
677
|
+
/**
|
|
678
|
+
* Set presence state on a channel
|
|
679
|
+
*/
|
|
680
|
+
setPresence: async (channel, state) => {
|
|
681
|
+
const route = this.getRoute("realtime");
|
|
682
|
+
await this.fetch(`${this.baseUrl}${route}/presence`, {
|
|
683
|
+
method: "PUT",
|
|
684
|
+
body: JSON.stringify({ channel, state })
|
|
685
|
+
});
|
|
686
|
+
},
|
|
687
|
+
/**
|
|
688
|
+
* Get presence information for a channel
|
|
689
|
+
*/
|
|
690
|
+
getPresence: async (channel) => {
|
|
691
|
+
const route = this.getRoute("realtime");
|
|
692
|
+
const res = await this.fetch(`${this.baseUrl}${route}/presence/${encodeURIComponent(channel)}`);
|
|
693
|
+
return this.unwrapResponse(res);
|
|
694
|
+
}
|
|
695
|
+
};
|
|
696
|
+
/**
|
|
697
|
+
* Workflow Services
|
|
698
|
+
*/
|
|
699
|
+
this.workflow = {
|
|
700
|
+
/**
|
|
701
|
+
* Get workflow configuration for an object
|
|
702
|
+
*/
|
|
703
|
+
getConfig: async (object) => {
|
|
704
|
+
const route = this.getRoute("workflow");
|
|
705
|
+
const res = await this.fetch(`${this.baseUrl}${route}/${encodeURIComponent(object)}/config`);
|
|
706
|
+
return this.unwrapResponse(res);
|
|
707
|
+
},
|
|
708
|
+
/**
|
|
709
|
+
* Get current workflow state for a record
|
|
710
|
+
*/
|
|
711
|
+
getState: async (object, recordId) => {
|
|
712
|
+
const route = this.getRoute("workflow");
|
|
713
|
+
const res = await this.fetch(`${this.baseUrl}${route}/${encodeURIComponent(object)}/${encodeURIComponent(recordId)}/state`);
|
|
714
|
+
return this.unwrapResponse(res);
|
|
715
|
+
},
|
|
716
|
+
/**
|
|
717
|
+
* Execute a workflow state transition
|
|
718
|
+
*/
|
|
719
|
+
transition: async (request) => {
|
|
720
|
+
const route = this.getRoute("workflow");
|
|
721
|
+
const res = await this.fetch(`${this.baseUrl}${route}/${encodeURIComponent(request.object)}/${encodeURIComponent(request.recordId)}/transition`, {
|
|
722
|
+
method: "POST",
|
|
723
|
+
body: JSON.stringify({
|
|
724
|
+
transition: request.transition,
|
|
725
|
+
comment: request.comment,
|
|
726
|
+
data: request.data
|
|
727
|
+
})
|
|
728
|
+
});
|
|
729
|
+
return this.unwrapResponse(res);
|
|
730
|
+
},
|
|
731
|
+
/**
|
|
732
|
+
* Approve a workflow step
|
|
733
|
+
*/
|
|
734
|
+
approve: async (request) => {
|
|
735
|
+
const route = this.getRoute("workflow");
|
|
736
|
+
const res = await this.fetch(`${this.baseUrl}${route}/${encodeURIComponent(request.object)}/${encodeURIComponent(request.recordId)}/approve`, {
|
|
737
|
+
method: "POST",
|
|
738
|
+
body: JSON.stringify({
|
|
739
|
+
comment: request.comment,
|
|
740
|
+
data: request.data
|
|
741
|
+
})
|
|
742
|
+
});
|
|
743
|
+
return this.unwrapResponse(res);
|
|
744
|
+
},
|
|
745
|
+
/**
|
|
746
|
+
* Reject a workflow step
|
|
747
|
+
*/
|
|
748
|
+
reject: async (request) => {
|
|
749
|
+
const route = this.getRoute("workflow");
|
|
750
|
+
const res = await this.fetch(`${this.baseUrl}${route}/${encodeURIComponent(request.object)}/${encodeURIComponent(request.recordId)}/reject`, {
|
|
751
|
+
method: "POST",
|
|
752
|
+
body: JSON.stringify({
|
|
753
|
+
reason: request.reason,
|
|
754
|
+
comment: request.comment
|
|
755
|
+
})
|
|
756
|
+
});
|
|
757
|
+
return this.unwrapResponse(res);
|
|
758
|
+
}
|
|
759
|
+
};
|
|
760
|
+
/**
|
|
761
|
+
* Views CRUD Services
|
|
762
|
+
*/
|
|
763
|
+
this.views = {
|
|
764
|
+
/**
|
|
765
|
+
* List views for an object
|
|
766
|
+
*/
|
|
767
|
+
list: async (object, type) => {
|
|
768
|
+
const route = this.getRoute("views");
|
|
769
|
+
const params = new URLSearchParams();
|
|
770
|
+
if (type) params.set("type", type);
|
|
771
|
+
const qs = params.toString();
|
|
772
|
+
const res = await this.fetch(`${this.baseUrl}${route}/${encodeURIComponent(object)}${qs ? `?${qs}` : ""}`);
|
|
773
|
+
return this.unwrapResponse(res);
|
|
774
|
+
},
|
|
775
|
+
/**
|
|
776
|
+
* Get a specific view
|
|
777
|
+
*/
|
|
778
|
+
get: async (object, viewId) => {
|
|
779
|
+
const route = this.getRoute("views");
|
|
780
|
+
const res = await this.fetch(`${this.baseUrl}${route}/${encodeURIComponent(object)}/${encodeURIComponent(viewId)}`);
|
|
781
|
+
return this.unwrapResponse(res);
|
|
782
|
+
},
|
|
783
|
+
/**
|
|
784
|
+
* Create a new view
|
|
785
|
+
*/
|
|
786
|
+
create: async (object, data) => {
|
|
787
|
+
const route = this.getRoute("views");
|
|
788
|
+
const res = await this.fetch(`${this.baseUrl}${route}/${encodeURIComponent(object)}`, {
|
|
789
|
+
method: "POST",
|
|
790
|
+
body: JSON.stringify({ object, data })
|
|
791
|
+
});
|
|
792
|
+
return this.unwrapResponse(res);
|
|
793
|
+
},
|
|
794
|
+
/**
|
|
795
|
+
* Update an existing view
|
|
796
|
+
*/
|
|
797
|
+
update: async (object, viewId, data) => {
|
|
798
|
+
const route = this.getRoute("views");
|
|
799
|
+
const res = await this.fetch(`${this.baseUrl}${route}/${encodeURIComponent(object)}/${encodeURIComponent(viewId)}`, {
|
|
800
|
+
method: "PUT",
|
|
801
|
+
body: JSON.stringify({ object, viewId, data })
|
|
802
|
+
});
|
|
803
|
+
return this.unwrapResponse(res);
|
|
804
|
+
},
|
|
805
|
+
/**
|
|
806
|
+
* Delete a view
|
|
807
|
+
*/
|
|
808
|
+
delete: async (object, viewId) => {
|
|
809
|
+
const route = this.getRoute("views");
|
|
810
|
+
const res = await this.fetch(`${this.baseUrl}${route}/${encodeURIComponent(object)}/${encodeURIComponent(viewId)}`, {
|
|
811
|
+
method: "DELETE"
|
|
812
|
+
});
|
|
813
|
+
return this.unwrapResponse(res);
|
|
814
|
+
}
|
|
815
|
+
};
|
|
816
|
+
/**
|
|
817
|
+
* Notification Services
|
|
818
|
+
*/
|
|
819
|
+
this.notifications = {
|
|
820
|
+
/**
|
|
821
|
+
* Register a device for push notifications
|
|
822
|
+
*/
|
|
823
|
+
registerDevice: async (request) => {
|
|
824
|
+
const route = this.getRoute("notifications");
|
|
825
|
+
const res = await this.fetch(`${this.baseUrl}${route}/devices`, {
|
|
826
|
+
method: "POST",
|
|
827
|
+
body: JSON.stringify(request)
|
|
828
|
+
});
|
|
829
|
+
return this.unwrapResponse(res);
|
|
830
|
+
},
|
|
831
|
+
/**
|
|
832
|
+
* Unregister a device from push notifications
|
|
833
|
+
*/
|
|
834
|
+
unregisterDevice: async (deviceId) => {
|
|
835
|
+
const route = this.getRoute("notifications");
|
|
836
|
+
const res = await this.fetch(`${this.baseUrl}${route}/devices/${encodeURIComponent(deviceId)}`, {
|
|
837
|
+
method: "DELETE"
|
|
838
|
+
});
|
|
839
|
+
return this.unwrapResponse(res);
|
|
840
|
+
},
|
|
841
|
+
/**
|
|
842
|
+
* Get notification preferences for the current user
|
|
843
|
+
*/
|
|
844
|
+
getPreferences: async () => {
|
|
845
|
+
const route = this.getRoute("notifications");
|
|
846
|
+
const res = await this.fetch(`${this.baseUrl}${route}/preferences`);
|
|
847
|
+
return this.unwrapResponse(res);
|
|
848
|
+
},
|
|
849
|
+
/**
|
|
850
|
+
* Update notification preferences
|
|
851
|
+
*/
|
|
852
|
+
updatePreferences: async (preferences) => {
|
|
853
|
+
const route = this.getRoute("notifications");
|
|
854
|
+
const res = await this.fetch(`${this.baseUrl}${route}/preferences`, {
|
|
855
|
+
method: "PUT",
|
|
856
|
+
body: JSON.stringify({ preferences })
|
|
857
|
+
});
|
|
858
|
+
return this.unwrapResponse(res);
|
|
859
|
+
},
|
|
860
|
+
/**
|
|
861
|
+
* List notifications for the current user
|
|
862
|
+
*/
|
|
863
|
+
list: async (options) => {
|
|
864
|
+
const route = this.getRoute("notifications");
|
|
865
|
+
const params = new URLSearchParams();
|
|
866
|
+
if (options?.read !== void 0) params.set("read", String(options.read));
|
|
867
|
+
if (options?.type) params.set("type", options.type);
|
|
868
|
+
if (options?.limit) params.set("limit", String(options.limit));
|
|
869
|
+
if (options?.cursor) params.set("cursor", options.cursor);
|
|
870
|
+
const qs = params.toString();
|
|
871
|
+
const res = await this.fetch(`${this.baseUrl}${route}${qs ? `?${qs}` : ""}`);
|
|
872
|
+
return this.unwrapResponse(res);
|
|
873
|
+
},
|
|
874
|
+
/**
|
|
875
|
+
* Mark specific notifications as read
|
|
876
|
+
*/
|
|
877
|
+
markRead: async (ids) => {
|
|
878
|
+
const route = this.getRoute("notifications");
|
|
879
|
+
const res = await this.fetch(`${this.baseUrl}${route}/read`, {
|
|
880
|
+
method: "POST",
|
|
881
|
+
body: JSON.stringify({ ids })
|
|
882
|
+
});
|
|
883
|
+
return this.unwrapResponse(res);
|
|
884
|
+
},
|
|
885
|
+
/**
|
|
886
|
+
* Mark all notifications as read
|
|
887
|
+
*/
|
|
888
|
+
markAllRead: async () => {
|
|
889
|
+
const route = this.getRoute("notifications");
|
|
890
|
+
const res = await this.fetch(`${this.baseUrl}${route}/read/all`, {
|
|
891
|
+
method: "POST"
|
|
892
|
+
});
|
|
893
|
+
return this.unwrapResponse(res);
|
|
894
|
+
}
|
|
895
|
+
};
|
|
896
|
+
/**
|
|
897
|
+
* AI Services
|
|
898
|
+
*/
|
|
899
|
+
this.ai = {
|
|
900
|
+
/**
|
|
901
|
+
* Natural language query — converts natural language to structured query
|
|
902
|
+
*/
|
|
903
|
+
nlq: async (request) => {
|
|
904
|
+
const route = this.getRoute("ai");
|
|
905
|
+
const res = await this.fetch(`${this.baseUrl}${route}/nlq`, {
|
|
906
|
+
method: "POST",
|
|
907
|
+
body: JSON.stringify(request)
|
|
908
|
+
});
|
|
909
|
+
return this.unwrapResponse(res);
|
|
910
|
+
},
|
|
911
|
+
/**
|
|
912
|
+
* Multi-turn AI chat
|
|
913
|
+
*/
|
|
914
|
+
chat: async (request) => {
|
|
915
|
+
const route = this.getRoute("ai");
|
|
916
|
+
const res = await this.fetch(`${this.baseUrl}${route}/chat`, {
|
|
917
|
+
method: "POST",
|
|
918
|
+
body: JSON.stringify(request)
|
|
919
|
+
});
|
|
920
|
+
return this.unwrapResponse(res);
|
|
921
|
+
},
|
|
922
|
+
/**
|
|
923
|
+
* AI-powered field value suggestions
|
|
924
|
+
*/
|
|
925
|
+
suggest: async (request) => {
|
|
926
|
+
const route = this.getRoute("ai");
|
|
927
|
+
const res = await this.fetch(`${this.baseUrl}${route}/suggest`, {
|
|
928
|
+
method: "POST",
|
|
929
|
+
body: JSON.stringify(request)
|
|
930
|
+
});
|
|
931
|
+
return this.unwrapResponse(res);
|
|
932
|
+
},
|
|
933
|
+
/**
|
|
934
|
+
* AI-powered data insights
|
|
935
|
+
*/
|
|
936
|
+
insights: async (request) => {
|
|
937
|
+
const route = this.getRoute("ai");
|
|
938
|
+
const res = await this.fetch(`${this.baseUrl}${route}/insights`, {
|
|
939
|
+
method: "POST",
|
|
940
|
+
body: JSON.stringify(request)
|
|
941
|
+
});
|
|
942
|
+
return this.unwrapResponse(res);
|
|
943
|
+
}
|
|
944
|
+
};
|
|
945
|
+
/**
|
|
946
|
+
* Internationalization Services
|
|
947
|
+
*/
|
|
948
|
+
this.i18n = {
|
|
949
|
+
/**
|
|
950
|
+
* Get available locales
|
|
951
|
+
*/
|
|
952
|
+
getLocales: async () => {
|
|
953
|
+
const route = this.getRoute("i18n");
|
|
954
|
+
const res = await this.fetch(`${this.baseUrl}${route}/locales`);
|
|
955
|
+
return this.unwrapResponse(res);
|
|
956
|
+
},
|
|
957
|
+
/**
|
|
958
|
+
* Get translations for a locale
|
|
959
|
+
*/
|
|
960
|
+
getTranslations: async (locale, options) => {
|
|
961
|
+
const route = this.getRoute("i18n");
|
|
962
|
+
const params = new URLSearchParams();
|
|
963
|
+
params.set("locale", locale);
|
|
964
|
+
if (options?.namespace) params.set("namespace", options.namespace);
|
|
965
|
+
if (options?.keys) params.set("keys", options.keys.join(","));
|
|
966
|
+
const res = await this.fetch(`${this.baseUrl}${route}/translations?${params.toString()}`);
|
|
967
|
+
return this.unwrapResponse(res);
|
|
968
|
+
},
|
|
969
|
+
/**
|
|
970
|
+
* Get translated field labels for an object
|
|
971
|
+
*/
|
|
972
|
+
getFieldLabels: async (object, locale) => {
|
|
973
|
+
const route = this.getRoute("i18n");
|
|
974
|
+
const res = await this.fetch(`${this.baseUrl}${route}/labels/${encodeURIComponent(object)}?locale=${encodeURIComponent(locale)}`);
|
|
975
|
+
return this.unwrapResponse(res);
|
|
976
|
+
}
|
|
977
|
+
};
|
|
534
978
|
/**
|
|
535
979
|
* Data Operations
|
|
536
980
|
*/
|
|
@@ -801,10 +1245,17 @@ var ObjectStackClient = class {
|
|
|
801
1245
|
ui: "/api/v1/ui",
|
|
802
1246
|
auth: "/api/v1/auth",
|
|
803
1247
|
analytics: "/api/v1/analytics",
|
|
804
|
-
hub: "/api/v1/hub",
|
|
805
1248
|
storage: "/api/v1/storage",
|
|
806
1249
|
automation: "/api/v1/automation",
|
|
807
|
-
packages: "/api/v1/packages"
|
|
1250
|
+
packages: "/api/v1/packages",
|
|
1251
|
+
permissions: "/api/v1/auth",
|
|
1252
|
+
// Permission endpoints are under /api/v1/auth per spec
|
|
1253
|
+
realtime: "/api/v1/realtime",
|
|
1254
|
+
workflow: "/api/v1/workflow",
|
|
1255
|
+
views: "/api/v1/ui/views",
|
|
1256
|
+
notifications: "/api/v1/notifications",
|
|
1257
|
+
ai: "/api/v1/ai",
|
|
1258
|
+
i18n: "/api/v1/i18n"
|
|
808
1259
|
};
|
|
809
1260
|
return routeMap[type] || `/api/v1/${type}`;
|
|
810
1261
|
}
|