@object-ui/react 16.0.0 → 16.1.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/CHANGELOG.md CHANGED
@@ -1,5 +1,153 @@
1
1
  # @object-ui/react
2
2
 
3
+ ## 16.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 803558e: feat(data): thread the host's authenticated fetch into `provider: 'api'` data sources (#2725)
8
+
9
+ `provider: 'api'` view data sources went through a bare `globalThis.fetch`, so
10
+ custom endpoints (gantt composite trees, report aggregates) carried only
11
+ same-origin cookies while every native `/api/v1/*` request carried
12
+ `Authorization: Bearer` — the moment cookie HMAC verification failed (dev
13
+ restart rotating the fallback auth secret, cookie expiry/rotation in prod)
14
+ those views 401'd while the rest of the app kept working.
15
+
16
+ - **`@object-ui/react`** — `SchemaRendererProvider` accepts an optional
17
+ `apiFetch`; nested providers inherit it from their parent so re-wrapped
18
+ subtrees (react pages, preview surfaces) keep the host's authentication.
19
+ `useViewData` defaults the api-provider adapter's fetch to the context
20
+ `apiFetch` (explicit `adapterOptions.fetch` still wins).
21
+ - **`@object-ui/auth`** — `createAuthenticatedFetch` gains a
22
+ `sameOriginOnly` option: cross-origin URLs pass through to the bare fetch
23
+ with no `Authorization` / `X-Tenant-ID` / `Accept-Language`, so metadata-
24
+ supplied third-party URLs never see the platform token.
25
+ - **`@object-ui/app-shell`** — the console wires
26
+ `createAuthenticatedFetch({ sameOriginOnly: true })` (settle-signal wrapped)
27
+ as `apiFetch` on the root `SchemaRendererProvider`.
28
+ - **`@object-ui/plugin-gantt`** — `ObjectGantt` resolves its api-provider
29
+ DataSource with the context `apiFetch`, covering reads and write-backs.
30
+
31
+ Behaviour is unchanged for hosts that don't provide `apiFetch` (bare fetch +
32
+ cookies, as before).
33
+
34
+ - 2e7d7f0: feat(evaluator): route `{ dialect: 'cel' }` component/action predicates to the canonical CEL engine (#2661)
35
+
36
+ Component and action `visible` / `disabled` / `hidden` predicates were evaluated
37
+ by the home-grown JS `ExpressionEvaluator`, while field rules
38
+ (`visibleWhen`/`readonlyWhen`/`requiredWhen`, via `fieldRules.ts`) and row/list
39
+ conditionals (via `evalRowPredicate`) already delegate to the canonical
40
+ `@objectstack/formula` engine. That split meant a `{ dialect: 'cel' }` predicate
41
+ in a renderer/action surface was executed as **JavaScript** — CEL-only forms
42
+ (`x in list`, `has()`, typed `==`, the `today()`/`daysFromNow()` catalog) behaved
43
+ differently from, or faulted against, the server's enforcement.
44
+
45
+ This converges the remaining tier onto the same engine:
46
+
47
+ - **`@object-ui/core`** — `ExpressionEvaluator.evaluateCondition` now detects a
48
+ `{ dialect: 'cel', source }` envelope and evaluates it on `@objectstack/formula`
49
+ (via `evalFieldPredicate`), binding the `record` namespace plus the whole
50
+ context bag as top-level scope (`record.*`, `features.*`, `user.*`, `app.*`).
51
+ Fail-soft to visible/enabled to match the legacy default; `throwOnError`
52
+ callers still fail closed on a _faulting_ predicate (a genuine `false` never
53
+ throws). This fixes every `SchemaRenderer` visibility/disabled read at once.
54
+ - **`@object-ui/react`** — `toPredicateInput` preserves a CEL envelope instead of
55
+ collapsing it to a `${source}` string, and `useCondition` accepts and forwards
56
+ the envelope (keyed on a stable `(dialect, source)` so it doesn't re-evaluate
57
+ each render). Action buttons (`action-icon`/`group`/`bar`/`button`) therefore
58
+ evaluate CEL `visible`/`enabled`/`disabled` on the canonical engine.
59
+
60
+ **Back-compat:** bare strings and `${…}` templates stay on the legacy JS path
61
+ (deprecation window); only an explicit `{ dialect: 'cel' }` envelope is rerouted.
62
+ `{ dialect: 'template' }` is unaffected.
63
+
64
+ Together with the `^15.1.1` alignment (#2662), a renderer CEL predicate now
65
+ reaches the identical verdict as the server — including the framework's
66
+ `dateField == today()` equality fix (objectstack-ai/framework#3205) once it
67
+ lands in a published 15.x. The broader home-grown-vs-canonical divergence
68
+ motivation is #2661.
69
+
70
+ ### Patch Changes
71
+
72
+ - 7cf4051: chore(deps): align every `@objectstack/*` dependency to `^16.0.0-rc.0`
73
+
74
+ Bumps `@objectstack/spec` / `client` / `formula` / `lint` from `^15.1.1` to the
75
+ `16.0.0-rc.0` pre-release across the workspace (root + `apps/console` +
76
+ `apps/site` + all consuming packages). ObjectUI's own packages are already on
77
+ major 16, so this closes the 15↔16 skew between ObjectUI and the `@objectstack`
78
+ contract libraries (which publish in lockstep with `spec`).
79
+
80
+ This is a dependency alignment, not a behavioral migration: the full workspace
81
+ build (43/43) and the `@objectstack`-consuming package test suites
82
+ (`core` / `app-shell` / `data-objectstack` / `plugin-form` / `types`) are green
83
+ against `16.0.0-rc.0` with no source changes required.
84
+
85
+ Practical effect: `@objectstack/client@16.0.0-rc.0` now ships
86
+ `data.batchTransaction` (framework #3271), so `ObjectStackAdapter`'s feature
87
+ detect (`typeof client.data.batchTransaction === 'function'`) routes
88
+ master-detail cross-object saves through the typed SDK method instead of the
89
+ raw `fetch('/api/v1/batch')` fallback — realizing the "verify SDK path" half of
90
+ #2694. The raw-fetch branch stays as a defensive fallback (removal tracked in
91
+ #2694).
92
+
93
+ - 549c67d: chore(lint): clear the mechanical baseline lint errors so these packages' lint gates protect them again
94
+
95
+ Extends the fields/core cleanup from #2709 (objectui#2713). These eight package
96
+ lints were red at baseline on `main`, so their per-package `lint` gate could not
97
+ catch new violations of the same class. Cleared every **error** (no behavior
98
+ change; warnings are out of scope):
99
+
100
+ - **`no-useless-catch`** (`data-objectstack`) — unwrapped five try/catch blocks
101
+ whose `catch` only re-threw; errors still propagate identically.
102
+ - **`preserve-caught-error`** (`cli`, `data-objectstack`, `react`) — the caught
103
+ error's message is inlined into the thrown `Error`; a scoped disable with a
104
+ justifying comment carries each one, because these packages target ES2020
105
+ whose lib types the 1-arg `Error` constructor only (so `{ cause }` won't
106
+ compile) — same reasoning as the core case in #2709.
107
+ - **`prefer-const`** (`plugin-calendar`, `plugin-map`) — `let`→`const` for
108
+ never-reassigned bindings.
109
+ - **`no-empty-object-type`** (`plugin-designer`) — empty extend-only interfaces
110
+ → equivalent `type` aliases.
111
+ - **`no-useless-assignment`** (`react`) — dropped a dead initializer that both
112
+ branches overwrite before it is read.
113
+ - **`no-require-imports`** (`plugin-calendar`, `plugin-timeline` tests) —
114
+ hoisted `vi.mock` factories now use an `async` factory with
115
+ `await import('react')` instead of `require('react')`.
116
+ - **stale `eslint-disable` directive** (`plugin-markdown`) — removed a
117
+ `react/no-danger` disable whose plugin is not loaded in the flat config (an
118
+ unknown-rule reference that ESLint v10 reports as an error); the rationale is
119
+ kept as a plain comment.
120
+
121
+ - Updated dependencies [0318118]
122
+ - Updated dependencies [1c8935a]
123
+ - Updated dependencies [af1b0db]
124
+ - Updated dependencies [8b8b744]
125
+ - Updated dependencies [7cf4051]
126
+ - Updated dependencies [8c1e415]
127
+ - Updated dependencies [0ea5036]
128
+ - Updated dependencies [2e7d7f0]
129
+ - Updated dependencies [94d4876]
130
+ - Updated dependencies [1100a8b]
131
+ - Updated dependencies [7abe4cd]
132
+ - Updated dependencies [549c67d]
133
+ - Updated dependencies [ebe6494]
134
+ - Updated dependencies [2b17339]
135
+ - Updated dependencies [31b77d4]
136
+ - Updated dependencies [6d4fbe6]
137
+ - Updated dependencies [0a3710b]
138
+ - Updated dependencies [f80aaf2]
139
+ - Updated dependencies [62b9ab5]
140
+ - Updated dependencies [1629313]
141
+ - Updated dependencies [29c6040]
142
+ - Updated dependencies [faebac3]
143
+ - Updated dependencies [2331ac9]
144
+ - Updated dependencies [199fa83]
145
+ - Updated dependencies [eee4ded]
146
+ - @object-ui/i18n@16.1.0
147
+ - @object-ui/core@16.1.0
148
+ - @object-ui/data-objectstack@16.1.0
149
+ - @object-ui/types@16.1.0
150
+
3
151
  ## 16.0.0
4
152
 
5
153
  ### Minor Changes
package/README.md CHANGED
@@ -81,6 +81,30 @@ function App() {
81
81
  }
82
82
  ```
83
83
 
84
+ ### SchemaRendererProvider
85
+
86
+ Injects the host's data source (and optional capabilities) into every renderer
87
+ below it:
88
+
89
+ ```tsx
90
+ import { SchemaRendererProvider } from '@object-ui/react'
91
+
92
+ <SchemaRendererProvider
93
+ dataSource={adapter}
94
+ // Optional: host-authenticated fetch used by `provider: 'api'` view data
95
+ // sources, so custom endpoints carry the same credentials (Authorization,
96
+ // tenant, locale headers) as the native data channel. When omitted,
97
+ // ApiDataSource falls back to the bare global fetch (cookies only).
98
+ apiFetch={authenticatedFetch}
99
+ >
100
+ <SchemaRenderer schema={schema} />
101
+ </SchemaRendererProvider>
102
+ ```
103
+
104
+ Nested providers inherit `apiFetch` from their parent when they don't supply
105
+ their own, so re-wrapped subtrees (embedded pages, preview surfaces) keep the
106
+ host's authentication.
107
+
84
108
  ## Hooks
85
109
 
86
110
  ### useSchemaContext
@@ -1,17 +1,26 @@
1
1
  import React from 'react';
2
2
  import type { DebugFlags } from '@object-ui/core';
3
+ /**
4
+ * Host-provided fetch used for `provider: 'api'` view data sources so custom
5
+ * endpoints carry the same credentials (Authorization, tenant, locale headers)
6
+ * as the native data channel. Optional — when absent, ApiDataSource falls back
7
+ * to the bare global fetch (cookies only).
8
+ */
9
+ export type ApiFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
3
10
  interface SchemaRendererContextType {
4
11
  dataSource: any;
5
12
  debug?: boolean;
6
13
  debugFlags?: DebugFlags;
14
+ apiFetch?: ApiFetch;
7
15
  }
8
16
  declare const SchemaRendererContext: React.Context<SchemaRendererContextType | null>;
9
17
  export { SchemaRendererContext };
10
- export declare const SchemaRendererProvider: ({ children, dataSource, debug, debugFlags, }: {
18
+ export declare const SchemaRendererProvider: ({ children, dataSource, debug, debugFlags, apiFetch, }: {
11
19
  children: React.ReactNode;
12
20
  dataSource: any;
13
21
  debug?: boolean;
14
22
  debugFlags?: DebugFlags;
23
+ apiFetch?: ApiFetch;
15
24
  }) => React.JSX.Element;
16
25
  export declare const useSchemaContext: () => SchemaRendererContextType;
17
26
  export declare const useDataScope: (path?: string) => any;
@@ -2,8 +2,13 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { createContext, useContext, useMemo } from 'react';
3
3
  const SchemaRendererContext = createContext(null);
4
4
  export { SchemaRendererContext };
5
- export const SchemaRendererProvider = ({ children, dataSource, debug, debugFlags, }) => {
6
- const value = useMemo(() => ({ dataSource, debug, debugFlags }), [dataSource, debug, debugFlags]);
5
+ export const SchemaRendererProvider = ({ children, dataSource, debug, debugFlags, apiFetch, }) => {
6
+ // Nested providers (react-page, studio preview surfaces) re-wrap with their
7
+ // own dataSource but rarely know about the host's authenticated fetch —
8
+ // inherit it from the parent context so provider:'api' auth survives nesting.
9
+ const parent = useContext(SchemaRendererContext);
10
+ const effectiveApiFetch = apiFetch ?? parent?.apiFetch;
11
+ const value = useMemo(() => ({ dataSource, debug, debugFlags, apiFetch: effectiveApiFetch }), [dataSource, debug, debugFlags, effectiveApiFetch]);
7
12
  return (_jsx(SchemaRendererContext.Provider, { value: value, children: children }));
8
13
  };
9
14
  export const useSchemaContext = () => {
@@ -222,6 +222,10 @@ export function useETagCache(userConfig = {}) {
222
222
  catch (e) {
223
223
  if (e instanceof Error && e.message.startsWith('Unsupported'))
224
224
  throw e;
225
+ // Any other parse failure collapses to a generic message; we can't
226
+ // attach `e` as the `Error` `cause` because this package targets
227
+ // ES2020, whose lib types the 1-arg `Error` constructor only.
228
+ // eslint-disable-next-line preserve-caught-error
225
229
  throw new Error('Invalid URL');
226
230
  }
227
231
  }
@@ -40,7 +40,10 @@ export declare function usePredicateScope(): Record<string, any>;
40
40
  * This is the canonical helper to use in renderers so we never end up with
41
41
  * `${[object Object]}` after JS template-literal interpolation.
42
42
  */
43
- export declare function toPredicateInput(value: unknown): string | boolean | undefined;
43
+ export declare function toPredicateInput(value: unknown): string | boolean | {
44
+ dialect: 'cel';
45
+ source: string;
46
+ } | undefined;
44
47
  /**
45
48
  * Hook for evaluating expressions with dynamic context
46
49
  *
@@ -51,7 +54,10 @@ export declare function toPredicateInput(value: unknown): string | boolean | und
51
54
  * ```
52
55
  */
53
56
  export declare function useExpression(expression: string | boolean | number | null | undefined, context?: Record<string, any>): any;
54
- export declare function useCondition(condition: string | boolean | undefined, context?: Record<string, any>, options?: {
57
+ export declare function useCondition(condition: string | boolean | undefined | {
58
+ dialect?: string;
59
+ source?: string;
60
+ }, context?: Record<string, any>, options?: {
55
61
  throwOnError?: boolean;
56
62
  label?: string;
57
63
  }): boolean;
@@ -63,7 +63,15 @@ export function toPredicateInput(value) {
63
63
  return `\${${value}}`;
64
64
  if (typeof value === 'object' && typeof value.source === 'string') {
65
65
  const src = value.source;
66
- return src ? `\${${src}}` : undefined;
66
+ if (!src)
67
+ return undefined;
68
+ // #2661 — preserve a CEL-dialect envelope so `useCondition` routes it to the
69
+ // canonical `@objectstack/formula` engine (identical verdict to the server),
70
+ // instead of collapsing it to a `${source}` string on the legacy JS path.
71
+ // Every other dialect (template / unset) keeps the legacy `${…}` behavior.
72
+ if (value.dialect === 'cel')
73
+ return { dialect: 'cel', source: src };
74
+ return `\${${src}}`;
67
75
  }
68
76
  return undefined;
69
77
  }
@@ -83,7 +83,8 @@ export function useRecordSearch(opts) {
83
83
  const candidates = useMemo(() => {
84
84
  if (!Array.isArray(objects) || objects.length === 0)
85
85
  return [];
86
- let pool = objects;
86
+ // Assigned in both branches below before it's read; no dead initializer.
87
+ let pool;
87
88
  if (Array.isArray(objectNames) && objectNames.length > 0) {
88
89
  const byName = new Map();
89
90
  for (const obj of objects) {
@@ -44,18 +44,26 @@ import { SchemaRendererContext } from '../context/SchemaRendererContext';
44
44
  */
45
45
  export function useViewData(options) {
46
46
  const { viewData, resource = '', params, dataSource: explicitDataSource, adapterOptions, skip = false, } = options;
47
- // Get context DataSource
47
+ // Get context DataSource + host-authenticated fetch
48
48
  const context = useContext(SchemaRendererContext);
49
49
  const contextDataSource = context?.dataSource ?? null;
50
+ const contextApiFetch = context?.apiFetch;
50
51
  // Resolve the DataSource — memoize to prevent re-creation on every render.
51
52
  // We key on the viewData provider + config identity.
52
53
  const resolvedDataSource = useMemo(() => {
53
54
  if (explicitDataSource)
54
55
  return explicitDataSource;
55
- return resolveDataSource(viewData, contextDataSource, adapterOptions);
56
+ // Default the 'api' provider's fetch to the host's authenticated fetch
57
+ // (SchemaRendererContext.apiFetch) so custom endpoints carry the same
58
+ // credentials as native requests; explicit adapterOptions still win.
59
+ return resolveDataSource(viewData, contextDataSource, {
60
+ fetch: contextApiFetch,
61
+ ...adapterOptions,
62
+ });
56
63
  // eslint-disable-next-line react-hooks/exhaustive-deps
57
64
  }, [
58
65
  explicitDataSource,
66
+ contextApiFetch,
59
67
  viewData?.provider,
60
68
  // For 'api' provider, key on read/write URLs
61
69
  viewData?.provider === 'api' ? viewData.read?.url : undefined,
package/dist/index.d.ts CHANGED
@@ -12,4 +12,5 @@ export * from './LazyPluginLoader';
12
12
  export * from './spec-bridge';
13
13
  export * from './data-invalidation';
14
14
  export { resolveI18nLabel } from './utils/i18n';
15
- export { I18nProvider, useObjectTranslation, useObjectLabel, useSafeFieldLabel, useI18nContext, createI18n, getDirection, getAvailableLanguages, formatDate, formatDateTime, formatRelativeTime, formatCurrency, formatNumber, type I18nConfig, type I18nProviderProps, type TranslationKeys, type DateFormatOptions, type CurrencyFormatOptions, type NumberFormatOptions, } from '@object-ui/i18n';
15
+ export { extractWriteErrorMessage, isPermissionError } from './utils/error-message';
16
+ export { I18nProvider, useObjectTranslation, useSafeTranslate, useObjectLabel, useSafeFieldLabel, useI18nContext, createI18n, getDirection, getAvailableLanguages, formatDate, formatDateTime, formatRelativeTime, formatCurrency, formatNumber, type I18nConfig, type I18nProviderProps, type TranslationKeys, type DateFormatOptions, type CurrencyFormatOptions, type NumberFormatOptions, } from '@object-ui/i18n';
package/dist/index.js CHANGED
@@ -13,5 +13,8 @@ export * from './spec-bridge';
13
13
  export * from './data-invalidation';
14
14
  // i18n utilities
15
15
  export { resolveI18nLabel } from './utils/i18n';
16
+ // Write-error surfacing utilities (shared by drag-write plugins so a failed
17
+ // PATCH — e.g. an RLS 403 — is never silently swallowed).
18
+ export { extractWriteErrorMessage, isPermissionError } from './utils/error-message';
16
19
  // Built-in i18n support
17
- export { I18nProvider, useObjectTranslation, useObjectLabel, useSafeFieldLabel, useI18nContext, createI18n, getDirection, getAvailableLanguages, formatDate, formatDateTime, formatRelativeTime, formatCurrency, formatNumber, } from '@object-ui/i18n';
20
+ export { I18nProvider, useObjectTranslation, useSafeTranslate, useObjectLabel, useSafeFieldLabel, useI18nContext, createI18n, getDirection, getAvailableLanguages, formatDate, formatDateTime, formatRelativeTime, formatCurrency, formatNumber, } from '@object-ui/i18n';
@@ -0,0 +1,21 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ /**
9
+ * Pull a clean, human-readable message out of a failed write. Strips noisy
10
+ * backend prefixes (`[Security] `, `PERMISSION_DENIED: `) so it reads well in a
11
+ * toast — mirrors `plugin-detail`'s `cleanError`. Returns `null` when nothing
12
+ * usable can be extracted, so callers fall back to a generic localized string.
13
+ */
14
+ export declare function extractWriteErrorMessage(err: unknown): string | null;
15
+ /**
16
+ * True when a failed write was rejected for authorization reasons (HTTP 403 /
17
+ * `PERMISSION_DENIED` / `FORBIDDEN` / a row-level-security denial). Lets a
18
+ * caller show a friendly "you don't have permission" toast rather than dumping
19
+ * the raw server text.
20
+ */
21
+ export declare function isPermissionError(err: unknown): boolean;
@@ -0,0 +1,89 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ /**
9
+ * Helpers for turning a failed data-source write into user-facing feedback.
10
+ *
11
+ * Different DataSource adapters throw differently shaped errors:
12
+ * - the ObjectStack adapter (`@object-ui/data-objectstack`) decorates the
13
+ * thrown Error with `httpStatus` + a machine-readable `code` and puts the
14
+ * server text on `.message` (sometimes also a parsed body on `.details`);
15
+ * - `ApiDataSource` appends the raw JSON response body as a tail on the
16
+ * message string (`… — {"error":…,"code":…}`).
17
+ *
18
+ * These helpers normalise across both so a caller's `catch` can surface the
19
+ * real reason (e.g. a row-level-security denial) instead of swallowing it.
20
+ */
21
+ function firstString(...vals) {
22
+ for (const v of vals) {
23
+ if (typeof v === 'string' && v.trim())
24
+ return v;
25
+ }
26
+ return null;
27
+ }
28
+ /**
29
+ * Pull a clean, human-readable message out of a failed write. Strips noisy
30
+ * backend prefixes (`[Security] `, `PERMISSION_DENIED: `) so it reads well in a
31
+ * toast — mirrors `plugin-detail`'s `cleanError`. Returns `null` when nothing
32
+ * usable can be extracted, so callers fall back to a generic localized string.
33
+ */
34
+ export function extractWriteErrorMessage(err) {
35
+ const e = err;
36
+ // 1) Structured server body, when the adapter attaches one.
37
+ let msg = e && typeof e === 'object'
38
+ ? firstString(e.details?.error, e.details?.message, e.error)
39
+ : null;
40
+ // 2) Otherwise the Error message itself. ApiDataSource embeds the raw JSON
41
+ // response body as a tail — parse it out when present.
42
+ if (!msg) {
43
+ const raw = err instanceof Error
44
+ ? err.message
45
+ : typeof err === 'string'
46
+ ? err
47
+ : typeof e?.message === 'string'
48
+ ? e.message
49
+ : '';
50
+ const brace = raw.indexOf('{');
51
+ if (brace >= 0) {
52
+ try {
53
+ const body = JSON.parse(raw.slice(brace));
54
+ msg = firstString(body?.error, body?.message);
55
+ }
56
+ catch {
57
+ /* tail wasn't JSON — fall through to the raw message */
58
+ }
59
+ }
60
+ if (!msg && raw.trim())
61
+ msg = raw;
62
+ }
63
+ if (!msg)
64
+ return null;
65
+ const clean = msg
66
+ .replace(/^\[[^\]]+\]\s*/, '')
67
+ .replace(/^[A-Z][A-Z0-9_]+:\s*/, '')
68
+ .trim();
69
+ return clean || null;
70
+ }
71
+ /**
72
+ * True when a failed write was rejected for authorization reasons (HTTP 403 /
73
+ * `PERMISSION_DENIED` / `FORBIDDEN` / a row-level-security denial). Lets a
74
+ * caller show a friendly "you don't have permission" toast rather than dumping
75
+ * the raw server text.
76
+ */
77
+ export function isPermissionError(err) {
78
+ const e = err;
79
+ if (!e || typeof e !== 'object')
80
+ return false;
81
+ const status = e.httpStatus ?? e.status ?? e.statusCode;
82
+ if (status === 403)
83
+ return true;
84
+ const code = firstString(e.code, e.details?.code) ?? '';
85
+ if (code === 'PERMISSION_DENIED' || code === 'FORBIDDEN')
86
+ return true;
87
+ const text = `${typeof e.message === 'string' ? e.message : ''} ${e.details?.error ?? e.error ?? ''}`;
88
+ return /row-level security|access denied|permission denied|not permitted/i.test(text);
89
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@object-ui/react",
3
- "version": "16.0.0",
3
+ "version": "16.1.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "React bindings and SchemaRenderer component for Object UI",
@@ -24,12 +24,12 @@
24
24
  }
25
25
  },
26
26
  "dependencies": {
27
- "@objectstack/spec": "^15.1.1",
28
- "react-hook-form": "^7.81.0",
29
- "@object-ui/core": "16.0.0",
30
- "@object-ui/data-objectstack": "16.0.0",
31
- "@object-ui/i18n": "16.0.0",
32
- "@object-ui/types": "16.0.0"
27
+ "@objectstack/spec": "^16.0.0-rc.0",
28
+ "react-hook-form": "^7.82.0",
29
+ "@object-ui/core": "16.1.0",
30
+ "@object-ui/data-objectstack": "16.1.0",
31
+ "@object-ui/i18n": "16.1.0",
32
+ "@object-ui/types": "16.1.0"
33
33
  },
34
34
  "peerDependencies": {
35
35
  "react": "^18.0.0 || ^19.0.0",