@colixsystems/widget-sdk 0.41.0 → 0.43.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 +11 -2
- package/dist/contract.cjs +34 -3
- package/dist/contract.js +34 -3
- package/dist/hooks.js +74 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +1 -0
- package/dist/index.native.js +1 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ The data layer lives in **four separate domain-client packages**, each instantia
|
|
|
8
8
|
| ----------- | ------- | -------- |
|
|
9
9
|
| `ctx.datastore` | `@colixsystems/datastore-client` | `tables.{list,get}`, `schema(tableId)`, `records(tableId).{ list(query), get(id), create(values), update(id,values) [PATCH], delete(id), aggregate(spec), permissions(recordId).{ list, grant, update, revoke } }` |
|
|
10
10
|
| `ctx.directory` | `@colixsystems/directory-client` | `me()`, `users.{list,get,invite,deactivate,reactivate}`, `groups.{list,create,remove,addMember,removeMember,listMine}`, `invites.{list,revoke,resend}` |
|
|
11
|
-
| `ctx.assets` | `@colixsystems/assets-client` | the Asset Manager: `get(id)`, `list(query)`, `upload(formData)` over `/files` — what `useAsset()`
|
|
11
|
+
| `ctx.assets` | `@colixsystems/assets-client` | the Asset Manager: `get(id)`, `list(query)`, `upload(formData)` over `/files` — what `useAsset()` (single asset by id) and `useAssetsByTag()` (every asset carrying a tag) resolve |
|
|
12
12
|
| `ctx.payments` | `@colixsystems/payments-client` | `requestPayment(body)`, `getPayment(id)` |
|
|
13
13
|
|
|
14
14
|
**Wire / casing: snake_case end to end.** The clients send and return snake_case **verbatim** (`created_at`, `group_ids`, `can_read`, `amount_cents`, `data_type`, `is_active`, …). There is **no case transform anywhere** — not on the client and not in the backend; the only casing boundary is Prisma `@map` (snake_case field → camelCase column). Author-defined record column values pass through verbatim. Every `list(...)` returns the `{ data, meta }` envelope; the read hooks unwrap `res.data` for you.
|
|
@@ -34,6 +34,7 @@ The data layer lives in **four separate domain-client packages**, each instantia
|
|
|
34
34
|
| **DATASTORE** | `useDatastoreMutation(table)` | `{ create, update, delete }` | `records(table).{ create, update (PATCH), delete }` — `datastore.write:*` |
|
|
35
35
|
| **DATASTORE** | `useRecordPermissions(tableId, recordId)` | `{ permissions, loading, error, grant, revoke, update, refetch }` | `records(table).permissions(record).{ list, grant, update, revoke }` — `acl.write:records` (+ `can_grant` on the record) |
|
|
36
36
|
| **FILES** (`ctx.assets`) | `useAsset(id)` | `{ url, file, loading, error, refetch }` | `ctx.assets.get` — no scope |
|
|
37
|
+
| **FILES** | `useAssetsByTag(tag, { type? })` | `{ assets, loading, error, refetch }` | `ctx.assets.list` (unwraps `{ data, meta }` to `assets`) — no scope. `type` defaults to `"image"`; pass `"all"` / `"audio"` / `"video"` / `"document"` to widen. Falsy `tag` collapses to `assets: []` without a round-trip. |
|
|
37
38
|
| **DIRECTORY** (`ctx.directory`) | `useDirectory(query?)` | `{ users, loading, error, refetch }` | `directory.users.list` — `directory.read:users` |
|
|
38
39
|
| **DIRECTORY** | `useUsers(query?)` | `{ users, loading, error, refetch, invite, deactivate, reactivate, remove }` | `directory.users.*` — `users.read:*` (edits also `users.write:*`; `remove()` also `users.delete:*`) |
|
|
39
40
|
| **DIRECTORY** | `useGroups(query?)` | `{ groups, loading, error, refetch, create, remove, addMember, removeMember }` | `directory.groups.*` — `groups.read:*` (mutations also `groups.write:*`) |
|
|
@@ -48,7 +49,15 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
|
|
|
48
49
|
|
|
49
50
|
## Status
|
|
50
51
|
|
|
51
|
-
`v0.
|
|
52
|
+
`v0.43.0` — pre-publish. The package surface (types, function names, export paths) is the v1 contract; runtime behaviour for some hooks is stubbed (each hook documents what's wired and what isn't). It is **not yet published to npm**.
|
|
53
|
+
|
|
54
|
+
### What's new in 0.43.0
|
|
55
|
+
|
|
56
|
+
**New `useAssetsByTag(tag, { type? })` hook (sc-1241).** Lists every tenant asset carrying a given tag — backs the built-in Gallery widget's "All images with a tag" source mode (which used to render a stub) and is a general SDK primitive any widget can call (an audio playlist filtered by mood, a document index by category, an image wall by topic). Reads `ctx.assets.list({ tag, type, limit })` (already injected by both the web Player and the native Expo export) and unwraps `{ data, meta }`. `type` defaults to `"image"` so the common Gallery case gets only images back; pass `"all"` (or `"audio"` / `"video"` / `"document"`) to widen. Falsy `tag` collapses to `assets: []` without a network round-trip. Additive — `CONTRACT.version` bumped to 1.30.0. The `assets` context slice grows a new required field `list: "function"` (both hosts already inject it).
|
|
57
|
+
|
|
58
|
+
### What's new in 0.42.0
|
|
59
|
+
|
|
60
|
+
**RELATION columns hydrate with a display label (sc-1181).** Record reads now return `{ id, label }` for ONE_TO_ONE / ONE_TO_MANY and `[{ id, label }, ...]` for MANY_TO_MANY (empty array when no links) — `label` is the value of the column pointed at by the new optional `display_column_id` on `DatastoreSchemaColumn`, or, when unset, the first STRING/TEXT column on the target table. Widgets should render `record.<rel>.label` (or `record.<rel>.map(r => r.label).join(", ")` for M:M) directly; `.id` is still there for the foreign-key case. The cell-formatting helpers in the built-in `DataList`, `TabbedDatalist`, and `FieldValue` widgets already walk arrays and prefer `label` over `name` / `id` — author widgets that need the same can copy that pattern. `CONTRACT.version` is unchanged.
|
|
52
61
|
|
|
53
62
|
### What's new in 0.41.0
|
|
54
63
|
|
package/dist/contract.cjs
CHANGED
|
@@ -175,6 +175,28 @@ const HOOKS = [
|
|
|
175
175
|
requiredContextSlice: ["assets.get"],
|
|
176
176
|
scopes: null,
|
|
177
177
|
},
|
|
178
|
+
{
|
|
179
|
+
name: "useAssetsByTag",
|
|
180
|
+
signature: "useAssetsByTag(tag, { type? } = {})",
|
|
181
|
+
description:
|
|
182
|
+
"List every tenant asset carrying a given tag. Backs the Gallery " +
|
|
183
|
+
"widget's tag-source mode but is a general SDK primitive — any widget " +
|
|
184
|
+
"can render a collection of assets the author groups by tag (image " +
|
|
185
|
+
"wall, audio playlist filtered by mood, document index by category). " +
|
|
186
|
+
"Reads ctx.assets.list({ tag, type, limit }) and unwraps the " +
|
|
187
|
+
"{ data, meta } envelope to the assets array. When tag is falsy the " +
|
|
188
|
+
"hook collapses to an empty result without a network round-trip. " +
|
|
189
|
+
"type defaults to 'image' so a Gallery using it gets only images " +
|
|
190
|
+
"back; pass type: 'all' (or 'audio' / 'video' / 'document') to widen.",
|
|
191
|
+
returnShape: {
|
|
192
|
+
assets: "Asset[]",
|
|
193
|
+
loading: "boolean",
|
|
194
|
+
error: "DatastoreError | null",
|
|
195
|
+
refetch: "() => Promise<void>",
|
|
196
|
+
},
|
|
197
|
+
requiredContextSlice: ["assets.list"],
|
|
198
|
+
scopes: null,
|
|
199
|
+
},
|
|
178
200
|
{
|
|
179
201
|
name: "useFilestoreFiles",
|
|
180
202
|
signature: "useFilestoreFiles({ spaceType, folderId?, q?, type? })",
|
|
@@ -924,9 +946,9 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
924
946
|
description:
|
|
925
947
|
"Injected @colixsystems/assets-client instance, FLATTENED so asset ops are top-level. " +
|
|
926
948
|
"{ get(id) -> Promise<{ id, url, ... }>, list(query) -> Promise<{ data, meta }>, upload(formData) }. " +
|
|
927
|
-
"Backs useAsset(); the returned
|
|
949
|
+
"Backs useAsset() (resolve one asset by id) and useAssetsByTag() (list every tenant asset carrying a tag); the returned assets already carry absolute urls the widget can drop into an <Image source>.",
|
|
928
950
|
required: true,
|
|
929
|
-
fields: { get: "function" },
|
|
951
|
+
fields: { get: "function", list: "function" },
|
|
930
952
|
},
|
|
931
953
|
filestore: {
|
|
932
954
|
description:
|
|
@@ -1627,7 +1649,16 @@ const CONTRACT = deepFreeze({
|
|
|
1627
1649
|
// useDatastoreRecord / useAsset) auto-subscribe their own `refetch`.
|
|
1628
1650
|
// No existing hook, primitive, manifest field, or token changed shape
|
|
1629
1651
|
// — minor bump on the pre-1.0 channel.
|
|
1630
|
-
|
|
1652
|
+
//
|
|
1653
|
+
// 1.30.0: additive (sc-1241) — `useAssetsByTag(tag, { type? })` lists every
|
|
1654
|
+
// tenant asset carrying a given tag. Backs the Gallery widget's
|
|
1655
|
+
// "All images with a tag" source mode (which used to render a stub),
|
|
1656
|
+
// but is a general SDK primitive any widget can call. Reads
|
|
1657
|
+
// `ctx.assets.list({ tag, type, limit })` (already injected by both
|
|
1658
|
+
// hosts) and unwraps `{ data, meta }`. New required field on the
|
|
1659
|
+
// `assets` context slice: `list: "function"`. No existing hook,
|
|
1660
|
+
// primitive, manifest field, or token changed shape — minor bump.
|
|
1661
|
+
version: "1.30.0",
|
|
1631
1662
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
1632
1663
|
hooks: HOOKS,
|
|
1633
1664
|
primitives: PRIMITIVES,
|
package/dist/contract.js
CHANGED
|
@@ -175,6 +175,28 @@ const HOOKS = [
|
|
|
175
175
|
requiredContextSlice: ["assets.get"],
|
|
176
176
|
scopes: null,
|
|
177
177
|
},
|
|
178
|
+
{
|
|
179
|
+
name: "useAssetsByTag",
|
|
180
|
+
signature: "useAssetsByTag(tag, { type? } = {})",
|
|
181
|
+
description:
|
|
182
|
+
"List every tenant asset carrying a given tag. Backs the Gallery " +
|
|
183
|
+
"widget's tag-source mode but is a general SDK primitive — any widget " +
|
|
184
|
+
"can render a collection of assets the author groups by tag (image " +
|
|
185
|
+
"wall, audio playlist filtered by mood, document index by category). " +
|
|
186
|
+
"Reads ctx.assets.list({ tag, type, limit }) and unwraps the " +
|
|
187
|
+
"{ data, meta } envelope to the assets array. When tag is falsy the " +
|
|
188
|
+
"hook collapses to an empty result without a network round-trip. " +
|
|
189
|
+
"type defaults to 'image' so a Gallery using it gets only images " +
|
|
190
|
+
"back; pass type: 'all' (or 'audio' / 'video' / 'document') to widen.",
|
|
191
|
+
returnShape: {
|
|
192
|
+
assets: "Asset[]",
|
|
193
|
+
loading: "boolean",
|
|
194
|
+
error: "DatastoreError | null",
|
|
195
|
+
refetch: "() => Promise<void>",
|
|
196
|
+
},
|
|
197
|
+
requiredContextSlice: ["assets.list"],
|
|
198
|
+
scopes: null,
|
|
199
|
+
},
|
|
178
200
|
{
|
|
179
201
|
name: "useFilestoreFiles",
|
|
180
202
|
signature: "useFilestoreFiles({ spaceType, folderId?, q?, type? })",
|
|
@@ -924,9 +946,9 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
924
946
|
description:
|
|
925
947
|
"Injected @colixsystems/assets-client instance, FLATTENED so asset ops are top-level. " +
|
|
926
948
|
"{ get(id) -> Promise<{ id, url, ... }>, list(query) -> Promise<{ data, meta }>, upload(formData) }. " +
|
|
927
|
-
"Backs useAsset(); the returned
|
|
949
|
+
"Backs useAsset() (resolve one asset by id) and useAssetsByTag() (list every tenant asset carrying a tag); the returned assets already carry absolute urls the widget can drop into an <Image source>.",
|
|
928
950
|
required: true,
|
|
929
|
-
fields: { get: "function" },
|
|
951
|
+
fields: { get: "function", list: "function" },
|
|
930
952
|
},
|
|
931
953
|
filestore: {
|
|
932
954
|
description:
|
|
@@ -1627,7 +1649,16 @@ const CONTRACT = deepFreeze({
|
|
|
1627
1649
|
// useDatastoreRecord / useAsset) auto-subscribe their own `refetch`.
|
|
1628
1650
|
// No existing hook, primitive, manifest field, or token changed shape
|
|
1629
1651
|
// — minor bump on the pre-1.0 channel.
|
|
1630
|
-
|
|
1652
|
+
//
|
|
1653
|
+
// 1.30.0: additive (sc-1241) — `useAssetsByTag(tag, { type? })` lists every
|
|
1654
|
+
// tenant asset carrying a given tag. Backs the Gallery widget's
|
|
1655
|
+
// "All images with a tag" source mode (which used to render a stub),
|
|
1656
|
+
// but is a general SDK primitive any widget can call. Reads
|
|
1657
|
+
// `ctx.assets.list({ tag, type, limit })` (already injected by both
|
|
1658
|
+
// hosts) and unwraps `{ data, meta }`. New required field on the
|
|
1659
|
+
// `assets` context slice: `list: "function"`. No existing hook,
|
|
1660
|
+
// primitive, manifest field, or token changed shape — minor bump.
|
|
1661
|
+
version: "1.30.0",
|
|
1631
1662
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
1632
1663
|
hooks: HOOKS,
|
|
1633
1664
|
primitives: PRIMITIVES,
|
package/dist/hooks.js
CHANGED
|
@@ -1197,6 +1197,80 @@ export function useAsset(assetId) {
|
|
|
1197
1197
|
return { url, asset, loading, error, refetch };
|
|
1198
1198
|
}
|
|
1199
1199
|
|
|
1200
|
+
/**
|
|
1201
|
+
* List every tenant asset carrying a given tag. Returns
|
|
1202
|
+
* `{ assets, loading, error, refetch }`. The widget passes a single `tag`
|
|
1203
|
+
* string plus an optional `{ type }` filter (defaults to `"image"` so the
|
|
1204
|
+
* common gallery case gets only images back; pass `"all"` / `"audio"` /
|
|
1205
|
+
* `"video"` / `"document"` to widen). The hook calls
|
|
1206
|
+
* `ctx.assets.list({ tag, type, limit: 1000 })` and unwraps the
|
|
1207
|
+
* `{ data, meta }` envelope to the array. When `tag` is falsy or whitespace
|
|
1208
|
+
* the hook collapses to an empty result without a network round-trip.
|
|
1209
|
+
*
|
|
1210
|
+
* Like `useAsset`, every returned row already carries an absolute `url` the
|
|
1211
|
+
* widget can drop into `<Image source>`.
|
|
1212
|
+
*/
|
|
1213
|
+
export function useAssetsByTag(tag, options) {
|
|
1214
|
+
const ctx = useWidgetContextOrThrow("useAssetsByTag");
|
|
1215
|
+
if (!ctx.assets || typeof ctx.assets.list !== "function") {
|
|
1216
|
+
throw new Error(
|
|
1217
|
+
"useAssetsByTag: host did not inject an assets client (ctx.assets.list)",
|
|
1218
|
+
);
|
|
1219
|
+
}
|
|
1220
|
+
const type = (options && options.type) || "image";
|
|
1221
|
+
const trimmedTag = typeof tag === "string" ? tag.trim() : "";
|
|
1222
|
+
const ready = trimmedTag.length > 0;
|
|
1223
|
+
|
|
1224
|
+
const [assets, setAssets] = useState([]);
|
|
1225
|
+
const [loading, setLoading] = useState(ready);
|
|
1226
|
+
const [error, setError] = useState(null);
|
|
1227
|
+
|
|
1228
|
+
const listRef = useRef(ctx.assets.list);
|
|
1229
|
+
listRef.current = ctx.assets.list;
|
|
1230
|
+
const argsRef = useRef({ tag: trimmedTag, type });
|
|
1231
|
+
argsRef.current = { tag: trimmedTag, type };
|
|
1232
|
+
const runRef = useRef(0);
|
|
1233
|
+
|
|
1234
|
+
const doFetch = useCallback(async () => {
|
|
1235
|
+
const myRun = ++runRef.current;
|
|
1236
|
+
const { tag: t, type: ty } = argsRef.current;
|
|
1237
|
+
if (!t) {
|
|
1238
|
+
setLoading(false);
|
|
1239
|
+
setError(null);
|
|
1240
|
+
setAssets([]);
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
setLoading(true);
|
|
1244
|
+
setError(null);
|
|
1245
|
+
try {
|
|
1246
|
+
const res = await listRef.current({ tag: t, type: ty, limit: 1000 });
|
|
1247
|
+
const rows = res && Array.isArray(res.data) ? res.data : [];
|
|
1248
|
+
if (runRef.current !== myRun) return;
|
|
1249
|
+
setAssets(rows);
|
|
1250
|
+
setLoading(false);
|
|
1251
|
+
} catch (err) {
|
|
1252
|
+
if (runRef.current !== myRun) return;
|
|
1253
|
+
setError(toDatastoreError(err));
|
|
1254
|
+
setLoading(false);
|
|
1255
|
+
}
|
|
1256
|
+
}, []);
|
|
1257
|
+
|
|
1258
|
+
useEffect(() => {
|
|
1259
|
+
doFetch();
|
|
1260
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1261
|
+
}, [trimmedTag, type]);
|
|
1262
|
+
|
|
1263
|
+
const refetch = useCallback(async () => {
|
|
1264
|
+
await doFetch();
|
|
1265
|
+
}, [doFetch]);
|
|
1266
|
+
|
|
1267
|
+
// sc-1179 — auto-subscribe to the page-level refresh tick so a Gallery in
|
|
1268
|
+
// tag mode picks up newly tagged assets when the author refreshes.
|
|
1269
|
+
useRefresh(refetch);
|
|
1270
|
+
|
|
1271
|
+
return { assets, loading, error, refetch };
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1200
1274
|
/* ============================================================================
|
|
1201
1275
|
* FILESTORE CLIENT — ctx.filestore (@colixsystems/filestore-client)
|
|
1202
1276
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -662,6 +662,13 @@ export interface DatastoreSchemaColumn {
|
|
|
662
662
|
relation_type?: "ONE_TO_ONE" | "ONE_TO_MANY" | "MANY_TO_MANY" | null;
|
|
663
663
|
/** For RELATION columns only — the id of the table this column points at. */
|
|
664
664
|
target_table_id?: string | null;
|
|
665
|
+
/**
|
|
666
|
+
* For RELATION columns only — id of a column on `target_table_id` whose
|
|
667
|
+
* value supplies the `label` hydrated next to the related record id in
|
|
668
|
+
* record responses. `null` falls back to the first STRING/TEXT column on
|
|
669
|
+
* the target table.
|
|
670
|
+
*/
|
|
671
|
+
display_column_id?: string | null;
|
|
665
672
|
/** True when this column is the table's display/identification column. */
|
|
666
673
|
is_identification?: boolean;
|
|
667
674
|
}
|
|
@@ -757,6 +764,35 @@ export function useAsset(assetId: string | null | undefined): {
|
|
|
757
764
|
refetch(): Promise<void>;
|
|
758
765
|
};
|
|
759
766
|
|
|
767
|
+
/**
|
|
768
|
+
* List every tenant asset carrying a given tag. Reads `ctx.assets.list({
|
|
769
|
+
* tag, type, limit })` and unwraps the `{ data, meta }` envelope.
|
|
770
|
+
*
|
|
771
|
+
* `type` defaults to `"image"` so the common Gallery case gets only images
|
|
772
|
+
* back; pass `"all"` (or `"audio"` / `"video"` / `"document"`) to widen.
|
|
773
|
+
* When `tag` is falsy the hook collapses to an empty result without a
|
|
774
|
+
* network round-trip.
|
|
775
|
+
*/
|
|
776
|
+
export function useAssetsByTag(
|
|
777
|
+
tag: string | null | undefined,
|
|
778
|
+
options?: {
|
|
779
|
+
type?: "image" | "audio" | "video" | "document" | "all";
|
|
780
|
+
},
|
|
781
|
+
): {
|
|
782
|
+
assets: Array<{
|
|
783
|
+
id: string;
|
|
784
|
+
url: string;
|
|
785
|
+
stored_filename?: string;
|
|
786
|
+
mime_type?: string;
|
|
787
|
+
size_bytes?: number;
|
|
788
|
+
tags?: string[];
|
|
789
|
+
[k: string]: unknown;
|
|
790
|
+
}>;
|
|
791
|
+
loading: boolean;
|
|
792
|
+
error: DatastoreError | null;
|
|
793
|
+
refetch(): Promise<void>;
|
|
794
|
+
};
|
|
795
|
+
|
|
760
796
|
export function useI18n(): {
|
|
761
797
|
locale: string;
|
|
762
798
|
t(key: string, fallback?: string): string;
|
package/dist/index.js
CHANGED
package/dist/index.native.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colixsystems/widget-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.43.0",
|
|
4
4
|
"description": "Common widget interface for AppStudio. Implements WidgetManifest, WidgetContext, property schema, and helper hooks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
],
|
|
40
40
|
"scripts": {
|
|
41
41
|
"build": "node scripts/build.js",
|
|
42
|
-
"test": "node --test src/__tests__/contract.test.js src/__tests__/hooks-users.test.js src/__tests__/hooks-groups.test.js src/__tests__/hooks-schema.test.js src/__tests__/hooks-mutation.test.js src/__tests__/hooks-record-permissions.test.js src/__tests__/hooks-subscription.test.js src/__tests__/linter-users-scope.test.js src/__tests__/linter-comments.test.js src/__tests__/manifest-actions.test.js src/__tests__/widget-translations.test.js src/__tests__/devserver.test.js src/__tests__/host-externals.test.js"
|
|
42
|
+
"test": "node --test src/__tests__/contract.test.js src/__tests__/hooks-users.test.js src/__tests__/hooks-groups.test.js src/__tests__/hooks-schema.test.js src/__tests__/hooks-assets-by-tag.test.js src/__tests__/hooks-mutation.test.js src/__tests__/hooks-record-permissions.test.js src/__tests__/hooks-subscription.test.js src/__tests__/linter-users-scope.test.js src/__tests__/linter-comments.test.js src/__tests__/manifest-actions.test.js src/__tests__/widget-translations.test.js src/__tests__/devserver.test.js src/__tests__/host-externals.test.js"
|
|
43
43
|
},
|
|
44
44
|
"engines": {
|
|
45
45
|
"node": ">=18"
|