@declarion/react 0.1.64 → 0.1.67
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -0
- package/dist-lib/api/agents.d.ts +22 -3
- package/dist-lib/api/auth-redirect.d.ts +3 -0
- package/dist-lib/api/auth.d.ts +14 -0
- package/dist-lib/api/client.d.ts +13 -0
- package/dist-lib/api/events.d.ts +17 -0
- package/dist-lib/components/agents/AgentChatPanel.d.ts +2 -1
- package/dist-lib/components/fields/RichTextField.d.ts +2 -0
- package/dist-lib/components/fields/index.d.ts +1 -0
- package/dist-lib/components/file-widgets/CircleImage.d.ts +2 -0
- package/dist-lib/components/file-widgets/DocumentCard.d.ts +2 -0
- package/dist-lib/components/file-widgets/FileList.d.ts +2 -0
- package/dist-lib/components/file-widgets/Grid.d.ts +2 -0
- package/dist-lib/components/file-widgets/ImageWidget.d.ts +12 -0
- package/dist-lib/components/file-widgets/LabeledSlots.d.ts +2 -0
- package/dist-lib/components/file-widgets/SquareImage.d.ts +2 -0
- package/dist-lib/components/file-widgets/WideBanner.d.ts +2 -0
- package/dist-lib/components/file-widgets/builtins.d.ts +1 -0
- package/dist-lib/components/file-widgets/cropper.d.ts +14 -0
- package/dist-lib/components/file-widgets/draft-scope.d.ts +22 -0
- package/dist-lib/components/file-widgets/helpers.d.ts +12 -0
- package/dist-lib/components/file-widgets/host.d.ts +19 -0
- package/dist-lib/components/file-widgets/index.d.ts +16 -0
- package/dist-lib/components/file-widgets/payload.d.ts +21 -0
- package/dist-lib/components/file-widgets/registry.d.ts +5 -0
- package/dist-lib/components/file-widgets/types.d.ts +15 -0
- package/dist-lib/components/file-widgets/upload.d.ts +25 -0
- package/dist-lib/components/file-widgets/value.d.ts +4 -0
- package/dist-lib/components/layout/ImpersonationBanner.d.ts +11 -0
- package/dist-lib/components/layout/ImpersonationFrame.d.ts +11 -0
- package/dist-lib/components/layout/ImpersonationStartModal.d.ts +22 -0
- package/dist-lib/components/layout/TenantChip.d.ts +9 -1
- package/dist-lib/components/list/QuickPeek.d.ts +13 -6
- package/dist-lib/components/pages/SmartDetailPage.d.ts +1 -1
- package/dist-lib/components/pages/SmartListPage.d.ts +1 -1
- package/dist-lib/components/primitives/Fields.d.ts +3 -3
- package/dist-lib/components/primitives/MenuItem.d.ts +2 -1
- package/dist-lib/components/shell/CommandPalette.d.ts +2 -1
- package/dist-lib/declarion-react.css +1 -1
- package/dist-lib/hooks/useAgentConversation.d.ts +11 -2
- package/dist-lib/hooks/useImpersonation.d.ts +23 -0
- package/dist-lib/hooks/useVersionWatcher.d.ts +30 -0
- package/dist-lib/index.d.ts +6 -0
- package/dist-lib/index.js +7043 -3406
- package/dist-lib/index.js.map +1 -1
- package/dist-lib/lib/rich-text.d.ts +14 -0
- package/dist-lib/lib/versionTracker.d.ts +66 -0
- package/dist-lib/types/api.d.ts +20 -0
- package/dist-lib/types/files.d.ts +76 -0
- package/dist-lib/types/schema.d.ts +11 -0
- package/dist-lib/vite/fingerprint-plugin.d.ts +45 -0
- package/dist-lib/vite/index.d.ts +21 -0
- package/dist-lib/vite/index.js +52 -0
- package/dist-lib/vite/index.js.map +1 -0
- package/package.json +13 -3
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { HydratedFile } from "../types/files";
|
|
2
|
+
export interface RichTextValue {
|
|
3
|
+
markdown: string;
|
|
4
|
+
files: Record<string, HydratedFile>;
|
|
5
|
+
}
|
|
6
|
+
export declare function richTextMarkdown(value: unknown): string;
|
|
7
|
+
export declare function richTextFiles(value: unknown): Record<string, HydratedFile>;
|
|
8
|
+
export declare function createRichTextValue(markdownValue: unknown, inlineFilesValue: unknown): RichTextValue;
|
|
9
|
+
export declare function withRichTextFile(value: unknown, file: HydratedFile): RichTextValue;
|
|
10
|
+
export declare function withoutRichTextFiles(value: unknown, removedIds: Iterable<string>): RichTextValue;
|
|
11
|
+
export declare function inlineFilesById(value: unknown): Record<string, HydratedFile>;
|
|
12
|
+
export declare function extractDeclarionFileIds(markdown: string): string[];
|
|
13
|
+
export declare function extractDeclarionFileId(url: string | undefined): string | null;
|
|
14
|
+
export declare function richTextTokenForFile(file: Pick<HydratedFile, "id" | "content_type" | "filename">): string;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* versionTracker — central staleness-detection state for the SPA.
|
|
3
|
+
*
|
|
4
|
+
* Every API response carries `X-Declarion-Version: <combined-fingerprint>`
|
|
5
|
+
* (set by the server's version_header middleware). The FIRST arrival
|
|
6
|
+
* in a bundle's lifetime is captured as the baseline; any later
|
|
7
|
+
* response with a different value triggers drift detection — the SPA
|
|
8
|
+
* either silently invalidates the schema query (if only schema differs)
|
|
9
|
+
* or prompts the user to reload (if assets/binary changed).
|
|
10
|
+
*
|
|
11
|
+
* **State is in-memory only — no sessionStorage / localStorage.** Each
|
|
12
|
+
* bundle load captures its own baseline from its first response. The
|
|
13
|
+
* bundle IS its own version contract; the live server's first answer
|
|
14
|
+
* defines what version the bundle should consider itself. Persisting
|
|
15
|
+
* across reloads is actively harmful: a stale baseline from a previous
|
|
16
|
+
* bundle could override the live answer, producing a "click Reload →
|
|
17
|
+
* page reloads → toast pops again" infinite loop. This is the same
|
|
18
|
+
* lesson behind why `index.html` is `Cache-Control: no-cache` — the
|
|
19
|
+
* bootstrap manifest must always be live, never replayed.
|
|
20
|
+
*
|
|
21
|
+
* Defense in depth runs three sources into the same comparator:
|
|
22
|
+
* 1. observe() — header read on every API response (apiFetch hook).
|
|
23
|
+
* 2. fetchProbe() — explicit GET /api/version (focus / online / 60s).
|
|
24
|
+
* 3. (optional) SSE `version-changed` event from useSSE.
|
|
25
|
+
*
|
|
26
|
+
* Cross-tab coordination via BroadcastChannel('declarion-version') —
|
|
27
|
+
* runtime IPC only, not persistence. When one tab detects drift it
|
|
28
|
+
* fans the decision out so all tabs reload together (avoids the
|
|
29
|
+
* half-old / half-new tab anti-pattern). Each tab still bootstraps
|
|
30
|
+
* its own baseline independently.
|
|
31
|
+
*/
|
|
32
|
+
export interface VersionDelta {
|
|
33
|
+
/** Reason the delta was emitted. Lets subscribers decide UX. */
|
|
34
|
+
kind: "schema-only" | "asset-or-binary" | "unknown";
|
|
35
|
+
/** Latest combined fingerprint observed. Drives the next baseline. */
|
|
36
|
+
observed: string;
|
|
37
|
+
/** Per-component breakdown when /api/version was reached. */
|
|
38
|
+
components?: VersionComponents;
|
|
39
|
+
}
|
|
40
|
+
export interface VersionComponents {
|
|
41
|
+
schema: string;
|
|
42
|
+
asset: string;
|
|
43
|
+
binary: string;
|
|
44
|
+
}
|
|
45
|
+
export type VersionDeltaListener = (delta: VersionDelta) => void;
|
|
46
|
+
/**
|
|
47
|
+
* Observe an API response. Captures the first-seen fingerprint and
|
|
48
|
+
* compares subsequent ones. Idempotent — safe to call from every
|
|
49
|
+
* apiFetch round-trip; backward-compatible — no-op when the header
|
|
50
|
+
* is absent (older server).
|
|
51
|
+
*/
|
|
52
|
+
export declare function observe(headers: Headers | undefined | null): void;
|
|
53
|
+
/**
|
|
54
|
+
* Hit /api/version with If-None-Match (cheap when nothing changed —
|
|
55
|
+
* 304 with no body). On any change, emit a typed delta.
|
|
56
|
+
*/
|
|
57
|
+
export declare function fetchProbe(): Promise<void>;
|
|
58
|
+
/**
|
|
59
|
+
* Subscribe to drift events. Called once from useVersionWatcher; the
|
|
60
|
+
* subscriber renders the UpdatePrompt (toast or silent invalidation).
|
|
61
|
+
*/
|
|
62
|
+
export declare function subscribe(cb: VersionDeltaListener): () => void;
|
|
63
|
+
/**
|
|
64
|
+
* Test-only: reset internal state. Not exposed via package exports.
|
|
65
|
+
*/
|
|
66
|
+
export declare function __resetForTests(): void;
|
package/dist-lib/types/api.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AccentDef, LocalizedString } from "./schema";
|
|
2
|
+
import type { HydratedFile } from "./files";
|
|
2
3
|
export interface ListMeta {
|
|
3
4
|
total: number;
|
|
4
5
|
page: number;
|
|
@@ -47,9 +48,26 @@ export interface AuthUser {
|
|
|
47
48
|
roles: string[];
|
|
48
49
|
permissions?: string[];
|
|
49
50
|
tenant?: Tenant;
|
|
51
|
+
/** Populated only when the request is acting under an impersonation session. */
|
|
52
|
+
impersonation?: ImpersonationInfo;
|
|
50
53
|
created_at: string;
|
|
51
54
|
updated_at: string;
|
|
52
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* ImpersonationInfo is the read-side projection of the active session
|
|
58
|
+
* surfaced by auth.me. Drives the banner, topbar chip, viewport frame,
|
|
59
|
+
* and the "Exit impersonation" menu item.
|
|
60
|
+
*/
|
|
61
|
+
export interface ImpersonationInfo {
|
|
62
|
+
session_id: string;
|
|
63
|
+
real_user_id: string;
|
|
64
|
+
real_user_email?: string;
|
|
65
|
+
target_user_id: string;
|
|
66
|
+
target_user_email?: string;
|
|
67
|
+
reason: string;
|
|
68
|
+
started_at: string;
|
|
69
|
+
expires_at: string;
|
|
70
|
+
}
|
|
53
71
|
export interface LoginResponse {
|
|
54
72
|
expires_at: string;
|
|
55
73
|
user: AuthUser;
|
|
@@ -87,6 +105,7 @@ export interface PublicBranding {
|
|
|
87
105
|
app_logo?: string;
|
|
88
106
|
app_initial?: string;
|
|
89
107
|
accent_color?: string;
|
|
108
|
+
ai_name?: string;
|
|
90
109
|
wordmark?: string;
|
|
91
110
|
version_label?: string;
|
|
92
111
|
auth_tagline?: LocalizedString;
|
|
@@ -199,6 +218,7 @@ export type Units = Record<string, string>;
|
|
|
199
218
|
/** A row enriched with $-prefixed sub-resources (as returned by Get or write responses). */
|
|
200
219
|
export interface EnrichedRow extends Record<string, unknown> {
|
|
201
220
|
$refs?: Refs;
|
|
221
|
+
$files?: Record<string, HydratedFile | HydratedFile[] | null>;
|
|
202
222
|
$statuses?: V2StatusInstance[];
|
|
203
223
|
$properties?: V2PropertyInstance[];
|
|
204
224
|
$params?: V2ParamInstance[];
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export type FileFieldMode = "single" | "array" | "named_map" | "rich_text_inline";
|
|
2
|
+
export interface FileFieldDisplay {
|
|
3
|
+
widget: string;
|
|
4
|
+
placeholder?: string;
|
|
5
|
+
crop_on_upload?: boolean;
|
|
6
|
+
aspect_ratio?: string;
|
|
7
|
+
focal_point?: boolean;
|
|
8
|
+
is_listable?: boolean;
|
|
9
|
+
listable_thumbnail?: string;
|
|
10
|
+
reorderable?: boolean;
|
|
11
|
+
bulk_upload?: boolean;
|
|
12
|
+
bulk_download?: boolean;
|
|
13
|
+
lazy_load?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface FileNamedSlotDef {
|
|
16
|
+
accepts?: string[];
|
|
17
|
+
max_size_bytes?: number;
|
|
18
|
+
svg_sanitize?: boolean;
|
|
19
|
+
pdf_strip_js?: boolean;
|
|
20
|
+
exif_strip?: boolean;
|
|
21
|
+
aspect_ratio?: string;
|
|
22
|
+
storage_backend?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface FileFieldDef {
|
|
25
|
+
name: string;
|
|
26
|
+
mode: FileFieldMode;
|
|
27
|
+
accepts?: string[];
|
|
28
|
+
max_size_bytes?: number;
|
|
29
|
+
max_count?: number;
|
|
30
|
+
max_total_size_bytes?: number;
|
|
31
|
+
required?: boolean;
|
|
32
|
+
slots?: Record<string, FileNamedSlotDef>;
|
|
33
|
+
display?: FileFieldDisplay;
|
|
34
|
+
}
|
|
35
|
+
export interface HydratedFile {
|
|
36
|
+
id: string;
|
|
37
|
+
filename: string;
|
|
38
|
+
content_type: string;
|
|
39
|
+
size_bytes: number;
|
|
40
|
+
sha256?: string;
|
|
41
|
+
width?: number;
|
|
42
|
+
height?: number;
|
|
43
|
+
page_count?: number;
|
|
44
|
+
uploaded_at?: string;
|
|
45
|
+
uploaded_by?: string;
|
|
46
|
+
url?: string;
|
|
47
|
+
presigned_url?: string;
|
|
48
|
+
derivations?: Record<string, string>;
|
|
49
|
+
metadata?: Record<string, unknown>;
|
|
50
|
+
position?: number;
|
|
51
|
+
slot?: string;
|
|
52
|
+
version?: {
|
|
53
|
+
number?: number;
|
|
54
|
+
is_current?: boolean;
|
|
55
|
+
supersedes_file_id?: string;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export type NamedFileFieldValue = Record<string, HydratedFile | null>;
|
|
59
|
+
export type FileFieldValue = HydratedFile | HydratedFile[] | NamedFileFieldValue | null;
|
|
60
|
+
export interface UploadProgress {
|
|
61
|
+
loaded: number;
|
|
62
|
+
total: number;
|
|
63
|
+
pct: number;
|
|
64
|
+
}
|
|
65
|
+
export interface UploadedFile {
|
|
66
|
+
id: string;
|
|
67
|
+
filename: string;
|
|
68
|
+
content_type: string;
|
|
69
|
+
size_bytes: number;
|
|
70
|
+
sha256: string;
|
|
71
|
+
dedup?: boolean;
|
|
72
|
+
width?: number;
|
|
73
|
+
height?: number;
|
|
74
|
+
url?: string;
|
|
75
|
+
metadata?: Record<string, unknown>;
|
|
76
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { OrderedMap } from "./ordmap";
|
|
2
|
+
import type { FileFieldDef } from "./files";
|
|
2
3
|
export { OrderedMap };
|
|
3
4
|
export type LocalizedString = Record<string, string>;
|
|
4
5
|
/**
|
|
@@ -8,6 +9,12 @@ export type LocalizedString = Record<string, string>;
|
|
|
8
9
|
*/
|
|
9
10
|
export declare const DETAIL_SCREEN_NONE = "none";
|
|
10
11
|
export declare function loc(ls: LocalizedString | undefined, lang?: string): string;
|
|
12
|
+
export declare const DISPLAY_KEY = "$display";
|
|
13
|
+
export declare function getDisplayValue(row: Record<string, unknown> | null | undefined, entity: {
|
|
14
|
+
display?: {
|
|
15
|
+
display_field?: string;
|
|
16
|
+
};
|
|
17
|
+
} | null | undefined): string | undefined;
|
|
11
18
|
export declare function fieldDisplayName(field: EntityField, fieldName: string, entities?: Record<string, Entity>): string;
|
|
12
19
|
export interface RefDisplay {
|
|
13
20
|
field: string;
|
|
@@ -53,6 +60,7 @@ export interface EntityField {
|
|
|
53
60
|
color?: string;
|
|
54
61
|
}>;
|
|
55
62
|
enum?: string;
|
|
63
|
+
files?: FileFieldDef;
|
|
56
64
|
ref?: RefConfig;
|
|
57
65
|
structure?: string;
|
|
58
66
|
array?: boolean;
|
|
@@ -183,6 +191,7 @@ export interface Entity {
|
|
|
183
191
|
description?: LocalizedString;
|
|
184
192
|
icon: string;
|
|
185
193
|
display_field?: string;
|
|
194
|
+
display_template?: string;
|
|
186
195
|
};
|
|
187
196
|
fields: OrderedMap<EntityField>;
|
|
188
197
|
soft_delete?: string;
|
|
@@ -194,6 +203,7 @@ export interface Entity {
|
|
|
194
203
|
actions?: Record<string, Action>;
|
|
195
204
|
handlers?: Record<string, HandlerDef>;
|
|
196
205
|
associations?: Record<string, AssociationDef>;
|
|
206
|
+
files?: OrderedMap<FileFieldDef>;
|
|
197
207
|
detail_screen?: string;
|
|
198
208
|
}
|
|
199
209
|
export declare function getPrimaryKeyFields(entity: Entity): string[];
|
|
@@ -469,6 +479,7 @@ export interface Branding {
|
|
|
469
479
|
app_logo?: string;
|
|
470
480
|
app_initial?: string;
|
|
471
481
|
accent_color?: string;
|
|
482
|
+
ai_name?: string;
|
|
472
483
|
wordmark?: string;
|
|
473
484
|
version_label?: string;
|
|
474
485
|
status_url?: string;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Plugin } from "vite";
|
|
2
|
+
export interface DeclarionFingerprintPluginOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Subdirectory of `outDir` to walk. Defaults to "assets" — Vite's
|
|
5
|
+
* standard hashed-output location. Override only if a consumer has
|
|
6
|
+
* customized `build.assetsDir`.
|
|
7
|
+
*/
|
|
8
|
+
assetsDir?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Filename of the emitted fingerprint, written next to `index.html`.
|
|
11
|
+
* Defaults to "bundle-fingerprint.txt". Match this on the server
|
|
12
|
+
* side (`apps/server/fingerprint.go:readBundleFingerprint`) if you
|
|
13
|
+
* customize.
|
|
14
|
+
*/
|
|
15
|
+
filename?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Vite plugin that emits a single-line `bundle-fingerprint.txt` next
|
|
19
|
+
* to `index.html` after every production build. The fingerprint is a
|
|
20
|
+
* SHA-256 over every file in `assetsDir/` — sorted by name, length-
|
|
21
|
+
* prefixed (`name\x00sha256\x00`) so concatenation is collision-safe.
|
|
22
|
+
*
|
|
23
|
+
* The Declarion server reads this file at boot and folds it into the
|
|
24
|
+
* combined build identity exposed via `X-Declarion-Version` and
|
|
25
|
+
* `/api/version`. When a consumer redeploys the UI bundle (changed
|
|
26
|
+
* JS/CSS/assets) the file changes → server fingerprint changes → SDK
|
|
27
|
+
* detects asset drift → user is prompted to reload to pick up the
|
|
28
|
+
* new code.
|
|
29
|
+
*
|
|
30
|
+
* Why a separate file rather than walking `assetsDir/` from Go at
|
|
31
|
+
* boot:
|
|
32
|
+
* - Eliminates a per-process I/O dependency on bundle layout
|
|
33
|
+
* conventions (Vite could change `assetsDir` defaults; the
|
|
34
|
+
* plugin is the canonical contract).
|
|
35
|
+
* - Computed once at build time, read once at boot — never on the
|
|
36
|
+
* hot path.
|
|
37
|
+
* - Lets the consumer image ship the fingerprint as an immutable
|
|
38
|
+
* artifact rather than re-deriving it at every server start.
|
|
39
|
+
*
|
|
40
|
+
* Backward compatible: if a consumer hasn't adopted the plugin yet,
|
|
41
|
+
* `bundle-fingerprint.txt` is absent at boot, the server logs once
|
|
42
|
+
* and degrades to binary-only drift detection (still correct — the
|
|
43
|
+
* binary fingerprint differs across builds anyway). No errors.
|
|
44
|
+
*/
|
|
45
|
+
export declare function declarionFingerprintPlugin(options?: DeclarionFingerprintPluginOptions): Plugin;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @declarion/react/vite — Vite plugin entry point.
|
|
3
|
+
*
|
|
4
|
+
* Separate from the SDK's main React entry (`@declarion/react`) so this
|
|
5
|
+
* Node-only code never accidentally pulls into a browser bundle.
|
|
6
|
+
*
|
|
7
|
+
* Usage in a consumer's `vite.config.ts`:
|
|
8
|
+
*
|
|
9
|
+
* import { declarionFingerprintPlugin } from "@declarion/react/vite";
|
|
10
|
+
*
|
|
11
|
+
* export default defineConfig({
|
|
12
|
+
* plugins: [react(), declarionFingerprintPlugin()],
|
|
13
|
+
* });
|
|
14
|
+
*
|
|
15
|
+
* One line, one plugin — works in every Declarion deployment mode
|
|
16
|
+
* (local-up, prebuilt-declarion, fully-prebuilt prod) because the
|
|
17
|
+
* plugin runs at consumer-build time regardless of where the SDK
|
|
18
|
+
* itself comes from (npm pin / vite alias / link).
|
|
19
|
+
*/
|
|
20
|
+
export { declarionFingerprintPlugin } from "./fingerprint-plugin";
|
|
21
|
+
export type { DeclarionFingerprintPluginOptions } from "./fingerprint-plugin";
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { createHash as e } from "node:crypto";
|
|
2
|
+
import { readFileSync as t, readdirSync as n, statSync as r, writeFileSync as i } from "node:fs";
|
|
3
|
+
import { join as a } from "node:path";
|
|
4
|
+
//#region src/vite/fingerprint-plugin.ts
|
|
5
|
+
function o(n = {}) {
|
|
6
|
+
let r = n.filename ?? "bundle-fingerprint.txt";
|
|
7
|
+
return {
|
|
8
|
+
name: "declarion-fingerprint",
|
|
9
|
+
apply: "build",
|
|
10
|
+
closeBundle: {
|
|
11
|
+
sequential: !0,
|
|
12
|
+
handler() {
|
|
13
|
+
let o = this.environment.config, c = o.build.outDir, l = a(c, n.assetsDir ?? o.build.assetsDir ?? "assets"), u = s(l);
|
|
14
|
+
u.sort();
|
|
15
|
+
let d = e("sha256");
|
|
16
|
+
for (let n of u) {
|
|
17
|
+
let r = a(l, n), i = e("sha256").update(t(r)).digest("hex");
|
|
18
|
+
d.update(`${n.length}:${n}\n`), d.update(`${i.length}:${i}\n`);
|
|
19
|
+
}
|
|
20
|
+
let f = d.digest("hex");
|
|
21
|
+
i(a(c, r), f + "\n", "utf8");
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function s(e) {
|
|
27
|
+
let t = [], i = [{
|
|
28
|
+
abs: e,
|
|
29
|
+
rel: ""
|
|
30
|
+
}];
|
|
31
|
+
for (; i.length > 0;) {
|
|
32
|
+
let { abs: e, rel: o } = i.pop(), s;
|
|
33
|
+
try {
|
|
34
|
+
s = n(e);
|
|
35
|
+
} catch (e) {
|
|
36
|
+
if (e && typeof e == "object" && "code" in e && e.code === "ENOENT") return [];
|
|
37
|
+
throw e;
|
|
38
|
+
}
|
|
39
|
+
for (let n of s) {
|
|
40
|
+
let s = a(e, n), c = o ? `${o}/${n}` : n;
|
|
41
|
+
r(s).isDirectory() ? i.push({
|
|
42
|
+
abs: s,
|
|
43
|
+
rel: c
|
|
44
|
+
}) : t.push(c);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return t;
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
export { o as declarionFingerprintPlugin };
|
|
51
|
+
|
|
52
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/vite/fingerprint-plugin.ts"],"sourcesContent":["import type { Plugin } from \"vite\";\nimport { createHash } from \"node:crypto\";\nimport {\n readdirSync,\n readFileSync,\n statSync,\n writeFileSync,\n} from \"node:fs\";\nimport { join } from \"node:path\";\n\nexport interface DeclarionFingerprintPluginOptions {\n /**\n * Subdirectory of `outDir` to walk. Defaults to \"assets\" — Vite's\n * standard hashed-output location. Override only if a consumer has\n * customized `build.assetsDir`.\n */\n assetsDir?: string;\n\n /**\n * Filename of the emitted fingerprint, written next to `index.html`.\n * Defaults to \"bundle-fingerprint.txt\". Match this on the server\n * side (`apps/server/fingerprint.go:readBundleFingerprint`) if you\n * customize.\n */\n filename?: string;\n}\n\n/**\n * Vite plugin that emits a single-line `bundle-fingerprint.txt` next\n * to `index.html` after every production build. The fingerprint is a\n * SHA-256 over every file in `assetsDir/` — sorted by name, length-\n * prefixed (`name\\x00sha256\\x00`) so concatenation is collision-safe.\n *\n * The Declarion server reads this file at boot and folds it into the\n * combined build identity exposed via `X-Declarion-Version` and\n * `/api/version`. When a consumer redeploys the UI bundle (changed\n * JS/CSS/assets) the file changes → server fingerprint changes → SDK\n * detects asset drift → user is prompted to reload to pick up the\n * new code.\n *\n * Why a separate file rather than walking `assetsDir/` from Go at\n * boot:\n * - Eliminates a per-process I/O dependency on bundle layout\n * conventions (Vite could change `assetsDir` defaults; the\n * plugin is the canonical contract).\n * - Computed once at build time, read once at boot — never on the\n * hot path.\n * - Lets the consumer image ship the fingerprint as an immutable\n * artifact rather than re-deriving it at every server start.\n *\n * Backward compatible: if a consumer hasn't adopted the plugin yet,\n * `bundle-fingerprint.txt` is absent at boot, the server logs once\n * and degrades to binary-only drift detection (still correct — the\n * binary fingerprint differs across builds anyway). No errors.\n */\nexport function declarionFingerprintPlugin(\n options: DeclarionFingerprintPluginOptions = {},\n): Plugin {\n const filename = options.filename ?? \"bundle-fingerprint.txt\";\n\n return {\n name: \"declarion-fingerprint\",\n apply: \"build\",\n closeBundle: {\n // `sequential: true` is the safe default — run after rollup has\n // finished writing the bundle, before any other closeBundle\n // hooks observe the output dir.\n sequential: true,\n handler() {\n // Read outDir + assetsDir from the resolved Vite config. We\n // don't capture them in a `configResolved` hook because\n // `closeBundle` already runs after config resolution.\n const cfg = this.environment.config;\n const outDir = cfg.build.outDir;\n const assetsDir = options.assetsDir ?? cfg.build.assetsDir ?? \"assets\";\n const assetsRoot = join(outDir, assetsDir);\n\n const files = collectFiles(assetsRoot);\n files.sort(); // deterministic order\n\n const h = createHash(\"sha256\");\n for (const relPath of files) {\n const abs = join(assetsRoot, relPath);\n const fileHash = createHash(\"sha256\")\n .update(readFileSync(abs))\n .digest(\"hex\");\n // Length-prefix discipline — same as the Go side\n // (store/migrations.go:215-282 + apps/server/fingerprint.go).\n // `a`+`bc` and `ab`+`c` must produce different hashes.\n h.update(`${relPath.length}:${relPath}\\n`);\n h.update(`${fileHash.length}:${fileHash}\\n`);\n }\n const combined = h.digest(\"hex\");\n\n writeFileSync(join(outDir, filename), combined + \"\\n\", \"utf8\");\n },\n },\n };\n}\n\n/**\n * Walks `root` recursively and returns POSIX-style relative paths of\n * every regular file. Sorted by the caller. Symlinks are followed\n * since Vite output dirs don't contain them in practice.\n */\nfunction collectFiles(root: string): string[] {\n const out: string[] = [];\n const stack: { abs: string; rel: string }[] = [{ abs: root, rel: \"\" }];\n while (stack.length > 0) {\n const { abs, rel } = stack.pop()!;\n let entries: string[];\n try {\n entries = readdirSync(abs);\n } catch (err) {\n // Missing assetsDir: nothing to fingerprint. Return empty;\n // the plugin still emits the file so the server boot path\n // doesn't have to special-case missing-yet-defined assets.\n if (\n err &&\n typeof err === \"object\" &&\n \"code\" in err &&\n (err as { code: string }).code === \"ENOENT\"\n ) {\n return [];\n }\n throw err;\n }\n for (const name of entries) {\n const childAbs = join(abs, name);\n const childRel = rel ? `${rel}/${name}` : name;\n if (statSync(childAbs).isDirectory()) {\n stack.push({ abs: childAbs, rel: childRel });\n } else {\n out.push(childRel);\n }\n }\n }\n return out;\n}\n"],"mappings":";;;;AAuDA,SAAgB,EACd,IAA6C,EAAE,EACvC;CACR,IAAM,IAAW,EAAQ,YAAY;AAErC,QAAO;EACL,MAAM;EACN,OAAO;EACP,aAAa;GAIX,YAAY;GACZ,UAAU;IAIR,IAAM,IAAM,KAAK,YAAY,QACvB,IAAS,EAAI,MAAM,QAEnB,IAAa,EAAK,GADN,EAAQ,aAAa,EAAI,MAAM,aAAa,SACpB,EAEpC,IAAQ,EAAa,EAAW;AACtC,MAAM,MAAM;IAEZ,IAAM,IAAI,EAAW,SAAS;AAC9B,SAAK,IAAM,KAAW,GAAO;KAC3B,IAAM,IAAM,EAAK,GAAY,EAAQ,EAC/B,IAAW,EAAW,SAAS,CAClC,OAAO,EAAa,EAAI,CAAC,CACzB,OAAO,MAAM;AAKhB,KADA,EAAE,OAAO,GAAG,EAAQ,OAAO,GAAG,EAAQ,IAAI,EAC1C,EAAE,OAAO,GAAG,EAAS,OAAO,GAAG,EAAS,IAAI;;IAE9C,IAAM,IAAW,EAAE,OAAO,MAAM;AAEhC,MAAc,EAAK,GAAQ,EAAS,EAAE,IAAW,MAAM,OAAO;;GAEjE;EACF;;AAQH,SAAS,EAAa,GAAwB;CAC5C,IAAM,IAAgB,EAAE,EAClB,IAAwC,CAAC;EAAE,KAAK;EAAM,KAAK;EAAI,CAAC;AACtE,QAAO,EAAM,SAAS,IAAG;EACvB,IAAM,EAAE,QAAK,WAAQ,EAAM,KAAK,EAC5B;AACJ,MAAI;AACF,OAAU,EAAY,EAAI;WACnB,GAAK;AAIZ,OACE,KACA,OAAO,KAAQ,YACf,UAAU,KACT,EAAyB,SAAS,SAEnC,QAAO,EAAE;AAEX,SAAM;;AAER,OAAK,IAAM,KAAQ,GAAS;GAC1B,IAAM,IAAW,EAAK,GAAK,EAAK,EAC1B,IAAW,IAAM,GAAG,EAAI,GAAG,MAAS;AAC1C,GAAI,EAAS,EAAS,CAAC,aAAa,GAClC,EAAM,KAAK;IAAE,KAAK;IAAU,KAAK;IAAU,CAAC,GAE5C,EAAI,KAAK,EAAS;;;AAIxB,QAAO"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@declarion/react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.67",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "React SDK for Declarion, the schema-driven business apps platform.",
|
|
@@ -15,6 +15,11 @@
|
|
|
15
15
|
"import": "./dist-lib/index.js",
|
|
16
16
|
"default": "./dist-lib/index.js"
|
|
17
17
|
},
|
|
18
|
+
"./vite": {
|
|
19
|
+
"types": "./dist-lib/vite/index.d.ts",
|
|
20
|
+
"import": "./dist-lib/vite/index.js",
|
|
21
|
+
"default": "./dist-lib/vite/index.js"
|
|
22
|
+
},
|
|
18
23
|
"./styles.css": "./dist-lib/declarion-react.css"
|
|
19
24
|
},
|
|
20
25
|
"files": [
|
|
@@ -48,8 +53,10 @@
|
|
|
48
53
|
"date-fns": "^4.1.0",
|
|
49
54
|
"react-markdown": "^10.1.0",
|
|
50
55
|
"react-router-dom": "^7.14.2",
|
|
56
|
+
"react-textarea-autosize": "^8.5.9",
|
|
51
57
|
"recharts": "^3.8.1",
|
|
52
58
|
"remark-breaks": "^4.0.0",
|
|
59
|
+
"remark-gfm": "^4.0.1",
|
|
53
60
|
"sonner": "^2.0.7",
|
|
54
61
|
"tailwind-merge": "^3.5.0",
|
|
55
62
|
"vanilla-jsoneditor": "^3.11.0",
|
|
@@ -65,6 +72,8 @@
|
|
|
65
72
|
"@types/react": "^19.0.0",
|
|
66
73
|
"@types/react-dom": "^19.0.0",
|
|
67
74
|
"@vitejs/plugin-react": "^6.0.1",
|
|
75
|
+
"eslint": "^10.2.1",
|
|
76
|
+
"eslint-plugin-react-hooks": "^7.1.1",
|
|
68
77
|
"jsdom": "^29.0.1",
|
|
69
78
|
"msw": "^2.13.4",
|
|
70
79
|
"react": "^19.0.0",
|
|
@@ -72,14 +81,15 @@
|
|
|
72
81
|
"tailwindcss": "^4.2.4",
|
|
73
82
|
"tsc-alias": "^1.8.16",
|
|
74
83
|
"typescript": "^6.0.3",
|
|
84
|
+
"typescript-eslint": "^8.59.0",
|
|
75
85
|
"vite": "^8.0.9",
|
|
76
86
|
"vitest": "^4.1.5"
|
|
77
87
|
},
|
|
78
88
|
"scripts": {
|
|
79
89
|
"dev": "vite",
|
|
80
90
|
"build": "tsc --noEmit && vite build",
|
|
81
|
-
"build:lib": "vite build --config vite.lib.config.ts && tsc -p tsconfig.lib.json && tsc-alias -p tsconfig.lib.json",
|
|
82
|
-
"lint": "tsc --noEmit",
|
|
91
|
+
"build:lib": "vite build --config vite.lib.config.ts && vite build --config vite.plugin.config.ts && tsc -p tsconfig.lib.json && tsc-alias -p tsconfig.lib.json",
|
|
92
|
+
"lint": "tsc --noEmit && eslint src",
|
|
83
93
|
"test": "vitest run",
|
|
84
94
|
"preview": "vite preview"
|
|
85
95
|
}
|