rastack 0.0.49 → 0.0.51

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.
Files changed (68) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/components/auto-form/AutoForm.tsx +13 -0
  3. package/components/auto-form/use-auto-form.ts +28 -0
  4. package/components/types.ts +8 -0
  5. package/dist/admin.js +13 -13
  6. package/dist/compile/analyze.d.ts +20 -0
  7. package/dist/compile/analyze.js +60 -49
  8. package/dist/compile/entities.d.ts +19 -0
  9. package/dist/compile/entities.js +87 -13
  10. package/dist/compile/index.d.ts +23 -4
  11. package/dist/compile/index.js +86 -7
  12. package/dist/compile/model.d.ts +38 -0
  13. package/dist/compile/openapi.d.ts +9 -0
  14. package/dist/compile/openapi.js +14 -0
  15. package/dist/define/index.d.ts +162 -21
  16. package/dist/define/index.js +28 -22
  17. package/dist/define/manifest.d.ts +64 -0
  18. package/dist/define/manifest.js +250 -0
  19. package/dist/import/tabular.d.ts +8 -2
  20. package/dist/import/tabular.js +1 -1
  21. package/dist/plugin/core.d.ts +108 -0
  22. package/dist/plugin/core.js +198 -0
  23. package/dist/plugin/index.d.ts +112 -0
  24. package/dist/plugin/index.js +203 -0
  25. package/dist/rastack-import.js +4 -1
  26. package/dist/validate/adapters.js +2 -0
  27. package/dist/validate/index.d.ts +1 -0
  28. package/dist/validate/index.js +1 -0
  29. package/dist/validate/machine.d.ts +23 -2
  30. package/dist/validate/machine.js +35 -2
  31. package/dist/validate/transitions.d.ts +86 -0
  32. package/dist/validate/transitions.js +199 -0
  33. package/dist/wasm/rastack_wasm.js +1 -1
  34. package/dist/wasm/rastack_wasm_bg.wasm +0 -0
  35. package/hooks/data.ts +221 -0
  36. package/hooks/entity.ts +228 -0
  37. package/hooks/form/entity-form.ts +358 -0
  38. package/hooks/form/form.ts +8 -1
  39. package/hooks/form/index.ts +7 -1
  40. package/hooks/index.ts +4 -0
  41. package/hooks/manifest.ts +77 -0
  42. package/hooks/registry.ts +56 -0
  43. package/package.json +1 -1
  44. package/plugin.ts +8 -0
  45. package/provider/provider.tsx +26 -5
  46. package/provider/types.ts +15 -3
  47. package/src/compile/analyze.ts +74 -45
  48. package/src/compile/entities.ts +111 -11
  49. package/src/compile/index.ts +108 -11
  50. package/src/compile/model.ts +40 -0
  51. package/src/compile/openapi.ts +13 -1
  52. package/src/define/index.ts +233 -29
  53. package/src/define/manifest.ts +278 -0
  54. package/src/import/tabular.ts +9 -2
  55. package/src/plugin/core.ts +236 -0
  56. package/src/plugin/index.ts +243 -0
  57. package/src/rastack-import.ts +4 -1
  58. package/src/validate/adapters.ts +1 -0
  59. package/src/validate/index.ts +1 -0
  60. package/src/validate/machine.ts +55 -3
  61. package/src/validate/transitions.ts +232 -0
  62. package/test/components.spec.ts +22 -0
  63. package/test/plugin.spec.ts +315 -0
  64. package/test/runtime-manifest.spec.ts +309 -0
  65. package/test/transitions.spec.ts +372 -0
  66. package/test/typed-hooks.spec.ts +412 -0
  67. package/wasm/rastack_wasm.js +1 -1
  68. package/wasm/rastack_wasm_bg.wasm +0 -0
@@ -9,7 +9,7 @@
9
9
  * Everything here is data-in/data-out (no filesystem), tested in
10
10
  * `tools/test/import.spec.ts`; the parsers and the CLI are the IO shell.
11
11
  */
12
- import { type FieldSpec, type FieldState, type RecordRun, type SpecRelation } from "../validate";
12
+ import { type FieldSpec, type FieldState, type RecordRun, type SpecRelation, type TransitionsModel } from "../validate";
13
13
  /** A parsed tabular source: one header row + string cell rows. */
14
14
  export interface TabularData {
15
15
  headers: string[];
@@ -24,7 +24,7 @@ export interface ColumnMapping {
24
24
  export interface ImportIssue {
25
25
  row: number;
26
26
  field: string;
27
- gate: FieldState;
27
+ gate: FieldState | "transition";
28
28
  constraint: string;
29
29
  message: string;
30
30
  }
@@ -60,6 +60,12 @@ export interface ValidateDatasetOptions {
60
60
  relatedIds?: Record<string, Set<string>>;
61
61
  /** Custom FK resolver (wins over `relatedIds`). */
62
62
  resolveRelation?: (relation: SpecRelation, value: unknown) => boolean;
63
+ /**
64
+ * The resource's state machine (`schema.rastack.json` `transitions`).
65
+ * Imported rows are creates, so the record-level `transition` gate rejects
66
+ * rows born mid-machine (a state other than the initial one).
67
+ */
68
+ transitions?: TransitionsModel;
63
69
  }
64
70
  /**
65
71
  * Run a whole tabular source through a resource's record machine. Each row is
@@ -47,7 +47,7 @@ function mapHeaders(headers, specs) {
47
47
  */
48
48
  function validateDataset(specs, data, options = {}) {
49
49
  const { columns, missingFields } = mapHeaders(data.headers, specs);
50
- const machine = (0, validate_1.compileRecordMachine)(specs);
50
+ const machine = (0, validate_1.compileRecordMachine)(specs, options.transitions);
51
51
  const ctx = (0, validate_1.createDatasetContext)(machine, options);
52
52
  const rows = [];
53
53
  const issues = [];
@@ -0,0 +1,108 @@
1
+ /**
2
+ * The dev-server schema compiler — the way around "classes need a compile
3
+ * step".
4
+ *
5
+ * Class/interface entities have no runtime shape (TypeScript erases their
6
+ * types), so *something* must run the `ts.Program` + `TypeChecker` to read
7
+ * fields, `DbConfig` brands and FK types off them. The insight is that it
8
+ * doesn't have to be a command the developer runs: this module packages the
9
+ * same `analyzeProject`/`compile` pass the CLI uses so it can live **inside
10
+ * the dev server that is already running** — a bundler plugin
11
+ * (`rastackPlugin`, Vite/Rollup-compatible) or a bare filesystem watcher
12
+ * (`startRastackWatcher`, for anything else). The schema recompiles on every
13
+ * save, the app imports it as the `virtual:rastack-manifest` module, and
14
+ * `rastack-env.d.ts` is rewritten so string-key row types stay fresh — the
15
+ * developer never runs `rastack compile`.
16
+ *
17
+ * Design mirrors the rest of `tools/src`: the decisions live here as pure
18
+ * (or thin-IO) functions, tested in `tools/test/plugin.spec.ts`; the plugin
19
+ * object in `./index.ts` is a shell over {@link createSchemaState}.
20
+ */
21
+ import type { EntityDiagnostic, Manifest } from "../compile";
22
+ /** The module id apps import the live manifest from. */
23
+ export declare const VIRTUAL_MANIFEST_ID = "virtual:rastack-manifest";
24
+ /** The resolved (bundler-internal) id — Rollup convention: `\0` prefix. */
25
+ export declare const RESOLVED_MANIFEST_ID: string;
26
+ /**
27
+ * The auto-registration module the plugin injects into `index.html`: it sets
28
+ * `globalThis.__RASTACK_MANIFEST__` before the app boots, which is what lets
29
+ * `<RAStackProvider mode="local">` (and bare `useData(Airport)`) run with
30
+ * **no manifest wiring in app code at all**. Kept un-prefixed (no `\0`) so
31
+ * its dev-server URL is a plain `/@id/…`.
32
+ */
33
+ export declare const VIRTUAL_REGISTER_ID = "virtual:rastack-manifest/register";
34
+ /** The key the register module assigns (mirrored in `rastack/hooks`). */
35
+ export declare const GLOBAL_MANIFEST_KEY = "__RASTACK_MANIFEST__";
36
+ export interface RastackPluginOptions {
37
+ /**
38
+ * Directory (or single file) scanned for entities — classes, interfaces
39
+ * and `resource()` calls alike, recursively (co-located definitions work).
40
+ * Default `"resources"`; pass `"src"` (or `"."`) for co-located entities.
41
+ */
42
+ resourcesDir?: string;
43
+ /**
44
+ * Where the artifacts land: `schema.rastack.json`, `openapi.json`,
45
+ * `rastack-env.d.ts` (the string-key type registry) and
46
+ * `rastack-plugin.d.ts` (types for the virtual module). Default
47
+ * `".rastack"` — include it in `tsconfig.json` once.
48
+ */
49
+ outDir?: string;
50
+ /** Skip writing the artifacts to disk (virtual module only). Default false. */
51
+ emitFiles?: boolean;
52
+ /** Where compile problems are reported. Default `console.error`. */
53
+ onError?: (error: unknown) => void;
54
+ }
55
+ export interface ResolvedPluginOptions {
56
+ resourcesDir: string;
57
+ outDir: string;
58
+ emitFiles: boolean;
59
+ onError: (error: unknown) => void;
60
+ }
61
+ export declare function resolveOptions(options?: RastackPluginOptions, root?: string): ResolvedPluginOptions;
62
+ /**
63
+ * Whether a changed file should trigger a schema recompile: a `.ts`/`.tsx`
64
+ * source under the scanned directory that is not itself an emitted artifact.
65
+ */
66
+ export declare function isEntitySource(file: string, options: Pick<ResolvedPluginOptions, "resourcesDir" | "outDir">): boolean;
67
+ /** The virtual module's source — the manifest as a plain ES module. */
68
+ export declare function manifestModuleCode(manifest: Manifest): string;
69
+ /**
70
+ * The register module's source: import the live manifest, publish it on the
71
+ * global the runtime falls back to. A separate module (rather than a side
72
+ * effect of the manifest module) so explicit `virtual:rastack-manifest`
73
+ * importers stay side-effect-free.
74
+ */
75
+ export declare function registerModuleCode(): string;
76
+ /** Types for the virtual module, written once beside `rastack-env.d.ts`. */
77
+ export declare function virtualModuleDts(): string;
78
+ export interface SchemaState {
79
+ /**
80
+ * The virtual module's current source. Compiles (lazily) when stale; on a
81
+ * compile error reports via `onError` and keeps serving the last good
82
+ * manifest — a half-typed entity must not blank the running app.
83
+ */
84
+ code(): string;
85
+ /** The current manifest (same laziness/fallback as {@link code}). */
86
+ manifest(): Manifest;
87
+ /** Mark the schema stale — the next `code()`/`manifest()` recompiles. */
88
+ invalidate(): void;
89
+ /** The error from the most recent failed compile, if it is still failing. */
90
+ error(): unknown;
91
+ /** Non-error diagnostics from the last successful compile. */
92
+ diagnostics(): EntityDiagnostic[];
93
+ options: ResolvedPluginOptions;
94
+ }
95
+ /**
96
+ * The plugin's whole brain: lazy compile, invalidation, keep-last-good.
97
+ * Both the bundler plugin and the bare watcher drive this one object.
98
+ */
99
+ export declare function createSchemaState(options?: RastackPluginOptions, root?: string): SchemaState;
100
+ /**
101
+ * One compile pass: the same artifacts `rastack compile` writes (manifest,
102
+ * OpenAPI, `rastack-env.d.ts`), plus the virtual module's type declaration.
103
+ * With `emitFiles: false` it only analyses (nothing touches disk).
104
+ */
105
+ export declare function runCompile(options: ResolvedPluginOptions): {
106
+ manifest: Manifest;
107
+ diagnostics: EntityDiagnostic[];
108
+ };
@@ -0,0 +1,198 @@
1
+ "use strict";
2
+ /**
3
+ * The dev-server schema compiler — the way around "classes need a compile
4
+ * step".
5
+ *
6
+ * Class/interface entities have no runtime shape (TypeScript erases their
7
+ * types), so *something* must run the `ts.Program` + `TypeChecker` to read
8
+ * fields, `DbConfig` brands and FK types off them. The insight is that it
9
+ * doesn't have to be a command the developer runs: this module packages the
10
+ * same `analyzeProject`/`compile` pass the CLI uses so it can live **inside
11
+ * the dev server that is already running** — a bundler plugin
12
+ * (`rastackPlugin`, Vite/Rollup-compatible) or a bare filesystem watcher
13
+ * (`startRastackWatcher`, for anything else). The schema recompiles on every
14
+ * save, the app imports it as the `virtual:rastack-manifest` module, and
15
+ * `rastack-env.d.ts` is rewritten so string-key row types stay fresh — the
16
+ * developer never runs `rastack compile`.
17
+ *
18
+ * Design mirrors the rest of `tools/src`: the decisions live here as pure
19
+ * (or thin-IO) functions, tested in `tools/test/plugin.spec.ts`; the plugin
20
+ * object in `./index.ts` is a shell over {@link createSchemaState}.
21
+ */
22
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
23
+ if (k2 === undefined) k2 = k;
24
+ var desc = Object.getOwnPropertyDescriptor(m, k);
25
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
26
+ desc = { enumerable: true, get: function() { return m[k]; } };
27
+ }
28
+ Object.defineProperty(o, k2, desc);
29
+ }) : (function(o, m, k, k2) {
30
+ if (k2 === undefined) k2 = k;
31
+ o[k2] = m[k];
32
+ }));
33
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
34
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
35
+ }) : function(o, v) {
36
+ o["default"] = v;
37
+ });
38
+ var __importStar = (this && this.__importStar) || (function () {
39
+ var ownKeys = function(o) {
40
+ ownKeys = Object.getOwnPropertyNames || function (o) {
41
+ var ar = [];
42
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
43
+ return ar;
44
+ };
45
+ return ownKeys(o);
46
+ };
47
+ return function (mod) {
48
+ if (mod && mod.__esModule) return mod;
49
+ var result = {};
50
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
51
+ __setModuleDefault(result, mod);
52
+ return result;
53
+ };
54
+ })();
55
+ Object.defineProperty(exports, "__esModule", { value: true });
56
+ exports.GLOBAL_MANIFEST_KEY = exports.VIRTUAL_REGISTER_ID = exports.RESOLVED_MANIFEST_ID = exports.VIRTUAL_MANIFEST_ID = void 0;
57
+ exports.resolveOptions = resolveOptions;
58
+ exports.isEntitySource = isEntitySource;
59
+ exports.manifestModuleCode = manifestModuleCode;
60
+ exports.registerModuleCode = registerModuleCode;
61
+ exports.virtualModuleDts = virtualModuleDts;
62
+ exports.createSchemaState = createSchemaState;
63
+ exports.runCompile = runCompile;
64
+ const fs = __importStar(require("fs"));
65
+ const path = __importStar(require("path"));
66
+ const compile_1 = require("../compile");
67
+ /** The module id apps import the live manifest from. */
68
+ exports.VIRTUAL_MANIFEST_ID = "virtual:rastack-manifest";
69
+ /** The resolved (bundler-internal) id — Rollup convention: `\0` prefix. */
70
+ exports.RESOLVED_MANIFEST_ID = "\0" + exports.VIRTUAL_MANIFEST_ID;
71
+ /**
72
+ * The auto-registration module the plugin injects into `index.html`: it sets
73
+ * `globalThis.__RASTACK_MANIFEST__` before the app boots, which is what lets
74
+ * `<RAStackProvider mode="local">` (and bare `useData(Airport)`) run with
75
+ * **no manifest wiring in app code at all**. Kept un-prefixed (no `\0`) so
76
+ * its dev-server URL is a plain `/@id/…`.
77
+ */
78
+ exports.VIRTUAL_REGISTER_ID = "virtual:rastack-manifest/register";
79
+ /** The key the register module assigns (mirrored in `rastack/hooks`). */
80
+ exports.GLOBAL_MANIFEST_KEY = "__RASTACK_MANIFEST__";
81
+ function resolveOptions(options = {}, root = process.cwd()) {
82
+ return {
83
+ resourcesDir: path.resolve(root, options.resourcesDir ?? "resources"),
84
+ outDir: path.resolve(root, options.outDir ?? ".rastack"),
85
+ emitFiles: options.emitFiles !== false,
86
+ onError: options.onError ??
87
+ ((error) => console.error(`[rastack] schema compile failed — serving the last good manifest\n` +
88
+ `${error instanceof Error ? error.message : String(error)}`)),
89
+ };
90
+ }
91
+ /**
92
+ * Whether a changed file should trigger a schema recompile: a `.ts`/`.tsx`
93
+ * source under the scanned directory that is not itself an emitted artifact.
94
+ */
95
+ function isEntitySource(file, options) {
96
+ const resolved = path.resolve(file);
97
+ if (!/\.tsx?$/.test(resolved) || resolved.endsWith(".d.ts"))
98
+ return false;
99
+ if (within(resolved, options.outDir))
100
+ return false;
101
+ return (within(resolved, options.resourcesDir) || resolved === options.resourcesDir);
102
+ }
103
+ function within(file, dir) {
104
+ const rel = path.relative(dir, file);
105
+ return rel !== "" && !rel.startsWith("..") && !path.isAbsolute(rel);
106
+ }
107
+ /** The virtual module's source — the manifest as a plain ES module. */
108
+ function manifestModuleCode(manifest) {
109
+ const json = JSON.stringify(manifest);
110
+ return (`// virtual:rastack-manifest — compiled by the rastack dev-server plugin.\n` +
111
+ `const manifest = ${json};\n` +
112
+ `export default manifest;\n` +
113
+ `export { manifest };\n`);
114
+ }
115
+ /**
116
+ * The register module's source: import the live manifest, publish it on the
117
+ * global the runtime falls back to. A separate module (rather than a side
118
+ * effect of the manifest module) so explicit `virtual:rastack-manifest`
119
+ * importers stay side-effect-free.
120
+ */
121
+ function registerModuleCode() {
122
+ return (`// ${exports.VIRTUAL_REGISTER_ID} — auto-injected by the rastack plugin.\n` +
123
+ `import manifest from ${JSON.stringify(exports.VIRTUAL_MANIFEST_ID)};\n` +
124
+ `globalThis.${exports.GLOBAL_MANIFEST_KEY} = manifest;\n` +
125
+ `export default manifest;\n`);
126
+ }
127
+ /** Types for the virtual module, written once beside `rastack-env.d.ts`. */
128
+ function virtualModuleDts() {
129
+ return ("// Generated by the rastack plugin — do not edit.\n" +
130
+ `declare module "${exports.VIRTUAL_MANIFEST_ID}" {\n` +
131
+ ' const manifest: import("rastack/define").Manifest;\n' +
132
+ " export default manifest;\n" +
133
+ " export { manifest };\n" +
134
+ "}\n");
135
+ }
136
+ /**
137
+ * The plugin's whole brain: lazy compile, invalidation, keep-last-good.
138
+ * Both the bundler plugin and the bare watcher drive this one object.
139
+ */
140
+ function createSchemaState(options = {}, root) {
141
+ const resolved = resolveOptions(options, root);
142
+ let current = null;
143
+ let stale = true;
144
+ let lastError = undefined;
145
+ const recompile = () => {
146
+ try {
147
+ const result = runCompile(resolved);
148
+ current = { manifest: result.manifest, diagnostics: result.diagnostics };
149
+ lastError = undefined;
150
+ }
151
+ catch (error) {
152
+ lastError = error;
153
+ resolved.onError(error);
154
+ if (!current)
155
+ throw error; // no last-good manifest to fall back to
156
+ }
157
+ finally {
158
+ stale = false;
159
+ }
160
+ };
161
+ const ensure = () => {
162
+ if (stale || !current)
163
+ recompile();
164
+ return current;
165
+ };
166
+ return {
167
+ code: () => manifestModuleCode(ensure().manifest),
168
+ manifest: () => ensure().manifest,
169
+ invalidate: () => {
170
+ stale = true;
171
+ },
172
+ error: () => lastError,
173
+ diagnostics: () => ensure().diagnostics,
174
+ options: resolved,
175
+ };
176
+ }
177
+ /**
178
+ * One compile pass: the same artifacts `rastack compile` writes (manifest,
179
+ * OpenAPI, `rastack-env.d.ts`), plus the virtual module's type declaration.
180
+ * With `emitFiles: false` it only analyses (nothing touches disk).
181
+ */
182
+ function runCompile(options) {
183
+ if (!options.emitFiles) {
184
+ // compile() always writes to disk; the analyse-only path applies the
185
+ // same error gate without touching the filesystem.
186
+ const { resources, diagnostics } = (0, compile_1.analyzeProject)(options.resourcesDir);
187
+ const errors = diagnostics.filter((d) => d.severity === "error");
188
+ if (errors.length)
189
+ throw new Error(errors.map((e) => e.message).join("\n"));
190
+ return { manifest: (0, compile_1.buildManifest)(resources), diagnostics };
191
+ }
192
+ const result = (0, compile_1.compile)(options.resourcesDir, options.outDir);
193
+ const dtsPath = path.join(options.outDir, "rastack-plugin.d.ts");
194
+ if (!fs.existsSync(dtsPath)) {
195
+ fs.writeFileSync(dtsPath, virtualModuleDts());
196
+ }
197
+ return { manifest: result.manifest, diagnostics: result.diagnostics };
198
+ }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * `rastack/plugin` — the schema compiles itself inside your dev server.
3
+ *
4
+ * Class and interface entities are pure types, so the manifest has to be
5
+ * derived by the TypeScript checker — but not by a command you run. Add the
6
+ * plugin to the bundler you already use and the whole `rastack compile`
7
+ * pass disappears into the dev loop:
8
+ *
9
+ * ```ts
10
+ * // vite.config.ts
11
+ * import { rastackPlugin } from "rastack/plugin";
12
+ * export default defineConfig({
13
+ * plugins: [rastackPlugin({ resourcesDir: "src/entities" })],
14
+ * });
15
+ * ```
16
+ *
17
+ * ```tsx
18
+ * // app — the manifest is a live import, recompiled on every save
19
+ * import manifest from "virtual:rastack-manifest";
20
+ * <RAStackProvider mode="local" manifest={manifest}>…</RAStackProvider>
21
+ * ```
22
+ *
23
+ * What it does on your behalf, on every relevant file save:
24
+ * - recompiles the entity graph (classes, interfaces, `resource()` calls);
25
+ * - serves the manifest as the `virtual:rastack-manifest` module and
26
+ * hot-invalidates it;
27
+ * - rewrites `.rastack/rastack-env.d.ts` (string-key row types) and
28
+ * `schema.rastack.json`/`openapi.json`, so the committed artifacts and the
29
+ * registry never go stale;
30
+ * - on a broken in-progress edit, reports the error and keeps serving the
31
+ * last good manifest.
32
+ *
33
+ * The plugin object is Vite/Rollup-compatible (Vite-specific hooks are
34
+ * ignored by plain Rollup). For dev servers with other plugin systems
35
+ * (Next/webpack, Metro/Expo), `startRastackWatcher` does the same
36
+ * recompile-on-save against the filesystem — call it once from your config —
37
+ * and `rastack dev` remains the zero-config path.
38
+ */
39
+ import { type RastackPluginOptions, type SchemaState } from "./core";
40
+ export { createSchemaState, GLOBAL_MANIFEST_KEY, isEntitySource, manifestModuleCode, registerModuleCode, resolveOptions, RESOLVED_MANIFEST_ID, runCompile, VIRTUAL_MANIFEST_ID, VIRTUAL_REGISTER_ID, virtualModuleDts, } from "./core";
41
+ export type { RastackPluginOptions, ResolvedPluginOptions, SchemaState, } from "./core";
42
+ /** One HTML tag descriptor in Vite's `transformIndexHtml` shape. */
43
+ export interface HtmlTagDescriptor {
44
+ tag: string;
45
+ attrs?: Record<string, string>;
46
+ children?: string;
47
+ injectTo?: "head" | "head-prepend" | "body" | "body-prepend";
48
+ }
49
+ /** The minimal bundler-plugin surface we emit (Vite superset of Rollup). */
50
+ export interface RastackBundlerPlugin {
51
+ name: string;
52
+ buildStart(): void;
53
+ resolveId(id: string): string | undefined;
54
+ load(id: string): string | undefined;
55
+ watchChange(id: string): void;
56
+ /**
57
+ * Vite-only: auto-register the schema. Injects a module script that sets
58
+ * `globalThis.__RASTACK_MANIFEST__` before the app boots, so the provider
59
+ * and hooks need no `manifest` wiring at all. Object form with
60
+ * `order: "pre"` — the tag must be injected *before* Vite scans the HTML
61
+ * entry, or the build ships the virtual import verbatim.
62
+ */
63
+ transformIndexHtml: {
64
+ order: "pre";
65
+ handler(html: string, ctx?: {
66
+ server?: unknown;
67
+ }): HtmlTagDescriptor[];
68
+ };
69
+ /** Vite-only: recompile + hot-invalidate the virtual manifest module. */
70
+ handleHotUpdate(ctx: {
71
+ file: string;
72
+ server: {
73
+ moduleGraph: {
74
+ getModuleById(id: string): unknown;
75
+ invalidateModule(mod: any): void;
76
+ };
77
+ };
78
+ modules: unknown[];
79
+ }): unknown[] | void;
80
+ /** The live schema state — exposed for tests and advanced callers. */
81
+ api: SchemaState;
82
+ }
83
+ /**
84
+ * The bundler plugin. Works as-is in Vite and Rollup; the returned object is
85
+ * plain data + closures, so any bundler with resolve/load/watch hooks can
86
+ * adapt it.
87
+ */
88
+ export declare function rastackPlugin(options?: RastackPluginOptions): RastackBundlerPlugin;
89
+ /** Alias so `import { rastack } from "rastack/plugin"` reads naturally. */
90
+ export declare const rastack: typeof rastackPlugin;
91
+ export interface RastackWatcher {
92
+ /** Stop watching. */
93
+ close(): void;
94
+ /** The live schema state (same object the recompiles flow through). */
95
+ state: SchemaState;
96
+ }
97
+ /**
98
+ * The plugin for everything that isn't Rollup-shaped: watch the resources
99
+ * directory and rewrite the artifacts (`schema.rastack.json`,
100
+ * `openapi.json`, `rastack-env.d.ts`) on every save. Call once from any
101
+ * Node-side config (next.config.js, metro.config.js, a dev script):
102
+ *
103
+ * ```js
104
+ * // next.config.js
105
+ * const { startRastackWatcher } = require("rastack/plugin");
106
+ * if (process.env.NODE_ENV === "development") startRastackWatcher();
107
+ * ```
108
+ *
109
+ * The app then imports `.rastack/schema.rastack.json` directly (a plain JSON
110
+ * import every bundler hot-reloads) instead of the virtual module.
111
+ */
112
+ export declare function startRastackWatcher(options?: RastackPluginOptions): RastackWatcher;
@@ -0,0 +1,203 @@
1
+ "use strict";
2
+ /**
3
+ * `rastack/plugin` — the schema compiles itself inside your dev server.
4
+ *
5
+ * Class and interface entities are pure types, so the manifest has to be
6
+ * derived by the TypeScript checker — but not by a command you run. Add the
7
+ * plugin to the bundler you already use and the whole `rastack compile`
8
+ * pass disappears into the dev loop:
9
+ *
10
+ * ```ts
11
+ * // vite.config.ts
12
+ * import { rastackPlugin } from "rastack/plugin";
13
+ * export default defineConfig({
14
+ * plugins: [rastackPlugin({ resourcesDir: "src/entities" })],
15
+ * });
16
+ * ```
17
+ *
18
+ * ```tsx
19
+ * // app — the manifest is a live import, recompiled on every save
20
+ * import manifest from "virtual:rastack-manifest";
21
+ * <RAStackProvider mode="local" manifest={manifest}>…</RAStackProvider>
22
+ * ```
23
+ *
24
+ * What it does on your behalf, on every relevant file save:
25
+ * - recompiles the entity graph (classes, interfaces, `resource()` calls);
26
+ * - serves the manifest as the `virtual:rastack-manifest` module and
27
+ * hot-invalidates it;
28
+ * - rewrites `.rastack/rastack-env.d.ts` (string-key row types) and
29
+ * `schema.rastack.json`/`openapi.json`, so the committed artifacts and the
30
+ * registry never go stale;
31
+ * - on a broken in-progress edit, reports the error and keeps serving the
32
+ * last good manifest.
33
+ *
34
+ * The plugin object is Vite/Rollup-compatible (Vite-specific hooks are
35
+ * ignored by plain Rollup). For dev servers with other plugin systems
36
+ * (Next/webpack, Metro/Expo), `startRastackWatcher` does the same
37
+ * recompile-on-save against the filesystem — call it once from your config —
38
+ * and `rastack dev` remains the zero-config path.
39
+ */
40
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
41
+ if (k2 === undefined) k2 = k;
42
+ var desc = Object.getOwnPropertyDescriptor(m, k);
43
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
44
+ desc = { enumerable: true, get: function() { return m[k]; } };
45
+ }
46
+ Object.defineProperty(o, k2, desc);
47
+ }) : (function(o, m, k, k2) {
48
+ if (k2 === undefined) k2 = k;
49
+ o[k2] = m[k];
50
+ }));
51
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
52
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
53
+ }) : function(o, v) {
54
+ o["default"] = v;
55
+ });
56
+ var __importStar = (this && this.__importStar) || (function () {
57
+ var ownKeys = function(o) {
58
+ ownKeys = Object.getOwnPropertyNames || function (o) {
59
+ var ar = [];
60
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
61
+ return ar;
62
+ };
63
+ return ownKeys(o);
64
+ };
65
+ return function (mod) {
66
+ if (mod && mod.__esModule) return mod;
67
+ var result = {};
68
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
69
+ __setModuleDefault(result, mod);
70
+ return result;
71
+ };
72
+ })();
73
+ Object.defineProperty(exports, "__esModule", { value: true });
74
+ exports.rastack = exports.virtualModuleDts = exports.VIRTUAL_REGISTER_ID = exports.VIRTUAL_MANIFEST_ID = exports.runCompile = exports.RESOLVED_MANIFEST_ID = exports.resolveOptions = exports.registerModuleCode = exports.manifestModuleCode = exports.isEntitySource = exports.GLOBAL_MANIFEST_KEY = exports.createSchemaState = void 0;
75
+ exports.rastackPlugin = rastackPlugin;
76
+ exports.startRastackWatcher = startRastackWatcher;
77
+ const fs = __importStar(require("fs"));
78
+ const path = __importStar(require("path"));
79
+ const core_1 = require("./core");
80
+ var core_2 = require("./core");
81
+ Object.defineProperty(exports, "createSchemaState", { enumerable: true, get: function () { return core_2.createSchemaState; } });
82
+ Object.defineProperty(exports, "GLOBAL_MANIFEST_KEY", { enumerable: true, get: function () { return core_2.GLOBAL_MANIFEST_KEY; } });
83
+ Object.defineProperty(exports, "isEntitySource", { enumerable: true, get: function () { return core_2.isEntitySource; } });
84
+ Object.defineProperty(exports, "manifestModuleCode", { enumerable: true, get: function () { return core_2.manifestModuleCode; } });
85
+ Object.defineProperty(exports, "registerModuleCode", { enumerable: true, get: function () { return core_2.registerModuleCode; } });
86
+ Object.defineProperty(exports, "resolveOptions", { enumerable: true, get: function () { return core_2.resolveOptions; } });
87
+ Object.defineProperty(exports, "RESOLVED_MANIFEST_ID", { enumerable: true, get: function () { return core_2.RESOLVED_MANIFEST_ID; } });
88
+ Object.defineProperty(exports, "runCompile", { enumerable: true, get: function () { return core_2.runCompile; } });
89
+ Object.defineProperty(exports, "VIRTUAL_MANIFEST_ID", { enumerable: true, get: function () { return core_2.VIRTUAL_MANIFEST_ID; } });
90
+ Object.defineProperty(exports, "VIRTUAL_REGISTER_ID", { enumerable: true, get: function () { return core_2.VIRTUAL_REGISTER_ID; } });
91
+ Object.defineProperty(exports, "virtualModuleDts", { enumerable: true, get: function () { return core_2.virtualModuleDts; } });
92
+ /**
93
+ * The bundler plugin. Works as-is in Vite and Rollup; the returned object is
94
+ * plain data + closures, so any bundler with resolve/load/watch hooks can
95
+ * adapt it.
96
+ */
97
+ function rastackPlugin(options = {}) {
98
+ const state = (0, core_1.createSchemaState)(options);
99
+ return {
100
+ name: "rastack",
101
+ buildStart() {
102
+ // Compile eagerly so authoring errors surface at server start, and the
103
+ // artifacts (env.d.ts, schema.rastack.json) exist before first load.
104
+ state.manifest();
105
+ },
106
+ resolveId(id) {
107
+ if (id === core_1.VIRTUAL_MANIFEST_ID)
108
+ return core_1.RESOLVED_MANIFEST_ID;
109
+ if (id === core_1.VIRTUAL_REGISTER_ID)
110
+ return core_1.VIRTUAL_REGISTER_ID;
111
+ return undefined;
112
+ },
113
+ load(id) {
114
+ if (id === core_1.RESOLVED_MANIFEST_ID)
115
+ return state.code();
116
+ if (id === core_1.VIRTUAL_REGISTER_ID)
117
+ return (0, core_1.registerModuleCode)();
118
+ return undefined;
119
+ },
120
+ watchChange(id) {
121
+ if ((0, core_1.isEntitySource)(id, state.options))
122
+ state.invalidate();
123
+ },
124
+ handleHotUpdate(ctx) {
125
+ if (!(0, core_1.isEntitySource)(ctx.file, state.options))
126
+ return;
127
+ state.invalidate();
128
+ const mod = ctx.server.moduleGraph.getModuleById(core_1.RESOLVED_MANIFEST_ID);
129
+ if (mod) {
130
+ ctx.server.moduleGraph.invalidateModule(mod);
131
+ return [...ctx.modules, mod];
132
+ }
133
+ },
134
+ transformIndexHtml: {
135
+ order: "pre",
136
+ handler(_html, ctx) {
137
+ // Dev serves resolved ids at /@id/<id>; build gets an inline module
138
+ // script that (because this runs `pre`, before Vite scans the HTML
139
+ // entry) is bundled, its import resolving back through
140
+ // resolveId/load above.
141
+ const attrs = { type: "module" };
142
+ const tag = ctx?.server
143
+ ? {
144
+ tag: "script",
145
+ attrs: { ...attrs, src: `/@id/${core_1.VIRTUAL_REGISTER_ID}` },
146
+ }
147
+ : {
148
+ tag: "script",
149
+ attrs,
150
+ children: `import ${JSON.stringify(core_1.VIRTUAL_REGISTER_ID)};`,
151
+ };
152
+ return [{ ...tag, injectTo: "head-prepend" }];
153
+ },
154
+ },
155
+ api: state,
156
+ };
157
+ }
158
+ /** Alias so `import { rastack } from "rastack/plugin"` reads naturally. */
159
+ exports.rastack = rastackPlugin;
160
+ /**
161
+ * The plugin for everything that isn't Rollup-shaped: watch the resources
162
+ * directory and rewrite the artifacts (`schema.rastack.json`,
163
+ * `openapi.json`, `rastack-env.d.ts`) on every save. Call once from any
164
+ * Node-side config (next.config.js, metro.config.js, a dev script):
165
+ *
166
+ * ```js
167
+ * // next.config.js
168
+ * const { startRastackWatcher } = require("rastack/plugin");
169
+ * if (process.env.NODE_ENV === "development") startRastackWatcher();
170
+ * ```
171
+ *
172
+ * The app then imports `.rastack/schema.rastack.json` directly (a plain JSON
173
+ * import every bundler hot-reloads) instead of the virtual module.
174
+ */
175
+ function startRastackWatcher(options = {}) {
176
+ const state = (0, core_1.createSchemaState)(options);
177
+ state.manifest(); // initial compile (writes the artifacts)
178
+ let timer;
179
+ const watcher = fs.watch(state.options.resourcesDir, { recursive: true }, (_event, filename) => {
180
+ // fs.watch reports paths relative to the watched directory.
181
+ const file = filename
182
+ ? path.join(state.options.resourcesDir, String(filename))
183
+ : undefined;
184
+ if (!file || !(0, core_1.isEntitySource)(file, state.options)) {
185
+ return;
186
+ }
187
+ state.invalidate();
188
+ // Debounce bursts (editors write twice, git checkouts touch many files).
189
+ if (timer)
190
+ clearTimeout(timer);
191
+ timer = setTimeout(() => {
192
+ state.manifest();
193
+ }, 100);
194
+ });
195
+ return {
196
+ close: () => {
197
+ if (timer)
198
+ clearTimeout(timer);
199
+ watcher.close();
200
+ },
201
+ state,
202
+ };
203
+ }
@@ -121,7 +121,10 @@ function main() {
121
121
  const data = loadFile(file);
122
122
  console.log(`\nrastack import ${path_1.default.basename(file)} → ${resourceName} ` +
123
123
  `[${data.headers.length} column(s), ${data.rows.length} row(s)]\n`);
124
- const report = (0, import_1.validateDataset)(specs, data, { relatedIds });
124
+ const report = (0, import_1.validateDataset)(specs, data, {
125
+ relatedIds,
126
+ transitions: resource.transitions,
127
+ });
125
128
  printReport(report);
126
129
  const outPath = getArg("--out");
127
130
  if (outPath) {