rastack 0.0.50 → 0.0.52

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 (72) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/auth/context.ts +73 -0
  3. package/auth/core.ts +293 -0
  4. package/auth/index.ts +23 -0
  5. package/components/auto-form/AutoForm.tsx +32 -12
  6. package/components/auto-form/use-auto-form.ts +75 -6
  7. package/components/data-table/DataTable.tsx +72 -45
  8. package/components/data-table/use-data-table.ts +57 -3
  9. package/components/index.ts +2 -0
  10. package/db.ts +9 -0
  11. package/dist/admin.js +13 -13
  12. package/dist/compile/analyze.d.ts +1 -7
  13. package/dist/compile/analyze.js +19 -70
  14. package/dist/compile/entities.js +163 -16
  15. package/dist/compile/model.d.ts +7 -3
  16. package/dist/compile/openapi.js +13 -3
  17. package/dist/compile/program.d.ts +2 -1
  18. package/dist/compile/program.js +7 -2
  19. package/dist/define/auth.d.ts +27 -0
  20. package/dist/define/auth.js +42 -0
  21. package/dist/define/db.d.ts +24 -1
  22. package/dist/define/db.js +1 -1
  23. package/dist/define/index.d.ts +82 -18
  24. package/dist/define/index.js +27 -21
  25. package/dist/define/manifest.d.ts +64 -0
  26. package/dist/define/manifest.js +259 -0
  27. package/dist/define/markers.d.ts +80 -0
  28. package/dist/define/markers.js +37 -0
  29. package/dist/define/types.d.ts +10 -0
  30. package/dist/define/types.js +26 -0
  31. package/dist/plugin/core.d.ts +108 -0
  32. package/dist/plugin/core.js +198 -0
  33. package/dist/plugin/index.d.ts +112 -0
  34. package/dist/plugin/index.js +203 -0
  35. package/dist/wasm/rastack_wasm.js +1 -1
  36. package/dist/wasm/rastack_wasm_bg.wasm +0 -0
  37. package/hooks/data.ts +18 -6
  38. package/hooks/entity.ts +11 -5
  39. package/hooks/form/entity-form.ts +12 -8
  40. package/hooks/manifest.ts +50 -14
  41. package/hooks/registry.ts +25 -8
  42. package/package.json +1 -1
  43. package/plugin.ts +8 -0
  44. package/provider/index.ts +1 -5
  45. package/provider/provider.tsx +61 -16
  46. package/provider/types.ts +26 -7
  47. package/provider/warehouse.ts +4 -3
  48. package/provider/wasm.ts +3 -1
  49. package/runtime.ts +1 -0
  50. package/src/compile/analyze.ts +27 -65
  51. package/src/compile/entities.ts +193 -19
  52. package/src/compile/model.ts +8 -9
  53. package/src/compile/openapi.ts +11 -3
  54. package/src/compile/program.ts +10 -3
  55. package/src/define/auth.ts +28 -0
  56. package/src/define/db.ts +26 -4
  57. package/src/define/index.ts +118 -24
  58. package/src/define/manifest.ts +287 -0
  59. package/src/define/markers.ts +83 -0
  60. package/src/define/types.ts +11 -0
  61. package/src/plugin/core.ts +236 -0
  62. package/src/plugin/index.ts +243 -0
  63. package/test/auth.spec.ts +207 -0
  64. package/test/compile.spec.ts +18 -7
  65. package/test/components.spec.ts +116 -0
  66. package/test/entities.spec.ts +123 -6
  67. package/test/plugin.spec.ts +315 -0
  68. package/test/runtime-manifest.spec.ts +309 -0
  69. package/test/typed-hooks.spec.ts +7 -2
  70. package/types.ts +7 -2
  71. package/wasm/rastack_wasm.js +1 -1
  72. package/wasm/rastack_wasm_bg.wasm +0 -0
@@ -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
+ }
@@ -375,7 +375,7 @@ function __wbg_get_imports() {
375
375
  return ret;
376
376
  },
377
377
  __wbindgen_cast_0000000000000001: function(arg0, arg1) {
378
- // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 85, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
378
+ // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 83, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
379
379
  const ret = makeMutClosure(arg0, arg1, wasm_bindgen_1a7aa9db5392c486___convert__closures_____invoke___wasm_bindgen_1a7aa9db5392c486___JsValue__core_7d5f0a2ba6a62c33___result__Result_____wasm_bindgen_1a7aa9db5392c486___JsError___true_);
380
380
  return ret;
381
381
  },
Binary file
package/hooks/data.ts CHANGED
@@ -49,14 +49,18 @@ import {
49
49
  } from "./entity";
50
50
  import { useRastackManifest } from "./manifest";
51
51
  import type { EntityTypes, RegisteredEntityKey } from "./registry";
52
- import type { Manifest, ResourceModel } from "../src/compile/model";
52
+ import type { ResourceModel } from "../src/compile/model";
53
+ import { toManifest, type ManifestInput } from "../src/define/manifest";
53
54
 
54
55
  export interface UseDataOptions<TRow>
55
56
  extends ListQueryOptions<Record<string, unknown>, TRow> {
56
57
  /** Gate the query (replaces the generated hooks' auth wiring). Default on. */
57
58
  enabled?: boolean;
58
- /** Manifest override — tests, or callers outside `<RAStackProvider>`. */
59
- manifest?: Manifest;
59
+ /**
60
+ * Manifest override — tests, or callers outside `<RAStackProvider>`.
61
+ * Accepts a compiled manifest or `resource()` definitions (array/module).
62
+ */
63
+ manifest?: ManifestInput;
60
64
  }
61
65
 
62
66
  /**
@@ -114,7 +118,11 @@ export function useData(
114
118
  options?: UseDataOptions<any>,
115
119
  ): UseDataResult<any> {
116
120
  const contextManifest = useRastackManifest();
117
- const manifest = options?.manifest ?? contextManifest;
121
+ const optionManifest = useMemo(
122
+ () => toManifest(options?.manifest),
123
+ [options?.manifest],
124
+ );
125
+ const manifest = optionManifest ?? contextManifest;
118
126
  const resolvedEntity = useMemo(
119
127
  () => resolveEntityRef(entity, manifest),
120
128
  [entity, manifest],
@@ -165,7 +173,7 @@ export function useData(
165
173
 
166
174
  export interface UseRecordOptions {
167
175
  enabled?: boolean;
168
- manifest?: Manifest;
176
+ manifest?: ManifestInput;
169
177
  }
170
178
 
171
179
  /** One record by id — the generic twin of the generated detail hooks. */
@@ -190,7 +198,11 @@ export function useRecord(
190
198
  options?: UseRecordOptions,
191
199
  ): IGetQueryResult<any> & { entity: ResolvedEntity } {
192
200
  const contextManifest = useRastackManifest();
193
- const manifest = options?.manifest ?? contextManifest;
201
+ const optionManifest = useMemo(
202
+ () => toManifest(options?.manifest),
203
+ [options?.manifest],
204
+ );
205
+ const manifest = optionManifest ?? contextManifest;
194
206
  const resolvedEntity = useMemo(
195
207
  () => resolveEntityRef(entity, manifest),
196
208
  [entity, manifest],
package/hooks/entity.ts CHANGED
@@ -27,6 +27,7 @@ import type {
27
27
  TransitionsModel,
28
28
  } from "../src/compile/model";
29
29
  import type { ResourceType, RowOf } from "../src/define";
30
+ import { isResourceValue, resourceModel } from "../src/define/manifest";
30
31
  import type { EntityTypes } from "./registry";
31
32
  import { normalizeTransitions } from "../src/validate/transitions";
32
33
 
@@ -46,7 +47,9 @@ export type EntityRef<TRow = any> =
46
47
  * The row type an entity reference implies — every reference form infers:
47
48
  * classes carry it (`InstanceType`), `resource()` values derive it from their
48
49
  * fields ({@link RowOf}), and `"app.model"` strings resolve through the
49
- * {@link EntityTypes} registry `rastack compile` emits (`rastack-env.d.ts`).
50
+ * {@link EntityTypes} registry — populated by a one-line hand-written
51
+ * `EntityTypesOf<…>` declaration, or by the `rastack-env.d.ts` that
52
+ * `rastack compile` emits. No path involves generated runtime code.
50
53
  */
51
54
  export type EntityRow<E> =
52
55
  E extends EntityClass<infer R>
@@ -116,7 +119,10 @@ export function resolveEntityRef(
116
119
  manifest?: Manifest,
117
120
  ): ResolvedEntity {
118
121
  // `{ app, model }` — a `resource()` value, an EntityDescriptor, or a plain
119
- // object. The manifest enriches it (fields, transitions) when available.
122
+ // object. The manifest enriches it (fields, transitions) when available;
123
+ // a `resource()` value that a manifest doesn't cover is **its own
124
+ // manifest** — the definition carries its fields/options at runtime, so no
125
+ // compile step (and no provider config) is needed for the DSL path.
120
126
  if (typeof ref === "object" && ref !== null) {
121
127
  const { app, model } = ref as { app?: unknown; model?: unknown };
122
128
  if (typeof app !== "string" || typeof model !== "string") {
@@ -124,9 +130,9 @@ export function resolveEntityRef(
124
130
  "useData/useForm: an object entity reference needs string `app` and `model` properties",
125
131
  );
126
132
  }
127
- const resource = manifest?.resources.find(
128
- (r) => r.app === app && r.model === model,
129
- );
133
+ const resource =
134
+ manifest?.resources.find((r) => r.app === app && r.model === model) ??
135
+ (isResourceValue(ref) ? resourceModel(ref) : undefined);
130
136
  const opts = (ref as { options?: { transitions?: unknown } }).options;
131
137
  return resolved(app, model, resource, opts?.transitions);
132
138
  }
@@ -51,11 +51,8 @@ import {
51
51
  import { useRastackManifest } from "../manifest";
52
52
  import type { EntityTypes, RegisteredEntityKey } from "../registry";
53
53
  import { writeSchema } from "../../src/compile/openapi";
54
- import type {
55
- FieldModel,
56
- Manifest,
57
- ResourceModel,
58
- } from "../../src/compile/model";
54
+ import type { FieldModel, ResourceModel } from "../../src/compile/model";
55
+ import { toManifest, type ManifestInput } from "../../src/define/manifest";
59
56
  import {
60
57
  allowedTransitions,
61
58
  applyTransition,
@@ -71,8 +68,11 @@ export interface UseEntityFormOptions<TRow> {
71
68
  /** `"put"` to save with PUT; default is POST on create / PATCH on update. */
72
69
  updateType?: FormUpdate;
73
70
  onSuccess?: (row: TRow) => void;
74
- /** Manifest override — tests, or callers outside `<RAStackProvider>`. */
75
- manifest?: Manifest;
71
+ /**
72
+ * Manifest override — tests, or callers outside `<RAStackProvider>`.
73
+ * Accepts a compiled manifest or `resource()` definitions (array/module).
74
+ */
75
+ manifest?: ManifestInput;
76
76
  }
77
77
 
78
78
  /** One transition available from the record's current state. */
@@ -144,7 +144,11 @@ export function useEntityForm<TRow = any, TName extends string = string>(
144
144
  options: UseEntityFormOptions<TRow> = {},
145
145
  ): EntityFormResult<TRow, TName> {
146
146
  const contextManifest = useRastackManifest();
147
- const manifest = options.manifest ?? contextManifest;
147
+ const optionManifest = useMemo(
148
+ () => toManifest(options.manifest),
149
+ [options.manifest],
150
+ );
151
+ const manifest = optionManifest ?? contextManifest;
148
152
  const resolvedEntity = useMemo(
149
153
  () => resolveEntityRef(entity, manifest),
150
154
  [entity, manifest],
package/hooks/manifest.ts CHANGED
@@ -1,11 +1,14 @@
1
1
  /**
2
- * The compiled manifest (`schema.rastack.json`) as React context — what lets
3
- * `useData(Airport)` / `useForm(Flight)` resolve a plain TypeScript type to
4
- * its resource at runtime with no generated code in between.
2
+ * The manifest as React context — what lets `useData(Airport)` /
3
+ * `useForm(Flight)` resolve a plain TypeScript type to its resource at
4
+ * runtime with no generated code in between.
5
5
  *
6
6
  * `<RAStackProvider manifest={…}>` supplies it (in every mode — the manifest
7
7
  * is required for the WASM engine anyway, and in `remote` mode it is the only
8
- * piece of compiler output the client needs). Hooks read it with
8
+ * schema the client needs). It accepts the compiled `schema.rastack.json`
9
+ * (object or JSON string) **or the `resource()` definitions themselves** — an
10
+ * array, or a whole module namespace — in which case the manifest is built at
11
+ * runtime and no compile step is involved. Hooks read it with
9
12
  * {@link useRastackManifest}; tests and one-off callers can bypass the context
10
13
  * by passing `manifest` directly in a hook's options.
11
14
  */
@@ -13,29 +16,62 @@
13
16
  import { createContext, createElement, useContext, useMemo } from "react";
14
17
  import type { ReactNode } from "react";
15
18
  import type { Manifest } from "../src/compile/model";
19
+ import { toManifest, type ManifestInput } from "../src/define/manifest";
16
20
 
17
21
  export const RastackManifestContext = createContext<Manifest | undefined>(
18
22
  undefined,
19
23
  );
20
24
 
21
25
  export interface RastackManifestProviderProps {
22
- /** `schema.rastack.json` — parsed object or JSON string. */
23
- manifest?: Manifest | object | string;
26
+ /**
27
+ * `schema.rastack.json` (parsed object or JSON string), or `resource()`
28
+ * definitions (an array or a module namespace) to build it from at runtime.
29
+ */
30
+ manifest?: ManifestInput;
24
31
  children?: ReactNode;
25
32
  }
26
33
 
27
- /** Provide the compiled manifest to every `useData`/`useForm` beneath it. */
34
+ /** Provide the manifest to every `useData`/`useForm` beneath it. */
28
35
  export function RastackManifestProvider(props: RastackManifestProviderProps) {
29
36
  const { manifest, children } = props;
30
- const value = useMemo<Manifest | undefined>(() => {
31
- if (manifest === undefined || manifest === null) return undefined;
32
- if (typeof manifest === "string") return JSON.parse(manifest) as Manifest;
33
- return manifest as Manifest;
34
- }, [manifest]);
37
+ const value = useMemo<Manifest | undefined>(
38
+ () => toManifest(manifest) ?? getGlobalManifest(),
39
+ [manifest],
40
+ );
35
41
  return createElement(RastackManifestContext.Provider, { value }, children);
36
42
  }
37
43
 
38
- /** The compiled manifest, when a provider supplies one. */
44
+ /**
45
+ * The manifest in effect: the nearest provider's, else the one the dev-server
46
+ * plugin registered globally (see {@link getGlobalManifest}) — which is what
47
+ * lets an app pass **no manifest anywhere**.
48
+ */
39
49
  export function useRastackManifest(): Manifest | undefined {
40
- return useContext(RastackManifestContext);
50
+ return useContext(RastackManifestContext) ?? getGlobalManifest();
51
+ }
52
+
53
+ // -- the auto-registered manifest -------------------------------------------
54
+
55
+ /**
56
+ * The global the `rastack/plugin` dev-server plugin assigns before the app
57
+ * boots (its injected register module runs first): the manifest compiled
58
+ * from your TypeScript entities, refreshed on every save. With it in place,
59
+ * `<RAStackProvider mode="local">` needs no `manifest` prop and `useData` /
60
+ * `useForm` resolve entities with zero schema wiring in app code — the
61
+ * types are the only thing you author.
62
+ */
63
+ export const GLOBAL_MANIFEST_KEY = "__RASTACK_MANIFEST__";
64
+
65
+ let cachedRaw: unknown;
66
+ let cachedManifest: Manifest | undefined;
67
+
68
+ /** The plugin-registered manifest, normalised (and cached per assignment). */
69
+ export function getGlobalManifest(): Manifest | undefined {
70
+ const raw = (globalThis as Record<string, unknown>)[GLOBAL_MANIFEST_KEY];
71
+ if (raw === undefined || raw === null) return undefined;
72
+ if (raw !== cachedRaw) {
73
+ cachedRaw = raw;
74
+ cachedManifest = toManifest(raw as ManifestInput);
75
+ }
76
+ return cachedManifest;
41
77
  }
package/hooks/registry.ts CHANGED
@@ -7,8 +7,27 @@
7
7
  *
8
8
  * TypeScript can't conjure an interface out of a string on its own, so the
9
9
  * registry maps manifest keys (`"app.model"`) to your own types via ordinary
10
- * declaration merging. **You don't write it by hand**: `rastack compile`
11
- * emits `rastack-env.d.ts` alongside the manifest —
10
+ * declaration merging. Note that classes and `resource()` values never need
11
+ * it — they infer on their own; the registry only serves the string form.
12
+ * Two ways to populate it, **neither generating runtime code**:
13
+ *
14
+ * 1. **Hand-written, no compile step** — point `EntityTypesOf` at your
15
+ * resources module once; it derives every `"app.model"` key and row type
16
+ * from the definitions and tracks them forever:
17
+ *
18
+ * ```ts
19
+ * // rastack-env.d.ts — written once by you, never regenerated
20
+ * import type { EntityTypesOf } from "rastack/define";
21
+ * declare module "rastack/hooks/registry" {
22
+ * interface EntityTypes
23
+ * extends EntityTypesOf<typeof import("./resources/airports")> {}
24
+ * }
25
+ * ```
26
+ *
27
+ * 2. **Emitted by `rastack compile`** as `rastack-env.d.ts` beside the
28
+ * manifest — one entry per class/interface entity in the compiled graph
29
+ * (which have no runtime value for `EntityTypesOf` to read), pointing at
30
+ * *your* declaration:
12
31
  *
13
32
  * ```ts
14
33
  * declare module "rastack/hooks/registry" {
@@ -18,12 +37,10 @@
18
37
  * }
19
38
  * ```
20
39
  *
21
- * — one entry per class/interface entity in the compiled graph, pointing at
22
- * *your* declaration (types only, no runtime code, nothing to import). With
23
- * the file in the program, `useData` / `useRecord` / `useForm` overloads
24
- * resolve `"app.model"` arguments through {@link EntityTypes}; unregistered
25
- * strings fall back to `any` (or an explicit `useData<ITerminal>(…)`
26
- * argument, which always still works).
40
+ * With either file in the program, `useData` / `useRecord` / `useForm`
41
+ * overloads resolve `"app.model"` arguments through {@link EntityTypes};
42
+ * unregistered strings fall back to `any` (or an explicit
43
+ * `useData<ITerminal>(…)` argument, which always still works).
27
44
  */
28
45
 
29
46
  /** `"app.model"` → row type. Populated by declaration merging — see above. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rastack",
3
- "version": "0.0.50",
3
+ "version": "0.0.52",
4
4
  "description": "",
5
5
  "main": "runtime.ts",
6
6
  "types": "runtime.ts",
package/plugin.ts ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * `rastack/plugin` — the dev-server schema compiler. Add it to the bundler
3
+ * you already run (Vite/Rollup: `rastackPlugin()`; anything else:
4
+ * `startRastackWatcher()`) and class/interface entities compile to the
5
+ * manifest on every save — no `rastack compile` command in the loop.
6
+ * Node-only: import from build config, never from app code.
7
+ */
8
+ export * from "./src/plugin";
package/provider/index.ts CHANGED
@@ -1,10 +1,6 @@
1
1
  export * from "./types";
2
2
  export * from "./provider";
3
- export {
4
- createWasmAdapter,
5
- createEngine,
6
- defaultEngineLoader,
7
- } from "./wasm";
3
+ export { createWasmAdapter, createEngine, defaultEngineLoader } from "./wasm";
8
4
  export {
9
5
  WarehousePersistence,
10
6
  seedFromIceberg,