@colixsystems/widget-sdk 0.17.0 → 0.19.0
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/README.md +135 -17
- package/dist/contract.cjs +156 -66
- package/dist/contract.js +161 -66
- package/dist/hooks.js +776 -387
- package/dist/index.d.ts +292 -37
- package/dist/index.js +2 -0
- package/dist/index.native.js +2 -0
- package/dist/linter.cjs +56 -0
- package/dist/linter.js +57 -0
- package/dist/manifest.cjs +75 -2
- package/dist/manifest.js +75 -2
- package/package.json +2 -2
package/dist/contract.js
CHANGED
|
@@ -168,12 +168,12 @@ const HOOKS = [
|
|
|
168
168
|
name: "useDirectory",
|
|
169
169
|
signature: "useDirectory(query?)",
|
|
170
170
|
returnShape: {
|
|
171
|
-
users: "Array<{ id, name, role }>",
|
|
171
|
+
users: "Array<{ id, name, role }> // snake_case rows; unwrapped from { data, meta }",
|
|
172
172
|
loading: "boolean",
|
|
173
173
|
error: "DatastoreError | null",
|
|
174
174
|
refetch: "() => Promise<void>",
|
|
175
175
|
},
|
|
176
|
-
requiredContextSlice: ["directory.
|
|
176
|
+
requiredContextSlice: ["directory.users"],
|
|
177
177
|
scopes: ["directory.read:users"],
|
|
178
178
|
},
|
|
179
179
|
{
|
|
@@ -207,28 +207,26 @@ const HOOKS = [
|
|
|
207
207
|
name: "useUsers",
|
|
208
208
|
signature: "useUsers(query?)",
|
|
209
209
|
description:
|
|
210
|
-
"AppUser administration
|
|
211
|
-
"deactivate,
|
|
212
|
-
"
|
|
213
|
-
"
|
|
210
|
+
"AppUser administration via the injected directory-client at " +
|
|
211
|
+
"ctx.directory.users.{list,get,invite,deactivate,reactivate}. Returns " +
|
|
212
|
+
"{ users, loading, error, refetch, invite, deactivate, reactivate, remove }. " +
|
|
213
|
+
"list returns the { data, meta } envelope verbatim — the hook unwraps " +
|
|
214
|
+
"res.data; rows are snake_case (is_active, …). Reads need users.read:* " +
|
|
215
|
+
"scope; mutations need users.write:*. The `invite` call accepts " +
|
|
216
|
+
"{ email, name, group_ids? } and returns the resulting AppUserInvite row " +
|
|
217
|
+
"(the email is sent by the host).",
|
|
214
218
|
returnShape: {
|
|
215
|
-
users: "Array<{ id, name, email?, role,
|
|
219
|
+
users: "Array<{ id, name, email?, role, is_active }> // snake_case rows; unwrapped from { data, meta }",
|
|
216
220
|
loading: "boolean",
|
|
217
221
|
error: "DirectoryError | null",
|
|
218
222
|
refetch: "() => Promise<void>",
|
|
219
223
|
invite:
|
|
220
|
-
"({ email, name,
|
|
224
|
+
"({ email, name, group_ids? }) => Promise<Invite> // rejects with DirectoryError",
|
|
221
225
|
deactivate: "(userId) => Promise<User> // rejects with DirectoryError",
|
|
222
226
|
reactivate: "(userId) => Promise<User> // rejects with DirectoryError",
|
|
223
227
|
remove: "(userId) => Promise<void> // rejects with DirectoryError",
|
|
224
228
|
},
|
|
225
|
-
requiredContextSlice: [
|
|
226
|
-
"users.listUsers",
|
|
227
|
-
"users.invite",
|
|
228
|
-
"users.deactivate",
|
|
229
|
-
"users.reactivate",
|
|
230
|
-
"users.remove",
|
|
231
|
-
],
|
|
229
|
+
requiredContextSlice: ["directory.users"],
|
|
232
230
|
scopes: ["users.read:*"],
|
|
233
231
|
},
|
|
234
232
|
// REQ-USERMGMT / REQ-ACL-SYS M3 — AppUserGroup administration. Returns
|
|
@@ -240,11 +238,14 @@ const HOOKS = [
|
|
|
240
238
|
name: "useGroups",
|
|
241
239
|
signature: "useGroups(query?)",
|
|
242
240
|
description:
|
|
243
|
-
"AppUserGroup administration
|
|
244
|
-
"create,
|
|
241
|
+
"AppUserGroup administration via the injected directory-client at " +
|
|
242
|
+
"ctx.directory.groups.{list,create,remove,addMember,removeMember,listMine}. " +
|
|
243
|
+
"Returns { groups, loading, error, refetch, create, remove, addMember, " +
|
|
244
|
+
"removeMember }. list returns the { data, meta } envelope verbatim — the " +
|
|
245
|
+
"hook unwraps res.data; rows are snake_case. Reads need groups.read:*; " +
|
|
245
246
|
"mutations need groups.write:*.",
|
|
246
247
|
returnShape: {
|
|
247
|
-
groups: "Array<{ id, name,
|
|
248
|
+
groups: "Array<{ id, name, member_count }> // snake_case rows; unwrapped from { data, meta }",
|
|
248
249
|
loading: "boolean",
|
|
249
250
|
error: "DirectoryError | null",
|
|
250
251
|
refetch: "() => Promise<void>",
|
|
@@ -256,15 +257,50 @@ const HOOKS = [
|
|
|
256
257
|
removeMember:
|
|
257
258
|
"(groupId, userId) => Promise<void> // rejects with DirectoryError",
|
|
258
259
|
},
|
|
259
|
-
requiredContextSlice: [
|
|
260
|
-
"groups.listGroups",
|
|
261
|
-
"groups.create",
|
|
262
|
-
"groups.remove",
|
|
263
|
-
"groups.addMember",
|
|
264
|
-
"groups.removeMember",
|
|
265
|
-
],
|
|
260
|
+
requiredContextSlice: ["directory.groups"],
|
|
266
261
|
scopes: ["groups.read:*"],
|
|
267
262
|
},
|
|
263
|
+
// REQ-ACL-06 / REQ-ACL-RELINHERIT-05 — per-record VirtualPermission
|
|
264
|
+
// management for a single record. Reads the injected datastore-client at
|
|
265
|
+
// ctx.datastore.records(tableId).permissions(recordId) (the recordPermissions
|
|
266
|
+
// facade was folded into the datastore-client). The backend gates the call
|
|
267
|
+
// on `can_grant` for the target record (Studio owners short-circuit;
|
|
268
|
+
// APP_USER actors must hold `can_grant` via REQ-ACL-05 / REQ-ACL-06). A
|
|
269
|
+
// widget that declares the scope but whose caller lacks the grant receives
|
|
270
|
+
// `PermissionError { code: 'FORBIDDEN' }`.
|
|
271
|
+
{
|
|
272
|
+
name: "useRecordPermissions",
|
|
273
|
+
signature: "useRecordPermissions(tableId, recordId)",
|
|
274
|
+
description:
|
|
275
|
+
"Manage per-record VirtualPermission grants on a single record via the " +
|
|
276
|
+
"injected datastore-client at " +
|
|
277
|
+
"ctx.datastore.records(tableId).permissions(recordId).{list,grant,update,revoke}. " +
|
|
278
|
+
"Returns { permissions, loading, error, grant, revoke, update, refetch } " +
|
|
279
|
+
"where permissions is Array<{ id, user_id, group_id, can_read, can_write, " +
|
|
280
|
+
"can_delete, can_grant }> (snake_case rows verbatim; list() returns the " +
|
|
281
|
+
"{ data, meta } envelope and the hook unwraps res.data). grant/update " +
|
|
282
|
+
"bodies are snake_case verbatim ({ user_id | group_id, can_read, " +
|
|
283
|
+
"can_write, can_delete, can_grant }). Mutating requires acl.write:records " +
|
|
284
|
+
"scope AND can_grant on the target record (REQ-ACL-RELINHERIT-05: " +
|
|
285
|
+
"APP_USER actors with can_grant are accepted, not only Studio owners). " +
|
|
286
|
+
"When tableId or recordId is null/empty the hook collapses to an empty " +
|
|
287
|
+
"no-op result without a network round-trip.",
|
|
288
|
+
returnShape: {
|
|
289
|
+
permissions:
|
|
290
|
+
"Array<{ id, user_id, group_id, can_read, can_write, can_delete, can_grant }> // snake_case rows; unwrapped from { data, meta }",
|
|
291
|
+
loading: "boolean",
|
|
292
|
+
error: "PermissionError | null",
|
|
293
|
+
grant:
|
|
294
|
+
"({ user_id?, group_id?, can_read?, can_write?, can_delete?, can_grant? }) => Promise<RecordPermission> // rejects with PermissionError",
|
|
295
|
+
revoke:
|
|
296
|
+
"(permissionId) => Promise<void> // rejects with PermissionError",
|
|
297
|
+
update:
|
|
298
|
+
"(permissionId, { can_read?, can_write?, can_delete?, can_grant? }) => Promise<RecordPermission> // rejects with PermissionError",
|
|
299
|
+
refetch: "() => Promise<void>",
|
|
300
|
+
},
|
|
301
|
+
requiredContextSlice: ["datastore.records"],
|
|
302
|
+
scopes: ["acl.write:records"],
|
|
303
|
+
},
|
|
268
304
|
// REQ-WSDK-PLATFORM §6 — Tier A SDK hooks.
|
|
269
305
|
{
|
|
270
306
|
name: "useClipboard",
|
|
@@ -414,10 +450,40 @@ const CATEGORIES = [
|
|
|
414
450
|
"DATA",
|
|
415
451
|
"MEDIA",
|
|
416
452
|
"COMMUNICATION",
|
|
453
|
+
// REQ-USERMGMT-06: app-administration widgets (User Management, …) the
|
|
454
|
+
// published app embeds for its own member management — its own palette
|
|
455
|
+
// section, distinct from COMMUNICATION.
|
|
456
|
+
"ADMINISTRATION",
|
|
417
457
|
"CUSTOM",
|
|
418
458
|
];
|
|
419
459
|
const PLATFORMS = ["web", "native"];
|
|
420
460
|
|
|
461
|
+
// REQ-WIDGET-ACTION — server-side actions a widget may declare in its
|
|
462
|
+
// manifest. Each runs in the shared isolated-vm action runner (see backend
|
|
463
|
+
// action-runner.service.js) on a cron schedule or in response to a record
|
|
464
|
+
// CRUD event — NEVER in the rendered app, so they never affect Player ↔
|
|
465
|
+
// export parity. The trigger vocabulary mirrors the backend Action model.
|
|
466
|
+
const ACTION_TRIGGER_TYPES = [
|
|
467
|
+
"schedule",
|
|
468
|
+
"record_created",
|
|
469
|
+
"record_updated",
|
|
470
|
+
"record_deleted",
|
|
471
|
+
];
|
|
472
|
+
// Globals the action script runs against (the runner's surface) — distinct
|
|
473
|
+
// from the React/SDK widget surface, so the component import/banned-API
|
|
474
|
+
// linter does NOT scan action scripts.
|
|
475
|
+
const ACTION_SCRIPT_GLOBALS = [
|
|
476
|
+
"datastore",
|
|
477
|
+
"fetch",
|
|
478
|
+
"console",
|
|
479
|
+
"record",
|
|
480
|
+
"tenantId",
|
|
481
|
+
"triggerType",
|
|
482
|
+
"triggerTableId",
|
|
483
|
+
];
|
|
484
|
+
// Mirrors action.service.js SCRIPT_MAX_BYTES.
|
|
485
|
+
const ACTION_SCRIPT_MAX_BYTES = 200 * 1024;
|
|
486
|
+
|
|
421
487
|
// Reverse-DNS-ish manifest id, e.g. "com.acme.charts.barchart". Two or
|
|
422
488
|
// more labels, lowercase alnum + hyphen, label starts with a letter. The
|
|
423
489
|
// analyzer + the SDK validator both read this from the contract so a
|
|
@@ -503,6 +569,17 @@ const MANIFEST_SCHEMA = {
|
|
|
503
569
|
description:
|
|
504
570
|
"Optional. Tables the widget needs, seeded into the workspace at install time. Authors wire them into the widget's `tableRef` properties via the Properties Panel — the SDK does not auto-bind. Limits: 8 tables, 24 columns per table. RELATION columns address siblings by `targetSuffix` (must be declared earlier in the array). Tables persist across uninstalls.",
|
|
505
571
|
},
|
|
572
|
+
actions: {
|
|
573
|
+
type: "object[]",
|
|
574
|
+
required: false,
|
|
575
|
+
description:
|
|
576
|
+
"Optional. Server-side actions the widget declares. Each runs in the shared isolated-vm action runner (cron- or record-triggered) — NEVER in the rendered app. Operators enable them per tenant from the Properties Panel; the action materialises DISABLED until they bind an integration API key (and, for record_* triggers, a target table) in the Actions admin page. Each entry: { key (stable, unique within the manifest), name, description?, triggerType (one of " +
|
|
577
|
+
ACTION_TRIGGER_TYPES.join(", ") +
|
|
578
|
+
"), scheduleCron? (required iff triggerType=='schedule'; node-cron syntax), timeoutMs? (100–300000), scriptSource (≤200 KiB; runs against " +
|
|
579
|
+
ACTION_SCRIPT_GLOBALS.join(", ") +
|
|
580
|
+
" — NOT the React surface, so SDK imports/hooks are unavailable) }. Do NOT include triggerTableId or apiKeyId — those are tenant-local and bound after install.",
|
|
581
|
+
default: [],
|
|
582
|
+
},
|
|
506
583
|
};
|
|
507
584
|
|
|
508
585
|
const WIDGET_CONTEXT_SHAPE = {
|
|
@@ -518,9 +595,10 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
518
595
|
fields: { id: "manifest.id", version: "manifest.version" },
|
|
519
596
|
},
|
|
520
597
|
user: {
|
|
521
|
-
description:
|
|
598
|
+
description:
|
|
599
|
+
"Signed-in user, host-provided VERBATIM (snake_case: { id, email, display_name, roles, group_ids }). Not a data-client.",
|
|
522
600
|
required: true,
|
|
523
|
-
fields: { id: "string", email: "string",
|
|
601
|
+
fields: { id: "string", email: "string", display_name: "string" },
|
|
524
602
|
},
|
|
525
603
|
workspace: {
|
|
526
604
|
description:
|
|
@@ -540,19 +618,30 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
540
618
|
},
|
|
541
619
|
datastore: {
|
|
542
620
|
description:
|
|
543
|
-
"
|
|
621
|
+
"Injected @colixsystems/datastore-client instance. " +
|
|
622
|
+
"{ tables: { list(), get(idOrName) }, schema(tableId) -> Promise<{ id, name, columns: [...] }>, " +
|
|
623
|
+
"records(tableId) -> { list(query) -> Promise<{ data, meta }>, get(id), create(values), update(id, values), delete(id), aggregate(spec), " +
|
|
624
|
+
"permissions(recordId) -> { list() -> Promise<{ data, meta }>, grant(body), update(permId, patch), revoke(permId) } } }. " +
|
|
625
|
+
"`records` backs the query/record/mutation hooks; `records(t).permissions(r)` backs useRecordPermissions(); `schema` backs useDatastoreSchema(). " +
|
|
626
|
+
"List methods return the { data, meta } envelope verbatim (hooks unwrap res.data); rows/bodies are snake_case (author column values keep their author-given names).",
|
|
544
627
|
required: true,
|
|
545
|
-
fields: { records: "function", schema: "function" },
|
|
628
|
+
fields: { records: "function", schema: "function", tables: "object" },
|
|
546
629
|
},
|
|
547
630
|
directory: {
|
|
548
631
|
description:
|
|
549
|
-
"
|
|
632
|
+
"Injected @colixsystems/directory-client instance. " +
|
|
633
|
+
"{ me(), users: { list(query?) -> Promise<{ data, meta }>, get(id), invite(body), deactivate(id), reactivate(id) }, " +
|
|
634
|
+
"groups: { list(query?) -> Promise<{ data, meta }>, create(body), remove(id), addMember(groupId, userId), removeMember(groupId, userId), listMine() }, " +
|
|
635
|
+
"invites: { list(), revoke(id), resend(id) } }. " +
|
|
636
|
+
"users backs useDirectory() + useUsers(); groups backs useGroups(). List methods return the { data, meta } envelope verbatim (hooks unwrap res.data); rows/bodies are snake_case. Reads gated by directory.read:users / users.read:* / groups.read:*; mutations by users.write:* / groups.write:*.",
|
|
550
637
|
required: true,
|
|
551
|
-
fields: {
|
|
638
|
+
fields: { users: "object", groups: "object" },
|
|
552
639
|
},
|
|
553
640
|
files: {
|
|
554
641
|
description:
|
|
555
|
-
"
|
|
642
|
+
"Injected @colixsystems/files-client instance, FLATTENED so file ops are top-level. " +
|
|
643
|
+
"{ get(id) -> Promise<{ id, url, ... }>, list(query) -> Promise<{ data, meta }>, upload(formData), folders: { list, create }, shares: { list, create, remove } }. " +
|
|
644
|
+
"Backs useFile(); the returned file already carries an absolute url the widget can drop into an <Image source>.",
|
|
556
645
|
required: true,
|
|
557
646
|
fields: { get: "function" },
|
|
558
647
|
},
|
|
@@ -569,42 +658,17 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
569
658
|
},
|
|
570
659
|
payments: {
|
|
571
660
|
description:
|
|
572
|
-
"
|
|
661
|
+
"Injected @colixsystems/payments-client instance (REQ-BILL-07-WIDGETPAY). { requestPayment(body) -> Promise<{ id, status, checkoutUrl? }>, getPayment(id) -> Promise<payment> }. Backs usePayments(); requires the payments.charge:appUser scope. The host opens hosted Checkout (or auto-confirms under the mock provider); the charge settles to the workspace owner.",
|
|
573
662
|
required: true,
|
|
574
663
|
fields: { requestPayment: "function", getPayment: "function" },
|
|
575
664
|
},
|
|
576
|
-
// REQ-
|
|
577
|
-
//
|
|
578
|
-
//
|
|
579
|
-
//
|
|
580
|
-
//
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
"AppUser administration. { listUsers(query?) -> Promise<User[]>, invite({ email, name, groupIds? }) -> Promise<Invite>, deactivate(userId) -> Promise<User>, reactivate(userId) -> Promise<User>, remove(userId) -> Promise<void> }. Backs useUsers(); reads require users.read:*, mutations require users.write:*.",
|
|
584
|
-
required: true,
|
|
585
|
-
fields: {
|
|
586
|
-
listUsers: "function",
|
|
587
|
-
invite: "function",
|
|
588
|
-
deactivate: "function",
|
|
589
|
-
reactivate: "function",
|
|
590
|
-
remove: "function",
|
|
591
|
-
},
|
|
592
|
-
},
|
|
593
|
-
// REQ-USERMGMT / REQ-ACL-SYS M3 — AppUserGroup administration facade
|
|
594
|
-
// backing useGroups(). Reads gated by `groups.read:*`; mutations by
|
|
595
|
-
// `groups.write:*`. Same X-Widget-Scopes + SystemAcl gating as users.
|
|
596
|
-
groups: {
|
|
597
|
-
description:
|
|
598
|
-
"AppUserGroup administration. { listGroups(query?) -> Promise<Group[]>, create({ name }) -> Promise<Group>, remove(groupId) -> Promise<void>, addMember(groupId, userId) -> Promise<void>, removeMember(groupId, userId) -> Promise<void> }. Backs useGroups(); reads require groups.read:*, mutations require groups.write:*.",
|
|
599
|
-
required: true,
|
|
600
|
-
fields: {
|
|
601
|
-
listGroups: "function",
|
|
602
|
-
create: "function",
|
|
603
|
-
remove: "function",
|
|
604
|
-
addMember: "function",
|
|
605
|
-
removeMember: "function",
|
|
606
|
-
},
|
|
607
|
-
},
|
|
665
|
+
// REQ-WSDK-DOMAIN-CLIENTS — the AppUser administration, AppUserGroup
|
|
666
|
+
// administration, and per-record VirtualPermission facades that used to
|
|
667
|
+
// live here (`users`, `groups`, `recordPermissions`) were folded into the
|
|
668
|
+
// injected domain-client instances: useUsers()/useGroups() read
|
|
669
|
+
// ctx.directory.{users,groups}; useRecordPermissions() reads
|
|
670
|
+
// ctx.datastore.records(table).permissions(record). See the `directory`
|
|
671
|
+
// and `datastore` slices above.
|
|
608
672
|
i18n: {
|
|
609
673
|
description: "{ t(key, fallback?), locale }.",
|
|
610
674
|
required: true,
|
|
@@ -813,12 +877,43 @@ const CONTRACT = deepFreeze({
|
|
|
813
877
|
// 1.7.0: additive — new useDatastoreSchema(tableId) hook + the
|
|
814
878
|
// datastore.schema host-context slice it reads (resolves a table's column
|
|
815
879
|
// structure at runtime via the existing ACL-gated GET /tables/:id).
|
|
816
|
-
|
|
880
|
+
//
|
|
881
|
+
// 1.8.0: two additive features.
|
|
882
|
+
// - REQ-WIDGET-ACTION — manifests may declare an optional `actions` array:
|
|
883
|
+
// server-side cron/record-triggered scripts the operator enables per
|
|
884
|
+
// tenant, run in the existing isolated-vm action runner (never in the
|
|
885
|
+
// rendered app). New fields actionTriggerTypes / actionScriptGlobals /
|
|
886
|
+
// actionScriptMaxBytes feed the docs + agent prompt. New ADMINISTRATION
|
|
887
|
+
// manifest category (REQ-USERMGMT-06).
|
|
888
|
+
// - REQ-ACL-06 — new useRecordPermissions(tableId, recordId) hook + the
|
|
889
|
+
// recordPermissions host-context slice it reads + the acl.write:records
|
|
890
|
+
// scope it gates on. Hits the existing REQ-ACL-06
|
|
891
|
+
// /api/v1/tables/:tableId/records/:recordId/permissions REST surface;
|
|
892
|
+
// the host normalises the wire shape into a single principalType+
|
|
893
|
+
// principalId pair widgets branch on. Caller still needs canGrant on
|
|
894
|
+
// the target record (REQ-ACL-RELINHERIT-05).
|
|
895
|
+
//
|
|
896
|
+
// 1.9.0: host-contract change (REQ-WSDK-DOMAIN-CLIENTS) — the host now
|
|
897
|
+
// injects domain-client INSTANCES on the WidgetContext rather than
|
|
898
|
+
// bespoke per-hook facades. ctx.datastore is a @colixsystems/datastore-client
|
|
899
|
+
// (records(t).list now returns the { data, meta } envelope verbatim;
|
|
900
|
+
// records(t).permissions(r) replaces the deleted ctx.recordPermissions),
|
|
901
|
+
// ctx.directory is a @colixsystems/directory-client (users/groups
|
|
902
|
+
// namespaces replace the deleted ctx.users / ctx.groups; list returns
|
|
903
|
+
// envelopes), ctx.files is a flattened @colixsystems/files-client,
|
|
904
|
+
// ctx.payments is a @colixsystems/payments-client. All rows/bodies are
|
|
905
|
+
// snake_case. Hooks now unwrap the list envelope (res.data ?? []). The
|
|
906
|
+
// SDK stays duck-typed — it imports none of the four data SDKs. Minor
|
|
907
|
+
// bump on the contract's pre-1.0 versioning (the breaking channel).
|
|
908
|
+
version: "1.9.0",
|
|
817
909
|
hooks: HOOKS,
|
|
818
910
|
primitives: PRIMITIVES,
|
|
819
911
|
manifestSchema: MANIFEST_SCHEMA,
|
|
820
912
|
manifestCategories: CATEGORIES,
|
|
821
913
|
manifestPlatforms: PLATFORMS,
|
|
914
|
+
actionTriggerTypes: ACTION_TRIGGER_TYPES,
|
|
915
|
+
actionScriptGlobals: ACTION_SCRIPT_GLOBALS,
|
|
916
|
+
actionScriptMaxBytes: ACTION_SCRIPT_MAX_BYTES,
|
|
822
917
|
themeTokens: DEFAULT_THEME_TOKENS,
|
|
823
918
|
widgetContextShape: WIDGET_CONTEXT_SHAPE,
|
|
824
919
|
bundleExportContract: BUNDLE_EXPORT_CONTRACT,
|