@magicvr/schema-ui-shell 0.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/app/App.d.ts +23 -0
- package/app/AuthGate.d.ts +20 -0
- package/app/HostFailureScreen.d.ts +18 -0
- package/app/LoginPage.d.ts +11 -0
- package/app/ManifestFailure.d.ts +3 -0
- package/app/branding.d.ts +33 -0
- package/app/config-events.d.ts +18 -0
- package/app/index.d.ts +15 -0
- package/app/navigation.d.ts +27 -0
- package/app/notification-bell.d.ts +10 -0
- package/components/data-table.d.ts +43 -0
- package/components/force-password-change.d.ts +1 -0
- package/components/invite-accept.d.ts +6 -0
- package/components/locale-switcher.d.ts +23 -0
- package/components/theme-toggle.d.ts +10 -0
- package/components/timezone-switcher.d.ts +16 -0
- package/components/ui/async-state.d.ts +27 -0
- package/components/ui/breadcrumbs.d.ts +78 -0
- package/components/ui/button.d.ts +11 -0
- package/components/ui/card.d.ts +8 -0
- package/components/ui/input.d.ts +5 -0
- package/components/ui/label.d.ts +3 -0
- package/components/ui/skeleton.d.ts +2 -0
- package/components/ui/textarea.d.ts +5 -0
- package/host/boot.d.ts +64 -0
- package/host/bootstrap.d.ts +105 -0
- package/host/claim.d.ts +75 -0
- package/host/failure.d.ts +97 -0
- package/host/return-intent.d.ts +55 -0
- package/i18n/catalog.d.ts +51 -0
- package/i18n/format.d.ts +17 -0
- package/i18n/locale.d.ts +51 -0
- package/i18n/runtime.d.ts +90 -0
- package/i18n/timezone.d.ts +57 -0
- package/index.js +23764 -0
- package/lib/datetime.d.ts +14 -0
- package/lib/fetch-timeout.d.ts +13 -0
- package/lib/utils.d.ts +2 -0
- package/package.json +30 -0
- package/renderer/confirm.d.ts +17 -0
- package/renderer/custom-components.d.ts +14 -0
- package/renderer/form-controls.d.ts +45 -0
- package/renderer/form-controls.types.d.ts +135 -0
- package/renderer/modal.d.ts +6 -0
- package/renderer/permissions.d.ts +53 -0
- package/renderer/reaction-engine.d.ts +92 -0
- package/renderer/reaction-expression.d.ts +74 -0
- package/renderer/reactions.d.ts +64 -0
- package/renderer/render.d.ts +194 -0
- package/renderer/render.types.d.ts +289 -0
- package/renderer/resource.d.ts +114 -0
- package/renderer/schema-table.d.ts +90 -0
- package/theme/theme.d.ts +64 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { type ComponentType, type ReactNode } from "react";
|
|
2
|
+
import { type UploadableFile } from "@/protocol/conformance/upload-orchestration";
|
|
3
|
+
import { type FormControlField } from "@/renderer/form-controls.types";
|
|
4
|
+
import { type ResourceList, type ResourceQuery } from "@/renderer/resource";
|
|
5
|
+
import { type RenderActionButtonNode, type RenderChartNode, type RenderFormNode, type RenderPageDocument, type RenderStatCardNode, type RenderTableNode } from "@/renderer/render.types";
|
|
6
|
+
/**
|
|
7
|
+
* R5 D-COMP minimal Renderer (resolve R4 F-002) + S4 Schema CRUD (GOAL-007).
|
|
8
|
+
*
|
|
9
|
+
* Dispatch layer: parses a page document, applies the frozen $context
|
|
10
|
+
* reaction engine to form field state, and renders whitelisted node types
|
|
11
|
+
* through the components in this directory. Unknown node types fail closed.
|
|
12
|
+
*
|
|
13
|
+
* S4 one-time completion (I-007-003 v0.2.2 §9): a SchemaCrudProvider owns the
|
|
14
|
+
* cross-node state a Schema-driven CRUD page needs — selected row (feeds
|
|
15
|
+
* recordView + edit-form prefill), per-table query (search form-to-query
|
|
16
|
+
* binding), a reload token, the active modal, the pending delete confirm, and
|
|
17
|
+
* a generic action executor that gates through the frozen `executeAction`
|
|
18
|
+
* engine and constructs requests with the pinned conformance constructor
|
|
19
|
+
* (`request-construction.ts`). Every page-level behaviour stays fixture-driven;
|
|
20
|
+
* after this completion page behavior remains schema-owned; core fixtures and
|
|
21
|
+
* module-owned schema packages can add pages without Renderer changes.
|
|
22
|
+
*
|
|
23
|
+
* Scope: the frozen §5 node whitelist — layout (grid/section/tabs),
|
|
24
|
+
* data/action (text/table/recordView/actionButton) and form. The form control
|
|
25
|
+
* whitelist itself is enforced by D-FORM (isWhitelistedFormControl /
|
|
26
|
+
* checkFormCapabilities) via gateRenderFormFields. The default app path wires
|
|
27
|
+
* a schema-driven table surface (SchemaTable, GOAL-004) as `tableRenderer`;
|
|
28
|
+
* a table node dispatched without one fails closed with an observable note.
|
|
29
|
+
*
|
|
30
|
+
* A-002 F-002-002 (GOAL-009 S1): form submission is blocked while any
|
|
31
|
+
* gate/reaction error is present — the submit button is disabled and
|
|
32
|
+
* handleSubmit re-rejects before any request can be constructed.
|
|
33
|
+
*/
|
|
34
|
+
export interface RendererComponentProps {
|
|
35
|
+
document: RenderPageDocument;
|
|
36
|
+
context: Record<string, unknown>;
|
|
37
|
+
/** Renders a table node; provided by the example page that owns data. */
|
|
38
|
+
tableRenderer?: (node: RenderTableNode) => ReactNode;
|
|
39
|
+
/** Renders statCard/chart nodes (supportsData display, registry); defaults to built-ins. */
|
|
40
|
+
dataRenderer?: (node: RenderStatCardNode | RenderChartNode) => ReactNode;
|
|
41
|
+
/** Invoked when an actionButton node is activated. */
|
|
42
|
+
onAction?: (node: RenderActionButtonNode) => void;
|
|
43
|
+
/**
|
|
44
|
+
* Session-internal navigation hook (ADR-0021 navigate actions; GOAL-015
|
|
45
|
+
* F-001): the host pushes the target onto its own history/visit stack so
|
|
46
|
+
* breadcrumbs survive. Falls back to window.location.assign when absent.
|
|
47
|
+
*/
|
|
48
|
+
onNavigate?: (url: string) => void;
|
|
49
|
+
/** Overrides the default FormControls component (keeps field wiring local). */
|
|
50
|
+
formComponent?: ComponentType<{
|
|
51
|
+
fields: FormControlField[];
|
|
52
|
+
values: Record<string, unknown>;
|
|
53
|
+
onChange: (id: string, value: unknown) => void;
|
|
54
|
+
fieldDisabled?: (id: string) => boolean;
|
|
55
|
+
onUpload?: (field: FormControlField, files: UploadableFile[]) => Promise<unknown>;
|
|
56
|
+
/** W11 · U-01/U-02: auth-aware transport for dynamic option sources. */
|
|
57
|
+
fetcher?: typeof fetch;
|
|
58
|
+
}>;
|
|
59
|
+
}
|
|
60
|
+
export interface SchemaCrudFeedback {
|
|
61
|
+
kind: "success" | "error";
|
|
62
|
+
message: string;
|
|
63
|
+
code?: string;
|
|
64
|
+
/** VP-007 S4: catalog key for the frontend localization floor. */
|
|
65
|
+
messageKey?: string;
|
|
66
|
+
/** VP-007 S4: interpolation params for messageKey. */
|
|
67
|
+
params?: Record<string, unknown>;
|
|
68
|
+
}
|
|
69
|
+
export interface SchemaCrudConfirm {
|
|
70
|
+
actionRef: string;
|
|
71
|
+
actionKey: string;
|
|
72
|
+
row: Record<string, unknown>;
|
|
73
|
+
requestMapping?: Record<string, unknown>;
|
|
74
|
+
message: string;
|
|
75
|
+
/** Batch trigger confirm (ADR-0022 D4/D5): carries the selection snapshot. */
|
|
76
|
+
batch?: {
|
|
77
|
+
tableId: string;
|
|
78
|
+
selection: TableSelection;
|
|
79
|
+
};
|
|
80
|
+
/** Toolbar item batchMapping carried through confirm. */
|
|
81
|
+
batchMapping?: Record<string, unknown>;
|
|
82
|
+
}
|
|
83
|
+
/** Selection snapshot (ADR-0022 D3): ordered keys + count. */
|
|
84
|
+
export interface TableSelection {
|
|
85
|
+
keys: unknown[];
|
|
86
|
+
count: number;
|
|
87
|
+
}
|
|
88
|
+
export type ActionResult = {
|
|
89
|
+
ok: true;
|
|
90
|
+
fieldErrors?: Array<{
|
|
91
|
+
field: string;
|
|
92
|
+
reason: string;
|
|
93
|
+
rowNumber?: number;
|
|
94
|
+
}>;
|
|
95
|
+
message?: string;
|
|
96
|
+
messageKey?: string;
|
|
97
|
+
} | {
|
|
98
|
+
ok: false;
|
|
99
|
+
code: string;
|
|
100
|
+
message: string;
|
|
101
|
+
messageKey?: string;
|
|
102
|
+
params?: Record<string, unknown>;
|
|
103
|
+
/** GOAL-014 D-002 §2: server field-level validation failures. */
|
|
104
|
+
fieldErrors?: Array<{
|
|
105
|
+
field: string;
|
|
106
|
+
reason: string;
|
|
107
|
+
rowNumber?: number;
|
|
108
|
+
}>;
|
|
109
|
+
};
|
|
110
|
+
export interface SchemaCrudValue {
|
|
111
|
+
selectedRow: Record<string, unknown> | null;
|
|
112
|
+
selectRow: (row: Record<string, unknown> | null) => void;
|
|
113
|
+
tableQuery: (id: string) => ResourceQuery | undefined;
|
|
114
|
+
setTableQuery: (id: string, query: ResourceQuery) => void;
|
|
115
|
+
reloadToken: number;
|
|
116
|
+
reloadList: () => void;
|
|
117
|
+
/**
|
|
118
|
+
* Fetches a resource list through the page-level in-flight coalescer:
|
|
119
|
+
* simultaneous consumers of the same URL share ONE network request (three
|
|
120
|
+
* statCards + the wallet-ensure probe on one "我的钱包" visit merge into a
|
|
121
|
+
* single GET /me). Requests are otherwise never memoized — every query,
|
|
122
|
+
* reset or reload refetches — and reloadList drops the in-flight map so a
|
|
123
|
+
* reload issued during a slow fetch starts its own fresh request.
|
|
124
|
+
* `transport` is the caller's own transport (a directly injected fixture or
|
|
125
|
+
* the auth fetcher); defaults to the provider's registered fetcher.
|
|
126
|
+
*/
|
|
127
|
+
fetchList: (dataSource: string, query: ResourceQuery, extraQuery?: string, transport?: typeof fetch) => Promise<ResourceList>;
|
|
128
|
+
/**
|
|
129
|
+
* Targeted display-data refresh (W25): bumps the per-URL refresh token for
|
|
130
|
+
* the standard display query of `dataSource` (statCard/chart consume it),
|
|
131
|
+
* so a consumer can refetch ONE surface without a full-page reload wave.
|
|
132
|
+
* Only applies to display nodes without route-param bindings (the standard
|
|
133
|
+
* DISPLAY_LIST_QUERY shape); tables/misc surfaces keep their data until a
|
|
134
|
+
* manual reload.
|
|
135
|
+
*/
|
|
136
|
+
refreshList: (dataSource: string) => void;
|
|
137
|
+
/** Current refresh token for a display dataSource (0 when untouched). */
|
|
138
|
+
listRefreshToken: (dataSource: string) => number;
|
|
139
|
+
activeModal: {
|
|
140
|
+
actionRef: string;
|
|
141
|
+
row: Record<string, unknown> | null;
|
|
142
|
+
title: string;
|
|
143
|
+
} | null;
|
|
144
|
+
modalRow: Record<string, unknown> | null;
|
|
145
|
+
openModal: (actionRef: string, row: Record<string, unknown> | null, title: string) => void;
|
|
146
|
+
closeModal: () => void;
|
|
147
|
+
pendingConfirm: SchemaCrudConfirm | null;
|
|
148
|
+
requestConfirm: (confirm: SchemaCrudConfirm) => void;
|
|
149
|
+
resolveConfirm: (confirmed: boolean) => Promise<void>;
|
|
150
|
+
feedback: SchemaCrudFeedback | null;
|
|
151
|
+
registerFetcher: (fetcher: typeof fetch) => void;
|
|
152
|
+
/** The currently registered transport (globalThis.fetch until injected). */
|
|
153
|
+
fetcher: typeof fetch;
|
|
154
|
+
/** Runs a request action end-to-end (gate → construct → fetch → feedback/reload). */
|
|
155
|
+
runRowAction: (actionRef: string, opts: RunRequestOptions) => Promise<ActionResult>;
|
|
156
|
+
/** Dispatches a toolbar/row action entry: modal open, confirm, or request. */
|
|
157
|
+
invokeAction: (item: Record<string, unknown>, row: Record<string, unknown> | null) => void;
|
|
158
|
+
/** Dispatches a batch toolbar trigger (ADR-0022): gate → confirm → request. */
|
|
159
|
+
invokeBatchAction: (item: Record<string, unknown>, tableId: string) => void;
|
|
160
|
+
/** Upload control transport (ADR-0012): resolves action/actionRef → validates → uploads. */
|
|
161
|
+
uploadFiles: (field: FormControlField, files: UploadableFile[]) => Promise<unknown>;
|
|
162
|
+
/** Per-table selection state (keys + count; normalized by the table). */
|
|
163
|
+
selection: (tableId: string) => TableSelection | undefined;
|
|
164
|
+
setSelection: (tableId: string, keys: unknown[]) => void;
|
|
165
|
+
clearSelection: (tableId: string) => void;
|
|
166
|
+
/** Submits a default-mode form against its `submitAction`. */
|
|
167
|
+
submitForm: (form: RenderFormNode, values: Record<string, unknown>) => Promise<ActionResult>;
|
|
168
|
+
/** Binds a search-mode form's fields to its target table query. */
|
|
169
|
+
searchFormSubmit: (form: RenderFormNode, values: Record<string, unknown>) => void;
|
|
170
|
+
effectivePermission: (targetId: string) => boolean;
|
|
171
|
+
/**
|
|
172
|
+
* Current route snapshot (from the render context; App injects
|
|
173
|
+
* route: {params, query}). Used for ADR-0039 dataSource bindings and
|
|
174
|
+
* create-modal readOnly seeding instead of reading window.location.
|
|
175
|
+
*/
|
|
176
|
+
route: {
|
|
177
|
+
query: Record<string, string>;
|
|
178
|
+
params: Record<string, string>;
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
export interface RunRequestOptions {
|
|
182
|
+
row?: Record<string, unknown> | null;
|
|
183
|
+
formValues?: Record<string, unknown>;
|
|
184
|
+
requestMapping?: Record<string, unknown>;
|
|
185
|
+
gateTargetId?: string;
|
|
186
|
+
confirmed?: boolean;
|
|
187
|
+
}
|
|
188
|
+
/** Page CRUD context consumed by custom components (rendered nodes). */
|
|
189
|
+
export declare const SchemaCrudContext: import("react").Context<SchemaCrudValue | null>;
|
|
190
|
+
/** Reads the page-level Schema CRUD provider (null when rendered bare). */
|
|
191
|
+
export declare function useSchemaCrud(): SchemaCrudValue | null;
|
|
192
|
+
export declare function RenderPage({ document, context, tableRenderer, dataFetcher, onAction, onNavigate, formComponent, }: RendererComponentProps & {
|
|
193
|
+
dataFetcher?: typeof fetch;
|
|
194
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { type FormControlField, type FormControlGateError } from "@/renderer/form-controls.types";
|
|
2
|
+
import { type FormControlStateMap, type ReactionError } from "@/renderer/reactions";
|
|
3
|
+
/**
|
|
4
|
+
* D-COMP page renderer (frozen §5 whitelist + I-PROTO-FULL-001 full registry
|
|
5
|
+
* surface; resolve R4 F-002).
|
|
6
|
+
*
|
|
7
|
+
* The renderer walks a page document's node tree and dispatches whitelisted
|
|
8
|
+
* node types to the components in this directory. It is deliberately minimal:
|
|
9
|
+
* only the node types surfaced by the example pages are handled, and any
|
|
10
|
+
* other type fails closed instead of rendering a silent fallback.
|
|
11
|
+
*
|
|
12
|
+
* A page document looks like the example pages' `PAGE_DOCUMENT`:
|
|
13
|
+
* { meta: { protocolVersion, requiredCapabilities }, body: Node }
|
|
14
|
+
*
|
|
15
|
+
* Node types supported (registry surface):
|
|
16
|
+
* - layout: grid / section / tabs
|
|
17
|
+
* - data/action: text / table / recordView / actionButton / statCard / chart
|
|
18
|
+
* - form: form → FormControls (with reactions applied to field state)
|
|
19
|
+
* The form control whitelist itself is enforced by D-FORM
|
|
20
|
+
* (isWhitelistedFormControl / checkFormCapabilities).
|
|
21
|
+
*/
|
|
22
|
+
export type RenderNodeType = "form" | "section" | "table" | "grid" | "tabs" | "text" | "recordView" | "actionButton" | "statCard" | "chart" | "custom";
|
|
23
|
+
export interface RenderMeta {
|
|
24
|
+
protocolVersion: string;
|
|
25
|
+
requiredCapabilities: string[];
|
|
26
|
+
}
|
|
27
|
+
export interface ReactionState {
|
|
28
|
+
/** fieldId → control state after reactions. */
|
|
29
|
+
state: FormControlStateMap;
|
|
30
|
+
errors: ReactionError[];
|
|
31
|
+
}
|
|
32
|
+
export interface RenderFormNode {
|
|
33
|
+
type: "form";
|
|
34
|
+
id?: string;
|
|
35
|
+
props: {
|
|
36
|
+
fields: Array<Record<string, unknown>>;
|
|
37
|
+
reactions?: unknown;
|
|
38
|
+
submitLabel?: string;
|
|
39
|
+
/** S2 (VP-007): i18n key resolved before `submitLabel` (local doc convention). */
|
|
40
|
+
submitLabelKey?: string;
|
|
41
|
+
/** Default-mode submit: the top-level action id to run on submit (S4). */
|
|
42
|
+
submitAction?: string;
|
|
43
|
+
/** Search-mode form: binds its fields to the target table's query (S4). */
|
|
44
|
+
mode?: "default" | "search";
|
|
45
|
+
targetTable?: string;
|
|
46
|
+
/** Section heading rendered above the fields (registry form `title`/`titleKey`). */
|
|
47
|
+
title?: string;
|
|
48
|
+
/** i18n key resolved before `title`. */
|
|
49
|
+
titleKey?: string;
|
|
50
|
+
/**
|
|
51
|
+
* ADR-0021 edit-form record GET prefill (registry since 2.1): loads current
|
|
52
|
+
* values from a detail GET and initializes the fields via `responseMapping`
|
|
53
|
+
* (field id → dot-path in the response). Requires capability
|
|
54
|
+
* `form.record.load`; search-mode forms forbid it.
|
|
55
|
+
*/
|
|
56
|
+
recordSource?: Record<string, unknown>;
|
|
57
|
+
/** GOAL-014 D-002 §4: responsive form column count (>1 enables the grid). */
|
|
58
|
+
columns?: number;
|
|
59
|
+
};
|
|
60
|
+
children?: RenderNode[];
|
|
61
|
+
}
|
|
62
|
+
export interface RenderSectionNode {
|
|
63
|
+
type: "section";
|
|
64
|
+
id?: string;
|
|
65
|
+
props?: Record<string, unknown>;
|
|
66
|
+
children: RenderNode[];
|
|
67
|
+
}
|
|
68
|
+
export interface RenderGridNode {
|
|
69
|
+
type: "grid";
|
|
70
|
+
id?: string;
|
|
71
|
+
props?: Record<string, unknown>;
|
|
72
|
+
children?: RenderNode[];
|
|
73
|
+
}
|
|
74
|
+
export interface RenderTabsNode {
|
|
75
|
+
type: "tabs";
|
|
76
|
+
id?: string;
|
|
77
|
+
props?: Record<string, unknown>;
|
|
78
|
+
children?: RenderNode[];
|
|
79
|
+
}
|
|
80
|
+
export interface RenderTextNode {
|
|
81
|
+
type: "text";
|
|
82
|
+
id?: string;
|
|
83
|
+
props?: {
|
|
84
|
+
text?: string;
|
|
85
|
+
/** i18n key resolved before `text` (F-01 · GOAL-003 A-003 F-001). */
|
|
86
|
+
textKey?: string;
|
|
87
|
+
};
|
|
88
|
+
children?: RenderNode[];
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* v2.9 DataRef subset consumed by data nodes (ADR-0039): source:api url +
|
|
92
|
+
* params (literal scalars or whole $context.route.query.* / params.* bindings).
|
|
93
|
+
*/
|
|
94
|
+
export interface RenderDataRef {
|
|
95
|
+
url: string;
|
|
96
|
+
params?: Record<string, unknown>;
|
|
97
|
+
}
|
|
98
|
+
export interface RenderTableNode {
|
|
99
|
+
type: "table";
|
|
100
|
+
id?: string;
|
|
101
|
+
/** v2.9 node-level DataRef (source:api). Preferred over props.dataSource. */
|
|
102
|
+
data?: RenderDataRef;
|
|
103
|
+
props: {
|
|
104
|
+
columns?: Array<Record<string, unknown>>;
|
|
105
|
+
actions?: Array<Record<string, unknown>>;
|
|
106
|
+
toolbar?: Array<Record<string, unknown>>;
|
|
107
|
+
dataSource?: string;
|
|
108
|
+
/** Table heading (literal fallback for titleKey). */
|
|
109
|
+
title?: string;
|
|
110
|
+
/** i18n key resolved before title. */
|
|
111
|
+
titleKey?: string;
|
|
112
|
+
/** Direct field name of each row's unique key (F-002 · I-010-001 v0.2.0 §3; default "id"). */
|
|
113
|
+
rowKey?: string;
|
|
114
|
+
/** ADR-0022 multi-select model (registry; mode: multiple only). */
|
|
115
|
+
selection?: {
|
|
116
|
+
mode?: string;
|
|
117
|
+
};
|
|
118
|
+
/** Schema-driven filters (select-only; see schemaTableFilters). */
|
|
119
|
+
filters?: Array<Record<string, unknown>>;
|
|
120
|
+
};
|
|
121
|
+
children?: RenderNode[];
|
|
122
|
+
}
|
|
123
|
+
/** Read-only field row on a recordView (registry `fields[]` since 2.4). */
|
|
124
|
+
export interface RenderRecordViewField {
|
|
125
|
+
key: string;
|
|
126
|
+
label?: string;
|
|
127
|
+
labelKey?: string;
|
|
128
|
+
}
|
|
129
|
+
export interface RenderRecordViewNode {
|
|
130
|
+
type: "recordView";
|
|
131
|
+
id?: string;
|
|
132
|
+
props?: {
|
|
133
|
+
record?: Record<string, unknown>;
|
|
134
|
+
/** Detail panel heading (registry `title` since 2.4). */
|
|
135
|
+
title?: string;
|
|
136
|
+
/** i18n key resolved before `title`. */
|
|
137
|
+
titleKey?: string;
|
|
138
|
+
/** Declared display fields; when set, only these rows render. */
|
|
139
|
+
fields?: RenderRecordViewField[];
|
|
140
|
+
};
|
|
141
|
+
children?: RenderNode[];
|
|
142
|
+
}
|
|
143
|
+
export interface RenderActionButtonNode {
|
|
144
|
+
type: "actionButton";
|
|
145
|
+
id?: string;
|
|
146
|
+
props?: {
|
|
147
|
+
label?: string;
|
|
148
|
+
/** i18n key resolved before `label` (S3). */
|
|
149
|
+
labelKey?: string;
|
|
150
|
+
actionId?: string;
|
|
151
|
+
visibleWhen?: unknown;
|
|
152
|
+
disabledWhen?: unknown;
|
|
153
|
+
/** Permission-intent key (ADR-0023 D4b mount); gates the button target. */
|
|
154
|
+
permissionIntent?: string;
|
|
155
|
+
/** Target id for the permission target / action gate (falls back to node id). */
|
|
156
|
+
key?: string;
|
|
157
|
+
/** Confirm message (shown before executing the referenced action). */
|
|
158
|
+
confirm?: string;
|
|
159
|
+
/** i18n key resolved before `confirm`. */
|
|
160
|
+
confirmKey?: string;
|
|
161
|
+
};
|
|
162
|
+
children?: RenderNode[];
|
|
163
|
+
}
|
|
164
|
+
export interface RenderStatCardNode {
|
|
165
|
+
type: "statCard";
|
|
166
|
+
id?: string;
|
|
167
|
+
/** v2.9 node-level DataRef (source:api). Preferred over props.dataSource. */
|
|
168
|
+
data?: RenderDataRef;
|
|
169
|
+
props?: {
|
|
170
|
+
label?: string;
|
|
171
|
+
/** i18n key resolved before `label` (F-01 · GOAL-003 A-003 F-001). */
|
|
172
|
+
labelKey?: string;
|
|
173
|
+
unit?: string;
|
|
174
|
+
/** plain | currency | percent (registry enum). */
|
|
175
|
+
format?: string;
|
|
176
|
+
/** Field of the dataSource rows to display (registry, required since 0.2). */
|
|
177
|
+
valueField?: string;
|
|
178
|
+
/** Single-slash same-origin data path (same invariant as table.dataSource). */
|
|
179
|
+
dataSource?: string;
|
|
180
|
+
};
|
|
181
|
+
children?: RenderNode[];
|
|
182
|
+
}
|
|
183
|
+
export interface RenderChartNode {
|
|
184
|
+
type: "chart";
|
|
185
|
+
id?: string;
|
|
186
|
+
/** v2.9 node-level DataRef (source:api). Preferred over props.dataSource. */
|
|
187
|
+
data?: RenderDataRef;
|
|
188
|
+
props?: {
|
|
189
|
+
/** line | bar | pie (registry enum, required). */
|
|
190
|
+
chartType?: string;
|
|
191
|
+
xField?: string;
|
|
192
|
+
yField?: string;
|
|
193
|
+
/** Single-slash same-origin data path (same invariant as table.dataSource). */
|
|
194
|
+
dataSource?: string;
|
|
195
|
+
};
|
|
196
|
+
children?: RenderNode[];
|
|
197
|
+
}
|
|
198
|
+
export interface RenderCustomNode {
|
|
199
|
+
type: "custom";
|
|
200
|
+
id?: string;
|
|
201
|
+
/** Registered component key (GOAL-018: renderer custom-component registry). */
|
|
202
|
+
component: string;
|
|
203
|
+
props?: Record<string, unknown>;
|
|
204
|
+
children?: RenderNode[];
|
|
205
|
+
}
|
|
206
|
+
export type RenderNode = RenderFormNode | RenderSectionNode | RenderGridNode | RenderTabsNode | RenderTextNode | RenderTableNode | RenderRecordViewNode | RenderActionButtonNode | RenderStatCardNode | RenderChartNode | RenderCustomNode;
|
|
207
|
+
/** Page-level action table entry (registry: modal | request | navigate). */
|
|
208
|
+
export interface RenderPageAction {
|
|
209
|
+
type: string;
|
|
210
|
+
/** Modal content; required when type=modal. */
|
|
211
|
+
content?: RenderNode;
|
|
212
|
+
/** Request/navigate target URL. */
|
|
213
|
+
url?: string;
|
|
214
|
+
/** HTTP method for request-shaped actions. */
|
|
215
|
+
method?: string;
|
|
216
|
+
/** Permission intent name, gated against page-level permissionCascade. */
|
|
217
|
+
permissionIntent?: string;
|
|
218
|
+
}
|
|
219
|
+
export interface RenderPageDocument {
|
|
220
|
+
meta: RenderMeta;
|
|
221
|
+
body: RenderNode;
|
|
222
|
+
/** Page-level action table referenced by actionRef / actionId. */
|
|
223
|
+
actions?: Record<string, RenderPageAction>;
|
|
224
|
+
}
|
|
225
|
+
export type RenderErrorCode = "RENDER_UNKNOWN_NODE_TYPE" | "RENDER_INVALID_BODY" | "RENDER_META_INVALID" | "RENDER_FORM_FIELD_INVALID";
|
|
226
|
+
export interface RenderError {
|
|
227
|
+
code: RenderErrorCode;
|
|
228
|
+
path: string;
|
|
229
|
+
message: string;
|
|
230
|
+
}
|
|
231
|
+
export type ActionGateErrorCode = "ACTION_GATE_EXPRESSION_INVALID";
|
|
232
|
+
export interface ActionGateError {
|
|
233
|
+
code: ActionGateErrorCode;
|
|
234
|
+
path: string;
|
|
235
|
+
message: string;
|
|
236
|
+
}
|
|
237
|
+
export type ActionGateResult = {
|
|
238
|
+
kind: "ok";
|
|
239
|
+
value: boolean;
|
|
240
|
+
} | {
|
|
241
|
+
kind: "error";
|
|
242
|
+
error: ActionGateError;
|
|
243
|
+
};
|
|
244
|
+
export interface RenderFormFieldGate {
|
|
245
|
+
/** Fields that passed the type whitelist and the version/capability gate. */
|
|
246
|
+
fields: FormControlField[];
|
|
247
|
+
/** Deterministic errors for rejected fields and gate failures. */
|
|
248
|
+
errors: FormControlGateError[];
|
|
249
|
+
}
|
|
250
|
+
/** Keeps only well-formed recordView field rows (key required). */
|
|
251
|
+
export declare function parseRecordViewFields(raw: unknown): RenderRecordViewField[];
|
|
252
|
+
export declare function isWhitelistedNodeType(type: string): type is RenderNodeType;
|
|
253
|
+
/**
|
|
254
|
+
* Resolves a dot-path on a record for `form.recordSource.responseMapping`
|
|
255
|
+
* (e.g. `"customer.name"` → record.customer.name). Missing segments or a
|
|
256
|
+
* non-object intermediate return `undefined` (field keeps its default/empty).
|
|
257
|
+
*/
|
|
258
|
+
export declare function resolveResponsePath(record: unknown, path: string): unknown;
|
|
259
|
+
/** Normalizes an unknown body value into a typed RenderNode, fail-closed. */
|
|
260
|
+
export declare function parseRenderNode(value: unknown, path: string): RenderNode | RenderError;
|
|
261
|
+
/** Collects all form fieldIds from a page document (for reaction resolution). */
|
|
262
|
+
export declare function collectFieldIds(node: RenderNode, into?: string[]): string[];
|
|
263
|
+
/** Resolves the reaction state for a form node's fields. */
|
|
264
|
+
export declare function resolveFormReactions(form: RenderFormNode, context: Record<string, unknown>): ReactionState;
|
|
265
|
+
/**
|
|
266
|
+
* Resolves a table action gate expression.
|
|
267
|
+
*
|
|
268
|
+
* Distinguishes an absent property (→ `absentDefault`) from an explicit but
|
|
269
|
+
* invalid expression (→ fail-closed `error`, no silent default). Valid $context
|
|
270
|
+
* expressions are evaluated against the frozen engine; booleans pass through.
|
|
271
|
+
*/
|
|
272
|
+
export declare function resolveActionGate(expression: unknown, context: Record<string, unknown>, absentDefault: boolean, path: string): ActionGateResult;
|
|
273
|
+
/**
|
|
274
|
+
* Evaluates a whitelisted table action's visibility/disabled gate.
|
|
275
|
+
* Explicit invalid expressions fail closed (hidden / disabled) and are reported.
|
|
276
|
+
*/
|
|
277
|
+
export declare function tableActionGate(action: Record<string, unknown>, context: Record<string, unknown>): {
|
|
278
|
+
visible: boolean;
|
|
279
|
+
disabled: boolean;
|
|
280
|
+
errors: ActionGateError[];
|
|
281
|
+
};
|
|
282
|
+
/**
|
|
283
|
+
* Parses and gates a form node's raw fields against the D-FORM §5 whitelist
|
|
284
|
+
* and the page meta version/capability rules (F-002 / F-003).
|
|
285
|
+
*
|
|
286
|
+
* Returns only the fields that pass every gate; rejected fields produce
|
|
287
|
+
* deterministic errors instead of being silently rendered (or silently dropped).
|
|
288
|
+
*/
|
|
289
|
+
export declare function gateRenderFormFields(metaValue: unknown, rawFields: unknown, path: string): RenderFormFieldGate;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { SortOrder } from "@/components/data-table";
|
|
2
|
+
export declare const DEFAULT_PAGE_SIZE = 10;
|
|
3
|
+
/**
|
|
4
|
+
* Frozen list-endpoint rule (I-010-001 v0.2.0 · A-001 F-001): `table.props.dataSource`
|
|
5
|
+
* must be a single-slash same-origin absolute path — starts with one `/`, no `//`
|
|
6
|
+
* (no protocol-relative host), no scheme, no whitespace, backslash, `?` or `#`.
|
|
7
|
+
* Query strings are appended by `buildResourceQuery`, never authored in dataSource;
|
|
8
|
+
* fragments are never allowed. Validated before any (auth) fetch is attempted.
|
|
9
|
+
* Mirrors `DataRef.url`'s `^/(?!/)[^\s\\]*$` but additionally rejects `?`/`#`.
|
|
10
|
+
*/
|
|
11
|
+
export declare const DATASOURCE_URL_PATTERN: RegExp;
|
|
12
|
+
/** A schema-driven resource row: any plain JSON object (no field whitelist). */
|
|
13
|
+
export interface ResourceItem {
|
|
14
|
+
[key: string]: unknown;
|
|
15
|
+
}
|
|
16
|
+
/** Unified list envelope frozen across resources: `{items,total,page,pageSize}`. */
|
|
17
|
+
export interface ResourceList {
|
|
18
|
+
items: ResourceItem[];
|
|
19
|
+
total: number;
|
|
20
|
+
page: number;
|
|
21
|
+
pageSize: number;
|
|
22
|
+
}
|
|
23
|
+
/** A generic resource list query. */
|
|
24
|
+
export interface ResourceQuery {
|
|
25
|
+
q?: string;
|
|
26
|
+
sort?: string;
|
|
27
|
+
order?: SortOrder;
|
|
28
|
+
page?: number;
|
|
29
|
+
pageSize?: number;
|
|
30
|
+
/** Schema-driven table filters (table node props.filters): field to value. */
|
|
31
|
+
filters?: Record<string, string>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Shared query for display components (statCard/chart) that read the first
|
|
35
|
+
* item + envelope of a list endpoint. The pageSize keeps the historical RPC
|
|
36
|
+
* shape (one generous bucket); wallet-ensure uses the exact same query so its
|
|
37
|
+
* existence probe coalesces with the statCards' request (per-page fetch cache).
|
|
38
|
+
*/
|
|
39
|
+
export declare const DISPLAY_LIST_QUERY: ResourceQuery;
|
|
40
|
+
/** One field-level validation failure (GOAL-014 D-002 §2.1). */
|
|
41
|
+
export interface FieldError {
|
|
42
|
+
field: string;
|
|
43
|
+
reason: string;
|
|
44
|
+
}
|
|
45
|
+
/** A resource API failure carrying the frozen envelope `{error, message}`. */
|
|
46
|
+
export declare class ResourceApiError extends Error {
|
|
47
|
+
readonly code: string;
|
|
48
|
+
readonly status: number;
|
|
49
|
+
/** VP-007 S4: stable catalog key when the server cataloged this error. */
|
|
50
|
+
readonly messageKey?: string;
|
|
51
|
+
/** VP-007 S4: interpolation params for messageKey. */
|
|
52
|
+
readonly params?: Record<string, unknown>;
|
|
53
|
+
/** GOAL-014: field-level validation failures, when the server attached them. */
|
|
54
|
+
readonly fieldErrors: FieldError[];
|
|
55
|
+
/** R1 correlation identifier for support/operator lookup. */
|
|
56
|
+
readonly correlationId?: string;
|
|
57
|
+
constructor(status: number, code: string, message: string, messageKey?: string, params?: Record<string, unknown>, fieldErrors?: FieldError[], correlationId?: string);
|
|
58
|
+
}
|
|
59
|
+
/** W19: missing self-wallet is an empty surface, not a hard page error. */
|
|
60
|
+
export declare function isWalletNotFoundError(err: unknown): boolean;
|
|
61
|
+
export declare const EMPTY_RESOURCE_LIST: ResourceList;
|
|
62
|
+
export type ResourceCreateBody = ResourceItem;
|
|
63
|
+
export type ResourcePatch = Partial<ResourceItem>;
|
|
64
|
+
/** F-001: single executable rule for a table list endpoint (fail-closed on any violation). */
|
|
65
|
+
export declare function isValidDataSource(url: string): boolean;
|
|
66
|
+
/** Reads the frozen error envelope from a non-OK response. */
|
|
67
|
+
export declare function readResourceApiError(response: Response, label: string): Promise<ResourceApiError>;
|
|
68
|
+
/** Serializes a ResourceQuery into a URL query string (query-serialization). */
|
|
69
|
+
/**
|
|
70
|
+
* v2.9 dataSource params resolution (ADR-0039, capability data.route-binding).
|
|
71
|
+
* Literal scalars pass through; whole `$context.route.query.*` /
|
|
72
|
+
* `$context.route.params.*` bindings are resolved from the route snapshot;
|
|
73
|
+
* a missing key (or absent route) is a tombstone — the parameter is dropped
|
|
74
|
+
* (ADR-0010 semantics, same as null values). Returns the merged query string
|
|
75
|
+
* (empty when nothing resolves).
|
|
76
|
+
*/
|
|
77
|
+
export declare function resolveDataParamsQuery(params: Record<string, unknown> | undefined, route: {
|
|
78
|
+
query?: Record<string, string>;
|
|
79
|
+
params?: Record<string, string>;
|
|
80
|
+
}): string;
|
|
81
|
+
export declare function buildResourceQuery(query: ResourceQuery): string;
|
|
82
|
+
/**
|
|
83
|
+
* Maps an unknown list payload to the unified ResourceList envelope, fail-closed
|
|
84
|
+
* on shape drift. Items are arbitrary JSON objects (F-002 rowKey is enforced at
|
|
85
|
+
* the table surface, not here).
|
|
86
|
+
*/
|
|
87
|
+
export declare function parseResourceList(value: unknown): ResourceList;
|
|
88
|
+
/**
|
|
89
|
+
* Builds the query-string portion of a resource list request — the exact
|
|
90
|
+
* construction `fetchResourceList` sends on the wire. `extraQuery` carries
|
|
91
|
+
* v2.9 ADR-0039 dataSource params that already resolved to literal key=value
|
|
92
|
+
* pairs and is merged over the standard q/sort/order/page/pageSize query.
|
|
93
|
+
*/
|
|
94
|
+
export declare function resourceListQueryString(query: ResourceQuery, extraQuery?: string): string;
|
|
95
|
+
/**
|
|
96
|
+
* The final URL for a resource list request (F-001-validated baseURL + query
|
|
97
|
+
* string). Shared by `fetchResourceList` and the renderer's per-page fetch
|
|
98
|
+
* cache so the cache key and the wire request can never drift apart.
|
|
99
|
+
*/
|
|
100
|
+
export declare function resourceListURL(baseURL: string, query: ResourceQuery, extraQuery?: string): string;
|
|
101
|
+
/**
|
|
102
|
+
* Fetches a page of a schema-driven resource list (request-construction).
|
|
103
|
+
*
|
|
104
|
+
* `extraQuery` carries v2.9 ADR-0039 dataSource params that already resolved
|
|
105
|
+
* to literal key=value pairs (e.g. `dictKey=order_status` from a
|
|
106
|
+
* `$context.route.query.*` binding). It is merged with the standard
|
|
107
|
+
* q/sort/order/page/pageSize query; baseURL itself must stay a bare
|
|
108
|
+
* single-slash same-origin path (F-001).
|
|
109
|
+
*/
|
|
110
|
+
export declare function fetchResourceList(fetcher: typeof fetch, baseURL: string, query: ResourceQuery, extraQuery?: string): Promise<ResourceList>;
|
|
111
|
+
/** Creates a resource row via POST; returns the 201 row. */
|
|
112
|
+
export declare function createResource(fetcher: typeof fetch, baseURL: string, body: ResourceCreateBody): Promise<ResourceItem>;
|
|
113
|
+
export declare function updateResource(fetcher: typeof fetch, baseURL: string, id: string, patch: ResourcePatch): Promise<ResourceItem>;
|
|
114
|
+
export declare function deleteResource(fetcher: typeof fetch, baseURL: string, id: string): Promise<void>;
|