rastack 0.0.63 → 0.0.69
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 +32 -0
- package/README.md +19 -24
- package/admin/AdminApp.tsx +56 -3
- package/admin/httpEngine.ts +5 -6
- package/admin/index.tsx +11 -85
- package/admin/ui/icons.tsx +9 -0
- package/admin/ui/styles.ts +3 -0
- package/admin/useAdmin.ts +41 -4
- package/dist/admin.js +17 -22
- package/dist/compile/analyze.js +11 -1
- package/dist/compile/model.d.ts +1 -1
- package/dist/compile/run.d.ts +2 -2
- package/dist/compile/run.js +2 -2
- package/dist/define/index.d.ts +10 -4
- package/dist/define/index.js +2 -2
- package/dist/define/manifest.js +7 -1
- package/dist/deploy/io.d.ts +50 -0
- package/dist/deploy/io.js +124 -0
- package/dist/deploy/workflows.js +1 -1
- package/dist/dev/harness.d.ts +19 -27
- package/dist/dev/harness.js +31 -40
- package/dist/dev/native-server.d.ts +29 -0
- package/dist/dev/native-server.js +174 -0
- package/dist/external/envelope.d.ts +1 -1
- package/dist/external/envelope.js +1 -1
- package/dist/external/service.d.ts +2 -2
- package/dist/external/service.js +2 -2
- package/dist/external/types.d.ts +1 -1
- package/dist/external/types.js +1 -1
- package/dist/rastack-admin.d.ts +6 -14
- package/dist/rastack-admin.js +60 -88
- package/dist/rastack-bootstrap.js +18 -47
- package/dist/rastack-design.d.ts +1 -2
- package/dist/rastack-design.js +1 -2
- package/dist/rastack-dev.d.ts +7 -10
- package/dist/rastack-dev.js +68 -80
- package/dist/rastack-init.d.ts +6 -3
- package/dist/rastack-init.js +43 -30
- package/dist/rastack.d.ts +1 -1
- package/dist/rastack.js +5 -6
- package/dist/validate/transitions.d.ts +1 -1
- package/dist/validate/transitions.js +1 -1
- package/hooks/query/api.ts +4 -6
- package/package.json +2 -2
- package/provider/index.ts +0 -1
- package/provider/provider.tsx +31 -134
- package/provider/types.ts +30 -69
- package/src/compile/analyze.ts +11 -1
- package/src/compile/model.ts +1 -1
- package/src/compile/run.ts +2 -2
- package/src/define/index.ts +7 -4
- package/src/define/manifest.ts +7 -1
- package/src/deploy/io.ts +162 -1
- package/src/deploy/workflows.ts +1 -1
- package/src/dev/harness.ts +30 -43
- package/src/dev/native-server.ts +164 -0
- package/src/external/envelope.ts +1 -1
- package/src/external/service.ts +2 -2
- package/src/external/types.ts +1 -1
- package/src/rastack-admin.ts +66 -107
- package/src/rastack-bootstrap.ts +24 -69
- package/src/rastack-design.ts +1 -2
- package/src/rastack-dev.ts +79 -91
- package/src/rastack-init.ts +50 -41
- package/src/rastack.ts +5 -6
- package/src/validate/transitions.ts +1 -1
- package/dist/rastack-wasm-build.d.ts +0 -19
- package/dist/rastack-wasm-build.js +0 -94
- package/dist/wasm/package.json +0 -4
- package/dist/wasm/rastack_wasm.d.ts +0 -100
- package/dist/wasm/rastack_wasm.js +0 -745
- package/dist/wasm/rastack_wasm_bg.wasm +0 -0
- package/dist/wasm/rastack_wasm_bg.wasm.d.ts +0 -28
- package/provider/wasm.ts +0 -126
- package/scripts/copy-wasm.js +0 -35
- package/src/rastack-wasm-build.ts +0 -113
package/provider/provider.tsx
CHANGED
|
@@ -14,23 +14,10 @@ import {
|
|
|
14
14
|
RastackEngine,
|
|
15
15
|
RAStackProviderConfig,
|
|
16
16
|
RadStatus,
|
|
17
|
-
WarehouseSource,
|
|
18
17
|
} from "./types";
|
|
19
|
-
import { createEngine, createWasmAdapter, defaultEngineLoader } from "./wasm";
|
|
20
18
|
import {
|
|
21
|
-
debounce,
|
|
22
|
-
downloadBlob,
|
|
23
|
-
manifestResources,
|
|
24
|
-
seedFromIceberg,
|
|
25
|
-
WarehousePersistence,
|
|
26
|
-
} from "./warehouse";
|
|
27
|
-
import {
|
|
28
|
-
CacheStorage,
|
|
29
19
|
defaultCacheStorage,
|
|
30
20
|
deleteIndexedDb,
|
|
31
|
-
httpFileSource,
|
|
32
|
-
IcebergFileCache,
|
|
33
|
-
isAccessRevoked,
|
|
34
21
|
purgeIdentityCache,
|
|
35
22
|
} from "../cache";
|
|
36
23
|
import {
|
|
@@ -45,13 +32,18 @@ import { getGlobalManifest } from "../hooks/manifest";
|
|
|
45
32
|
const RastackContext = createContext<RastackClient | null>(null);
|
|
46
33
|
|
|
47
34
|
/**
|
|
48
|
-
* The one place a React app configures *where the API runs
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
35
|
+
* The one place a React app configures *where the API runs*. Every mode talks
|
|
36
|
+
* HTTP to a native Rust server (`rastack-server` locally, Lambda in
|
|
37
|
+
* production). The browser does not execute the engine.
|
|
38
|
+
*
|
|
39
|
+
* - `local` — same origin (or `baseURL`). `rastack dev` proxies `/api` to the
|
|
40
|
+
* native process whose warehouse is the on-disk directory.
|
|
41
|
+
* - `s3` — the same HTTP client. The native process owns an `s3://` warehouse.
|
|
42
|
+
* The page does not open the bucket.
|
|
43
|
+
* - `remote` — HTTP to `baseURL` (Lambda or a server you already started).
|
|
52
44
|
*
|
|
53
45
|
* ```tsx
|
|
54
|
-
* <RAStackProvider mode="local" manifest={schema}
|
|
46
|
+
* <RAStackProvider mode="local" manifest={schema}>
|
|
55
47
|
* <App />
|
|
56
48
|
* </RAStackProvider>
|
|
57
49
|
* ```
|
|
@@ -62,8 +54,6 @@ export function RAStackProvider({
|
|
|
62
54
|
}: RAStackProviderConfig & { children: ReactNode }): JSX.Element {
|
|
63
55
|
const [status, setStatus] = useState<RadStatus>("idle");
|
|
64
56
|
const engineRef = useRef<RastackEngine | null>(null);
|
|
65
|
-
const persistRef = useRef<(() => void) & { flush: () => void }>();
|
|
66
|
-
const cacheStorageRef = useRef<CacheStorage | null>(null);
|
|
67
57
|
|
|
68
58
|
// The `manifest` prop is optional: when the rastack dev-server plugin is
|
|
69
59
|
// in the bundler it registers the compiled schema globally before the app
|
|
@@ -90,7 +80,6 @@ export function RAStackProvider({
|
|
|
90
80
|
? userAuth(config.identity, config.groups ?? [])
|
|
91
81
|
: undefined);
|
|
92
82
|
const identity = config.identity ?? auth?.sub;
|
|
93
|
-
const groups = config.groups ?? auth?.groups ?? [];
|
|
94
83
|
|
|
95
84
|
// Locally persisted state is namespaced per identity, so two accounts on
|
|
96
85
|
// one device never share bytes.
|
|
@@ -99,10 +88,12 @@ export function RAStackProvider({
|
|
|
99
88
|
identity,
|
|
100
89
|
);
|
|
101
90
|
|
|
102
|
-
/** Delete
|
|
91
|
+
/** Delete leftover device bytes (file cache, old warehouse snapshots). */
|
|
103
92
|
const purgeLocal = async () => {
|
|
104
|
-
|
|
105
|
-
await purgeIdentityCache(
|
|
93
|
+
try {
|
|
94
|
+
await purgeIdentityCache(defaultCacheStorage(), identity);
|
|
95
|
+
} catch {
|
|
96
|
+
/* no IndexedDB in this runtime */
|
|
106
97
|
}
|
|
107
98
|
await deleteIndexedDb(warehouseDb);
|
|
108
99
|
// Replicated rows in the sync engine's store are derived from the same
|
|
@@ -132,87 +123,15 @@ export function RAStackProvider({
|
|
|
132
123
|
});
|
|
133
124
|
}
|
|
134
125
|
|
|
135
|
-
|
|
126
|
+
// Every mode is HTTP to a native server. `local` uses this origin
|
|
127
|
+
// (`rastack dev` proxies `/api`). `s3` is the same client — the native
|
|
128
|
+
// process holds the `s3://` warehouse. `remote` with no baseURL keeps
|
|
129
|
+
// the historical default (NEXT_PUBLIC_BASE_URL or 127.0.0.1:8000).
|
|
130
|
+
if (config.mode === "remote" && config.baseURL === undefined) {
|
|
131
|
+
resetApi();
|
|
132
|
+
} else {
|
|
136
133
|
configureApi({ baseURL: config.baseURL ?? "", adapter: null });
|
|
137
|
-
if (config.baseURL === undefined) resetApi();
|
|
138
|
-
setStatusSafe("ready");
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// -- WASM modes (local / s3) --
|
|
143
|
-
if (!manifest) {
|
|
144
|
-
throw new Error(
|
|
145
|
-
"RAStackProvider: no manifest found for local/s3 mode — add rastackPlugin() to your bundler (rastack/plugin), or pass the `manifest` prop.",
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
setStatusSafe("loading");
|
|
150
|
-
const loader = config.loadEngine ?? defaultEngineLoader;
|
|
151
|
-
const mod = await loader();
|
|
152
|
-
if (cancelled) return;
|
|
153
|
-
const engine = createEngine(mod, manifest, config.openapi);
|
|
154
|
-
|
|
155
|
-
// Run the in-browser engine as the signed-in identity so owner stamping
|
|
156
|
-
// and row-level security match the server exactly.
|
|
157
|
-
if (identity) {
|
|
158
|
-
engine.setAuth?.(identity, groups);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Warehouse persistence: IndexedDB for `local`, an S3 sink for `s3`.
|
|
162
|
-
const persistence = pickPersistence(config, warehouseDb);
|
|
163
|
-
|
|
164
|
-
setStatusSafe("seeding");
|
|
165
|
-
try {
|
|
166
|
-
// A persisted local copy (with the user's edits) wins; otherwise seed
|
|
167
|
-
// from the warehouse's own Iceberg files — through the local file
|
|
168
|
-
// cache by default, so a warm client revalidates one small pointer
|
|
169
|
-
// per table and reads everything else from the device.
|
|
170
|
-
const existing = await persistence.load();
|
|
171
|
-
if (existing) {
|
|
172
|
-
await engine.importWarehouse(existing);
|
|
173
|
-
} else {
|
|
174
|
-
const base = config.warehouseBaseUrl ?? "/data/warehouse/";
|
|
175
|
-
const resources = manifestResources(manifest);
|
|
176
|
-
if (config.cache === false) {
|
|
177
|
-
await seedFromIceberg(engine, base, resources);
|
|
178
|
-
} else {
|
|
179
|
-
const storage =
|
|
180
|
-
typeof config.cache === "object"
|
|
181
|
-
? config.cache
|
|
182
|
-
: defaultCacheStorage();
|
|
183
|
-
cacheStorageRef.current = storage;
|
|
184
|
-
const cache = new IcebergFileCache({
|
|
185
|
-
storage,
|
|
186
|
-
source: config.fileSource ?? httpFileSource(base),
|
|
187
|
-
identity,
|
|
188
|
-
maxOfflineMs: config.maxOfflineMs,
|
|
189
|
-
});
|
|
190
|
-
await cache.seed(engine, resources);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
} catch (error) {
|
|
194
|
-
if (isAccessRevoked(error)) {
|
|
195
|
-
// Access is gone ⇒ so is every locally persisted byte it fetched.
|
|
196
|
-
await handleAccessRevoked();
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
throw error;
|
|
200
134
|
}
|
|
201
|
-
if (cancelled) return;
|
|
202
|
-
|
|
203
|
-
const persist = debounce(() => {
|
|
204
|
-
void engine
|
|
205
|
-
.exportWarehouse()
|
|
206
|
-
.then((blob) => persistence.save(blob))
|
|
207
|
-
.catch((error) => config.onError?.(error));
|
|
208
|
-
}, 400);
|
|
209
|
-
persistRef.current = persist;
|
|
210
|
-
|
|
211
|
-
engineRef.current = engine;
|
|
212
|
-
configureApi({
|
|
213
|
-
baseURL: "",
|
|
214
|
-
adapter: createWasmAdapter(engine, persist),
|
|
215
|
-
});
|
|
216
135
|
setStatusSafe("ready");
|
|
217
136
|
}
|
|
218
137
|
|
|
@@ -224,7 +143,6 @@ export function RAStackProvider({
|
|
|
224
143
|
|
|
225
144
|
return () => {
|
|
226
145
|
cancelled = true;
|
|
227
|
-
persistRef.current?.flush();
|
|
228
146
|
if (ejectToken !== undefined) API.interceptors.request.eject(ejectToken);
|
|
229
147
|
resetApi();
|
|
230
148
|
engineRef.current = null;
|
|
@@ -241,19 +159,17 @@ export function RAStackProvider({
|
|
|
241
159
|
return engineRef.current;
|
|
242
160
|
},
|
|
243
161
|
async exportWarehouse() {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
);
|
|
248
|
-
}
|
|
249
|
-
return engineRef.current.exportWarehouse();
|
|
162
|
+
throw new Error(
|
|
163
|
+
"The warehouse lives in the native rastack-server process. The browser does not snapshot it.",
|
|
164
|
+
);
|
|
250
165
|
},
|
|
251
|
-
async downloadWarehouse(
|
|
252
|
-
|
|
253
|
-
|
|
166
|
+
async downloadWarehouse() {
|
|
167
|
+
throw new Error(
|
|
168
|
+
"The warehouse lives in the native rastack-server process. The browser does not snapshot it.",
|
|
169
|
+
);
|
|
254
170
|
},
|
|
255
171
|
async persistNow() {
|
|
256
|
-
|
|
172
|
+
/* The native server writes the warehouse as it handles each request. */
|
|
257
173
|
},
|
|
258
174
|
async purgeLocal() {
|
|
259
175
|
await purgeLocal();
|
|
@@ -280,25 +196,6 @@ export function RAStackProvider({
|
|
|
280
196
|
);
|
|
281
197
|
}
|
|
282
198
|
|
|
283
|
-
function pickPersistence(
|
|
284
|
-
config: RAStackProviderConfig,
|
|
285
|
-
databaseName: string,
|
|
286
|
-
): WarehouseSource {
|
|
287
|
-
if (config.mode === "s3") {
|
|
288
|
-
if (!config.warehouse) {
|
|
289
|
-
throw new Error(
|
|
290
|
-
"RAStackProvider: `warehouse` (a WarehouseSource over S3) is required in s3 mode.",
|
|
291
|
-
);
|
|
292
|
-
}
|
|
293
|
-
return config.warehouse;
|
|
294
|
-
}
|
|
295
|
-
if (config.persist === false) {
|
|
296
|
-
return { load: async () => null, save: async () => {} };
|
|
297
|
-
}
|
|
298
|
-
const store = new WarehousePersistence(databaseName);
|
|
299
|
-
return { load: () => store.load(), save: (blob) => store.save(blob) };
|
|
300
|
-
}
|
|
301
|
-
|
|
302
199
|
/** Suffix a local database/cache name with the identity that owns it. */
|
|
303
200
|
function withIdentity(name: string, identity?: string): string {
|
|
304
201
|
return identity ? `${name}::${identity}` : name;
|
|
@@ -315,7 +212,7 @@ export function useRastackClient(): RastackClient {
|
|
|
315
212
|
return client;
|
|
316
213
|
}
|
|
317
214
|
|
|
318
|
-
/** Just the current status — handy for a loading gate
|
|
215
|
+
/** Just the current status — handy for a loading gate. */
|
|
319
216
|
export function useRadStatus(): RadStatus {
|
|
320
217
|
return useRastackClient().status;
|
|
321
218
|
}
|
package/provider/types.ts
CHANGED
|
@@ -2,19 +2,17 @@
|
|
|
2
2
|
* Configuration for `<RAStackProvider>` — the single place a React app chooses
|
|
3
3
|
* *where the API runs*:
|
|
4
4
|
*
|
|
5
|
-
* - `remote` — talk to the Rust API over HTTP (Lambda in prod,
|
|
6
|
-
*
|
|
7
|
-
* - `local` —
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* loaded from and saved back to S3, so a static site reads and writes the
|
|
13
|
-
* object-storage database directly.
|
|
5
|
+
* - `remote` — talk to the Rust API over HTTP (Lambda in prod, a
|
|
6
|
+
* `rastack-server` you already started).
|
|
7
|
+
* - `local` — HTTP to the native `rastack-server` that `rastack dev` starts
|
|
8
|
+
* and proxies at `/api` on this origin. The warehouse is that process's
|
|
9
|
+
* on-disk directory. The browser does not run the engine.
|
|
10
|
+
* - `s3` — the same HTTP client. The native server (Lambda, or
|
|
11
|
+
* `rastack-server` pointed at `s3://`) owns the bucket. The page does not.
|
|
14
12
|
*/
|
|
15
13
|
export type RadMode = "remote" | "local" | "s3";
|
|
16
14
|
|
|
17
|
-
/** The
|
|
15
|
+
/** The `handle` seam the admin console uses. Backed by HTTP to the native server. */
|
|
18
16
|
export interface RastackEngine {
|
|
19
17
|
handle(
|
|
20
18
|
method: string,
|
|
@@ -22,34 +20,14 @@ export interface RastackEngine {
|
|
|
22
20
|
query: Record<string, string> | undefined,
|
|
23
21
|
body: unknown,
|
|
24
22
|
): Promise<{ status: number; body: any }>;
|
|
25
|
-
/**
|
|
23
|
+
/** Not available. The native server owns warehouse files. */
|
|
26
24
|
seedFile(key: string, bytes: Uint8Array): Promise<void>;
|
|
27
25
|
importWarehouse(blob: Uint8Array): Promise<void>;
|
|
28
26
|
exportWarehouse(): Promise<Uint8Array>;
|
|
29
|
-
/**
|
|
30
|
-
* Run requests as a signed-in identity (Cognito `sub` + `cognito:groups`),
|
|
31
|
-
* so owner stamping / row-level security in the in-browser engine match the
|
|
32
|
-
* server. Optional for older engine bundles.
|
|
33
|
-
*/
|
|
27
|
+
/** Not used. Identity is the bearer token the native server verifies. */
|
|
34
28
|
setAuth?(subject: string | undefined, groups?: string[]): void;
|
|
35
29
|
}
|
|
36
30
|
|
|
37
|
-
/** Constructor of the generated `RastackApi` class. */
|
|
38
|
-
export interface RastackEngineCtor {
|
|
39
|
-
new (manifestJson: string, openapiJson?: string): RastackEngine;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** What a WASM bundle loader must resolve to. */
|
|
43
|
-
export interface RastackWasmModule {
|
|
44
|
-
/** wasm-pack `web` target exposes an init fn that fetches the `.wasm`. */
|
|
45
|
-
default?: (input?: any) => Promise<any>;
|
|
46
|
-
init?: (input?: any) => Promise<any>;
|
|
47
|
-
RastackApi: RastackEngineCtor;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** Load and initialise the compiled WASM engine module. */
|
|
51
|
-
export type RastackEngineLoader = () => Promise<RastackWasmModule>;
|
|
52
|
-
|
|
53
31
|
/** Where to read/write the warehouse blob in `s3` mode (or any custom sink). */
|
|
54
32
|
export interface WarehouseSource {
|
|
55
33
|
/** Fetch the current warehouse blob, or `null` if none exists yet. */
|
|
@@ -71,7 +49,7 @@ export interface RAStackProviderConfig {
|
|
|
71
49
|
/**
|
|
72
50
|
* The signed-in identity as a typed {@link import("../auth").RastackAuth} —
|
|
73
51
|
* the one auth object the whole stack shares. Supplies `identity`/`groups`
|
|
74
|
-
* when those aren't set,
|
|
52
|
+
* when those aren't set, and is
|
|
75
53
|
* provided to the component tree (via `rastack/auth`'s context) so
|
|
76
54
|
* `<AutoForm>` / `<DataTable>` gate their affordances against each
|
|
77
55
|
* resource's `permission` + `access` policy automatically.
|
|
@@ -81,7 +59,7 @@ export interface RAStackProviderConfig {
|
|
|
81
59
|
* The signed-in identity — the verified Cognito `sub`. It namespaces every
|
|
82
60
|
* locally persisted byte (Iceberg file cache, warehouse snapshot DB), so
|
|
83
61
|
* accounts sharing a device can't read each other's data, and it is the
|
|
84
|
-
* subject the
|
|
62
|
+
* subject the native server stamps/filters owner rows with. Defaults to
|
|
85
63
|
* `auth.sub`.
|
|
86
64
|
*/
|
|
87
65
|
identity?: string;
|
|
@@ -115,50 +93,33 @@ export interface RAStackProviderConfig {
|
|
|
115
93
|
* `schema.rastack.json` (object or JSON string), the
|
|
116
94
|
* `virtual:rastack-manifest` module, or `resource()` definitions (an array
|
|
117
95
|
* or a module namespace), from which the provider builds the identical
|
|
118
|
-
* manifest at runtime.
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
* type to its resource at runtime.
|
|
96
|
+
* manifest at runtime. The native server reads the compiled manifest from
|
|
97
|
+
* disk; this copy is what lets `useData(Entity)` / `useForm(Entity)`
|
|
98
|
+
* resolve a plain TypeScript type to its resource in the browser.
|
|
122
99
|
*/
|
|
123
100
|
manifest?: object | readonly object[] | string;
|
|
124
101
|
/** `openapi.json` — object or JSON string. Optional (`/api/schema/`). */
|
|
125
102
|
openapi?: object | string;
|
|
126
103
|
/**
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
* `version-hint.text` → snapshot metadata → `.parquet` data files — exactly
|
|
130
|
-
* as the server walks it over object storage. Defaults to `/data/warehouse/`.
|
|
131
|
-
* Ignored once a persisted local copy exists.
|
|
104
|
+
* Unused by the request path. The native server opens the warehouse
|
|
105
|
+
* directory itself. Kept so existing call sites still type-check.
|
|
132
106
|
*/
|
|
133
107
|
warehouseBaseUrl?: string;
|
|
134
|
-
/**
|
|
108
|
+
/** Unused. The native server writes the warehouse itself. */
|
|
135
109
|
persist?: boolean;
|
|
136
110
|
/** IndexedDB database name for the local warehouse. */
|
|
137
111
|
databaseName?: string;
|
|
138
112
|
/**
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
* pointer per table and read everything else from the device. `false`
|
|
142
|
-
* disables caching; default on. Immutable Parquet/metadata files are cached
|
|
143
|
-
* forever by key — invalidation is driven by Iceberg's own snapshot pointer.
|
|
113
|
+
* Unused by the request path. The native server reads the warehouse from
|
|
114
|
+
* disk or S3. Kept so existing call sites still type-check.
|
|
144
115
|
*/
|
|
145
116
|
cache?: boolean | import("../cache").CacheStorage;
|
|
146
|
-
/**
|
|
147
|
-
* Where warehouse files are fetched from — defaults to HTTP GETs against
|
|
148
|
-
* `warehouseBaseUrl`. Supply a SigV4-signing source (AWS SDK S3 client over
|
|
149
|
-
* Cognito Identity Pool credentials) for `s3` mode. Must throw
|
|
150
|
-
* `AccessRevokedError` on 401/403 so revocation deletes local data.
|
|
151
|
-
*/
|
|
117
|
+
/** Unused. The browser does not fetch warehouse files into an engine. */
|
|
152
118
|
fileSource?: import("../cache").WarehouseFileSource;
|
|
153
|
-
/**
|
|
119
|
+
/** Unused. The native server owns the `s3://` warehouse. */
|
|
154
120
|
warehouse?: WarehouseSource;
|
|
155
|
-
/**
|
|
156
|
-
|
|
157
|
-
* your bundler can resolve the generated bundle) if the default loader can't
|
|
158
|
-
* find it. Defaults to importing `rastack/wasm/rastack_wasm.js`.
|
|
159
|
-
*/
|
|
160
|
-
loadEngine?: RastackEngineLoader;
|
|
161
|
-
|
|
121
|
+
/** Unused. The native server opens the warehouse directory on disk. */
|
|
122
|
+
seedWarehouse?: () => Uint8Array | null | Promise<Uint8Array | null>;
|
|
162
123
|
// -- lifecycle --
|
|
163
124
|
onStatusChange?: (status: RadStatus) => void;
|
|
164
125
|
onError?: (error: unknown) => void;
|
|
@@ -166,8 +127,8 @@ export interface RAStackProviderConfig {
|
|
|
166
127
|
|
|
167
128
|
export type RadStatus =
|
|
168
129
|
| "idle"
|
|
169
|
-
| "loading" //
|
|
170
|
-
| "seeding" //
|
|
130
|
+
| "loading" // contacting the native server
|
|
131
|
+
| "seeding" // unused; the native server opens the warehouse itself
|
|
171
132
|
| "ready"
|
|
172
133
|
| "revoked" // access revoked — local data has been deleted
|
|
173
134
|
| "error";
|
|
@@ -178,13 +139,13 @@ export interface RastackClient {
|
|
|
178
139
|
status: RadStatus;
|
|
179
140
|
/** The resolved identity this client runs as (from `auth` or `identity`). */
|
|
180
141
|
auth?: import("../auth").RastackAuth;
|
|
181
|
-
/**
|
|
142
|
+
/** Always null. The engine is the native server, not an object in the page. */
|
|
182
143
|
engine: RastackEngine | null;
|
|
183
|
-
/**
|
|
144
|
+
/** Throws. The native server owns the warehouse. */
|
|
184
145
|
exportWarehouse(): Promise<Uint8Array>;
|
|
185
|
-
/**
|
|
146
|
+
/** Throws. The native server owns the warehouse. */
|
|
186
147
|
downloadWarehouse(filename?: string): Promise<void>;
|
|
187
|
-
/**
|
|
148
|
+
/** No-op. The native server writes each request as it handles it. */
|
|
188
149
|
persistNow(): Promise<void>;
|
|
189
150
|
/**
|
|
190
151
|
* Delete everything this identity has persisted on the device — the Iceberg
|
package/src/compile/analyze.ts
CHANGED
|
@@ -179,7 +179,17 @@ function analyzeField(
|
|
|
179
179
|
const app = literalTypeProp(fieldType, "__app", checker, initializer);
|
|
180
180
|
const model = literalTypeProp(fieldType, "__model", checker, initializer);
|
|
181
181
|
if (app && model) {
|
|
182
|
-
|
|
182
|
+
const fk: FieldModel = { name, type: "fk", relation: { app, model } };
|
|
183
|
+
// `s.ref(() => X, { null: true })` — a nullable relation (a shared secret
|
|
184
|
+
// with no project, an unassigned task). The options object is the ref
|
|
185
|
+
// call's second argument, read the same way scalar constraints are.
|
|
186
|
+
if (ts.isCallExpression(initializer)) {
|
|
187
|
+
const opts = literalToValue(initializer.arguments[1]);
|
|
188
|
+
if (opts && typeof opts === "object" && opts.null === true) {
|
|
189
|
+
fk.null = true;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return fk;
|
|
183
193
|
}
|
|
184
194
|
return undefined;
|
|
185
195
|
}
|
package/src/compile/model.ts
CHANGED
|
@@ -87,7 +87,7 @@ export interface TransitionEdgeModel {
|
|
|
87
87
|
/**
|
|
88
88
|
* A resource's declarative state machine, keyed off one state field. Compiled
|
|
89
89
|
* into the manifest and enforced on every write surface: the TS record machine
|
|
90
|
-
* (forms, `rastack import`) and `rastack-api-core` (
|
|
90
|
+
* (forms, `rastack import`) and `rastack-api-core` (the native server) all
|
|
91
91
|
* reject a write that jumps between states without a declared edge.
|
|
92
92
|
*/
|
|
93
93
|
export interface TransitionsModel {
|
package/src/compile/run.ts
CHANGED
|
@@ -9,8 +9,8 @@ import { compile } from "./index";
|
|
|
9
9
|
*
|
|
10
10
|
* This is the single entry point the two real modalities of use share instead
|
|
11
11
|
* of shelling out to a standalone `compile` command:
|
|
12
|
-
* • `rastack dev` / `rastack admin` — local development (compile, then
|
|
13
|
-
* the
|
|
12
|
+
* • `rastack dev` / `rastack admin` — local development (compile, then start
|
|
13
|
+
* the native rastack-server; the browser is only the UI), and
|
|
14
14
|
* • `rastack ci` — the deploy pipeline (compile the manifest the Rust backend
|
|
15
15
|
* reads + the OpenAPI contract, then build/ship).
|
|
16
16
|
*/
|
package/src/define/index.ts
CHANGED
|
@@ -66,6 +66,9 @@ export interface Ref<App extends string, Model extends string> {
|
|
|
66
66
|
readonly __model: Model;
|
|
67
67
|
/** The authored `() => Resource` thunk — carried at runtime. */
|
|
68
68
|
readonly target?: () => ResourceType<App, Model, any>;
|
|
69
|
+
/** Optional FK constraints — `null: true` makes the relation nullable
|
|
70
|
+
* (e.g. a shared secret with no project, or an unassigned task). */
|
|
71
|
+
readonly options?: { null?: boolean };
|
|
69
72
|
}
|
|
70
73
|
|
|
71
74
|
export interface StringOptions {
|
|
@@ -153,8 +156,9 @@ export const s = {
|
|
|
153
156
|
*/
|
|
154
157
|
ref<A extends string, M extends string>(
|
|
155
158
|
thunk: () => ResourceType<A, M, any>,
|
|
159
|
+
options?: { null?: boolean },
|
|
156
160
|
): Ref<A, M> {
|
|
157
|
-
return { __rastackRef: true, target: thunk } as Ref<A, M>;
|
|
161
|
+
return { __rastackRef: true, target: thunk, options } as Ref<A, M>;
|
|
158
162
|
},
|
|
159
163
|
};
|
|
160
164
|
|
|
@@ -171,8 +175,7 @@ export interface SyncOptions {
|
|
|
171
175
|
|
|
172
176
|
/**
|
|
173
177
|
* Row-level access control. Enforced by `rastack-api-core`, so the rules are
|
|
174
|
-
* identical on the server (
|
|
175
|
-
* engine.
|
|
178
|
+
* identical on the native server (laptop and Lambda).
|
|
176
179
|
*
|
|
177
180
|
* - `ownerField` — the field that records the owning identity (the verified
|
|
178
181
|
* token `sub`). Rows are only listed/read/written by their owner; the field
|
|
@@ -212,7 +215,7 @@ export type Permission = "authenticatedOrReadOnly" | "authenticated" | "public";
|
|
|
212
215
|
* transition may fire from, `to` the state it enters, and `set` the effect —
|
|
213
216
|
* field patches the engine applies alongside the state change. The block is
|
|
214
217
|
* static data: it compiles into the manifest and is enforced by
|
|
215
|
-
* `rastack-api-core` on
|
|
218
|
+
* `rastack-api-core` on the native server (laptop and Lambda), so a
|
|
216
219
|
* transition behaves as a declarative, typesafe backend function with no
|
|
217
220
|
* per-resource server code.
|
|
218
221
|
*/
|
package/src/define/manifest.ts
CHANGED
|
@@ -121,11 +121,17 @@ function runtimeField(
|
|
|
121
121
|
`manifestFromResources: ${res.app}.${res.model}.${name} is an s.ref(...) whose target thunk did not return a resource() value`,
|
|
122
122
|
);
|
|
123
123
|
}
|
|
124
|
-
|
|
124
|
+
const fk: FieldModel = {
|
|
125
125
|
name,
|
|
126
126
|
type: "fk",
|
|
127
127
|
relation: { app: target.app, model: target.model },
|
|
128
128
|
};
|
|
129
|
+
// A nullable FK (`s.ref(() => X, { null: true })`) — e.g. a shared secret
|
|
130
|
+
// with no project — so the engine allows and stores a null relation.
|
|
131
|
+
if ((field.options as { null?: boolean } | undefined)?.null === true) {
|
|
132
|
+
fk.null = true;
|
|
133
|
+
}
|
|
134
|
+
return fk;
|
|
129
135
|
}
|
|
130
136
|
|
|
131
137
|
if (typeof field.__rastackScalar !== "string") return undefined;
|