From 83b5a2b4ff987ebb53fc29ee58c6da7c7dae9d59 Mon Sep 17 00:00:00 2001 From: CodeMash Date: Mon, 5 Oct 2026 16:14:14 +0300 Subject: [PATCH 1/3] chore(deps): require @norbix.ai/ts 4.10.0 @norbix.ai/ts 4.10.0 sends PUT for the module enable / disable calls (database, files, push, sms, email, payments, logs, membership, code) and carries the env field on schema list rows. Co-Authored-By: Claude Opus 5.5 --- package-lock.json | 10 +++++----- package.json | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2ff29e2..a13eec4 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.9.0", + "@norbix.ai/ts": "^4.10.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.9.0", + "@norbix.ai/ts": ">=4.10.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.9.0", - "resolved": "https://registry.npmjs.org/@norbix.ai/ts/-/ts-4.9.0.tgz", - "integrity": "sha512-uiGav57sRmzQ5jYBbspUHW8c4R50m03Q0Y23Tenol+6lGdUk0S8UDHoAVyILCKAjbi3EJZkzBavGuox9RgV0qw==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@norbix.ai/ts/-/ts-4.10.0.tgz", + "integrity": "sha512-yGKbR9nwbt7jEB06Qdzg0HzEXqwzKsfU9h+wB4c03Il8qFH/7Gi2zkcMLCmSATcenho809MjZLuo4DPGXiH+aA==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index 4d9cd35..2c5e9e6 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "prepublishOnly": "npm run build" }, "peerDependencies": { - "@norbix.ai/ts": ">=4.9.0", + "@norbix.ai/ts": ">=4.10.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.9.0", + "@norbix.ai/ts": "^4.10.0", "@reduxjs/toolkit": "^2.12.0", "@semantic-release/commit-analyzer": "^13.0.1", "@semantic-release/github": "^12.0.9", From d3c73c5ff9abaf0b798c84191c16515be5d53676 Mon Sep 17 00:00:00 2001 From: CodeMash Date: Mon, 5 Oct 2026 16:16:32 +0300 Subject: [PATCH 2/3] test(hub): module switches send PUT; schema list follows env Every enable / disable module hook (database, files, logs, payments, membership, email, sms, push) is checked to be a mutation that reaches its SDK method and invalidates its module tag, and, through the real @norbix.ai/ts client over a fake fetch, to send PUT on the wire. getDatabaseSchemas with env TEST sends the environment and gets rows that carry env; list settings keep env in the arguments. Plan steps 2 and 3 of docs/tasks/database-put-enable.md. Co-Authored-By: Claude Opus 5.5 --- tests/module-switch.test.ts | 183 ++++++++++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 tests/module-switch.test.ts diff --git a/tests/module-switch.test.ts b/tests/module-switch.test.ts new file mode 100644 index 0000000..17d0259 --- /dev/null +++ b/tests/module-switch.test.ts @@ -0,0 +1,183 @@ +import { Norbix } from '@norbix.ai/ts'; +import { describe, expect, it, vi } from 'vitest'; + +import { createNorbixBaseQuery } from '../src/baseQuery.js'; +import { hubDatabase } from '../src/hooks/hub/database.js'; +import { hubFiles } from '../src/hooks/hub/files.js'; +import { hubLogs } from '../src/hooks/hub/logs.js'; +import { hubMembership } from '../src/hooks/hub/membership.js'; +import { hubNotifications } from '../src/hooks/hub/notifications.js'; +import { hubPayments } from '../src/hooks/hub/payments.js'; + +/** Same fake builder the other hook tests use: it keeps the definition and its kind. */ +function fakeBuilder() { + return { + query: (def: Record) => ({ ...def, __kind: 'query' }), + mutation: (def: Record) => ({ ...def, __kind: 'mutation' }), + } as never; +} + +type Def = { + __kind: 'query' | 'mutation'; + query: (args: unknown) => (norbix: unknown) => unknown; + providesTags?: unknown; + invalidatesTags?: unknown; +}; +type Factory = (b: never) => Record; +type SchemasResponse = Awaited>; +type SchemaRow = NonNullable['items']>[number]; + +/** + * Module switches (gateway refactoringV2, @norbix.ai/ts 4.10.0): every one + * moved from GET to PUT. `[hook, SDK module, path, tags, hook module]`. + * hub.code has no hooks in this package, so EnableCode / DisableCode are not listed. + */ +const SWITCHES: Array<[string, string, string, string[], Factory]> = [ + ['enableDatabase', 'database', '/database/enable', ['Database'], hubDatabase], + ['disableDatabase', 'database', '/database/disable', ['Database'], hubDatabase], + ['enableFiles', 'files', '/files/enable', ['Files'], hubFiles], + ['disableFiles', 'files', '/files/disable', ['Files'], hubFiles], + ['enableLogging', 'logs', '/logs/enable', ['Logs'], hubLogs], + ['disableLogging', 'logs', '/logs/disable', ['Logs'], hubLogs], + ['enablePayments', 'payments', '/payments/enable', ['Payments'], hubPayments], + ['disablePayments', 'payments', '/payments/disable', ['Payments'], hubPayments], + ['enableMembership', 'membership', '/membership/enable', ['Membership'], hubMembership], + ['disableMembership', 'membership', '/membership/disable', ['Membership'], hubMembership], + ['enableEmail', 'notifications', '/notifications/email/enable', ['Emails'], hubNotifications], + ['disableEmail', 'notifications', '/notifications/email/disable', ['Emails'], hubNotifications], + ['enableSms', 'notifications', '/notifications/sms/enable', ['Sms'], hubNotifications], + ['disableSms', 'notifications', '/notifications/sms/disable', ['Sms'], hubNotifications], + ['enablePush', 'notifications', '/notifications/push/enable', ['Push'], hubNotifications], + ['disablePush', 'notifications', '/notifications/push/disable', ['Push'], hubNotifications], +]; + +/** + * The real `@norbix.ai/ts` client and this package's base query over a fake + * fetch — never a real server. Records verb, URL and headers of every call. + */ +function realChain(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; + headers: InstanceType; + }> = []; + const fetchImpl = vi.fn(async (...[input, init]: Parameters) => { + seen.push({ + url: String(input), + method: (init?.method ?? 'GET').toUpperCase(), + 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 baseQuery = createNorbixBaseQuery(() => norbix); + const run = (def: Def, args: unknown) => + baseQuery(def.query(args) as never, {} as never, {}) as Promise<{ + data?: unknown; + error?: unknown; + }>; + return { seen, run }; +} + +describe('module switch hooks (enable / disable)', () => { + it.each(SWITCHES)( + '%s is a mutation that reaches hub.%s and invalidates its module tag', + (hook, sdkModule, _path, tags, factory) => { + const def = factory(fakeBuilder())[hook] as Def; + const calls: string[] = []; + const client = { + hub: { + [sdkModule]: new Proxy( + {}, + { + get: (_t, name: string) => (args: unknown) => { + calls.push(`${name}:${JSON.stringify(args)}`); + return Promise.resolve({}); + }, + }, + ), + }, + }; + + void def.query({ projectId: 'p1' })(client); + + expect({ kind: def.__kind, calls, invalidates: def.invalidatesTags }).toEqual({ + kind: 'mutation', + calls: [`${hook}:{"projectId":"p1"}`], + invalidates: tags, + }); + }, + ); + + it.each(SWITCHES)('%s (hub.%s) sends PUT on the wire', async (hook, _m, path, _t, factory) => { + const { seen, run } = realChain(200, {}); + const out = await run(factory(fakeBuilder())[hook] as Def, {}); + + expect({ + error: out.error, + calls: seen.map((s) => ({ method: s.method, url: s.url.split('?')[0] })), + }).toEqual({ + error: undefined, + calls: [{ method: 'PUT', url: `https://hub.norbix.io/v2${path}` }], + }); + }); +}); + +describe('schema list per environment', () => { + const endpoints = hubDatabase(fakeBuilder()) as unknown as Record; + + it('getDatabaseSchemas with env TEST targets TEST and returns rows that carry env', async () => { + const rows: Partial[] = [{ viewId: 'sch_1', schemaName: 'orders', env: 'TEST' }]; + const { seen, run } = realChain(200, { list: { items: rows } }); + + const out = (await run(endpoints.getDatabaseSchemas, { env: 'TEST' })) as { + data?: SchemasResponse; + error?: unknown; + }; + + const sent = seen[0]; + const envOnWire = + sent.headers.get('norbix-env') ?? new globalThis.URL(sent.url).searchParams.get('env'); + expect({ + error: out.error, + method: sent.method, + url: sent.url.split('?')[0], + envOnWire, + rowEnv: out.data?.list?.items?.map((r) => r.env), + }).toEqual({ + error: undefined, + method: 'GET', + url: 'https://hub.norbix.io/v2/database/schemas', + envOnWire: 'TEST', + rowEnv: ['TEST'], + }); + }); + + it('list settings for a schema keep env in the arguments, so each environment caches apart', () => { + const calls: unknown[] = []; + const client = { + hub: { + database: { + getDatabaseSchemaListSettings: (a: unknown) => (calls.push(a), Promise.resolve({})), + }, + }, + }; + + void endpoints.getDatabaseSchemaListSettings.query({ id: 'orders', env: 'TEST' })(client); + void endpoints.getDatabaseSchemaListSettings.query({ id: 'orders' })(client); + + expect(calls).toEqual([{ id: 'orders', env: 'TEST' }, { id: 'orders' }]); + }); +}); From caf98685f921a1ddf861d70efaf32f7df11e3397 Mon Sep 17 00:00:00 2001 From: CodeMash Date: Mon, 5 Oct 2026 16:17:22 +0300 Subject: [PATCH 3/3] docs(readme): module switches send PUT; schemas per environment Lists the enable / disable hooks of the 8 modules with their PUT routes (@norbix.ai/ts 4.10.0; the gateway GET alias goes after 0.2), and explains that the schema list and its list settings follow the request environment, and how to keep the RTK cache apart per environment. Adds the task file docs/tasks/database-put-enable.md. Co-Authored-By: Claude Opus 5.5 --- README.md | 34 +++++++++++++++++++++++ docs/tasks/database-put-enable.md | 45 +++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 docs/tasks/database-put-enable.md diff --git a/README.md b/README.md index 0fa46b2..026e5ce 100644 --- a/README.md +++ b/README.md @@ -221,6 +221,40 @@ Every `hub.database` method (73) and every `api.database` method (22) of `@norbi | `useFindOwnQuery` | `api.database.findOwn` | provides `DatabaseCollections/` | | `useFindMergedTermTreeQuery` | `api.database.findMergedTermTree` | provides `DatabaseTaxonomyTerms/` | +### Module switches send `PUT` (core SDK 4.10.0) + +The module enable / disable hooks were always mutations, so nothing changes in your code. From `@norbix.ai/ts` 4.10.0 they send `PUT` instead of `GET` on the wire, matching the gateway: + +| Hooks | Route | Cache | +| ------------------------------------------------------------- | ---------------------------------------------------- | ------------------------ | +| `useEnableDatabaseMutation`, `useDisableDatabaseMutation` | `PUT /{version}/database/enable\|disable` | invalidates `Database` | +| `useEnableFilesMutation`, `useDisableFilesMutation` | `PUT /{version}/files/enable\|disable` | invalidates `Files` | +| `useEnableLoggingMutation`, `useDisableLoggingMutation` | `PUT /{version}/logs/enable\|disable` | invalidates `Logs` | +| `useEnablePaymentsMutation`, `useDisablePaymentsMutation` | `PUT /{version}/payments/enable\|disable` | invalidates `Payments` | +| `useEnableMembershipMutation`, `useDisableMembershipMutation` | `PUT /{version}/membership/enable\|disable` | invalidates `Membership` | +| `useEnableEmailMutation`, `useDisableEmailMutation` | `PUT /{version}/notifications/email/enable\|disable` | invalidates `Emails` | +| `useEnableSmsMutation`, `useDisableSmsMutation` | `PUT /{version}/notifications/sms/enable\|disable` | invalidates `Sms` | +| `useEnablePushMutation`, `useDisablePushMutation` | `PUT /{version}/notifications/push/enable\|disable` | invalidates `Push` | + +The gateway still answers the old `GET` on these paths for now (it logs a deprecation warning) and will drop it after gateway 0.2, so upgrade the core SDK before then. The scheduler switch moved to `PUT` earlier (core SDK 4.6.0). The code module (`hub.code`) has no hooks in this package yet; call `useNorbix().hub.code.enableCode(...)` directly. + +### 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. + +RTK Query keys its cache by endpoint + arguments, and the client's environment is not an argument. So either pass `env` in the arguments — each environment then has its own cache entry — or reset the cache when you switch: + +```tsx +// Per call: TEST and PROD cache apart. +const { data } = useGetDatabaseSchemasQuery({ env: 'TEST' }); + +// Or switch the whole client and drop what was cached for the old environment. +norbix.setEnvironment('TEST'); +dispatch(norbixApi.util.invalidateTags(['DatabaseSchemas'])); +``` + +The paging cursors of the schema list (`startingAfter` / `endingBefore`) are schema view ids (`sch_…`). A cursor saved against an older gateway will not match; start the list again from the first page. + ### End-user AI chat — `norbix.api.ai` For a signed-in project user. `useStartEndUserChatTurnMutation` answers at once with a `turnId`; the answer streams on the user's SSE channel `ai-chat:{projectId}:{authId}` — open it with `norbix.aiChat({ authId })` from `@norbix.ai/ts` (a foreign channel is refused with 403 `AiChatChannelRefused`, no retry). Needs `@norbix.ai/ts` >= 4.3.0. diff --git a/docs/tasks/database-put-enable.md b/docs/tasks/database-put-enable.md new file mode 100644 index 0000000..7d768ed --- /dev/null +++ b/docs/tasks/database-put-enable.md @@ -0,0 +1,45 @@ +# database-put-enable — React-Redux side of the gateway module-switch and schema-env changes + +## Goal + +Move `@norbix.ai/react-redux` onto `@norbix.ai/ts` 4.10.0, so the module +enable / disable hooks send `PUT` and the schema list follows the request +environment, and prove both with tests. + +Not in scope: hooks for `hub.code` (the package has no code module at all), a +cache that follows `norbix.setEnvironment(...)` by itself. + +## Plan + +1. chore(deps): require @norbix.ai/ts 4.10.0 — done, `package.json`, `package-lock.json` +2. test(hub): the 16 module-switch hooks (8 modules) are mutations and send PUT on the wire — done, `tests/module-switch.test.ts` +3. test(database): the schema list cache is keyed by `env`; rows carry `env` — done, `tests/module-switch.test.ts` +4. docs(readme): module switch is PUT; schema list and list settings are per environment — done, `README.md` + +## Changes + +| file | what changed | plan step # | +| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | +| `package.json`, `package-lock.json` | `@norbix.ai/ts` peer `>=4.10.0`, dev `^4.10.0` | 1 | +| `tests/module-switch.test.ts` | new: every module-switch hook is a mutation, reaches its SDK method and sends PUT through a fake fetch; schema list sends `env` and rows carry it | 2, 3 | +| `README.md` | "Module switches send PUT" and "Schemas per environment" sections | 4 | + +## Findings + +- The hooks were already mutations before this change (`src/hooks/hub/*.ts`), so no hook changed kind and no cache tag changed. Only the wire verb moved, inside `@norbix.ai/ts`. Left as is. +- There are no hooks for `hub.code` (`enableCode` / `disableCode` and the rest of the code module). An existing gap, not part of this task. Open. +- `norbix.setEnvironment(...)` does not refetch: RTK Query keys its cache by endpoint + args, and the client environment is not part of the args. Same caveat as regions; documented in the README: pass `env`, or invalidate `DatabaseSchemas` after a switch. Open (a `getClient`-aware cache key would fix it for every hook). +- `npm run format:check` reports 21 files on `main` that are not Prettier-clean (for example `tests/integrations.test.ts`, `tests/provider.test.tsx`). Not touched here. Open. +- The schema-list paging cursor changed from Redis keys to `sch_…` ids on the gateway. A cursor an app kept across the deploy will not match; RTK does not persist cursors by default. Documented, nothing to change here. + +## Rejected / moved out + +- (none) + +## Needs you + +- (none) + +## Open questions + +- (none)