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.
- package/CHANGELOG.md +4 -0
- package/auth/context.ts +73 -0
- package/auth/core.ts +293 -0
- package/auth/index.ts +23 -0
- package/components/auto-form/AutoForm.tsx +32 -12
- package/components/auto-form/use-auto-form.ts +75 -6
- package/components/data-table/DataTable.tsx +72 -45
- package/components/data-table/use-data-table.ts +57 -3
- package/components/index.ts +2 -0
- package/db.ts +9 -0
- package/dist/admin.js +13 -13
- package/dist/compile/analyze.d.ts +1 -7
- package/dist/compile/analyze.js +19 -70
- package/dist/compile/entities.js +163 -16
- package/dist/compile/model.d.ts +7 -3
- package/dist/compile/openapi.js +13 -3
- package/dist/compile/program.d.ts +2 -1
- package/dist/compile/program.js +7 -2
- package/dist/define/auth.d.ts +27 -0
- package/dist/define/auth.js +42 -0
- package/dist/define/db.d.ts +24 -1
- package/dist/define/db.js +1 -1
- package/dist/define/index.d.ts +82 -18
- package/dist/define/index.js +27 -21
- package/dist/define/manifest.d.ts +64 -0
- package/dist/define/manifest.js +259 -0
- package/dist/define/markers.d.ts +80 -0
- package/dist/define/markers.js +37 -0
- package/dist/define/types.d.ts +10 -0
- package/dist/define/types.js +26 -0
- package/dist/plugin/core.d.ts +108 -0
- package/dist/plugin/core.js +198 -0
- package/dist/plugin/index.d.ts +112 -0
- package/dist/plugin/index.js +203 -0
- package/dist/wasm/rastack_wasm.js +1 -1
- package/dist/wasm/rastack_wasm_bg.wasm +0 -0
- package/hooks/data.ts +18 -6
- package/hooks/entity.ts +11 -5
- package/hooks/form/entity-form.ts +12 -8
- package/hooks/manifest.ts +50 -14
- package/hooks/registry.ts +25 -8
- package/package.json +1 -1
- package/plugin.ts +8 -0
- package/provider/index.ts +1 -5
- package/provider/provider.tsx +61 -16
- package/provider/types.ts +26 -7
- package/provider/warehouse.ts +4 -3
- package/provider/wasm.ts +3 -1
- package/runtime.ts +1 -0
- package/src/compile/analyze.ts +27 -65
- package/src/compile/entities.ts +193 -19
- package/src/compile/model.ts +8 -9
- package/src/compile/openapi.ts +11 -3
- package/src/compile/program.ts +10 -3
- package/src/define/auth.ts +28 -0
- package/src/define/db.ts +26 -4
- package/src/define/index.ts +118 -24
- package/src/define/manifest.ts +287 -0
- package/src/define/markers.ts +83 -0
- package/src/define/types.ts +11 -0
- package/src/plugin/core.ts +236 -0
- package/src/plugin/index.ts +243 -0
- package/test/auth.spec.ts +207 -0
- package/test/compile.spec.ts +18 -7
- package/test/components.spec.ts +116 -0
- package/test/entities.spec.ts +123 -6
- package/test/plugin.spec.ts +315 -0
- package/test/runtime-manifest.spec.ts +309 -0
- package/test/typed-hooks.spec.ts +7 -2
- package/types.ts +7 -2
- package/wasm/rastack_wasm.js +1 -1
- 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:
|
|
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 {
|
|
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
|
-
/**
|
|
59
|
-
|
|
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
|
|
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?:
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
/**
|
|
75
|
-
|
|
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
|
|
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
|
|
3
|
-
* `
|
|
4
|
-
*
|
|
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
|
-
*
|
|
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
|
-
/**
|
|
23
|
-
|
|
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
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
/**
|
|
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.
|
|
11
|
-
*
|
|
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
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
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
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,
|