rastack 0.0.57 → 0.0.59

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 (86) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +190 -0
  3. package/admin/index.tsx +19 -13
  4. package/admin/ui/BootScreen.tsx +201 -0
  5. package/dist/admin.js +156 -18
  6. package/dist/deploy/ci.js +2 -2
  7. package/dist/deploy/io.js +2 -2
  8. package/dist/dev/harness.d.ts +1 -1
  9. package/dist/dev/harness.js +21 -5
  10. package/dist/external/cache.d.ts +28 -0
  11. package/dist/external/cache.js +35 -0
  12. package/dist/external/envelope.d.ts +35 -0
  13. package/dist/external/envelope.js +48 -0
  14. package/dist/external/graphql.d.ts +25 -0
  15. package/dist/external/graphql.js +78 -0
  16. package/dist/external/handler.d.ts +52 -0
  17. package/dist/external/handler.js +139 -0
  18. package/dist/external/index.d.ts +19 -0
  19. package/dist/external/index.js +38 -0
  20. package/dist/external/lambda.d.ts +49 -0
  21. package/dist/external/lambda.js +86 -0
  22. package/dist/external/rest.d.ts +17 -0
  23. package/dist/external/rest.js +46 -0
  24. package/dist/external/route.d.ts +12 -0
  25. package/dist/external/route.js +33 -0
  26. package/dist/external/service.d.ts +50 -0
  27. package/dist/external/service.js +59 -0
  28. package/dist/external/types.d.ts +107 -0
  29. package/dist/external/types.js +22 -0
  30. package/dist/external/util.d.ts +16 -0
  31. package/dist/external/util.js +58 -0
  32. package/dist/rastack-dev.js +200 -4
  33. package/dist/rastack-wasm-build.d.ts +1 -1
  34. package/dist/rastack-wasm-build.js +6 -6
  35. package/dist/wasm/rastack_wasm_bg.wasm +0 -0
  36. package/external.ts +10 -0
  37. package/hooks/query/delete.ts +12 -11
  38. package/hooks/query/fetch.ts +6 -8
  39. package/hooks/query/interfaces.ts +1 -4
  40. package/hooks/query/list.ts +16 -9
  41. package/hooks/query/update.ts +8 -6
  42. package/package.json +25 -2
  43. package/scripts/copy-templates.js +1 -1
  44. package/scripts/copy-wasm.js +1 -1
  45. package/scripts/dev-install.mjs +63 -0
  46. package/src/deploy/ci.ts +2 -2
  47. package/src/deploy/io.ts +2 -2
  48. package/src/dev/harness.ts +21 -5
  49. package/src/external/cache.ts +53 -0
  50. package/src/external/envelope.ts +78 -0
  51. package/src/external/graphql.ts +98 -0
  52. package/src/external/handler.ts +207 -0
  53. package/src/external/index.ts +25 -0
  54. package/src/external/lambda.ts +108 -0
  55. package/src/external/rest.ts +60 -0
  56. package/src/external/route.ts +33 -0
  57. package/src/external/service.ts +86 -0
  58. package/src/external/types.ts +121 -0
  59. package/src/external/util.ts +57 -0
  60. package/src/rastack-dev.ts +233 -6
  61. package/src/rastack-wasm-build.ts +6 -6
  62. package/jest.config.cjs +0 -12
  63. package/test/auth.spec.ts +0 -207
  64. package/test/cache.spec.ts +0 -263
  65. package/test/cognito.spec.ts +0 -281
  66. package/test/compile.spec.ts +0 -240
  67. package/test/components.spec.ts +0 -524
  68. package/test/csv-schema.spec.ts +0 -143
  69. package/test/deploy.spec.ts +0 -571
  70. package/test/dev.spec.ts +0 -414
  71. package/test/entities.spec.ts +0 -597
  72. package/test/import.spec.ts +0 -241
  73. package/test/plugin.spec.ts +0 -315
  74. package/test/runtime-manifest.spec.ts +0 -309
  75. package/test/schema-entities.spec.ts +0 -688
  76. package/test/tokens.spec.ts +0 -302
  77. package/test/transitions.spec.ts +0 -372
  78. package/test/typed-hooks.spec.ts +0 -412
  79. package/test/update.spec.ts +0 -152
  80. package/test/validate.spec.ts +0 -319
  81. package/tsconfig.json +0 -27
  82. package/wasm/package.json +0 -4
  83. package/wasm/rastack_wasm.d.ts +0 -100
  84. package/wasm/rastack_wasm.js +0 -745
  85. package/wasm/rastack_wasm_bg.wasm +0 -0
  86. package/wasm/rastack_wasm_bg.wasm.d.ts +0 -28
@@ -1,309 +0,0 @@
1
- /**
2
- * The runtime manifest builder — `resource()` definitions are their own
3
- * manifest, no `rastack compile` step required.
4
- *
5
- * The headline test is **parity**: the same definitions, authored once as
6
- * source (compiled by the real compiler) and once as runtime values
7
- * (assembled by `manifestFromResources`), must produce the identical
8
- * manifest. That equivalence is what makes the compile step optional for the
9
- * DSL path. Around it: the provider-input normalisation (`toManifest`),
10
- * manifest-free entity resolution in the hooks, and the hand-written
11
- * `EntityTypesOf` registry types.
12
- */
13
-
14
- import { mkdirSync, rmSync, writeFileSync } from "fs";
15
- import { join } from "path";
16
- import { compile } from "../src/compile";
17
- import {
18
- isResourceValue,
19
- manifestFromResources,
20
- resource,
21
- resourceModel,
22
- s,
23
- toManifest,
24
- type EntityKeyOf,
25
- type EntityTypesOf,
26
- } from "../src/define";
27
- import { resolveEntityRef } from "../hooks/entity";
28
- import { initialValuesFromResource } from "../hooks/form/entity-form";
29
- import { writeSchema } from "../src/compile/openapi";
30
- import type { Manifest } from "../src/compile/model";
31
-
32
- // ---------------------------------------------------------------------------
33
- // The same definitions twice — as source for the compiler, and as runtime
34
- // values for manifestFromResources. Keep the two in sync: the parity test
35
- // exists to prove the two paths agree for identical input.
36
- // ---------------------------------------------------------------------------
37
-
38
- const FIXTURE_SOURCE = `
39
- import { resource, s } from "rastack/define";
40
-
41
- export const Airport = resource(
42
- "airports",
43
- "airport",
44
- {
45
- code: s.string({ maxLength: 3, unique: true }),
46
- name: s.string({ maxLength: 120 }),
47
- open: s.bool({ default: true }),
48
- elevation: s.int({ null: true }),
49
- },
50
- {
51
- search: ["code", "name"],
52
- ordering: ["code"],
53
- admin: { listDisplay: ["code", "name"] },
54
- permission: "authenticatedOrReadOnly",
55
- },
56
- );
57
-
58
- export const Terminal = resource("airports", "terminal", {
59
- airport: s.ref(() => Airport),
60
- label: s.string({ maxLength: 20 }),
61
- });
62
-
63
- export const Flight = resource(
64
- "airports",
65
- "flight",
66
- {
67
- number: s.string({ maxLength: 8 }),
68
- status: s.string({ maxLength: 20 }),
69
- owner_id: s.int(),
70
- },
71
- {
72
- search: ["number"],
73
- sync: { mode: "local", scope: "owner_id={user.id}", confirmed: ["update"] },
74
- access: { ownerField: "owner_id", scope: "owner", adminGroups: ["staff"] },
75
- transitions: {
76
- field: "status",
77
- states: ["scheduled", "boarding", "departed", "cancelled"],
78
- initial: "scheduled",
79
- on: {
80
- board: { from: "scheduled", to: "boarding" },
81
- depart: { from: "boarding", to: "departed" },
82
- cancel: { from: ["scheduled", "boarding"], to: "cancelled" },
83
- },
84
- },
85
- },
86
- );
87
- `;
88
-
89
- const Airport = resource(
90
- "airports",
91
- "airport",
92
- {
93
- code: s.string({ maxLength: 3, unique: true }),
94
- name: s.string({ maxLength: 120 }),
95
- open: s.bool({ default: true }),
96
- elevation: s.int({ null: true }),
97
- },
98
- {
99
- search: ["code", "name"],
100
- ordering: ["code"],
101
- admin: { listDisplay: ["code", "name"] },
102
- permission: "authenticatedOrReadOnly",
103
- },
104
- );
105
-
106
- const Terminal = resource("airports", "terminal", {
107
- airport: s.ref(() => Airport),
108
- label: s.string({ maxLength: 20 }),
109
- });
110
-
111
- const Flight = resource(
112
- "airports",
113
- "flight",
114
- {
115
- number: s.string({ maxLength: 8 }),
116
- status: s.string({ maxLength: 20 }),
117
- owner_id: s.int(),
118
- },
119
- {
120
- search: ["number"],
121
- sync: { mode: "local", scope: "owner_id={user.id}", confirmed: ["update"] },
122
- access: { ownerField: "owner_id", scope: "owner", adminGroups: ["staff"] },
123
- transitions: {
124
- field: "status",
125
- states: ["scheduled", "boarding", "departed", "cancelled"],
126
- initial: "scheduled",
127
- on: {
128
- board: { from: "scheduled", to: "boarding" },
129
- depart: { from: "boarding", to: "departed" },
130
- cancel: { from: ["scheduled", "boarding"], to: "cancelled" },
131
- },
132
- },
133
- },
134
- );
135
-
136
- /** The manifest as it would be serialised — what every consumer reads. */
137
- function asJson(manifest: Manifest): unknown {
138
- return JSON.parse(JSON.stringify(manifest));
139
- }
140
-
141
- describe("manifestFromResources — parity with rastack compile", () => {
142
- const workRoot = join(__dirname, "runtime-manifest-fixtures");
143
- const resourcesDir = join(workRoot, "resources");
144
- const outDir = join(workRoot, ".rastack");
145
-
146
- beforeAll(() => {
147
- mkdirSync(resourcesDir, { recursive: true });
148
- writeFileSync(join(resourcesDir, "airline.ts"), FIXTURE_SOURCE);
149
- });
150
-
151
- afterAll(() => {
152
- rmSync(workRoot, { recursive: true, force: true });
153
- });
154
-
155
- it("produces the exact manifest the compiler emits for the same definitions", () => {
156
- const compiled = compile(workRoot, outDir).manifest;
157
- const runtime = manifestFromResources(Airport, Terminal, Flight);
158
- expect(asJson(runtime)).toEqual(asJson(compiled));
159
-
160
- // Belt and braces: the interesting derived bits are actually present.
161
- const flight = runtime.resources.find((r) => r.model === "flight")!;
162
- expect(flight.transitions?.initial).toBe("scheduled");
163
- const status = flight.fields.find((f) => f.name === "status")!;
164
- expect(status.options).toEqual([
165
- "scheduled",
166
- "boarding",
167
- "departed",
168
- "cancelled",
169
- ]);
170
- expect(status.default).toBe("scheduled"); // machine initial → field default
171
- const terminal = runtime.resources.find((r) => r.model === "terminal")!;
172
- expect(terminal.relations).toEqual([
173
- { field: "airport", target: "airports.airport", kind: "fk" },
174
- ]);
175
- });
176
-
177
- it("orders resources like the compiler (app, then model)", () => {
178
- const runtime = manifestFromResources(Flight, Terminal, Airport);
179
- expect(runtime.resources.map((r) => r.model)).toEqual([
180
- "airport",
181
- "flight",
182
- "terminal",
183
- ]);
184
- });
185
-
186
- it("rejects an s.ref whose runtime target is missing", () => {
187
- const broken = {
188
- __rastack: "resource",
189
- app: "airports",
190
- model: "gate",
191
- fields: { terminal: { __rastackRef: true } },
192
- options: {},
193
- } as any;
194
- expect(() => resourceModel(broken)).toThrow(/target thunk/);
195
- });
196
- });
197
-
198
- describe("toManifest — everything a provider may be handed", () => {
199
- const expected = asJson(manifestFromResources(Airport, Terminal));
200
-
201
- it("passes a compiled manifest through (object and JSON string)", () => {
202
- const compiledShape = manifestFromResources(Airport, Terminal);
203
- expect(toManifest(compiledShape)).toBe(compiledShape);
204
- expect(asJson(toManifest(JSON.stringify(compiledShape))!)).toEqual(expected);
205
- });
206
-
207
- it("builds from an array of resource() values", () => {
208
- expect(asJson(toManifest([Airport, Terminal])!)).toEqual(expected);
209
- });
210
-
211
- it("builds from a module namespace of resource() exports", () => {
212
- const moduleNamespace = { Airport, Terminal, unrelated: 42, helper: () => 0 };
213
- expect(asJson(toManifest(moduleNamespace)!)).toEqual(expected);
214
- });
215
-
216
- it("builds from a single resource() value and handles empties", () => {
217
- const single = toManifest(Airport)!;
218
- expect(single.resources.map((r) => r.model)).toEqual(["airport"]);
219
- expect(toManifest(undefined)).toBeUndefined();
220
- expect(toManifest(null)).toBeUndefined();
221
- });
222
-
223
- it("classifies resource() values", () => {
224
- expect(isResourceValue(Airport)).toBe(true);
225
- expect(isResourceValue({ app: "a", model: "b" })).toBe(false);
226
- expect(isResourceValue("airports.airport")).toBe(false);
227
- });
228
- });
229
-
230
- describe("hooks without a manifest — the definition is its own manifest", () => {
231
- it("resolveEntityRef synthesises the resource from a resource() value", () => {
232
- const resolved = resolveEntityRef(Flight); // no manifest anywhere
233
- expect(resolved.listUrl).toBe("/api/airports/v1/flight/");
234
- expect(resolved.resource).toBeDefined();
235
- expect(resolved.resource!.relations).toEqual([]);
236
- expect(resolved.transitions?.on.board).toEqual({
237
- from: ["scheduled"],
238
- to: "boarding",
239
- });
240
- // A supplied manifest still wins — the synthesised model is the fallback.
241
- const manifest = manifestFromResources(Flight);
242
- expect(resolveEntityRef(Flight, manifest).resource).toBe(
243
- manifest.resources[0],
244
- );
245
- });
246
-
247
- it("feeds the same write schema and form defaults the compiled path does", () => {
248
- const resolved = resolveEntityRef(Flight);
249
- const schema = writeSchema(resolved.resource!, false);
250
- expect(schema.properties.status.enum).toEqual([
251
- "scheduled",
252
- "boarding",
253
- "departed",
254
- "cancelled",
255
- ]);
256
- expect(schema["x-rastack-transitions"]).toBe(resolved.resource!.transitions);
257
- expect(initialValuesFromResource(resolved.resource!)).toEqual({
258
- number: "",
259
- status: "scheduled",
260
- owner_id: "",
261
- });
262
- });
263
-
264
- it("synthesises FK relations through the runtime target thunk", () => {
265
- const resolved = resolveEntityRef(Terminal);
266
- expect(resolved.resource!.relations).toEqual([
267
- { field: "airport", target: "airports.airport", kind: "fk" },
268
- ]);
269
- const schema = writeSchema(resolved.resource!, false);
270
- expect(schema.properties.airport["x-rastack-relation"]).toEqual({
271
- app: "airports",
272
- model: "airport",
273
- });
274
- });
275
- });
276
-
277
- // ---------------------------------------------------------------------------
278
- // EntityTypesOf — the hand-written registry, typed from the module itself
279
- // ---------------------------------------------------------------------------
280
-
281
- describe("EntityTypesOf — string-key row types with no codegen", () => {
282
- it("derives keys and rows from a resources module", () => {
283
- const moduleShape = { Airport, Terminal, Flight, notAResource: 42 };
284
- type Registry = EntityTypesOf<typeof moduleShape>;
285
-
286
- // The declaration-merging pattern the docs recommend must compile:
287
- // an interface extending the mapped type.
288
- interface Merged extends EntityTypesOf<typeof moduleShape> {}
289
-
290
- const key: EntityKeyOf<typeof Airport> = "airports.airport";
291
- const row: Registry["airports.airport"] = {
292
- id: 1,
293
- code: "LHR",
294
- name: "Heathrow",
295
- open: true,
296
- elevation: null, // `null: true` → number | null
297
- };
298
- const merged: Merged["airports.terminal"] = {
299
- id: 2,
300
- airport: 1,
301
- label: "T5",
302
- };
303
- // @ts-expect-error — `open` is a boolean
304
- const wrong: Registry["airports.airport"] = { ...row, open: "yes" };
305
- // @ts-expect-error — non-resource exports contribute no registry keys
306
- const nope: Registry["notAResource"] = 42;
307
- expect([key, row, merged, wrong, nope]).toBeDefined();
308
- });
309
- });