rastack 0.0.51 → 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 +2 -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.js +7 -2
- 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 +19 -2
- package/dist/define/manifest.js +10 -1
- 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/wasm/rastack_wasm.js +1 -1
- package/dist/wasm/rastack_wasm_bg.wasm +0 -0
- package/package.json +1 -1
- package/provider/index.ts +1 -5
- package/provider/provider.tsx +42 -11
- package/provider/types.ts +14 -2
- package/provider/warehouse.ts +4 -3
- package/provider/wasm.ts +3 -1
- package/runtime.ts +1 -0
- package/src/compile/analyze.ts +15 -3
- 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 +20 -2
- package/src/define/manifest.ts +10 -1
- package/src/define/markers.ts +83 -0
- package/src/define/types.ts +11 -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/types.ts +7 -2
- package/wasm/rastack_wasm.js +1 -1
- package/wasm/rastack_wasm_bg.wasm +0 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Entity-level authorization markers — auth as *TypeScript types*.
|
|
4
|
+
*
|
|
5
|
+
* Class and interface entities have no options bag, so their access policy is
|
|
6
|
+
* expressed the same way their columns are: in the type system. Attach a
|
|
7
|
+
* marker with `implements` (classes) or `extends` (interfaces) and the
|
|
8
|
+
* compiler reads the policy straight off the heritage clause:
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { AdminGroups, IsAuthenticated, Owner, Group } from "rastack/auth";
|
|
12
|
+
*
|
|
13
|
+
* // Per-user rows: RLS on `owner`, staff sees everything.
|
|
14
|
+
* export class Itinerary implements IsAuthenticated, AdminGroups<"staff"> {
|
|
15
|
+
* owner!: string & Owner;
|
|
16
|
+
* notes!: string;
|
|
17
|
+
* }
|
|
18
|
+
*
|
|
19
|
+
* // Group-owned dataset: rows belong to a team and are *physically stored*
|
|
20
|
+
* // under that team's own storage prefix (groups/{team}/…).
|
|
21
|
+
* export class Report implements IsAuthenticated, AdminGroups<"staff"> {
|
|
22
|
+
* team!: string & Group;
|
|
23
|
+
* title!: string;
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* The markers are phantom types: every property is optional and never exists
|
|
28
|
+
* at runtime, so `implements` needs no members and instances stay plain data.
|
|
29
|
+
* Each marker family uses its own phantom key, so markers combine freely —
|
|
30
|
+
* while two *permission* markers on one entity conflict in the type checker,
|
|
31
|
+
* which is exactly right.
|
|
32
|
+
*
|
|
33
|
+
* JSDoc equivalents exist for every marker (`@rastackPermission`,
|
|
34
|
+
* `@rastackAdminGroups staff, ops`, `@rastackScope owner|group|shared`, and
|
|
35
|
+
* field-level `@owner` / `@group`), mirroring the column-constraint tags.
|
|
36
|
+
*/
|
|
37
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `rastack/types` compatibility surface — one module aliasing the two
|
|
3
|
+
* split authoring subpaths: `rastack/db` (the `DbConfig` column brands) and
|
|
4
|
+
* `rastack/auth` (the entity-level auth markers + `Owner`/`Group` brands).
|
|
5
|
+
* Prefer those; everything stays re-exported here so existing imports keep
|
|
6
|
+
* working. The compiler maps all three specifiers (see `compile/program.ts`);
|
|
7
|
+
* the published package re-exports the same modules from `tools/types.ts`.
|
|
8
|
+
*/
|
|
9
|
+
export * from "./db";
|
|
10
|
+
export * from "./markers";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The `rastack/types` compatibility surface — one module aliasing the two
|
|
4
|
+
* split authoring subpaths: `rastack/db` (the `DbConfig` column brands) and
|
|
5
|
+
* `rastack/auth` (the entity-level auth markers + `Owner`/`Group` brands).
|
|
6
|
+
* Prefer those; everything stays re-exported here so existing imports keep
|
|
7
|
+
* working. The compiler maps all three specifiers (see `compile/program.ts`);
|
|
8
|
+
* the published package re-exports the same modules from `tools/types.ts`.
|
|
9
|
+
*/
|
|
10
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
+
}
|
|
16
|
+
Object.defineProperty(o, k2, desc);
|
|
17
|
+
}) : (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
o[k2] = m[k];
|
|
20
|
+
}));
|
|
21
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
22
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
23
|
+
};
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
__exportStar(require("./db"), exports);
|
|
26
|
+
__exportStar(require("./markers"), exports);
|
|
@@ -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/package.json
CHANGED
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,
|
package/provider/provider.tsx
CHANGED
|
@@ -33,6 +33,12 @@ import {
|
|
|
33
33
|
isAccessRevoked,
|
|
34
34
|
purgeIdentityCache,
|
|
35
35
|
} from "../cache";
|
|
36
|
+
import {
|
|
37
|
+
RastackAuthProvider,
|
|
38
|
+
userAuth,
|
|
39
|
+
type ManifestLike,
|
|
40
|
+
type RastackAuth,
|
|
41
|
+
} from "../auth";
|
|
36
42
|
import { toManifest } from "../src/define/manifest";
|
|
37
43
|
import { getGlobalManifest } from "../hooks/manifest";
|
|
38
44
|
|
|
@@ -74,17 +80,29 @@ export function RAStackProvider({
|
|
|
74
80
|
config.onStatusChange?.(next);
|
|
75
81
|
};
|
|
76
82
|
|
|
83
|
+
// One resolved identity, whichever way it was supplied: the typed `auth`
|
|
84
|
+
// object, or the raw `identity` / `groups` strings (which win when both are
|
|
85
|
+
// given). This is what the engine runs as, what namespaces local bytes, and
|
|
86
|
+
// what the component tree gates its affordances with.
|
|
87
|
+
const auth: RastackAuth | undefined =
|
|
88
|
+
config.auth ??
|
|
89
|
+
(config.identity != null
|
|
90
|
+
? userAuth(config.identity, config.groups ?? [])
|
|
91
|
+
: undefined);
|
|
92
|
+
const identity = config.identity ?? auth?.sub;
|
|
93
|
+
const groups = config.groups ?? auth?.groups ?? [];
|
|
94
|
+
|
|
77
95
|
// Locally persisted state is namespaced per identity, so two accounts on
|
|
78
96
|
// one device never share bytes.
|
|
79
97
|
const warehouseDb = withIdentity(
|
|
80
98
|
config.databaseName || "rastack-warehouse",
|
|
81
|
-
|
|
99
|
+
identity,
|
|
82
100
|
);
|
|
83
101
|
|
|
84
102
|
/** Delete everything this identity persisted locally. */
|
|
85
103
|
const purgeLocal = async () => {
|
|
86
104
|
if (cacheStorageRef.current) {
|
|
87
|
-
await purgeIdentityCache(cacheStorageRef.current,
|
|
105
|
+
await purgeIdentityCache(cacheStorageRef.current, identity);
|
|
88
106
|
}
|
|
89
107
|
await deleteIndexedDb(warehouseDb);
|
|
90
108
|
// Replicated rows in the sync engine's store are derived from the same
|
|
@@ -136,8 +154,8 @@ export function RAStackProvider({
|
|
|
136
154
|
|
|
137
155
|
// Run the in-browser engine as the signed-in identity so owner stamping
|
|
138
156
|
// and row-level security match the server exactly.
|
|
139
|
-
if (
|
|
140
|
-
engine.setAuth?.(
|
|
157
|
+
if (identity) {
|
|
158
|
+
engine.setAuth?.(identity, groups);
|
|
141
159
|
}
|
|
142
160
|
|
|
143
161
|
// Warehouse persistence: IndexedDB for `local`, an S3 sink for `s3`.
|
|
@@ -166,7 +184,7 @@ export function RAStackProvider({
|
|
|
166
184
|
const cache = new IcebergFileCache({
|
|
167
185
|
storage,
|
|
168
186
|
source: config.fileSource ?? httpFileSource(base),
|
|
169
|
-
identity
|
|
187
|
+
identity,
|
|
170
188
|
maxOfflineMs: config.maxOfflineMs,
|
|
171
189
|
});
|
|
172
190
|
await cache.seed(engine, resources);
|
|
@@ -191,7 +209,10 @@ export function RAStackProvider({
|
|
|
191
209
|
persistRef.current = persist;
|
|
192
210
|
|
|
193
211
|
engineRef.current = engine;
|
|
194
|
-
configureApi({
|
|
212
|
+
configureApi({
|
|
213
|
+
baseURL: "",
|
|
214
|
+
adapter: createWasmAdapter(engine, persist),
|
|
215
|
+
});
|
|
195
216
|
setStatusSafe("ready");
|
|
196
217
|
}
|
|
197
218
|
|
|
@@ -209,18 +230,21 @@ export function RAStackProvider({
|
|
|
209
230
|
engineRef.current = null;
|
|
210
231
|
};
|
|
211
232
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
212
|
-
}, [config.mode, config.baseURL, config.warehouseBaseUrl,
|
|
233
|
+
}, [config.mode, config.baseURL, config.warehouseBaseUrl, identity]);
|
|
213
234
|
|
|
214
235
|
const client = useMemo<RastackClient>(
|
|
215
236
|
() => ({
|
|
216
237
|
mode: config.mode,
|
|
217
238
|
status,
|
|
239
|
+
auth,
|
|
218
240
|
get engine() {
|
|
219
241
|
return engineRef.current;
|
|
220
242
|
},
|
|
221
243
|
async exportWarehouse() {
|
|
222
244
|
if (!engineRef.current) {
|
|
223
|
-
throw new Error(
|
|
245
|
+
throw new Error(
|
|
246
|
+
"exportWarehouse is only available in local/s3 mode.",
|
|
247
|
+
);
|
|
224
248
|
}
|
|
225
249
|
return engineRef.current.exportWarehouse();
|
|
226
250
|
},
|
|
@@ -239,13 +263,18 @@ export function RAStackProvider({
|
|
|
239
263
|
},
|
|
240
264
|
}),
|
|
241
265
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
242
|
-
[config.mode, status,
|
|
266
|
+
[config.mode, status, identity],
|
|
243
267
|
);
|
|
244
268
|
|
|
245
269
|
return (
|
|
246
270
|
<RastackContext.Provider value={client}>
|
|
247
271
|
<RastackManifestProvider manifest={manifest}>
|
|
248
|
-
|
|
272
|
+
<RastackAuthProvider
|
|
273
|
+
auth={auth}
|
|
274
|
+
manifest={manifest as ManifestLike | undefined}
|
|
275
|
+
>
|
|
276
|
+
{children}
|
|
277
|
+
</RastackAuthProvider>
|
|
249
278
|
</RastackManifestProvider>
|
|
250
279
|
</RastackContext.Provider>
|
|
251
280
|
);
|
|
@@ -279,7 +308,9 @@ function withIdentity(name: string, identity?: string): string {
|
|
|
279
308
|
export function useRastackClient(): RastackClient {
|
|
280
309
|
const client = useContext(RastackContext);
|
|
281
310
|
if (!client) {
|
|
282
|
-
throw new Error(
|
|
311
|
+
throw new Error(
|
|
312
|
+
"useRastackClient must be used within a <RAStackProvider>.",
|
|
313
|
+
);
|
|
283
314
|
}
|
|
284
315
|
return client;
|
|
285
316
|
}
|
package/provider/types.ts
CHANGED
|
@@ -68,14 +68,24 @@ export interface RAStackProviderConfig {
|
|
|
68
68
|
getToken?: () => string | Promise<string>;
|
|
69
69
|
|
|
70
70
|
// -- identity & local-data security (all modes) --
|
|
71
|
+
/**
|
|
72
|
+
* The signed-in identity as a typed {@link import("../auth").RastackAuth} —
|
|
73
|
+
* the one auth object the whole stack shares. Supplies `identity`/`groups`
|
|
74
|
+
* when those aren't set, runs the WASM engine as this identity, and is
|
|
75
|
+
* provided to the component tree (via `rastack/auth`'s context) so
|
|
76
|
+
* `<AutoForm>` / `<DataTable>` gate their affordances against each
|
|
77
|
+
* resource's `permission` + `access` policy automatically.
|
|
78
|
+
*/
|
|
79
|
+
auth?: import("../auth").RastackAuth;
|
|
71
80
|
/**
|
|
72
81
|
* The signed-in identity — the verified Cognito `sub`. It namespaces every
|
|
73
82
|
* locally persisted byte (Iceberg file cache, warehouse snapshot DB), so
|
|
74
83
|
* accounts sharing a device can't read each other's data, and it is the
|
|
75
|
-
* subject the WASM engine stamps/filters owner rows with.
|
|
84
|
+
* subject the WASM engine stamps/filters owner rows with. Defaults to
|
|
85
|
+
* `auth.sub`.
|
|
76
86
|
*/
|
|
77
87
|
identity?: string;
|
|
78
|
-
/** Verified group claims (`cognito:groups`)
|
|
88
|
+
/** Verified group claims (`cognito:groups`). Defaults to `auth.groups`. */
|
|
79
89
|
groups?: string[];
|
|
80
90
|
/**
|
|
81
91
|
* Fired when the data source reports this identity's access has been
|
|
@@ -166,6 +176,8 @@ export type RadStatus =
|
|
|
166
176
|
export interface RastackClient {
|
|
167
177
|
mode: RadMode;
|
|
168
178
|
status: RadStatus;
|
|
179
|
+
/** The resolved identity this client runs as (from `auth` or `identity`). */
|
|
180
|
+
auth?: import("../auth").RastackAuth;
|
|
169
181
|
/** The live WASM engine (null in `remote` mode). */
|
|
170
182
|
engine: RastackEngine | null;
|
|
171
183
|
/** Snapshot the local warehouse to a portable blob (WASM modes only). */
|
package/provider/warehouse.ts
CHANGED
|
@@ -26,8 +26,7 @@ export class WarehousePersistence {
|
|
|
26
26
|
private db: Promise<IDBDatabase> | null;
|
|
27
27
|
|
|
28
28
|
constructor(private databaseName: string) {
|
|
29
|
-
this.db =
|
|
30
|
-
typeof indexedDB !== "undefined" ? openDb(databaseName) : null;
|
|
29
|
+
this.db = typeof indexedDB !== "undefined" ? openDb(databaseName) : null;
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
get enabled(): boolean {
|
|
@@ -109,7 +108,9 @@ export async function seedFromIceberg(
|
|
|
109
108
|
if (!metaBytes) continue;
|
|
110
109
|
await engine.seedFile(metaKey, metaBytes);
|
|
111
110
|
|
|
112
|
-
const meta = JSON.parse(decoder.decode(metaBytes)) as {
|
|
111
|
+
const meta = JSON.parse(decoder.decode(metaBytes)) as {
|
|
112
|
+
data_files?: string[];
|
|
113
|
+
};
|
|
113
114
|
for (const rel of meta.data_files || []) {
|
|
114
115
|
const key = `${prefix}/${rel}`;
|
|
115
116
|
const bytes = await fetchBytes(base + key);
|
package/provider/wasm.ts
CHANGED
|
@@ -13,7 +13,9 @@ const runtimeImport: (specifier: string) => Promise<any> =
|
|
|
13
13
|
new Function("s", "return import(s)") as any;
|
|
14
14
|
|
|
15
15
|
export const defaultEngineLoader: RastackEngineLoader = async () => {
|
|
16
|
-
const mod: RastackWasmModule = await runtimeImport(
|
|
16
|
+
const mod: RastackWasmModule = await runtimeImport(
|
|
17
|
+
"rastack/wasm/rastack_wasm.js",
|
|
18
|
+
);
|
|
17
19
|
const init = mod.default || mod.init;
|
|
18
20
|
if (typeof init === "function") {
|
|
19
21
|
await init();
|
package/runtime.ts
CHANGED
package/src/compile/analyze.ts
CHANGED
|
@@ -69,7 +69,9 @@ export function analyzeDsl(
|
|
|
69
69
|
|
|
70
70
|
// Deterministic order: app, then model.
|
|
71
71
|
resources.sort((a, b) =>
|
|
72
|
-
a.app === b.app
|
|
72
|
+
a.app === b.app
|
|
73
|
+
? a.model.localeCompare(b.model)
|
|
74
|
+
: a.app.localeCompare(b.app),
|
|
73
75
|
);
|
|
74
76
|
types.sort((a, b) => a.key.localeCompare(b.key));
|
|
75
77
|
return { resources, types };
|
|
@@ -114,7 +116,12 @@ function analyzeResourceCall(
|
|
|
114
116
|
const app = stringLiteralArg(call.arguments[0]);
|
|
115
117
|
const model = stringLiteralArg(call.arguments[1]);
|
|
116
118
|
const fieldsArg = call.arguments[2];
|
|
117
|
-
if (
|
|
119
|
+
if (
|
|
120
|
+
!app ||
|
|
121
|
+
!model ||
|
|
122
|
+
!fieldsArg ||
|
|
123
|
+
!ts.isObjectLiteralExpression(fieldsArg)
|
|
124
|
+
) {
|
|
118
125
|
return undefined;
|
|
119
126
|
}
|
|
120
127
|
|
|
@@ -178,7 +185,12 @@ function analyzeField(
|
|
|
178
185
|
}
|
|
179
186
|
|
|
180
187
|
// Scalar: the field type carries a `__rastackScalar` string-literal discriminant.
|
|
181
|
-
const scalar = literalTypeProp(
|
|
188
|
+
const scalar = literalTypeProp(
|
|
189
|
+
fieldType,
|
|
190
|
+
"__rastackScalar",
|
|
191
|
+
checker,
|
|
192
|
+
initializer,
|
|
193
|
+
);
|
|
182
194
|
if (!scalar) return undefined;
|
|
183
195
|
|
|
184
196
|
const field: FieldModel = { name, type: scalar as ScalarType };
|