From b72b88d3c652454f9cc2e2283a99e18002dbf550 Mon Sep 17 00:00:00 2001 From: CodeMash Date: Wed, 7 Oct 2026 11:15:15 +0300 Subject: [PATCH 1/8] =?UTF-8?q?docs(tasks):=20schema-content=20react-redux?= =?UTF-8?q?=20task=20file=20=E2=80=94=20core=20SDK=20branch=20overlay,=20p?= =?UTF-8?q?lan?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Step 1 of docs/tasks/schema-content-react-redux.md. Built against the @norbix.ai/ts branch audit/schema-content as an npm-pack tarball installed with --no-save; package.json stays on 4.11.0 until the core SDK release. Co-Authored-By: Claude Fable 5.1 --- docs/tasks/schema-content-react-redux.md | 48 ++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 docs/tasks/schema-content-react-redux.md diff --git a/docs/tasks/schema-content-react-redux.md b/docs/tasks/schema-content-react-redux.md new file mode 100644 index 0000000..84de342 --- /dev/null +++ b/docs/tasks/schema-content-react-redux.md @@ -0,0 +1,48 @@ +# Schema content — react-redux hooks for expanded references and files by id + +## Goal + +Give `@norbix.ai/react-redux` the hooks the schema-content campaign adds to the core SDK (`@norbix.ai/ts` branch `audit/schema-content`): `expandReferences` on the three end-user record reads, and the two "file by id" reads. + +Not in scope: merging — this branch ships to `main` only after the gateway campaign lands and `@norbix.ai/ts` releases; the generated DTO changes (ObjectFieldDto, ArrayFieldDto, JsonFieldDto, CurrencyDefaultDto, the new field properties, the new error codes) — this package carries no DTO of its own, it reads them from the core SDK's types; the `norbix.collection()` helper (plain SDK, no hook). + +## Plan + +1. chore(deps): build against the core SDK branch — done. `npm pack` in `/Users/djovaisas/Projects/norbix/worktrees/norbix-js/audit/schema-content` → `norbix.ai-ts-1.2.0.tgz` (scratch folder), then `npm install --no-save ` in this worktree. `package.json` / `package-lock.json` untouched: the tarball is a local overlay, not a dependency anyone can install. See "Needs you". +2. feat(files): `useGetFileByIdQuery` (`hub.files.getFileById`, `GET /{version}/files/item/by-id`) and `useGetFileByIdApiQuery` (`api.files.getFileById`, `GET /{version}/files/{filesIntegrationId}/by-id/{id}`) — done, `src/hooks/hub/files.ts`, `src/hooks/api/files.ts`. +3. docs(database): `expandReferences` and `arrayFilters` on the existing record hooks — done, `src/hooks/api/database.ts` (hook comments; no new hook: the three find hooks already pass the whole request through, the option is a request field). +4. test(files, database): hook-shape tests, fake-transport tests and real-SDK fake-fetch wire tests — done, `tests/files.test.ts`, `tests/database.test.ts`. +5. docs(readme): "Expanded references and files by id" section — done, `README.md`. +6. ship: `nbx-ship --no-merge` — done, pull request link in "Needs you". + +## Changes + +| file | what changed | plan step # | +| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | +| `src/hooks/api/files.ts` | `getFileByIdApi` query (provides `Files`); header route list and count 10 → 11 | 2 | +| `src/hooks/hub/files.ts` | `getFileById` query (provides `Files`); header route list and count 22 → 23 | 2 | +| `src/hooks/api/database.ts` | comments on `findCollection` / `findOne` / `findOwn` (`expandReferences`, error 056) and `updateOne` / `updateMany` (`arrayFilters`) | 3 | +| `tests/files.test.ts` | counts 23 / 11; both by-id hooks reach their SDK method and are `Files` queries; not shadowed in the flat map; real-SDK fake-fetch: route with the ids substituted, `isPublic` / `publicUrl` parsed | 4 | +| `tests/database.test.ts` | real-SDK fake-fetch: `expandReferences=true` reaches the wire on `findCollection`, `findOne`, `findOwn` (Api), `arrayFilters` on `updateOne`; a 056 refusal lands as the serialized error | 4 | +| `README.md` | section "Expanded references and files by id (core SDK, schema-content)" | 5 | +| `docs/tasks/schema-content-react-redux.md` | this file | — | + +## Findings + +- The hub `getFileById` request DTO (`CodeMashHub2.GetFileById`) carries `filesIntegrationId` and `id` as plain `@ApiMember`s with no path params, so the core SDK sends them as query string on `GET /v2/files/item/by-id`; the Api one substitutes both into the path. The wire tests pin both. Nothing to fix here. +- `apiDatabase` hooks still pass `(arg as { collectionName?: string })` for the tag id — the arg type already has `collectionName`, the cast is a leftover from before the typed `Arg<>` helper. Left as is (not this task). +- `npx prettier --check .` reports the same 20 pre-existing files as the last task (docs, examples, `tests/provider.test.tsx`). Not touched. + +## Rejected / moved out + +- Bumping `@norbix.ai/ts` in `package.json` to the version carrying these methods — impossible now: that version is not published (the core SDK branch is also waiting for the gateway campaign). The bump is the first commit on this branch once `@norbix.ai/ts` releases; until then the pull request checks run against npm's 4.11.0 and the typecheck / tests are red there by design (see "Needs you"). +- A per-id cache tag for the by-id reads (like `getPublicFile`'s `Files/PUBLIC:`) — not done: `isPublic` / `publicUrl` are part of the by-id answer, and publish / unpublish / delete invalidate the plain `Files` tag, so the whole-tag refetch is the correct one; a private id tag would make those answers stale. + +## Needs you + +- The pull request is opened with `--no-merge`. Its checks are red on purpose until `@norbix.ai/ts` publishes the schema-content release: then (1) `npm install --save-dev @norbix.ai/ts@` and bump the peer range in `package.json` on this branch, (2) re-run `npm test`, `npm run typecheck`, `npm run lint`, (3) merge with "Rebase and merge". +- Nothing else. + +## Open questions + +- None. From 142c1daf8f331f1a0e59d3a3c17a3ffd985a318b Mon Sep 17 00:00:00 2001 From: CodeMash Date: Wed, 7 Oct 2026 11:15:17 +0300 Subject: [PATCH 2/8] =?UTF-8?q?feat(files):=20useGetFileByIdQuery=20and=20?= =?UTF-8?q?useGetFileByIdApiQuery=20=E2=80=94=20read=20a=20file=20by=20its?= =?UTF-8?q?=20stable=20id?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Step 2 of docs/tasks/schema-content-react-redux.md. hub.files.getFileById (GET /{version}/files/item/by-id) and api.files.getFileById (GET /{version}/files/{filesIntegrationId}/by-id/{id}); the Api hook carries the Api suffix like deleteFileApi because both surfaces share one flat endpoint map. Both provide Files. Endpoint counts 23 / 11. Co-Authored-By: Claude Fable 5.1 --- src/hooks/api/files.ts | 28 ++++++++++++++++++++++++++-- src/hooks/hub/files.ts | 23 +++++++++++++++++++++-- 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/src/hooks/api/files.ts b/src/hooks/api/files.ts index a1b31a0..cae697d 100644 --- a/src/hooks/api/files.ts +++ b/src/hooks/api/files.ts @@ -1,4 +1,4 @@ -// AUTO-GENERATED — full coverage of `norbix.api.files` (10 endpoints). +// AUTO-GENERATED — full coverage of `norbix.api.files` (11 endpoints). // Synced from the norbix core SDK surface. Re-run the hook sync to refresh. // // The gateway routes behind these hooks, so the endpoint-coverage matrix can @@ -10,6 +10,7 @@ // DELETE /{version}/files/{filesIntegrationId}/bulk // POST /{version}/files/{filesIntegrationId}/commit // GET /{version}/files/{filesIntegrationId}/download +// GET /{version}/files/{filesIntegrationId}/by-id/{id} // GET /{version}/files/{filesIntegrationId}/info // GET /{version}/files/{filesIntegrationId}/sign // POST /{version}/files/{filesIntegrationId}/upload-url @@ -24,6 +25,7 @@ type CommitUpload = Norbix['api']['files']['commitUpload']; type DeleteFileApi = Norbix['api']['files']['deleteFileApi']; type DeleteManyFilesApi = Norbix['api']['files']['deleteManyFilesApi']; type DownloadFileApi = Norbix['api']['files']['downloadFileApi']; +type GetFileByIdApi = Norbix['api']['files']['getFileById']; type GetFileInfo = Norbix['api']['files']['getFileInfo']; type GetSignedUrl = Norbix['api']['files']['getSignedUrl']; type ListFiles = Norbix['api']['files']['listFiles']; @@ -32,7 +34,7 @@ type GetPublicFile = Norbix['api']['files']['getPublicFile']; type TestFilesIntegrationApi = Norbix['api']['files']['testFilesIntegration']; /** - * `api.files` — 10 endpoints, 1:1 with the core SDK. + * `api.files` — 11 endpoints, 1:1 with the core SDK. */ export const apiFiles = (b: Builder) => ({ commitUpload: b.mutation, Arg>({ @@ -55,6 +57,28 @@ export const apiFiles = (b: Builder) => ({ providesTags: ['Files'], }), + /** + * Reads one file by its stable id — the value a record's file field stores, + * and the `id` an expanded reference (`expandReferences: true` on a find) + * returns. Answers the file's `resource`, `path`, `isPublic` and + * `publicUrl`; an id no storage of the integration holds is not found. + * + * Wraps `api.files.getFileById` + * (`GET /{version}/files/{filesIntegrationId}/by-id/{id}`). The Hub has a + * by-id read of the same SDK name (`hub.files.getFileById`, hook + * `useGetFileByIdQuery`); both live in one flat endpoint map, so this one + * carries the `Api` suffix like `deleteFileApi` and `downloadFileApi`. + * Hook: `useGetFileByIdApiQuery`. + * + * Cached under `Files`, like `getFileInfo`: a publish / unpublish or a + * delete invalidates `Files`, and `isPublic` / `publicUrl` are part of this + * answer, so the whole-tag refetch is the right one here. + */ + getFileByIdApi: b.query, Arg>({ + query: (args) => (norbix) => norbix.api.files.getFileById(args), + providesTags: ['Files'], + }), + getFileInfo: b.query, Arg>({ query: (args) => (norbix) => norbix.api.files.getFileInfo(args), providesTags: ['Files'], diff --git a/src/hooks/hub/files.ts b/src/hooks/hub/files.ts index 79af685..819c0ee 100644 --- a/src/hooks/hub/files.ts +++ b/src/hooks/hub/files.ts @@ -1,4 +1,4 @@ -// AUTO-GENERATED — full coverage of `norbix.hub.files` (22 endpoints). +// AUTO-GENERATED — full coverage of `norbix.hub.files` (23 endpoints). // Synced from the norbix core SDK surface. Re-run the hook sync to refresh. // // The gateway routes behind these hooks, so the endpoint-coverage matrix can @@ -9,6 +9,7 @@ // PUT /{version}/files/disable // GET /{version}/files/folder // GET /{version}/files/item +// GET /{version}/files/item/by-id // POST /{version}/files/item/public // POST /{version}/files/item/private // POST /{version}/files/folder/public @@ -48,6 +49,7 @@ type GetFilesIntegrations = Norbix['hub']['files']['getFilesIntegrations']; type SaveFilesIntegration = Norbix['hub']['files']['saveFilesIntegration']; type SetFilesIntegrationAsDefault = Norbix['hub']['files']['setFilesIntegrationAsDefault']; type GetFile = Norbix['hub']['files']['getFile']; +type GetFileById = Norbix['hub']['files']['getFileById']; type GetFolderFiles = Norbix['hub']['files']['getFolderFiles']; type TestFilesIntegration = Norbix['hub']['files']['testFilesIntegration']; type MakeFilePublic = Norbix['hub']['files']['makeFilePublic']; @@ -56,7 +58,7 @@ type MakeFolderPublic = Norbix['hub']['files']['makeFolderPublic']; type MakeFolderPrivate = Norbix['hub']['files']['makeFolderPrivate']; /** - * `hub.files` — 22 endpoints, 1:1 with the core SDK. + * `hub.files` — 23 endpoints, 1:1 with the core SDK. */ export const hubFiles = (b: Builder) => ({ disableFiles: b.mutation, Arg>({ @@ -139,6 +141,23 @@ export const hubFiles = (b: Builder) => ({ providesTags: ['Files'], }), + /** + * Reads one file by its stable id (`nbfl_…` or its bare UUID) — the value a + * record's file field stores, and the `id` an expanded reference returns. + * Unlike `getFile`, which needs the path, this survives a move or rename. + * + * Wraps `hub.files.getFileById` (`GET /{version}/files/item/by-id`). + * Hook: `useGetFileByIdQuery`. The end-user read of the same name is + * `useGetFileByIdApiQuery` (`api.files.getFileById`). + * + * Cached under `Files` like `getFile`: publish, unpublish and delete + * invalidate it, and `isPublic` / `publicUrl` are part of this answer. + */ + getFileById: b.query, Arg>({ + query: (args) => (norbix) => norbix.hub.files.getFileById(args), + providesTags: ['Files'], + }), + getFolderFiles: b.query, Arg>({ query: (args) => (norbix) => norbix.hub.files.getFolderFiles(args), providesTags: ['Files'], From 6ea653161bbdfc33854d55866f5fae1c3cfc4579 Mon Sep 17 00:00:00 2001 From: CodeMash Date: Wed, 7 Oct 2026 11:15:18 +0300 Subject: [PATCH 3/8] docs(database): expandReferences on the record reads, arrayFilters on the updates Step 3 of docs/tasks/schema-content-react-redux.md. No new hook: the option is a field of the request the existing findCollection / findOne / findOwn hooks pass through. Comments explain the { id, display } shape, error CM-ERRORS-DATABASE-056 and the cache behaviour. Co-Authored-By: Claude Fable 5.1 --- src/hooks/api/database.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/hooks/api/database.ts b/src/hooks/api/database.ts index b7732d1..a846f53 100644 --- a/src/hooks/api/database.ts +++ b/src/hooks/api/database.ts @@ -37,6 +37,21 @@ type GetDatabaseSchemas = Norbix['api']['database']['getDatabaseSchemas']; */ export const apiDatabase = (b: Builder) => ({ // ---- Collections — Read ---- + /** + * `expandReferences: true` on this request (and on `findOne` / `findOwn`) + * makes every reference value come back as `{ id, display }` — `display` + * is the target's `displayField` per the schema, `null` when the target is + * gone — instead of the stored id. It needs read permission on every + * source the schema links to (users, roles, taxonomy, collection, files); + * otherwise the read is refused with `CM-ERRORS-DATABASE-056` naming the + * source, which lands in `error` like any other gateway refusal. + * + * The cache key is still the collection: an expanded read and a plain read + * of the same collection are two entries under one tag, and a write to a + * referenced collection does NOT refetch the expanded one — `refetch()` + * when a display value must be fresh. A file reference's `id` is what + * `useGetFileByIdApiQuery` reads. + */ findCollection: b.query, Arg>({ query: (args) => (norbix) => norbix.api.database.find(args), providesTags: (_res, _err, arg) => [ @@ -47,6 +62,7 @@ export const apiDatabase = (b: Builder) => ({ ], }), + // Same `expandReferences` option as `findCollection`. findOne: b.query, Arg>({ query: (args) => (norbix) => norbix.api.database.findOne(args), providesTags: (_res, _err, arg) => [ @@ -58,6 +74,7 @@ export const apiDatabase = (b: Builder) => ({ }), // Only the records the caller is responsible for (owner-scoped find). + // Same `expandReferences` option as `findCollection`. findOwn: b.query, Arg>({ query: (args) => (norbix) => norbix.api.database.findOwn(args), providesTags: (_res, _err, arg) => [ @@ -117,6 +134,12 @@ export const apiDatabase = (b: Builder) => ({ ], }), + /** + * `update` may address nested paths (`{"$set":{"address.city":"Vilnius"}}`) + * and array elements by position (`items.0.qty`) or by filter + * (`items.$[it].qty` + `arrayFilters: '[{"it.sku":"A1"}]'` — a JSON string, + * same as on `updateMany`). + */ updateOne: b.mutation, Arg>({ query: (args) => (norbix) => norbix.api.database.updateOne(args), invalidatesTags: (_res, _err, arg) => [ @@ -127,6 +150,7 @@ export const apiDatabase = (b: Builder) => ({ ], }), + // Takes the same `arrayFilters` JSON string as `updateOne`. updateMany: b.mutation, Arg>({ query: (args) => (norbix) => norbix.api.database.updateMany(args), invalidatesTags: (_res, _err, arg) => [ From 766eba8238fe74bee20f18010080806045ff90aa Mon Sep 17 00:00:00 2001 From: CodeMash Date: Wed, 7 Oct 2026 11:15:19 +0300 Subject: [PATCH 4/8] test(files, database): files by id and expandReferences / arrayFilters reach the wire Step 4 of docs/tasks/schema-content-react-redux.md. Hook shape (counts 23 / 11, Files query tags, no shadowing in the flat map), fake-transport reach tests, and real-SDK fake-fetch tests: Api by-id substitutes both ids into the path, Hub by-id sends them as query string, a 404 lands as the serialized error; expandReferences=true reaches find / findOne / findOwn and a plain read sends none, a 056 refusal lands as the serialized error, updateOne carries arrayFilters. Co-Authored-By: Claude Fable 5.1 --- tests/database.test.ts | 149 ++++++++++++++++++++++++++++++++++++++ tests/files.test.ts | 157 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 300 insertions(+), 6 deletions(-) diff --git a/tests/database.test.ts b/tests/database.test.ts index bba1304..8bfe867 100644 --- a/tests/database.test.ts +++ b/tests/database.test.ts @@ -390,3 +390,152 @@ describe('hub records through the real SDK (fake fetch)', () => { }); }); }); + +/** + * Schema-content campaign: `expandReferences` on the three end-user record + * reads and `arrayFilters` on the record updates are request fields, not new + * hooks — so what matters is that the existing hooks carry them to the wire + * unchanged. Real `@norbix.ai/ts` client + this package's base query, fake + * fetch, never a real server. + */ +describe('api records through the real SDK (fake fetch): expandReferences, arrayFilters', () => { + function run(key: string, args: unknown, status: number, body: unknown) { + // Only globalThis.* here: the lint config declares no DOM globals. + type FetchFn = typeof globalThis.fetch; + const seen: Array<{ url: string; method: string; body: unknown }> = []; + const fetchImpl = vi.fn(async (...[input, init]: Parameters) => { + seen.push({ + url: String(input), + method: (init?.method ?? 'GET').toUpperCase(), + body: init?.body ? JSON.parse(String(init.body)) : undefined, + }); + return new globalThis.Response(JSON.stringify(body), { + status, + headers: { 'Content-Type': 'application/json' }, + }); + }) as unknown as FetchFn; + const norbix = new Norbix({ + bearerToken: 'test-token', + projectId: 'test-project', + apiVersion: 'v2', + hubVersion: 'v2', + baseUrl: { api: 'https://api.norbix.io', hub: 'https://hub.norbix.io' }, + fetch: fetchImpl, + }); + const endpoints = apiDatabase(fakeBuilder()) as unknown as Record; + const baseQuery = createNorbixBaseQuery(() => norbix); + const result = baseQuery(endpoints[key].query(args) as never, {} as never, {}); + return { seen, result }; + } + + /** What reached the gateway, whether the SDK put it in the query string or the body. */ + function sent(entry: { url: string; body: unknown }) { + const url = new globalThis.URL(entry.url); + return { + path: url.pathname, + fields: { ...Object.fromEntries(url.searchParams), ...((entry.body as object) ?? {}) }, + }; + } + + const EXPANDED = { + result: + '[{"_id":"a1","author":{"id":"u1","display":"Ada"},"cover":{"id":"nbfl_42","display":"cover.png"}}]', + totalCount: 1, + }; + + it.each([ + ['findCollection', '/v2/database/collections/articles'], + ['findOwn', '/v2/database/collections/articles/own'], + ])( + '%s sends expandReferences=true and hands back the { id, display } values', + async (key, path) => { + const { seen, result } = run( + key, + { collectionName: 'articles', pageSize: 10, expandReferences: true }, + 200, + EXPANDED, + ); + + const out = (await result) as { data?: typeof EXPANDED; error?: unknown }; + + expect(out.error).toBeUndefined(); + expect(seen).toHaveLength(1); + const wire = sent(seen[0]); + expect(wire.path).toBe(path); + expect(String(wire.fields.expandReferences)).toBe('true'); + const [first] = JSON.parse(out.data?.result ?? '[]') as Array<{ + author: { id: string; display: string | null }; + }>; + expect(first.author).toEqual({ id: 'u1', display: 'Ada' }); + }, + ); + + it('findOne sends expandReferences=true on the record route', async () => { + const { seen, result } = run( + 'findOne', + { collectionName: 'articles', id: 'a1', expandReferences: true }, + 200, + { result: '{"_id":"a1","author":{"id":"u1","display":"Ada"}}' }, + ); + + const out = (await result) as { error?: unknown }; + + expect(out.error).toBeUndefined(); + const wire = sent(seen[0]); + expect(wire.path).toBe('/v2/database/collections/articles/a1'); + expect(String(wire.fields.expandReferences)).toBe('true'); + }); + + it('a plain read sends no expandReferences at all (default: stored ids)', async () => { + const { seen } = run('findCollection', { collectionName: 'articles' }, 200, { + result: '[]', + totalCount: 0, + }); + + await new Promise((r) => globalThis.setTimeout(r, 0)); + expect(sent(seen[0]).fields).not.toHaveProperty('expandReferences'); + }); + + it('a read refused for a missing source permission lands as the 056 error', async () => { + const { result } = run('findOwn', { collectionName: 'articles', expandReferences: true }, 403, { + responseStatus: { + errorCode: 'CM-ERRORS-DATABASE-056', + message: 'Expanding references needs read permission on users', + }, + }); + + const out = (await result) as { data?: unknown; error?: unknown }; + + expect(out.data).toBeUndefined(); + expect(out.error).toMatchObject({ + status: 403, + code: 'CM-ERRORS-DATABASE-056', + message: 'Expanding references needs read permission on users', + }); + }); + + it('updateOne carries arrayFilters next to the update (JSON strings, untouched)', async () => { + const { seen, result } = run( + 'updateOne', + { + collectionName: 'orders', + id: 'o1', + update: '{"$set":{"items.$[it].qty":3}}', + arrayFilters: '[{"it.sku":"A1"}]', + }, + 200, + { result: { matchedCount: 1, modifiedCount: 1 } }, + ); + + const out = (await result) as { error?: unknown }; + + expect(out.error).toBeUndefined(); + expect(seen[0].method).toBe('PUT'); + const wire = sent(seen[0]); + expect(wire.path).toBe('/v2/database/collections/orders/o1'); + expect(wire.fields).toMatchObject({ + update: '{"$set":{"items.$[it].qty":3}}', + arrayFilters: '[{"it.sku":"A1"}]', + }); + }); +}); diff --git a/tests/files.test.ts b/tests/files.test.ts index 1520e8c..0c1e980 100644 --- a/tests/files.test.ts +++ b/tests/files.test.ts @@ -45,14 +45,14 @@ function fakeNorbix() { 'enableFilesTrigger', 'getFilesTrigger', 'getFilesTriggers', 'saveFilesTrigger', 'deleteFilesIntegration', 'disableFilesIntegration', 'enableFilesIntegration', 'getFilesIntegration', 'getFilesIntegrations', 'saveFilesIntegration', - 'setFilesIntegrationAsDefault', 'getFile', 'getFolderFiles', + 'setFilesIntegrationAsDefault', 'getFile', 'getFileById', 'getFolderFiles', 'testFilesIntegration', 'makeFilePublic', 'makeFilePrivate', 'makeFolderPublic', 'makeFolderPrivate', ]; const apiNames = [ 'commitUpload', 'deleteFileApi', 'deleteManyFilesApi', 'downloadFileApi', - 'getFileInfo', 'getSignedUrl', 'listFiles', 'requestUploadUrl', 'getPublicFile', - 'testFilesIntegration', + 'getFileById', 'getFileInfo', 'getSignedUrl', 'listFiles', 'requestUploadUrl', + 'getPublicFile', 'testFilesIntegration', ]; const hub = Object.fromEntries(names.map((n) => [n, record(`hub.${n}`)])); const api = Object.fromEntries(apiNames.map((n) => [n, record(`api.${n}`)])); @@ -62,14 +62,14 @@ function fakeNorbix() { describe('hubFiles', () => { const endpoints = hubFiles(fakeBuilder().builder) as unknown as Record; - it('covers all 22 hub Files endpoints of the core SDK', () => { + it('covers all 23 hub Files endpoints of the core SDK', () => { expect(Object.keys(endpoints).sort()).toEqual( [ 'disableFiles', 'enableFiles', 'deleteFilesTrigger', 'disableFilesTrigger', 'enableFilesTrigger', 'getFilesTrigger', 'getFilesTriggers', 'saveFilesTrigger', 'deleteFilesIntegration', 'disableFilesIntegration', 'enableFilesIntegration', 'getFilesIntegration', 'getFilesIntegrations', 'saveFilesIntegration', - 'setFilesIntegrationAsDefault', 'getFile', 'getFolderFiles', + 'setFilesIntegrationAsDefault', 'getFile', 'getFileById', 'getFolderFiles', 'testFilesIntegration', 'makeFilePublic', 'makeFilePrivate', 'makeFolderPublic', 'makeFolderPrivate', ].sort(), @@ -109,11 +109,13 @@ describe('hubFiles', () => { describe('apiFiles', () => { const endpoints = apiFiles(fakeBuilder().builder) as unknown as Record; - it('covers all 10 api Files endpoints of the core SDK', () => { + it('covers all 11 api Files endpoints of the core SDK', () => { expect(Object.keys(endpoints).sort()).toEqual( [ 'commitUpload', 'deleteFileApi', 'deleteManyFilesApi', 'downloadFileApi', 'getFileInfo', 'getSignedUrl', 'listFiles', 'requestUploadUrl', 'getPublicFile', + // api.files.getFileById — suffixed like deleteFileApi, see the by-id describe. + 'getFileByIdApi', // api.files.testFilesIntegration — suffixed, see the next describe. 'testFilesIntegrationApi', ].sort(), @@ -190,6 +192,149 @@ describe('apiFiles.testFilesIntegrationApi', () => { }); }); +/** + * Files by id (schema-content campaign): `hub.files.getFileById` + * (GET /{version}/files/item/by-id) and `api.files.getFileById` + * (GET /{version}/files/{filesIntegrationId}/by-id/{id}). The id is the value + * a record's file field stores and the `id` an expanded reference returns. + */ +describe('files by id', () => { + const hub = hubFiles(fakeBuilder().builder) as unknown as Record; + const api = apiFiles(fakeBuilder().builder) as unknown as Record; + + it.each([ + ['hub', 'getFileById', 'hub.getFileById'], + ['api', 'getFileByIdApi', 'api.getFileById'], + ])('%s %s reaches %s with the caller args', async (surface, key, method) => { + const { calls, client } = fakeNorbix(); + const args = { filesIntegrationId: 'nbin_1', id: 'nbfl_42' }; + + await (surface === 'hub' ? hub : api)[key].query(args)(client); + + expect(calls).toEqual([{ method, args }]); + }); + + it('both are queries cached under Files, like getFile and getFileInfo', () => { + // isPublic / publicUrl are part of the by-id answer, and publish / + // unpublish / delete invalidate the plain Files tag — so a whole-tag + // entry refetches exactly when it should, unlike a private id tag. + for (const def of [hub.getFileById, api.getFileByIdApi]) { + expect(def.__kind).toBe('query'); + expect(def.providesTags).toEqual(['Files']); + } + }); + + it('the Api one is not shadowed by, and does not shadow, the Hub one', async () => { + const all = buildEndpoints(fakeBuilder().builder as never) as unknown as Record; + const { calls, client } = fakeNorbix(); + + await all.getFileByIdApi.query({ filesIntegrationId: 'nbin_1', id: 'nbfl_42' })(client); + await all.getFileById.query({ filesIntegrationId: 'nbin_1', id: 'nbfl_42' })(client); + + expect(calls.map((c) => c.method)).toEqual(['api.getFileById', 'hub.getFileById']); + }); +}); + +/** + * Both by-id hooks through the real `@norbix.ai/ts` client and this + * package's base query with a fake fetch — never a real server. Pins the + * verb, the route (the Api one substitutes both ids into the path, the Hub + * one sends them as query string), the project scope headers and the parsed + * answer with `isPublic` / `publicUrl`. + */ +describe('files by id through the real SDK (fake fetch)', () => { + const FILE_BODY = { + file: { id: 'nbfl_42', fileName: 'invoice.pdf', path: 'docs/invoice.pdf' }, + isPublic: true, + publicUrl: 'https://api.norbix.io/v2/files/public/nbpf_abc/invoice.pdf', + responseStatus: { isSuccess: true }, + }; + + function run(key: string, status: number, body: unknown) { + // Only globalThis.* here: the lint config declares no DOM globals. + type FetchFn = typeof globalThis.fetch; + const seen: Array<{ + url: string; + method: string; + body: unknown; + headers: InstanceType; + }> = []; + const fetchImpl = vi.fn(async (...[input, init]: Parameters) => { + seen.push({ + url: String(input), + method: (init?.method ?? 'GET').toUpperCase(), + body: init?.body ? JSON.parse(String(init.body)) : undefined, + headers: new globalThis.Headers(init?.headers ?? {}), + }); + return new globalThis.Response(JSON.stringify(body), { + status, + headers: { 'Content-Type': 'application/json' }, + }); + }) as unknown as FetchFn; + const norbix = new Norbix({ + bearerToken: 'test-token', + projectId: 'test-project', + apiVersion: 'v2', + hubVersion: 'v2', + baseUrl: { api: 'https://api.norbix.io', hub: 'https://hub.norbix.io' }, + fetch: fetchImpl, + }); + const all = buildEndpoints(fakeBuilder().builder as never) as unknown as Record; + const baseQuery = createNorbixBaseQuery(() => norbix); + const call = all[key].query({ filesIntegrationId: 'nbin_1', id: 'nbfl_42' }); + const result = baseQuery(call as never, {} as never, {}); + return { seen, result }; + } + + it('Api: GET /v2/files//by-id/, both ids in the path', async () => { + const { seen, result } = run('getFileByIdApi', 200, FILE_BODY); + + const out = (await result) as { data?: typeof FILE_BODY; error?: unknown }; + + expect(out.error).toBeUndefined(); + expect(seen).toHaveLength(1); + expect(seen[0].method).toBe('GET'); + expect(seen[0].body).toBeUndefined(); + const url = new globalThis.URL(seen[0].url); + expect(url.origin + url.pathname).toBe('https://api.norbix.io/v2/files/nbin_1/by-id/nbfl_42'); + expect(seen[0].headers.get('Authorization')).toBe('Bearer test-token'); + expect(seen[0].headers.get('X-CM-ProjectId')).toBe('test-project'); + expect(out.data?.file?.id).toBe('nbfl_42'); + expect(out.data?.isPublic).toBe(true); + expect(out.data?.publicUrl).toBe(FILE_BODY.publicUrl); + }); + + it('Hub: GET /v2/files/item/by-id with the integration and the id as query', async () => { + const { seen, result } = run('getFileById', 200, FILE_BODY); + + const out = (await result) as { data?: typeof FILE_BODY; error?: unknown }; + + expect(out.error).toBeUndefined(); + expect(seen[0].method).toBe('GET'); + const url = new globalThis.URL(seen[0].url); + expect(url.origin + url.pathname).toBe('https://hub.norbix.io/v2/files/item/by-id'); + expect(url.searchParams.get('filesIntegrationId')).toBe('nbin_1'); + expect(url.searchParams.get('id')).toBe('nbfl_42'); + expect(seen[0].headers.get('X-CM-ProjectId')).toBe('test-project'); + expect(out.data?.file?.path).toBe('docs/invoice.pdf'); + }); + + it('an unknown id comes back as the usual serialized not-found error', async () => { + const { result } = run('getFileByIdApi', 404, { + responseStatus: { errorCode: 'NotFound', message: 'File nbfl_42 was not found' }, + }); + + const out = (await result) as { data?: unknown; error?: unknown }; + + expect(out.data).toBeUndefined(); + expect(out.error).toMatchObject({ + status: 404, + code: 'NotFound', + message: 'File nbfl_42 was not found', + }); + }); +}); + /** * The same hook driven through the real `@norbix.ai/ts` client and this * package's base query, with a fake fetch — never a real server. Proves the From 307b55af4e4b7dd4a8e880fec1e33419ff252372 Mon Sep 17 00:00:00 2001 From: CodeMash Date: Wed, 7 Oct 2026 11:15:20 +0300 Subject: [PATCH 5/8] docs(readme): expanded references and files by id Step 5 of docs/tasks/schema-content-react-redux.md. Co-Authored-By: Claude Fable 5.1 --- README.md | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/README.md b/README.md index cd169d3..3174456 100644 --- a/README.md +++ b/README.md @@ -249,6 +249,57 @@ await deleteMany({ collectionName: 'orders', filter: '{}', allRecords: true }); Other gateway rules that now show up as errors from these hooks: an update body with `$` operators (`$inc`, `$set`, …) is refused with `CM-ERRORS-DATABASE-035` (send the plain fields to set); a broken insert or replace document answers `CM-ERRORS-DATABASE-036`; soft-deleted records are "not found" for update, replace and change-owner, and a bulk update skips them. A caller with only own-record rights may now use the bulk hooks; they touch only that caller's records. +### Expanded references and files by id (core SDK, schema-content) + +A reference field (user, role, taxonomy term, record of another collection, +file) stores an id. Ask the three end-user reads for `expandReferences: true` +and every reference value comes back as `{ id, display }` — `display` is the +target's display field as the schema names it, `null` when the target is gone. +Nothing new to wire: the option is a field of the request the hooks already +pass through. + +```tsx +const { data } = useFindCollectionQuery({ + collectionName: 'articles', + pageSize: 20, + expandReferences: true, +}); +// data.result → '[{"_id":"a1","author":{"id":"u1","display":"Ada"},"cover":{"id":"nbfl_42","display":"cover.png"}}]' + +useFindOneQuery({ collectionName: 'articles', id: 'a1', expandReferences: true }); +useFindOwnQuery({ collectionName: 'articles', expandReferences: true }); +``` + +An expanded read needs read permission on every source the schema links to +(users, roles, taxonomy, collection, files). Without it the read is refused +with `CM-ERRORS-DATABASE-056`, naming the source, and lands in `error` like +any other refusal. The cache key stays the collection: a write to a +_referenced_ collection does not refetch the expanded read — `refetch()` when +a display value must be fresh. + +A file reference's `id` is a stable file id (`nbfl_…`), not a path. Two hooks +read a file by that id, so a moved or renamed file is still found: + +| Hook | SDK method | Route | Cache | +| ------------------------ | ----------------------- | ------------------------------------------------------ | ---------------- | +| `useGetFileByIdQuery` | `hub.files.getFileById` | `GET /{version}/files/item/by-id` | provides `Files` | +| `useGetFileByIdApiQuery` | `api.files.getFileById` | `GET /{version}/files/{filesIntegrationId}/by-id/{id}` | provides `Files` | + +```tsx +const { data } = useGetFileByIdApiQuery({ filesIntegrationId: 'nbin_1', id: 'nbfl_42' }); +// data.file (resource, path), data.isPublic, data.publicUrl +``` + +The Api hook carries the `Api` suffix for the same reason `useDeleteFileApiMutation` +does: both surfaces share one flat endpoint map. Both answers include +`isPublic` / `publicUrl`, and publishing, unpublishing or deleting a file +invalidates `Files`, so they refetch exactly then. + +Nested documents and arrays are plain JSON in `document` / `update`. An update +addresses a nested path (`{"$set":{"address.city":"Vilnius"}}`) or an array +element by filter — `items.$[it].qty` with `arrayFilters: '[{"it.sku":"A1"}]'` +(a JSON string) on `useUpdateOneMutation` / `useUpdateManyMutation`. + ### Schemas per environment `useGetDatabaseSchemasQuery` returns only the schemas of the environment the request targets: the `env` argument, or else the client's environment (`norbix.setEnvironment(...)`, sent as the `norbix-env` header), or `PROD` when neither is set. Each row carries `env`. The schema list settings (`useGetDatabaseSchemaListSettingsQuery` / `useUpdateDatabaseSchemaListSettingsMutation`) are stored per environment too. From 5b687a8550eb931a80d74ba121f71824c0c3ef2f Mon Sep 17 00:00:00 2001 From: CodeMash Date: Wed, 7 Oct 2026 11:15:48 +0300 Subject: [PATCH 6/8] docs(tasks): record the commitlint step-prefix finding Co-Authored-By: Claude Fable 5.1 --- docs/tasks/schema-content-react-redux.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/tasks/schema-content-react-redux.md b/docs/tasks/schema-content-react-redux.md index 84de342..9a84d74 100644 --- a/docs/tasks/schema-content-react-redux.md +++ b/docs/tasks/schema-content-react-redux.md @@ -31,6 +31,7 @@ Not in scope: merging — this branch ships to `main` only after the gateway cam - The hub `getFileById` request DTO (`CodeMashHub2.GetFileById`) carries `filesIntegrationId` and `id` as plain `@ApiMember`s with no path params, so the core SDK sends them as query string on `GET /v2/files/item/by-id`; the Api one substitutes both into the path. The wire tests pin both. Nothing to fix here. - `apiDatabase` hooks still pass `(arg as { collectionName?: string })` for the tag id — the arg type already has `collectionName`, the cast is a leftover from before the typed `Arg<>` helper. Left as is (not this task). +- This repo's commitlint (`husky` commit-msg hook, `@commitlint/config-conventional`) refuses a `[N]` step prefix on the subject, so the commits here are plain conventional subjects with `Step N of docs/tasks/…` in the body. Same as every earlier task in this repo. - `npx prettier --check .` reports the same 20 pre-existing files as the last task (docs, examples, `tests/provider.test.tsx`). Not touched. ## Rejected / moved out From efd5a920472c24ae83d70343363144c25f5069fb Mon Sep 17 00:00:00 2001 From: CodeMash Date: Wed, 7 Oct 2026 11:16:07 +0300 Subject: [PATCH 7/8] docs(tasks): pull request link Co-Authored-By: Claude Fable 5.1 --- docs/tasks/schema-content-react-redux.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tasks/schema-content-react-redux.md b/docs/tasks/schema-content-react-redux.md index 9a84d74..25f056e 100644 --- a/docs/tasks/schema-content-react-redux.md +++ b/docs/tasks/schema-content-react-redux.md @@ -41,7 +41,7 @@ Not in scope: merging — this branch ships to `main` only after the gateway cam ## Needs you -- The pull request is opened with `--no-merge`. Its checks are red on purpose until `@norbix.ai/ts` publishes the schema-content release: then (1) `npm install --save-dev @norbix.ai/ts@` and bump the peer range in `package.json` on this branch, (2) re-run `npm test`, `npm run typecheck`, `npm run lint`, (3) merge with "Rebase and merge". +- Pull request https://github.com/norbix-code/react-redux/pull/43 is opened with `--no-merge`. Its checks are red on purpose until `@norbix.ai/ts` publishes the schema-content release: then (1) `npm install --save-dev @norbix.ai/ts@` and bump the peer range in `package.json` on this branch, (2) re-run `npm test`, `npm run typecheck`, `npm run lint`, (3) merge with "Rebase and merge". - Nothing else. ## Open questions From b515ac7798b2b45254cc8b206eb8481902818d34 Mon Sep 17 00:00:00 2001 From: CodeMash Date: Wed, 7 Oct 2026 13:13:47 +0300 Subject: [PATCH 8/8] =?UTF-8?q?build(deps):=20@norbix.ai/ts=204.14.0=20?= =?UTF-8?q?=E2=80=94=20the=20released=20schema-content=20contract?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dev dependency ^4.14.0 and peer range >=4.14.0: the release carries files.getFileById and the typed expandReferences / arrayFilters fields that the hooks on this branch use, so the checks no longer run against 4.11.0. The lockfile also moves source-map-js to 1.2.2 (GHSA-68fv-2mgg-jv7q) and the bundled npm CLI to 11.21.0. Task file: plan steps 7 and 8, the Needs-you line for the remaining OSV finding on the bundled postcss-selector-parser. Co-Authored-By: Claude Fable 5.1 --- docs/tasks/schema-content-react-redux.md | 5 +- package-lock.json | 58 ++++++++++++------------ package.json | 4 +- 3 files changed, 34 insertions(+), 33 deletions(-) diff --git a/docs/tasks/schema-content-react-redux.md b/docs/tasks/schema-content-react-redux.md index 25f056e..6eea794 100644 --- a/docs/tasks/schema-content-react-redux.md +++ b/docs/tasks/schema-content-react-redux.md @@ -14,6 +14,8 @@ Not in scope: merging — this branch ships to `main` only after the gateway cam 4. test(files, database): hook-shape tests, fake-transport tests and real-SDK fake-fetch wire tests — done, `tests/files.test.ts`, `tests/database.test.ts`. 5. docs(readme): "Expanded references and files by id" section — done, `README.md`. 6. ship: `nbx-ship --no-merge` — done, pull request link in "Needs you". +7. build(deps): `@norbix.ai/ts` `^4.14.0` (dev) / `>=4.14.0` (peer) — done, `package.json`, `package-lock.json` (sdk-ts #79 released as v4.14.0 on 2026-10-07; lockfile also moves `source-map-js` 1.2.1 → 1.2.2 and the bundled `npm` 11.19.1 → 11.21.0 for the OSV scan). +8. merge: "Rebase and merge" once the Security scan is green — todo, see "Needs you". ## Changes @@ -41,8 +43,7 @@ Not in scope: merging — this branch ships to `main` only after the gateway cam ## Needs you -- Pull request https://github.com/norbix-code/react-redux/pull/43 is opened with `--no-merge`. Its checks are red on purpose until `@norbix.ai/ts` publishes the schema-content release: then (1) `npm install --save-dev @norbix.ai/ts@` and bump the peer range in `package.json` on this branch, (2) re-run `npm test`, `npm run typecheck`, `npm run lint`, (3) merge with "Rebase and merge". -- Nothing else. +- Pull request https://github.com/norbix-code/react-redux/pull/43: the `@norbix.ai/ts` bump to 4.14.0 is on the branch (plan step 7; lint, typecheck, 269 tests, build green locally). One check stays red and it is not the SDK: **Security scan → OSV** flags `postcss-selector-parser` 7.1.4 (GHSA-rj75-hqrm-r3gf, dev-only, bundled inside the `npm` CLI that `@semantic-release/npm` runs). No update fixes it — npm 11.21.0, the newest in the range semantic-release accepts, still bundles 7.1.4, and overrides cannot change bundled packages. The fix is a dated `[[IgnoredVulns]]` entry in `osv-scanner.toml` like the three `brace-expansion` ones (pull request 34). The agent did not add it (an ignore entry is your call). Add it (`id = "GHSA-rj75-hqrm-r3gf"`, `ignoreUntil = 2026-11-07`, dev-only reason), push, then `nbx-ship --wait-release --cleanup` from this worktree. ## Open questions diff --git a/package-lock.json b/package-lock.json index aaaefad..0121663 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@commitlint/cli": "^21.2.2", "@commitlint/config-conventional": "^21.2.2", "@eslint/js": "^10.0.1", - "@norbix.ai/ts": "^4.11.0", + "@norbix.ai/ts": "^4.14.0", "@reduxjs/toolkit": "^2.12.0", "@semantic-release/commit-analyzer": "^13.0.1", "@semantic-release/github": "^12.0.9", @@ -45,7 +45,7 @@ "node": ">=18" }, "peerDependencies": { - "@norbix.ai/ts": ">=4.11.0", + "@norbix.ai/ts": ">=4.14.0", "@reduxjs/toolkit": ">=2.0.0", "react": ">=18.0.0", "react-redux": ">=9.0.0" @@ -1490,9 +1490,9 @@ } }, "node_modules/@norbix.ai/ts": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@norbix.ai/ts/-/ts-4.11.0.tgz", - "integrity": "sha512-Amrs58kmKBnegPCNdrYtiHaCwRnx9hN+JHS+qneLRAzmQuJ86evMDohn13qs+YwBgvvhOkwEb76opcs31y3Isg==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@norbix.ai/ts/-/ts-4.14.0.tgz", + "integrity": "sha512-ROfO1CVuxGl705VDgI9c9Kq5exEhaSG2kKbwkQej2Jm+XJWAj8rt9pNXQhqrDHr5HEQqbjVjEyd2KxEjtLOAMA==", "dev": true, "license": "MIT", "engines": { @@ -7005,9 +7005,9 @@ } }, "node_modules/npm": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/npm/-/npm-11.19.1.tgz", - "integrity": "sha512-ztsxKxt/kkIaAs+2i0GU6I+DRmUdrNasxTZKJe9TCdSjKxlhah/4r/hl5ygMD6XAg1qZ9c2TNomR4qgOydp10g==", + "version": "11.21.0", + "resolved": "https://registry.npmjs.org/npm/-/npm-11.21.0.tgz", + "integrity": "sha512-Zov8KhamNneiLdELtj5YALtNmJW4L4fCLTzjfpzXG2w6MSHcf0UxgdlK5uloCuksWT+7mGUU7wi79cO6RqivPg==", "bundleDependencies": [ "@isaacs/string-locale-compare", "@npmcli/arborist", @@ -7086,8 +7086,8 @@ ], "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/arborist": "^9.9.1", - "@npmcli/config": "^10.12.0", + "@npmcli/arborist": "^9.9.2", + "@npmcli/config": "^10.14.0", "@npmcli/fs": "^5.0.0", "@npmcli/map-workspaces": "^5.0.3", "@npmcli/metavuln-calculator": "^9.0.3", @@ -7111,12 +7111,12 @@ "is-cidr": "^6.0.4", "json-parse-even-better-errors": "^5.0.0", "libnpmaccess": "^10.0.3", - "libnpmdiff": "^8.1.12", - "libnpmexec": "^10.3.2", - "libnpmfund": "^7.0.26", + "libnpmdiff": "^8.1.13", + "libnpmexec": "^10.3.3", + "libnpmfund": "^7.0.27", "libnpmorg": "^8.0.1", - "libnpmpack": "^9.1.13", - "libnpmpublish": "^11.2.0", + "libnpmpack": "^9.1.14", + "libnpmpublish": "^11.2.1", "libnpmsearch": "^9.0.1", "libnpmteam": "^8.0.2", "libnpmversion": "^8.0.4", @@ -7233,7 +7233,7 @@ } }, "node_modules/npm/node_modules/@npmcli/arborist": { - "version": "9.9.1", + "version": "9.9.2", "dev": true, "inBundle": true, "license": "ISC", @@ -7281,7 +7281,7 @@ } }, "node_modules/npm/node_modules/@npmcli/config": { - "version": "10.12.0", + "version": "10.14.0", "dev": true, "inBundle": true, "license": "ISC", @@ -8000,12 +8000,12 @@ } }, "node_modules/npm/node_modules/libnpmdiff": { - "version": "8.1.12", + "version": "8.1.13", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^9.9.1", + "@npmcli/arborist": "^9.9.2", "@npmcli/installed-package-contents": "^4.0.0", "binary-extensions": "^3.0.0", "diff": "^8.0.2", @@ -8019,13 +8019,13 @@ } }, "node_modules/npm/node_modules/libnpmexec": { - "version": "10.3.2", + "version": "10.3.3", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { "@gar/promise-retry": "^1.0.0", - "@npmcli/arborist": "^9.9.1", + "@npmcli/arborist": "^9.9.2", "@npmcli/package-json": "^7.0.0", "@npmcli/run-script": "^10.0.0", "ci-info": "^4.0.0", @@ -8042,12 +8042,12 @@ } }, "node_modules/npm/node_modules/libnpmfund": { - "version": "7.0.26", + "version": "7.0.27", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^9.9.1" + "@npmcli/arborist": "^9.9.2" }, "engines": { "node": "^20.17.0 || >=22.9.0" @@ -8067,12 +8067,12 @@ } }, "node_modules/npm/node_modules/libnpmpack": { - "version": "9.1.13", + "version": "9.1.14", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^9.9.1", + "@npmcli/arborist": "^9.9.2", "@npmcli/run-script": "^10.0.0", "npm-package-arg": "^13.0.0", "pacote": "^21.0.2" @@ -8082,7 +8082,7 @@ } }, "node_modules/npm/node_modules/libnpmpublish": { - "version": "11.2.0", + "version": "11.2.1", "dev": true, "inBundle": true, "license": "ISC", @@ -10312,9 +10312,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", "dev": true, "license": "BSD-3-Clause", "engines": { diff --git a/package.json b/package.json index 31e28e9..e9b8759 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "prepublishOnly": "npm run build" }, "peerDependencies": { - "@norbix.ai/ts": ">=4.11.0", + "@norbix.ai/ts": ">=4.14.0", "@reduxjs/toolkit": ">=2.0.0", "react": ">=18.0.0", "react-redux": ">=9.0.0" @@ -68,7 +68,7 @@ "@commitlint/cli": "^21.2.2", "@commitlint/config-conventional": "^21.2.2", "@eslint/js": "^10.0.1", - "@norbix.ai/ts": "^4.11.0", + "@norbix.ai/ts": "^4.14.0", "@reduxjs/toolkit": "^2.12.0", "@semantic-release/commit-analyzer": "^13.0.1", "@semantic-release/github": "^12.0.9",