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.
- package/CHANGELOG.md +4 -0
- package/components/auto-form/AutoForm.tsx +13 -0
- package/components/auto-form/use-auto-form.ts +28 -0
- package/components/types.ts +8 -0
- package/dist/admin.js +13 -13
- package/dist/compile/analyze.d.ts +20 -0
- package/dist/compile/analyze.js +60 -49
- package/dist/compile/entities.d.ts +19 -0
- package/dist/compile/entities.js +87 -13
- package/dist/compile/index.d.ts +23 -4
- package/dist/compile/index.js +86 -7
- package/dist/compile/model.d.ts +38 -0
- package/dist/compile/openapi.d.ts +9 -0
- package/dist/compile/openapi.js +14 -0
- package/dist/define/index.d.ts +162 -21
- package/dist/define/index.js +28 -22
- package/dist/define/manifest.d.ts +64 -0
- package/dist/define/manifest.js +250 -0
- package/dist/import/tabular.d.ts +8 -2
- package/dist/import/tabular.js +1 -1
- 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/rastack-import.js +4 -1
- package/dist/validate/adapters.js +2 -0
- package/dist/validate/index.d.ts +1 -0
- package/dist/validate/index.js +1 -0
- package/dist/validate/machine.d.ts +23 -2
- package/dist/validate/machine.js +35 -2
- package/dist/validate/transitions.d.ts +86 -0
- package/dist/validate/transitions.js +199 -0
- package/dist/wasm/rastack_wasm.js +1 -1
- package/dist/wasm/rastack_wasm_bg.wasm +0 -0
- package/hooks/data.ts +221 -0
- package/hooks/entity.ts +228 -0
- package/hooks/form/entity-form.ts +358 -0
- package/hooks/form/form.ts +8 -1
- package/hooks/form/index.ts +7 -1
- package/hooks/index.ts +4 -0
- package/hooks/manifest.ts +77 -0
- package/hooks/registry.ts +56 -0
- package/package.json +1 -1
- package/plugin.ts +8 -0
- package/provider/provider.tsx +26 -5
- package/provider/types.ts +15 -3
- package/src/compile/analyze.ts +74 -45
- package/src/compile/entities.ts +111 -11
- package/src/compile/index.ts +108 -11
- package/src/compile/model.ts +40 -0
- package/src/compile/openapi.ts +13 -1
- package/src/define/index.ts +233 -29
- package/src/define/manifest.ts +278 -0
- package/src/import/tabular.ts +9 -2
- package/src/plugin/core.ts +236 -0
- package/src/plugin/index.ts +243 -0
- package/src/rastack-import.ts +4 -1
- package/src/validate/adapters.ts +1 -0
- package/src/validate/index.ts +1 -0
- package/src/validate/machine.ts +55 -3
- package/src/validate/transitions.ts +232 -0
- package/test/components.spec.ts +22 -0
- package/test/plugin.spec.ts +315 -0
- package/test/runtime-manifest.spec.ts +309 -0
- package/test/transitions.spec.ts +372 -0
- package/test/typed-hooks.spec.ts +412 -0
- package/wasm/rastack_wasm.js +1 -1
- package/wasm/rastack_wasm_bg.wasm +0 -0
package/dist/compile/openapi.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.buildOpenApi = buildOpenApi;
|
|
4
4
|
exports.openApiToJson = openApiToJson;
|
|
5
|
+
exports.writeSchema = writeSchema;
|
|
5
6
|
/**
|
|
6
7
|
* Emit an OpenAPI 3 document from the analysed resources. The output reproduces
|
|
7
8
|
* the *exact* contract the existing `tools/` codegen and `stack/` runtime rely
|
|
@@ -86,6 +87,14 @@ function readSchema(resource) {
|
|
|
86
87
|
}
|
|
87
88
|
return { type: "object", properties, required };
|
|
88
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* The write JSON Schema for a resource — what the API accepts on
|
|
92
|
+
* create/update. Exported because the generic `useForm(Entity)` hook derives
|
|
93
|
+
* the *same* schema at runtime straight from the manifest, so a form
|
|
94
|
+
* validates against exactly the contract the emitted OpenAPI documents.
|
|
95
|
+
* Resources with a state machine carry it as `x-rastack-transitions`, the
|
|
96
|
+
* write-side twin of `x-rastack-relation`.
|
|
97
|
+
*/
|
|
89
98
|
function writeSchema(resource, patched) {
|
|
90
99
|
const properties = {};
|
|
91
100
|
const required = [];
|
|
@@ -100,6 +109,9 @@ function writeSchema(resource, patched) {
|
|
|
100
109
|
const schema = { type: "object", properties };
|
|
101
110
|
if (required.length)
|
|
102
111
|
schema.required = required;
|
|
112
|
+
if (resource.transitions) {
|
|
113
|
+
schema["x-rastack-transitions"] = resource.transitions;
|
|
114
|
+
}
|
|
103
115
|
return schema;
|
|
104
116
|
}
|
|
105
117
|
function paginatedSchema(pascalModel) {
|
|
@@ -127,6 +139,8 @@ function fieldProperty(field) {
|
|
|
127
139
|
const base = scalarProperty(field.type);
|
|
128
140
|
if (field.maxLength !== undefined)
|
|
129
141
|
base.maxLength = field.maxLength;
|
|
142
|
+
if (field.options && field.options.length > 0)
|
|
143
|
+
base.enum = [...field.options];
|
|
130
144
|
if (field.null)
|
|
131
145
|
base.nullable = true;
|
|
132
146
|
return base;
|
package/dist/define/index.d.ts
CHANGED
|
@@ -22,18 +22,48 @@
|
|
|
22
22
|
* });
|
|
23
23
|
* ```
|
|
24
24
|
*/
|
|
25
|
-
/**
|
|
26
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Every constraint a scalar builder may be handed, merged — the shape the
|
|
27
|
+
* runtime value carries so a manifest can be assembled without the compiler.
|
|
28
|
+
*/
|
|
29
|
+
export interface ScalarFieldOptions {
|
|
30
|
+
maxLength?: number;
|
|
31
|
+
unique?: boolean;
|
|
32
|
+
primaryKey?: boolean;
|
|
33
|
+
default?: unknown;
|
|
34
|
+
null?: boolean;
|
|
35
|
+
autoNow?: boolean;
|
|
36
|
+
autoNowAdd?: boolean;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* A scalar field. The literal `__rastackScalar` type is what the compiler
|
|
40
|
+
* reads; `Nullable` phantom-carries the `null: true` option so {@link RowOf}
|
|
41
|
+
* can add `| null` to the inferred row type. The authored options ride along
|
|
42
|
+
* on the runtime value too, so `manifestFromResources()` can build the same
|
|
43
|
+
* manifest `rastack compile` emits — with no compile step at all.
|
|
44
|
+
*/
|
|
45
|
+
export interface Scalar<T extends string, Nullable extends boolean = boolean> {
|
|
27
46
|
readonly __rastackScalar: T;
|
|
47
|
+
readonly __rastackNullable?: Nullable;
|
|
48
|
+
/** The constraint options as authored — carried at runtime. */
|
|
49
|
+
readonly options?: ScalarFieldOptions;
|
|
28
50
|
}
|
|
51
|
+
/** Whether an options literal declares `null: true` (as a type). */
|
|
52
|
+
type NullOf<O> = O extends {
|
|
53
|
+
null: true;
|
|
54
|
+
} ? true : false;
|
|
29
55
|
/**
|
|
30
56
|
* A foreign-key field. `App`/`Model` are carried as string-literal type
|
|
31
|
-
* arguments so the compiler can recover the relation target purely from
|
|
57
|
+
* arguments so the compiler can recover the relation target purely from
|
|
58
|
+
* types; the target thunk is *also* kept on the runtime value so
|
|
59
|
+
* `manifestFromResources()` can recover the relation with no compiler.
|
|
32
60
|
*/
|
|
33
61
|
export interface Ref<App extends string, Model extends string> {
|
|
34
62
|
readonly __rastackRef: true;
|
|
35
63
|
readonly __app: App;
|
|
36
64
|
readonly __model: Model;
|
|
65
|
+
/** The authored `() => Resource` thunk — carried at runtime. */
|
|
66
|
+
readonly target?: () => ResourceType<App, Model, any>;
|
|
37
67
|
}
|
|
38
68
|
export interface StringOptions {
|
|
39
69
|
maxLength?: number;
|
|
@@ -58,24 +88,24 @@ export interface DateOptions {
|
|
|
58
88
|
null?: boolean;
|
|
59
89
|
}
|
|
60
90
|
/**
|
|
61
|
-
* Field builders. Each returns a
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
91
|
+
* Field builders. Each returns a value whose *type* encodes the field kind
|
|
92
|
+
* (what the compiler reads — it analyses the source, it does not execute it)
|
|
93
|
+
* and whose runtime shape keeps the authored options/target, so the same
|
|
94
|
+
* definition doubles as its own manifest via `manifestFromResources()`.
|
|
65
95
|
*/
|
|
66
96
|
export declare const s: {
|
|
67
|
-
string(
|
|
68
|
-
int(
|
|
69
|
-
float(
|
|
70
|
-
bool(
|
|
71
|
-
datetime(
|
|
72
|
-
uuid(
|
|
97
|
+
string<const O extends StringOptions = StringOptions>(opts?: O): Scalar<"string", NullOf<O>>;
|
|
98
|
+
int<const O extends NumberOptions = NumberOptions>(opts?: O): Scalar<"int", NullOf<O>>;
|
|
99
|
+
float<const O extends NumberOptions = NumberOptions>(opts?: O): Scalar<"float", NullOf<O>>;
|
|
100
|
+
bool<const O extends BoolOptions = BoolOptions>(opts?: O): Scalar<"bool", NullOf<O>>;
|
|
101
|
+
datetime<const O extends DateOptions = DateOptions>(opts?: O): Scalar<"datetime", NullOf<O>>;
|
|
102
|
+
uuid<const O extends StringOptions = StringOptions>(opts?: O): Scalar<"uuid", NullOf<O>>;
|
|
73
103
|
/**
|
|
74
104
|
* A foreign key. The target is `() => SomeResource`; the compiler resolves
|
|
75
|
-
* the relation from the *type* `Ref<App, Model>` this returns
|
|
76
|
-
*
|
|
105
|
+
* the relation from the *type* `Ref<App, Model>` this returns, and the
|
|
106
|
+
* runtime keeps the thunk so `manifestFromResources()` can resolve it too.
|
|
77
107
|
*/
|
|
78
|
-
ref<A extends string, M extends string>(
|
|
108
|
+
ref<A extends string, M extends string>(thunk: () => ResourceType<A, M, any>): Ref<A, M>;
|
|
79
109
|
};
|
|
80
110
|
export type AnyField = Scalar<string> | Ref<string, string>;
|
|
81
111
|
/** Local-first sync configuration (mirrors the old Python `class Sync:`). */
|
|
@@ -105,6 +135,52 @@ export interface AccessOptions {
|
|
|
105
135
|
scope?: "owner" | "shared";
|
|
106
136
|
adminGroups?: string[];
|
|
107
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* One edge of a resource's state machine. `from` is the state (or states) the
|
|
140
|
+
* transition may fire from, `to` the state it enters, and `set` the effect —
|
|
141
|
+
* field patches the engine applies alongside the state change. The block is
|
|
142
|
+
* static data: it compiles into the manifest and is enforced by
|
|
143
|
+
* `rastack-api-core` on every surface (server, Lambda, in-browser WASM), so a
|
|
144
|
+
* transition behaves as a declarative, typesafe backend function with no
|
|
145
|
+
* per-resource server code.
|
|
146
|
+
*/
|
|
147
|
+
export interface TransitionEdge {
|
|
148
|
+
from: string | readonly string[];
|
|
149
|
+
to: string;
|
|
150
|
+
set?: Record<string, string | number | boolean | null>;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* A resource's declarative state machine, keyed off one state field:
|
|
154
|
+
*
|
|
155
|
+
* ```ts
|
|
156
|
+
* transitions: {
|
|
157
|
+
* field: "status",
|
|
158
|
+
* states: ["scheduled", "boarding", "departed", "cancelled"],
|
|
159
|
+
* initial: "scheduled",
|
|
160
|
+
* on: {
|
|
161
|
+
* board: { from: "scheduled", to: "boarding" },
|
|
162
|
+
* depart: { from: "boarding", to: "departed" },
|
|
163
|
+
* cancel: { from: ["scheduled", "boarding"], to: "cancelled" },
|
|
164
|
+
* },
|
|
165
|
+
* }
|
|
166
|
+
* ```
|
|
167
|
+
*
|
|
168
|
+
* `states` becomes the field's allowed-value set (`enum` in OpenAPI, the
|
|
169
|
+
* `member` gate in the validation machine) and `initial` its default. A write
|
|
170
|
+
* that moves the field between states without a declared edge is rejected on
|
|
171
|
+
* every surface, and `useForm(Flight).transition("board")` exposes the edges
|
|
172
|
+
* as typed form actions.
|
|
173
|
+
*/
|
|
174
|
+
export interface TransitionsOptions {
|
|
175
|
+
/** The field carrying the machine state. */
|
|
176
|
+
field: string;
|
|
177
|
+
/** Every state the field may hold. */
|
|
178
|
+
states: readonly string[];
|
|
179
|
+
/** The state new records start in. */
|
|
180
|
+
initial?: string;
|
|
181
|
+
/** Named transitions. */
|
|
182
|
+
on: Record<string, TransitionEdge>;
|
|
183
|
+
}
|
|
108
184
|
export interface ResourceOptions {
|
|
109
185
|
/** DRF-style search fields. */
|
|
110
186
|
search?: string[];
|
|
@@ -120,19 +196,84 @@ export interface ResourceOptions {
|
|
|
120
196
|
permission?: "authenticatedOrReadOnly" | "authenticated" | "public";
|
|
121
197
|
/** Row-level security + physical tenant partitioning. */
|
|
122
198
|
access?: AccessOptions;
|
|
199
|
+
/** Declarative state machine over one field, enforced on every write surface. */
|
|
200
|
+
transitions?: TransitionsOptions;
|
|
123
201
|
}
|
|
124
202
|
/**
|
|
125
203
|
* A resource definition. `App`/`Model` are preserved as string-literal type
|
|
126
|
-
* arguments so a {@link Ref} to this resource carries a recoverable target
|
|
204
|
+
* arguments so a {@link Ref} to this resource carries a recoverable target,
|
|
205
|
+
* and the options object's literal type is preserved so downstream types can
|
|
206
|
+
* read the declared transition names (`useForm(Flight).transition("board")`
|
|
207
|
+
* only accepts names the resource actually declares).
|
|
127
208
|
*/
|
|
128
|
-
export interface ResourceType<App extends string, Model extends string, F> {
|
|
209
|
+
export interface ResourceType<App extends string, Model extends string, F, O extends ResourceOptions = ResourceOptions> {
|
|
129
210
|
readonly __rastack: "resource";
|
|
130
211
|
readonly __app: App;
|
|
131
212
|
readonly __model: Model;
|
|
132
213
|
readonly app: App;
|
|
133
214
|
readonly model: Model;
|
|
134
215
|
readonly fields: F;
|
|
135
|
-
readonly options:
|
|
216
|
+
readonly options: O;
|
|
136
217
|
}
|
|
137
|
-
/** Define a resource. Capture app/model as literal types
|
|
138
|
-
export declare function resource<App extends string, Model extends string, F extends Record<string, AnyField
|
|
218
|
+
/** Define a resource. Capture app/model (and options) as literal types. */
|
|
219
|
+
export declare function resource<App extends string, Model extends string, F extends Record<string, AnyField>, const O extends ResourceOptions = ResourceOptions>(app: App, model: Model, fields: F, options?: O): ResourceType<App, Model, F, O>;
|
|
220
|
+
/**
|
|
221
|
+
* The transition names an entity declares — from a `resource()` value's
|
|
222
|
+
* options, or from a class's `static transitions` block. Entities with no
|
|
223
|
+
* transitions resolve to `never`, so `form.transition(...)` is uncallable on
|
|
224
|
+
* them at the type level.
|
|
225
|
+
*/
|
|
226
|
+
export type TransitionNamesOf<E> = E extends {
|
|
227
|
+
options: {
|
|
228
|
+
transitions: {
|
|
229
|
+
on: infer On;
|
|
230
|
+
};
|
|
231
|
+
};
|
|
232
|
+
} ? Extract<keyof On, string> : E extends {
|
|
233
|
+
transitions: {
|
|
234
|
+
on: infer On;
|
|
235
|
+
};
|
|
236
|
+
} ? Extract<keyof On, string> : E extends string ? string : never;
|
|
237
|
+
type ScalarTsType<T extends string> = T extends "int" | "float" ? number : T extends "bool" ? boolean : string;
|
|
238
|
+
/** The TypeScript value type one field of a `resource()` definition holds. */
|
|
239
|
+
export type FieldValueOf<F> = F extends Scalar<infer T, infer N> ? N extends true ? ScalarTsType<T> | null : ScalarTsType<T> : F extends Ref<string, string> ? number | string : unknown;
|
|
240
|
+
/**
|
|
241
|
+
* The row type a `resource()` definition implies — what `useData(Flight)`
|
|
242
|
+
* infers with **no type argument**: each field's scalar kind maps to its TS
|
|
243
|
+
* type (`null: true` adds `| null`), FKs are ids, plus the server-assigned
|
|
244
|
+
* `id`. Classes carry their own row type, and interfaces resolve through the
|
|
245
|
+
* `EntityTypes` registry (`rastack/hooks/registry`), so every reference form
|
|
246
|
+
* infers.
|
|
247
|
+
*/
|
|
248
|
+
export type RowOf<R> = R extends ResourceType<string, string, infer F, ResourceOptions> ? {
|
|
249
|
+
id: number | string;
|
|
250
|
+
} & {
|
|
251
|
+
[K in keyof F]: FieldValueOf<F[K]>;
|
|
252
|
+
} : never;
|
|
253
|
+
/** The `"app.model"` registry key a `resource()` definition implies. */
|
|
254
|
+
export type EntityKeyOf<R> = R extends ResourceType<infer A, infer M, any, ResourceOptions> ? `${A}.${M}` : never;
|
|
255
|
+
/**
|
|
256
|
+
* The `EntityTypes` registry entries a module of `resource()` definitions
|
|
257
|
+
* implies — the *hand-written* alternative to the `rastack-env.d.ts` that
|
|
258
|
+
* `rastack compile` emits. Point it at your resources module once and the
|
|
259
|
+
* registry tracks it forever, with no compile step in the loop:
|
|
260
|
+
*
|
|
261
|
+
* ```ts
|
|
262
|
+
* // rastack-env.d.ts — written once by you, never regenerated
|
|
263
|
+
* import type { EntityTypesOf } from "rastack/define";
|
|
264
|
+
* declare module "rastack/hooks/registry" {
|
|
265
|
+
* interface EntityTypes
|
|
266
|
+
* extends EntityTypesOf<typeof import("./resources/airports")> {}
|
|
267
|
+
* }
|
|
268
|
+
* ```
|
|
269
|
+
*
|
|
270
|
+
* Non-resource exports are dropped, so `typeof import("…")` over a whole
|
|
271
|
+
* module is safe. (Class entities carry their own row type — they never need
|
|
272
|
+
* the registry; interfaces still register via the compile-emitted file.)
|
|
273
|
+
*/
|
|
274
|
+
export type EntityTypesOf<M> = {
|
|
275
|
+
[K in keyof M as EntityKeyOf<M[K]>]: RowOf<M[K]>;
|
|
276
|
+
};
|
|
277
|
+
export { isResourceValue, manifestFromResources, resourceModel, toManifest, } from "./manifest";
|
|
278
|
+
export type { AnyResourceValue, ManifestInput } from "./manifest";
|
|
279
|
+
export type { Manifest, ResourceModel } from "../compile/model";
|
package/dist/define/index.js
CHANGED
|
@@ -24,43 +24,43 @@
|
|
|
24
24
|
* ```
|
|
25
25
|
*/
|
|
26
26
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
-
exports.s = void 0;
|
|
27
|
+
exports.toManifest = exports.resourceModel = exports.manifestFromResources = exports.isResourceValue = exports.s = void 0;
|
|
28
28
|
exports.resource = resource;
|
|
29
29
|
/**
|
|
30
|
-
* Field builders. Each returns a
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
30
|
+
* Field builders. Each returns a value whose *type* encodes the field kind
|
|
31
|
+
* (what the compiler reads — it analyses the source, it does not execute it)
|
|
32
|
+
* and whose runtime shape keeps the authored options/target, so the same
|
|
33
|
+
* definition doubles as its own manifest via `manifestFromResources()`.
|
|
34
34
|
*/
|
|
35
35
|
exports.s = {
|
|
36
|
-
string(
|
|
37
|
-
return { __rastackScalar: "string" };
|
|
36
|
+
string(opts) {
|
|
37
|
+
return { __rastackScalar: "string", options: opts };
|
|
38
38
|
},
|
|
39
|
-
int(
|
|
40
|
-
return { __rastackScalar: "int" };
|
|
39
|
+
int(opts) {
|
|
40
|
+
return { __rastackScalar: "int", options: opts };
|
|
41
41
|
},
|
|
42
|
-
float(
|
|
43
|
-
return { __rastackScalar: "float" };
|
|
42
|
+
float(opts) {
|
|
43
|
+
return { __rastackScalar: "float", options: opts };
|
|
44
44
|
},
|
|
45
|
-
bool(
|
|
46
|
-
return { __rastackScalar: "bool" };
|
|
45
|
+
bool(opts) {
|
|
46
|
+
return { __rastackScalar: "bool", options: opts };
|
|
47
47
|
},
|
|
48
|
-
datetime(
|
|
49
|
-
return { __rastackScalar: "datetime" };
|
|
48
|
+
datetime(opts) {
|
|
49
|
+
return { __rastackScalar: "datetime", options: opts };
|
|
50
50
|
},
|
|
51
|
-
uuid(
|
|
52
|
-
return { __rastackScalar: "uuid" };
|
|
51
|
+
uuid(opts) {
|
|
52
|
+
return { __rastackScalar: "uuid", options: opts };
|
|
53
53
|
},
|
|
54
54
|
/**
|
|
55
55
|
* A foreign key. The target is `() => SomeResource`; the compiler resolves
|
|
56
|
-
* the relation from the *type* `Ref<App, Model>` this returns
|
|
57
|
-
*
|
|
56
|
+
* the relation from the *type* `Ref<App, Model>` this returns, and the
|
|
57
|
+
* runtime keeps the thunk so `manifestFromResources()` can resolve it too.
|
|
58
58
|
*/
|
|
59
|
-
ref(
|
|
60
|
-
return { __rastackRef: true };
|
|
59
|
+
ref(thunk) {
|
|
60
|
+
return { __rastackRef: true, target: thunk };
|
|
61
61
|
},
|
|
62
62
|
};
|
|
63
|
-
/** Define a resource. Capture app/model as literal types
|
|
63
|
+
/** Define a resource. Capture app/model (and options) as literal types. */
|
|
64
64
|
function resource(app, model, fields, options = {}) {
|
|
65
65
|
return {
|
|
66
66
|
__rastack: "resource",
|
|
@@ -72,3 +72,9 @@ function resource(app, model, fields, options = {}) {
|
|
|
72
72
|
options,
|
|
73
73
|
};
|
|
74
74
|
}
|
|
75
|
+
// The runtime manifest builder — a resource() definition is its own manifest.
|
|
76
|
+
var manifest_1 = require("./manifest");
|
|
77
|
+
Object.defineProperty(exports, "isResourceValue", { enumerable: true, get: function () { return manifest_1.isResourceValue; } });
|
|
78
|
+
Object.defineProperty(exports, "manifestFromResources", { enumerable: true, get: function () { return manifest_1.manifestFromResources; } });
|
|
79
|
+
Object.defineProperty(exports, "resourceModel", { enumerable: true, get: function () { return manifest_1.resourceModel; } });
|
|
80
|
+
Object.defineProperty(exports, "toManifest", { enumerable: true, get: function () { return manifest_1.toManifest; } });
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The runtime manifest builder — `resource()` definitions are their own
|
|
3
|
+
* manifest, **no `rastack compile` step required**.
|
|
4
|
+
*
|
|
5
|
+
* The DSL values carry everything the canonical manifest needs at runtime
|
|
6
|
+
* (field kinds, constraint options, FK target thunks, resource options), so
|
|
7
|
+
* this module assembles the exact `schema.rastack.json` shape the compiler
|
|
8
|
+
* emits — same fields, same relations, same normalisation — as a pure
|
|
9
|
+
* function:
|
|
10
|
+
*
|
|
11
|
+
* ```tsx
|
|
12
|
+
* import { Airport, Terminal, Flight } from "./resources/airports";
|
|
13
|
+
*
|
|
14
|
+
* <RAStackProvider mode="local" manifest={[Airport, Terminal, Flight]}>
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* The compiler (`tools/src/compile/analyze.ts`) imports the option
|
|
18
|
+
* normalisers below, so the two paths cannot drift: `rastack compile` remains
|
|
19
|
+
* the way to get class/interface entities, `openapi.json`, and the artifacts
|
|
20
|
+
* the Rust backend and admin read — but the typed hooks and the in-browser
|
|
21
|
+
* engine need only this.
|
|
22
|
+
*
|
|
23
|
+
* Everything here is browser-safe: no `typescript`, no filesystem.
|
|
24
|
+
*/
|
|
25
|
+
import type { AccessModel, AdminModel, Manifest, ResourceModel, SyncModel } from "../compile/model";
|
|
26
|
+
import type { ResourceType } from "./index";
|
|
27
|
+
/** Any `resource()` value, generics erased. */
|
|
28
|
+
export type AnyResourceValue = ResourceType<string, string, any, any>;
|
|
29
|
+
/** Everything `toManifest()` understands — see {@link toManifest}. */
|
|
30
|
+
export type ManifestInput = Manifest | object | string | AnyResourceValue | readonly AnyResourceValue[];
|
|
31
|
+
/** Whether a value is a `resource()` definition. */
|
|
32
|
+
export declare function isResourceValue(value: unknown): value is AnyResourceValue;
|
|
33
|
+
/**
|
|
34
|
+
* One `resource()` value → its canonical {@link ResourceModel} — the same
|
|
35
|
+
* shape `rastack compile` derives from the source, produced from the runtime
|
|
36
|
+
* value instead.
|
|
37
|
+
*/
|
|
38
|
+
export declare function resourceModel(res: AnyResourceValue): ResourceModel;
|
|
39
|
+
/**
|
|
40
|
+
* Assemble the canonical manifest from `resource()` values at runtime —
|
|
41
|
+
* accepts the values themselves, arrays of them, or whole module namespaces
|
|
42
|
+
* (`import * as resources from "./resources"`), in any mix. Output order
|
|
43
|
+
* matches the compiler's (app, then model), so the result is byte-for-byte
|
|
44
|
+
* the manifest `rastack compile` would emit for the same definitions.
|
|
45
|
+
*/
|
|
46
|
+
export declare function manifestFromResources(...inputs: Array<AnyResourceValue | readonly AnyResourceValue[] | Record<string, unknown>>): Manifest;
|
|
47
|
+
/**
|
|
48
|
+
* Normalise anything a provider may be handed as "the manifest": a compiled
|
|
49
|
+
* manifest (object or JSON string), a `resource()` value, an array of them,
|
|
50
|
+
* or a module namespace containing them. This is what lets
|
|
51
|
+
* `<RAStackProvider manifest={[Airport, Terminal]}>` work with no compiled
|
|
52
|
+
* `schema.rastack.json` anywhere.
|
|
53
|
+
*/
|
|
54
|
+
export declare function toManifest(input: ManifestInput | null | undefined): Manifest | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* Attach a normalised `transitions` block to a resource and derive what the
|
|
57
|
+
* machine implies about its state field: the `states` become the field's
|
|
58
|
+
* allowed-value set (`enum` in OpenAPI, the `member` gate in validation), and
|
|
59
|
+
* `initial` becomes its default when the field doesn't declare one.
|
|
60
|
+
*/
|
|
61
|
+
export declare function applyTransitions(resource: ResourceModel, raw: unknown): void;
|
|
62
|
+
export declare function normaliseSync(sync: any): SyncModel | undefined;
|
|
63
|
+
export declare function normaliseAccess(access: any): AccessModel | undefined;
|
|
64
|
+
export declare function normaliseAdmin(admin: any): AdminModel | false | undefined;
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The runtime manifest builder — `resource()` definitions are their own
|
|
4
|
+
* manifest, **no `rastack compile` step required**.
|
|
5
|
+
*
|
|
6
|
+
* The DSL values carry everything the canonical manifest needs at runtime
|
|
7
|
+
* (field kinds, constraint options, FK target thunks, resource options), so
|
|
8
|
+
* this module assembles the exact `schema.rastack.json` shape the compiler
|
|
9
|
+
* emits — same fields, same relations, same normalisation — as a pure
|
|
10
|
+
* function:
|
|
11
|
+
*
|
|
12
|
+
* ```tsx
|
|
13
|
+
* import { Airport, Terminal, Flight } from "./resources/airports";
|
|
14
|
+
*
|
|
15
|
+
* <RAStackProvider mode="local" manifest={[Airport, Terminal, Flight]}>
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* The compiler (`tools/src/compile/analyze.ts`) imports the option
|
|
19
|
+
* normalisers below, so the two paths cannot drift: `rastack compile` remains
|
|
20
|
+
* the way to get class/interface entities, `openapi.json`, and the artifacts
|
|
21
|
+
* the Rust backend and admin read — but the typed hooks and the in-browser
|
|
22
|
+
* engine need only this.
|
|
23
|
+
*
|
|
24
|
+
* Everything here is browser-safe: no `typescript`, no filesystem.
|
|
25
|
+
*/
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
exports.isResourceValue = isResourceValue;
|
|
28
|
+
exports.resourceModel = resourceModel;
|
|
29
|
+
exports.manifestFromResources = manifestFromResources;
|
|
30
|
+
exports.toManifest = toManifest;
|
|
31
|
+
exports.applyTransitions = applyTransitions;
|
|
32
|
+
exports.normaliseSync = normaliseSync;
|
|
33
|
+
exports.normaliseAccess = normaliseAccess;
|
|
34
|
+
exports.normaliseAdmin = normaliseAdmin;
|
|
35
|
+
const transitions_1 = require("../validate/transitions");
|
|
36
|
+
/** Whether a value is a `resource()` definition. */
|
|
37
|
+
function isResourceValue(value) {
|
|
38
|
+
return (!!value &&
|
|
39
|
+
typeof value === "object" &&
|
|
40
|
+
value.__rastack === "resource");
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* One `resource()` value → its canonical {@link ResourceModel} — the same
|
|
44
|
+
* shape `rastack compile` derives from the source, produced from the runtime
|
|
45
|
+
* value instead.
|
|
46
|
+
*/
|
|
47
|
+
function resourceModel(res) {
|
|
48
|
+
const fields = [];
|
|
49
|
+
const relations = [];
|
|
50
|
+
for (const [name, value] of Object.entries((res.fields ?? {}))) {
|
|
51
|
+
const field = runtimeField(res, name, value);
|
|
52
|
+
if (!field)
|
|
53
|
+
continue;
|
|
54
|
+
fields.push(field);
|
|
55
|
+
if (field.type === "fk" && field.relation) {
|
|
56
|
+
relations.push({
|
|
57
|
+
field: name,
|
|
58
|
+
target: `${field.relation.app}.${field.relation.model}`,
|
|
59
|
+
kind: "fk",
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const options = (res.options ?? {});
|
|
64
|
+
const model = {
|
|
65
|
+
app: res.app,
|
|
66
|
+
model: res.model,
|
|
67
|
+
fields,
|
|
68
|
+
relations,
|
|
69
|
+
sync: normaliseSync(options.sync),
|
|
70
|
+
admin: normaliseAdmin(options.admin),
|
|
71
|
+
search: options.search,
|
|
72
|
+
ordering: options.ordering,
|
|
73
|
+
permission: options.permission,
|
|
74
|
+
access: normaliseAccess(options.access),
|
|
75
|
+
};
|
|
76
|
+
applyTransitions(model, options.transitions);
|
|
77
|
+
return model;
|
|
78
|
+
}
|
|
79
|
+
/** Resolve one runtime field value to its {@link FieldModel}. */
|
|
80
|
+
function runtimeField(res, name, value) {
|
|
81
|
+
if (!value || typeof value !== "object")
|
|
82
|
+
return undefined;
|
|
83
|
+
const field = value;
|
|
84
|
+
// Foreign key: the runtime value keeps the `() => Resource` thunk, so the
|
|
85
|
+
// relation target is recovered by calling it — the runtime twin of the
|
|
86
|
+
// compiler's "read the Ref<App, Model> type arguments" step.
|
|
87
|
+
if (field.__rastackRef === true) {
|
|
88
|
+
const target = field.target?.();
|
|
89
|
+
if (!isResourceValue(target)) {
|
|
90
|
+
throw new Error(`manifestFromResources: ${res.app}.${res.model}.${name} is an s.ref(...) whose target thunk did not return a resource() value`);
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
name,
|
|
94
|
+
type: "fk",
|
|
95
|
+
relation: { app: target.app, model: target.model },
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
if (typeof field.__rastackScalar !== "string")
|
|
99
|
+
return undefined;
|
|
100
|
+
const model = {
|
|
101
|
+
name,
|
|
102
|
+
type: field.__rastackScalar,
|
|
103
|
+
};
|
|
104
|
+
// Constraint mapping mirrors the compiler's `applyConstraints` exactly.
|
|
105
|
+
const opts = field.options;
|
|
106
|
+
if (opts && typeof opts === "object") {
|
|
107
|
+
if (typeof opts.maxLength === "number")
|
|
108
|
+
model.maxLength = opts.maxLength;
|
|
109
|
+
if (opts.unique === true)
|
|
110
|
+
model.unique = true;
|
|
111
|
+
if (opts.primaryKey === true)
|
|
112
|
+
model.primaryKey = true;
|
|
113
|
+
if (opts.null === true)
|
|
114
|
+
model.null = true;
|
|
115
|
+
if (opts.default !== undefined)
|
|
116
|
+
model.default = opts.default;
|
|
117
|
+
}
|
|
118
|
+
return model;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Assemble the canonical manifest from `resource()` values at runtime —
|
|
122
|
+
* accepts the values themselves, arrays of them, or whole module namespaces
|
|
123
|
+
* (`import * as resources from "./resources"`), in any mix. Output order
|
|
124
|
+
* matches the compiler's (app, then model), so the result is byte-for-byte
|
|
125
|
+
* the manifest `rastack compile` would emit for the same definitions.
|
|
126
|
+
*/
|
|
127
|
+
function manifestFromResources(...inputs) {
|
|
128
|
+
const resources = collectResources(inputs).map(resourceModel);
|
|
129
|
+
resources.sort((a, b) => a.app === b.app ? a.model.localeCompare(b.model) : a.app.localeCompare(b.app));
|
|
130
|
+
return { version: 1, resources };
|
|
131
|
+
}
|
|
132
|
+
/** Flatten values / arrays / module namespaces into the resource list. */
|
|
133
|
+
function collectResources(inputs) {
|
|
134
|
+
const out = [];
|
|
135
|
+
const seen = new Set();
|
|
136
|
+
const push = (res) => {
|
|
137
|
+
if (!seen.has(res)) {
|
|
138
|
+
seen.add(res);
|
|
139
|
+
out.push(res);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
for (const input of inputs) {
|
|
143
|
+
if (isResourceValue(input)) {
|
|
144
|
+
push(input);
|
|
145
|
+
}
|
|
146
|
+
else if (Array.isArray(input)) {
|
|
147
|
+
for (const item of input)
|
|
148
|
+
if (isResourceValue(item))
|
|
149
|
+
push(item);
|
|
150
|
+
}
|
|
151
|
+
else if (input && typeof input === "object") {
|
|
152
|
+
for (const item of Object.values(input)) {
|
|
153
|
+
if (isResourceValue(item))
|
|
154
|
+
push(item);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return out;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Normalise anything a provider may be handed as "the manifest": a compiled
|
|
162
|
+
* manifest (object or JSON string), a `resource()` value, an array of them,
|
|
163
|
+
* or a module namespace containing them. This is what lets
|
|
164
|
+
* `<RAStackProvider manifest={[Airport, Terminal]}>` work with no compiled
|
|
165
|
+
* `schema.rastack.json` anywhere.
|
|
166
|
+
*/
|
|
167
|
+
function toManifest(input) {
|
|
168
|
+
if (input === null || input === undefined)
|
|
169
|
+
return undefined;
|
|
170
|
+
if (typeof input === "string")
|
|
171
|
+
return JSON.parse(input);
|
|
172
|
+
if (Array.isArray(input) || isResourceValue(input)) {
|
|
173
|
+
return manifestFromResources(input);
|
|
174
|
+
}
|
|
175
|
+
const record = input;
|
|
176
|
+
// A compiled manifest passes through untouched.
|
|
177
|
+
if (Array.isArray(record.resources))
|
|
178
|
+
return input;
|
|
179
|
+
// A module namespace of resource() exports builds one.
|
|
180
|
+
const values = Object.values(record).filter(isResourceValue);
|
|
181
|
+
if (values.length)
|
|
182
|
+
return manifestFromResources(values);
|
|
183
|
+
return input;
|
|
184
|
+
}
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
186
|
+
// Option normalisers — shared with the compiler (`compile/analyze.ts`), so
|
|
187
|
+
// the runtime and compiled manifests can never disagree about a resource.
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
/**
|
|
190
|
+
* Attach a normalised `transitions` block to a resource and derive what the
|
|
191
|
+
* machine implies about its state field: the `states` become the field's
|
|
192
|
+
* allowed-value set (`enum` in OpenAPI, the `member` gate in validation), and
|
|
193
|
+
* `initial` becomes its default when the field doesn't declare one.
|
|
194
|
+
*/
|
|
195
|
+
function applyTransitions(resource, raw) {
|
|
196
|
+
const transitions = (0, transitions_1.normalizeTransitions)(raw);
|
|
197
|
+
if (!transitions)
|
|
198
|
+
return;
|
|
199
|
+
resource.transitions = transitions;
|
|
200
|
+
const stateField = resource.fields.find((f) => f.name === transitions.field);
|
|
201
|
+
if (stateField && stateField.type !== "fk") {
|
|
202
|
+
stateField.options = [...transitions.states];
|
|
203
|
+
if (stateField.default === undefined && transitions.initial !== undefined) {
|
|
204
|
+
stateField.default = transitions.initial;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function normaliseSync(sync) {
|
|
209
|
+
if (!sync || typeof sync !== "object")
|
|
210
|
+
return undefined;
|
|
211
|
+
const mode = sync.mode === "local" ? "local" : "server";
|
|
212
|
+
if (mode === "server" && !sync.confirmed) {
|
|
213
|
+
// A bare server resource carries no sync semantics.
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
const model = { mode };
|
|
217
|
+
if (typeof sync.scope === "string")
|
|
218
|
+
model.scope = sync.scope;
|
|
219
|
+
if (Array.isArray(sync.confirmed))
|
|
220
|
+
model.confirmed = sync.confirmed;
|
|
221
|
+
if (sync.load === "lazy")
|
|
222
|
+
model.load = "lazy";
|
|
223
|
+
if (sync.clientIds === true)
|
|
224
|
+
model.clientIds = true;
|
|
225
|
+
return model;
|
|
226
|
+
}
|
|
227
|
+
function normaliseAccess(access) {
|
|
228
|
+
if (!access || typeof access !== "object")
|
|
229
|
+
return undefined;
|
|
230
|
+
const model = {};
|
|
231
|
+
if (typeof access.ownerField === "string")
|
|
232
|
+
model.ownerField = access.ownerField;
|
|
233
|
+
if (access.scope === "owner" || access.scope === "shared") {
|
|
234
|
+
model.scope = access.scope;
|
|
235
|
+
}
|
|
236
|
+
if (Array.isArray(access.adminGroups)) {
|
|
237
|
+
model.adminGroups = access.adminGroups.filter((g) => typeof g === "string");
|
|
238
|
+
}
|
|
239
|
+
return Object.keys(model).length ? model : undefined;
|
|
240
|
+
}
|
|
241
|
+
function normaliseAdmin(admin) {
|
|
242
|
+
if (admin === false)
|
|
243
|
+
return false;
|
|
244
|
+
if (!admin || typeof admin !== "object")
|
|
245
|
+
return undefined;
|
|
246
|
+
const model = {};
|
|
247
|
+
if (Array.isArray(admin.listDisplay))
|
|
248
|
+
model.listDisplay = admin.listDisplay;
|
|
249
|
+
return model;
|
|
250
|
+
}
|