rastack 0.0.16

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 (140) hide show
  1. package/CHANGELOG.md +43013 -0
  2. package/dist/.prettierrc +7 -0
  3. package/dist/compile/analyze.d.ts +12 -0
  4. package/dist/compile/analyze.js +233 -0
  5. package/dist/compile/index.d.ts +19 -0
  6. package/dist/compile/index.js +107 -0
  7. package/dist/compile/manifest.d.ts +4 -0
  8. package/dist/compile/manifest.js +12 -0
  9. package/dist/compile/model.d.ts +54 -0
  10. package/dist/compile/model.js +7 -0
  11. package/dist/compile/openapi.d.ts +11 -0
  12. package/dist/compile/openapi.js +233 -0
  13. package/dist/compile/program.d.ts +10 -0
  14. package/dist/compile/program.js +61 -0
  15. package/dist/csv-schema.d.ts +28 -0
  16. package/dist/csv-schema.js +162 -0
  17. package/dist/define/index.d.ts +117 -0
  18. package/dist/define/index.js +74 -0
  19. package/dist/entity-generation/delete-method.d.ts +1 -0
  20. package/dist/entity-generation/delete-method.js +31 -0
  21. package/dist/entity-generation/form.d.ts +13 -0
  22. package/dist/entity-generation/form.js +136 -0
  23. package/dist/entity-generation/get-method.d.ts +1 -0
  24. package/dist/entity-generation/get-method.js +67 -0
  25. package/dist/entity-generation/imports.d.ts +5 -0
  26. package/dist/entity-generation/imports.js +91 -0
  27. package/dist/entity-generation/sync-method.d.ts +30 -0
  28. package/dist/entity-generation/sync-method.js +158 -0
  29. package/dist/entity-generation/update-method.d.ts +1 -0
  30. package/dist/entity-generation/update-method.js +29 -0
  31. package/dist/index.d.ts +2 -0
  32. package/dist/index.js +53 -0
  33. package/dist/rad-compile.d.ts +11 -0
  34. package/dist/rad-compile.js +82 -0
  35. package/dist/rad-wasm-build.d.ts +14 -0
  36. package/dist/rad-wasm-build.js +74 -0
  37. package/dist/rad.d.ts +17 -0
  38. package/dist/rad.js +88 -0
  39. package/dist/scan.d.ts +12 -0
  40. package/dist/scan.js +79 -0
  41. package/dist/schema/camel-to-pastel.d.ts +1 -0
  42. package/dist/schema/camel-to-pastel.js +7 -0
  43. package/dist/schema/capitalise-first-letter.d.ts +1 -0
  44. package/dist/schema/capitalise-first-letter.js +7 -0
  45. package/dist/schema/extract-response.d.ts +2 -0
  46. package/dist/schema/extract-response.js +30 -0
  47. package/dist/schema/fetch-schema.d.ts +7 -0
  48. package/dist/schema/fetch-schema.js +23 -0
  49. package/dist/schema/remove-non-model-paths.d.ts +2 -0
  50. package/dist/schema/remove-non-model-paths.js +15 -0
  51. package/dist/schema/to-camel-case.d.ts +1 -0
  52. package/dist/schema/to-camel-case.js +11 -0
  53. package/dist/schema/to-pastel-case.d.ts +1 -0
  54. package/dist/schema/to-pastel-case.js +10 -0
  55. package/dist/schema-convert.d.ts +1 -0
  56. package/dist/schema-convert.js +63 -0
  57. package/dist/schema-entities.d.ts +14 -0
  58. package/dist/schema-entities.js +293 -0
  59. package/dist/schema-fetch.d.ts +1 -0
  60. package/dist/schema-fetch.js +81 -0
  61. package/dist/schema-full.d.ts +1 -0
  62. package/dist/schema-full.js +45 -0
  63. package/dist/schema-index.d.ts +1 -0
  64. package/dist/schema-index.js +34 -0
  65. package/dist/schema-params.d.ts +1 -0
  66. package/dist/schema-params.js +100 -0
  67. package/dist/seed.d.ts +17 -0
  68. package/dist/seed.js +252 -0
  69. package/hooks/form/form.ts +207 -0
  70. package/hooks/form/index.ts +8 -0
  71. package/hooks/form/interfaces.ts +217 -0
  72. package/hooks/form/structure.ts +39 -0
  73. package/hooks/form/validate-schema.ts +49 -0
  74. package/hooks/index.ts +3 -0
  75. package/hooks/query/api.ts +42 -0
  76. package/hooks/query/delete.ts +45 -0
  77. package/hooks/query/fetch.ts +48 -0
  78. package/hooks/query/index.ts +21 -0
  79. package/hooks/query/interfaces.ts +111 -0
  80. package/hooks/query/list.ts +286 -0
  81. package/hooks/query/update.ts +88 -0
  82. package/hooks/query/url.ts +31 -0
  83. package/hooks/real-time/index.ts +1 -0
  84. package/hooks/real-time/pusher.ts +43 -0
  85. package/jest.config.cjs +6 -0
  86. package/package.json +57 -0
  87. package/provider/index.ts +13 -0
  88. package/provider/provider.tsx +187 -0
  89. package/provider/types.ts +114 -0
  90. package/provider/warehouse.ts +167 -0
  91. package/provider/wasm.ts +124 -0
  92. package/runtime.ts +3 -0
  93. package/src/.prettierrc +7 -0
  94. package/src/compile/analyze.ts +224 -0
  95. package/src/compile/index.ts +86 -0
  96. package/src/compile/manifest.ts +10 -0
  97. package/src/compile/model.ts +69 -0
  98. package/src/compile/openapi.ts +266 -0
  99. package/src/compile/program.ts +40 -0
  100. package/src/csv-schema.ts +187 -0
  101. package/src/define/index.ts +162 -0
  102. package/src/entity-generation/delete-method.ts +37 -0
  103. package/src/entity-generation/form.ts +169 -0
  104. package/src/entity-generation/get-method.ts +80 -0
  105. package/src/entity-generation/imports.ts +110 -0
  106. package/src/entity-generation/sync-method.ts +222 -0
  107. package/src/entity-generation/update-method.ts +38 -0
  108. package/src/index.ts +77 -0
  109. package/src/rad-compile.ts +88 -0
  110. package/src/rad-wasm-build.ts +102 -0
  111. package/src/rad.ts +101 -0
  112. package/src/scan.ts +89 -0
  113. package/src/schema/camel-to-pastel.ts +3 -0
  114. package/src/schema/capitalise-first-letter.ts +3 -0
  115. package/src/schema/extract-response.ts +38 -0
  116. package/src/schema/fetch-schema.ts +12 -0
  117. package/src/schema/remove-non-model-paths.ts +13 -0
  118. package/src/schema/to-camel-case.ts +7 -0
  119. package/src/schema/to-pastel-case.ts +6 -0
  120. package/src/schema-convert.ts +61 -0
  121. package/src/schema-entities.ts +401 -0
  122. package/src/schema-fetch.ts +82 -0
  123. package/src/schema-full.ts +38 -0
  124. package/src/schema-index.ts +29 -0
  125. package/src/schema-params.ts +116 -0
  126. package/src/seed.ts +297 -0
  127. package/sync/engine.ts +392 -0
  128. package/sync/hooks.ts +450 -0
  129. package/sync/index.ts +8 -0
  130. package/sync/persistence.ts +237 -0
  131. package/sync/provider.tsx +91 -0
  132. package/sync/registry.ts +36 -0
  133. package/sync/store.ts +126 -0
  134. package/sync/transactions.ts +300 -0
  135. package/sync/types.ts +94 -0
  136. package/test/compile.spec.ts +192 -0
  137. package/test/csv-schema.spec.ts +143 -0
  138. package/test/schema-entities.spec.ts +688 -0
  139. package/tsconfig.json +24 -0
  140. package/types.ts +19 -0
@@ -0,0 +1,88 @@
1
+ import { AxiosResponse } from "axios";
2
+ import { useMutation, useQueryClient } from "react-query";
3
+ import { IUpdateQueryResult, WriteQueryVariables } from "./interfaces";
4
+ import { API } from "./api";
5
+ import { stringifyQueryParams } from "./url";
6
+
7
+ interface UpdateQueryProps<TParam> {
8
+ putUrl?: ((params: TParam) => string) | string;
9
+ patchUrl?: ((params: TParam) => string) | string;
10
+ postUrl?: ((params: TParam) => string) | string;
11
+ listQueryCacheKey?: string | undefined;
12
+ itemQueryCacheKey?: string | undefined;
13
+ }
14
+
15
+ export function useUpdateQuery<TParams, TPayload, TResult>({
16
+ putUrl,
17
+ patchUrl,
18
+ postUrl,
19
+ listQueryCacheKey,
20
+ itemQueryCacheKey,
21
+ }: UpdateQueryProps<TParams>): IUpdateQueryResult<TParams, TPayload, TResult> {
22
+ if (!putUrl && !patchUrl && !postUrl) {
23
+ throw new Error("Either putUrl, patchUrl, or postUrl must be provided");
24
+ }
25
+ const queryClient = useQueryClient();
26
+
27
+ return useMutation<
28
+ TResult,
29
+ unknown,
30
+ WriteQueryVariables<TParams, TPayload>,
31
+ unknown
32
+ >(({ params, payload }) => {
33
+ return new Promise((resolve, reject) => {
34
+ let urlType: "put" | "patch" | "post" = "patch";
35
+ let url: string | undefined;
36
+
37
+ if (putUrl) {
38
+ url = typeof putUrl === "function" ? putUrl(params) : putUrl;
39
+ urlType = "put";
40
+ } else if (patchUrl) {
41
+ url = typeof patchUrl === "function" ? patchUrl(params) : patchUrl;
42
+ urlType = "patch";
43
+ } else if (postUrl) {
44
+ url = typeof postUrl === "function" ? postUrl(params) : postUrl;
45
+ urlType = "post";
46
+ }
47
+
48
+ if (!url) {
49
+ throw new Error("URL must be provided for the API call");
50
+ }
51
+
52
+ // @ts-ignore
53
+ const urlParams = stringifyQueryParams([], params);
54
+ const serverURL = `${url}${urlParams ? `?${urlParams}` : ""}`;
55
+
56
+ const getAPIFunction = (
57
+ urlType: string,
58
+ url: string,
59
+ payload: TPayload,
60
+ ) => {
61
+ switch (urlType) {
62
+ case "put":
63
+ return API.put(serverURL, payload);
64
+ case "patch":
65
+ return API.patch(serverURL, payload);
66
+ case "post":
67
+ return API.post(serverURL, payload);
68
+ default:
69
+ throw new Error("Invalid URL type");
70
+ }
71
+ };
72
+
73
+ getAPIFunction(urlType, url, payload)
74
+ .then((response: AxiosResponse<TResult>) => {
75
+ if (itemQueryCacheKey) {
76
+ queryClient.invalidateQueries([itemQueryCacheKey]);
77
+ }
78
+ if (listQueryCacheKey) {
79
+ queryClient.invalidateQueries(listQueryCacheKey);
80
+ }
81
+ resolve(response.data);
82
+ })
83
+ .catch((error) => {
84
+ reject(error);
85
+ });
86
+ });
87
+ });
88
+ }
@@ -0,0 +1,31 @@
1
+ export function stringifyQueryParams<TParam>(
2
+ pathParameters: (keyof TParam)[],
3
+ filteredQueryParams?: Partial<TParam>,
4
+ ): string | undefined {
5
+ if (!filteredQueryParams) return undefined;
6
+
7
+ const urlParams = new URLSearchParams();
8
+
9
+ for (const key in filteredQueryParams) {
10
+ if (pathParameters.includes(key as any)) {
11
+ continue;
12
+ }
13
+ if (Object.prototype.hasOwnProperty.call(filteredQueryParams, key)) {
14
+ const value = filteredQueryParams[key];
15
+
16
+ if (Array.isArray(value)) {
17
+ value.forEach((item) => {
18
+ if (item !== undefined && item !== null) {
19
+ urlParams.append(key, item.toString());
20
+ }
21
+ });
22
+ } else {
23
+ if (value !== undefined && value !== null) {
24
+ urlParams.set(key, value.toString());
25
+ }
26
+ }
27
+ }
28
+ }
29
+
30
+ return urlParams.toString();
31
+ }
@@ -0,0 +1 @@
1
+ export { usePusher } from "./pusher";
@@ -0,0 +1,43 @@
1
+ import { useEffect } from "react";
2
+ import { useSyncEngineOptional } from "../../sync/provider";
3
+
4
+ export interface PusherWakeOptions {
5
+ /** Channel to subscribe to. Defaults to "rad-sync". */
6
+ channel?: string;
7
+ /** Event name carrying sync notifications. Defaults to "sync". */
8
+ event?: string;
9
+ /** A connected Pusher client instance. */
10
+ client?: any;
11
+ }
12
+
13
+ /**
14
+ * Optional push transport for the sync engine. Given a connected Pusher
15
+ * client (passed in, or found on `window.Pusher.instances`), subscribes to
16
+ * a channel and wakes the engine whenever a sync event arrives — the engine
17
+ * then pulls the actual changes over HTTP. Push is a latency optimization
18
+ * only; the long-poll `/changes` loop stays the source of truth, so this
19
+ * hook is always safe to omit.
20
+ */
21
+ export function usePusher(options?: PusherWakeOptions) {
22
+ const engine = useSyncEngineOptional();
23
+ const channelName = options?.channel || "rad-sync";
24
+ const eventName = options?.event || "sync";
25
+ const client =
26
+ options?.client ??
27
+ (typeof window !== "undefined"
28
+ ? (window as any).Pusher?.instances?.[0]
29
+ : undefined);
30
+
31
+ useEffect(() => {
32
+ if (!engine || !client) {
33
+ return;
34
+ }
35
+ const channel = client.subscribe(channelName);
36
+ const handler = () => engine.wake();
37
+ channel.bind(eventName, handler);
38
+ return () => {
39
+ channel.unbind(eventName, handler);
40
+ client.unsubscribe(channelName);
41
+ };
42
+ }, [engine, client, channelName, eventName]);
43
+ }
@@ -0,0 +1,6 @@
1
+ module.exports = {
2
+ preset: 'ts-jest',
3
+ testEnvironment: 'node',
4
+ testMatch: ['**/test/**/*.spec.ts'],
5
+ moduleFileExtensions: ['ts', 'js'],
6
+ };
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "rastack",
3
+ "version": "0.0.16",
4
+ "description": "",
5
+ "main": "runtime.ts",
6
+ "types": "runtime.ts",
7
+ "bin": {
8
+ "rastack": "dist/index.js",
9
+ "rad": "dist/rad.js"
10
+ },
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "scripts": {
15
+ "format": "prettier --write \"**/*.{ts,tsx,md}\"",
16
+ "build": "tsc && cp src/.prettierrc dist/.prettierrc",
17
+ "start": "node dist/index.js",
18
+ "test": "jest"
19
+ },
20
+ "keywords": [],
21
+ "author": "",
22
+ "license": "ISC",
23
+ "dependencies": {
24
+ "@apidevtools/json-schema-ref-parser": "^15.4.0",
25
+ "@tanstack/react-query": "^5.101.2",
26
+ "@ts-stack/openapi-spec": "^3.1.5",
27
+ "@types/json-schema": "^7.0.15",
28
+ "@types/react": "^19.2.17",
29
+ "ajv": "^8.20.0",
30
+ "ajv-formats": "^3.0.1",
31
+ "axios": "^1.16.0",
32
+ "formik": "^2.4.9",
33
+ "js-yaml": "^5.2.1",
34
+ "json-schema": "^0.4.0",
35
+ "json-schema-to-typescript": "^15.0.4",
36
+ "jsonschema": "^1.5.0",
37
+ "lodash": "^4.18.0",
38
+ "luxon": "^3.7.2",
39
+ "minimatch": "^10.2.5",
40
+ "prettier": "^3.9.4",
41
+ "react-query": "^3.39.3",
42
+ "typescript": "^6.0.3",
43
+ "usehooks-ts": "^3.1.1"
44
+ },
45
+ "peerDependencies": {
46
+ "react": "^19.2.7"
47
+ },
48
+ "devDependencies": {
49
+ "@types/jest": "^30.0.0",
50
+ "@types/js-yaml": "^4.0.9",
51
+ "@types/mocha": "^10.0.6",
52
+ "@types/node": "^26.1.0",
53
+ "eslint": "^10.6.0",
54
+ "jest": "^30.4.2",
55
+ "ts-jest": "^29.4.11"
56
+ }
57
+ }
@@ -0,0 +1,13 @@
1
+ export * from "./types";
2
+ export * from "./provider";
3
+ export {
4
+ createWasmAdapter,
5
+ createEngine,
6
+ defaultEngineLoader,
7
+ } from "./wasm";
8
+ export {
9
+ WarehousePersistence,
10
+ seedFromIceberg,
11
+ manifestResources,
12
+ downloadBlob,
13
+ } from "./warehouse";
@@ -0,0 +1,187 @@
1
+ import React, {
2
+ createContext,
3
+ ReactNode,
4
+ useContext,
5
+ useEffect,
6
+ useMemo,
7
+ useRef,
8
+ useState,
9
+ } from "react";
10
+ import { API, configureApi, resetApi } from "../hooks/query/api";
11
+ import {
12
+ RadClient,
13
+ RadEngine,
14
+ RAStackProviderConfig,
15
+ RadStatus,
16
+ WarehouseSource,
17
+ } from "./types";
18
+ import { createEngine, createWasmAdapter, defaultEngineLoader } from "./wasm";
19
+ import {
20
+ debounce,
21
+ downloadBlob,
22
+ manifestResources,
23
+ seedFromIceberg,
24
+ WarehousePersistence,
25
+ } from "./warehouse";
26
+
27
+ const RadContext = createContext<RadClient | null>(null);
28
+
29
+ /**
30
+ * The one place a React app configures *where the API runs* — over HTTP
31
+ * (`remote`), in the browser via WebAssembly (`local`), or in the browser
32
+ * against S3 (`s3`). It reconfigures the shared axios client in place, so every
33
+ * generated hook keeps working with no changes.
34
+ *
35
+ * ```tsx
36
+ * <RAStackProvider mode="local" manifest={schema} warehouseBaseUrl="/data/warehouse/">
37
+ * <App />
38
+ * </RAStackProvider>
39
+ * ```
40
+ */
41
+ export function RAStackProvider({
42
+ children,
43
+ ...config
44
+ }: RAStackProviderConfig & { children: ReactNode }): JSX.Element {
45
+ const [status, setStatus] = useState<RadStatus>("idle");
46
+ const engineRef = useRef<RadEngine | null>(null);
47
+ const persistRef = useRef<(() => void) & { flush: () => void }>();
48
+
49
+ const setStatusSafe = (next: RadStatus) => {
50
+ setStatus(next);
51
+ config.onStatusChange?.(next);
52
+ };
53
+
54
+ useEffect(() => {
55
+ let cancelled = false;
56
+ let ejectToken: number | undefined;
57
+
58
+ async function setup() {
59
+ // Bearer-token interceptor works in every mode.
60
+ if (config.getToken) {
61
+ ejectToken = API.interceptors.request.use(async (request) => {
62
+ const token = await config.getToken!();
63
+ request.headers = request.headers || ({} as any);
64
+ (request.headers as any).Authorization = `Bearer ${token}`;
65
+ return request;
66
+ });
67
+ }
68
+
69
+ if (config.mode === "remote") {
70
+ configureApi({ baseURL: config.baseURL ?? "", adapter: null });
71
+ if (config.baseURL === undefined) resetApi();
72
+ setStatusSafe("ready");
73
+ return;
74
+ }
75
+
76
+ // -- WASM modes (local / s3) --
77
+ if (!config.manifest) {
78
+ throw new Error("RAStackProvider: `manifest` is required in local/s3 mode.");
79
+ }
80
+
81
+ setStatusSafe("loading");
82
+ const loader = config.loadEngine ?? defaultEngineLoader;
83
+ const mod = await loader();
84
+ if (cancelled) return;
85
+ const engine = createEngine(mod, config.manifest, config.openapi);
86
+
87
+ // Warehouse persistence: IndexedDB for `local`, an S3 sink for `s3`.
88
+ const persistence = pickPersistence(config);
89
+
90
+ setStatusSafe("seeding");
91
+ // A persisted local copy (with the user's edits) wins; otherwise seed
92
+ // from the committed Iceberg warehouse — a directory of files walked over
93
+ // HTTP, not a blob.
94
+ const existing = await persistence.load();
95
+ if (existing) {
96
+ await engine.importWarehouse(existing);
97
+ } else {
98
+ const base = config.warehouseBaseUrl ?? "/data/warehouse/";
99
+ await seedFromIceberg(engine, base, manifestResources(config.manifest));
100
+ }
101
+ if (cancelled) return;
102
+
103
+ const persist = debounce(() => {
104
+ void engine
105
+ .exportWarehouse()
106
+ .then((blob) => persistence.save(blob))
107
+ .catch((error) => config.onError?.(error));
108
+ }, 400);
109
+ persistRef.current = persist;
110
+
111
+ engineRef.current = engine;
112
+ configureApi({ baseURL: "", adapter: createWasmAdapter(engine, persist) });
113
+ setStatusSafe("ready");
114
+ }
115
+
116
+ setup().catch((error) => {
117
+ if (cancelled) return;
118
+ config.onError?.(error);
119
+ setStatusSafe("error");
120
+ });
121
+
122
+ return () => {
123
+ cancelled = true;
124
+ persistRef.current?.flush();
125
+ if (ejectToken !== undefined) API.interceptors.request.eject(ejectToken);
126
+ resetApi();
127
+ engineRef.current = null;
128
+ };
129
+ // eslint-disable-next-line react-hooks/exhaustive-deps
130
+ }, [config.mode, config.baseURL, config.warehouseBaseUrl]);
131
+
132
+ const client = useMemo<RadClient>(
133
+ () => ({
134
+ mode: config.mode,
135
+ status,
136
+ get engine() {
137
+ return engineRef.current;
138
+ },
139
+ async exportWarehouse() {
140
+ if (!engineRef.current) {
141
+ throw new Error("exportWarehouse is only available in local/s3 mode.");
142
+ }
143
+ return engineRef.current.exportWarehouse();
144
+ },
145
+ async downloadWarehouse(filename = "warehouse-snapshot.json") {
146
+ const blob = await this.exportWarehouse();
147
+ downloadBlob(blob, filename);
148
+ },
149
+ async persistNow() {
150
+ persistRef.current?.flush();
151
+ },
152
+ }),
153
+ [config.mode, status],
154
+ );
155
+
156
+ return <RadContext.Provider value={client}>{children}</RadContext.Provider>;
157
+ }
158
+
159
+ function pickPersistence(config: RAStackProviderConfig): WarehouseSource {
160
+ if (config.mode === "s3") {
161
+ if (!config.warehouse) {
162
+ throw new Error(
163
+ "RAStackProvider: `warehouse` (a WarehouseSource over S3) is required in s3 mode.",
164
+ );
165
+ }
166
+ return config.warehouse;
167
+ }
168
+ if (config.persist === false) {
169
+ return { load: async () => null, save: async () => {} };
170
+ }
171
+ const store = new WarehousePersistence(config.databaseName || "rad-warehouse");
172
+ return { load: () => store.load(), save: (blob) => store.save(blob) };
173
+ }
174
+
175
+ /** Access the imperative Rad client (engine, status, warehouse helpers). */
176
+ export function useRadClient(): RadClient {
177
+ const client = useContext(RadContext);
178
+ if (!client) {
179
+ throw new Error("useRadClient must be used within a <RAStackProvider>.");
180
+ }
181
+ return client;
182
+ }
183
+
184
+ /** Just the current status — handy for a loading gate while WASM boots. */
185
+ export function useRadStatus(): RadStatus {
186
+ return useRadClient().status;
187
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Configuration for `<RAStackProvider>` — the single place a React app chooses
3
+ * *where the API runs*:
4
+ *
5
+ * - `remote` — talk to the Rust API over HTTP (Lambda in prod, `rad serve`
6
+ * locally). The classic client/server split.
7
+ * - `local` — compile-free local development: the entire API runs in the
8
+ * browser via WebAssembly over an in-memory Iceberg warehouse, seeded from a
9
+ * repo `data/` folder and persisted to IndexedDB. No server, no network,
10
+ * instant reads/writes.
11
+ * - `s3` — full client mode: the same WASM engine, but the warehouse is
12
+ * loaded from and saved back to S3, so a static site reads and writes the
13
+ * object-storage database directly.
14
+ */
15
+ export type RadMode = "remote" | "local" | "s3";
16
+
17
+ /** The subset of the WASM `RadApi` class the provider uses. */
18
+ export interface RadEngine {
19
+ handle(
20
+ method: string,
21
+ path: string,
22
+ query: Record<string, string> | undefined,
23
+ body: unknown,
24
+ ): Promise<{ status: number; body: any }>;
25
+ /** Seed one Iceberg file (metadata or `.parquet` data file) at its key. */
26
+ seedFile(key: string, bytes: Uint8Array): Promise<void>;
27
+ importWarehouse(blob: Uint8Array): Promise<void>;
28
+ exportWarehouse(): Promise<Uint8Array>;
29
+ }
30
+
31
+ /** Constructor of the generated `RadApi` class. */
32
+ export interface RadEngineCtor {
33
+ new (manifestJson: string, openapiJson?: string): RadEngine;
34
+ }
35
+
36
+ /** What a WASM bundle loader must resolve to. */
37
+ export interface RadWasmModule {
38
+ /** wasm-pack `web` target exposes an init fn that fetches the `.wasm`. */
39
+ default?: (input?: any) => Promise<any>;
40
+ init?: (input?: any) => Promise<any>;
41
+ RadApi: RadEngineCtor;
42
+ }
43
+
44
+ /** Load and initialise the compiled WASM engine module. */
45
+ export type RadEngineLoader = () => Promise<RadWasmModule>;
46
+
47
+ /** Where to read/write the warehouse blob in `s3` mode (or any custom sink). */
48
+ export interface WarehouseSource {
49
+ /** Fetch the current warehouse blob, or `null` if none exists yet. */
50
+ load(): Promise<Uint8Array | null>;
51
+ /** Persist the warehouse blob (called debounced after writes). */
52
+ save(blob: Uint8Array): Promise<void>;
53
+ }
54
+
55
+ export interface RAStackProviderConfig {
56
+ mode: RadMode;
57
+
58
+ // -- remote mode --
59
+ /** HTTP base URL. Defaults to `NEXT_PUBLIC_BASE_URL` or localhost:8000. */
60
+ baseURL?: string;
61
+ /** Attach a Bearer token to every request. */
62
+ getToken?: () => string | Promise<string>;
63
+
64
+ // -- local / s3 mode --
65
+ /** `schema.rad.json` — object or JSON string. Required for WASM modes. */
66
+ manifest?: object | string;
67
+ /** `openapi.json` — object or JSON string. Optional (`/api/schema/`). */
68
+ openapi?: object | string;
69
+ /**
70
+ * Base URL of the committed Iceberg warehouse (a *directory of files*, not a
71
+ * blob) to seed `local` mode from. The provider walks the tree —
72
+ * `version-hint.text` → snapshot metadata → `.parquet` data files — exactly
73
+ * as the server walks it over object storage. Defaults to `/data/warehouse/`.
74
+ * Ignored once a persisted local copy exists.
75
+ */
76
+ warehouseBaseUrl?: string;
77
+ /** Persist the local warehouse to IndexedDB between reloads (default true). */
78
+ persist?: boolean;
79
+ /** IndexedDB database name for the local warehouse. */
80
+ databaseName?: string;
81
+ /** `s3` mode: how to load/save the warehouse blob from object storage. */
82
+ warehouse?: WarehouseSource;
83
+ /**
84
+ * Override how the WASM module is loaded. Supply this from app code (where
85
+ * your bundler can resolve the generated bundle) if the default loader can't
86
+ * find it. Defaults to importing `rastack/wasm/rad_wasm.js`.
87
+ */
88
+ loadEngine?: RadEngineLoader;
89
+
90
+ // -- lifecycle --
91
+ onStatusChange?: (status: RadStatus) => void;
92
+ onError?: (error: unknown) => void;
93
+ }
94
+
95
+ export type RadStatus =
96
+ | "idle"
97
+ | "loading" // fetching/instantiating the WASM engine
98
+ | "seeding" // importing the warehouse
99
+ | "ready"
100
+ | "error";
101
+
102
+ /** Imperative handle exposed via `useRadClient()`. */
103
+ export interface RadClient {
104
+ mode: RadMode;
105
+ status: RadStatus;
106
+ /** The live WASM engine (null in `remote` mode). */
107
+ engine: RadEngine | null;
108
+ /** Snapshot the local warehouse to a portable blob (WASM modes only). */
109
+ exportWarehouse(): Promise<Uint8Array>;
110
+ /** Download that snapshot as a file — a portable backup of local edits. */
111
+ downloadWarehouse(filename?: string): Promise<void>;
112
+ /** Force-persist now (IndexedDB and/or the `s3` sink). */
113
+ persistNow(): Promise<void>;
114
+ }