rastack 0.0.50 → 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 +4 -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.d.ts +1 -7
- package/dist/compile/analyze.js +19 -70
- 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 +82 -18
- package/dist/define/index.js +27 -21
- package/dist/define/manifest.d.ts +64 -0
- package/dist/define/manifest.js +259 -0
- 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/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/wasm/rastack_wasm.js +1 -1
- package/dist/wasm/rastack_wasm_bg.wasm +0 -0
- package/hooks/data.ts +18 -6
- package/hooks/entity.ts +11 -5
- package/hooks/form/entity-form.ts +12 -8
- package/hooks/manifest.ts +50 -14
- package/hooks/registry.ts +25 -8
- package/package.json +1 -1
- package/plugin.ts +8 -0
- package/provider/index.ts +1 -5
- package/provider/provider.tsx +61 -16
- package/provider/types.ts +26 -7
- package/provider/warehouse.ts +4 -3
- package/provider/wasm.ts +3 -1
- package/runtime.ts +1 -0
- package/src/compile/analyze.ts +27 -65
- 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 +118 -24
- package/src/define/manifest.ts +287 -0
- package/src/define/markers.ts +83 -0
- package/src/define/types.ts +11 -0
- package/src/plugin/core.ts +236 -0
- package/src/plugin/index.ts +243 -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/test/plugin.spec.ts +315 -0
- package/test/runtime-manifest.spec.ts +309 -0
- package/test/typed-hooks.spec.ts +7 -2
- package/types.ts +7 -2
- package/wasm/rastack_wasm.js +1 -1
- package/wasm/rastack_wasm_bg.wasm +0 -0
package/provider/provider.tsx
CHANGED
|
@@ -33,6 +33,14 @@ 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";
|
|
42
|
+
import { toManifest } from "../src/define/manifest";
|
|
43
|
+
import { getGlobalManifest } from "../hooks/manifest";
|
|
36
44
|
|
|
37
45
|
const RastackContext = createContext<RastackClient | null>(null);
|
|
38
46
|
|
|
@@ -57,22 +65,44 @@ export function RAStackProvider({
|
|
|
57
65
|
const persistRef = useRef<(() => void) & { flush: () => void }>();
|
|
58
66
|
const cacheStorageRef = useRef<CacheStorage | null>(null);
|
|
59
67
|
|
|
68
|
+
// The `manifest` prop is optional: when the rastack dev-server plugin is
|
|
69
|
+
// in the bundler it registers the compiled schema globally before the app
|
|
70
|
+
// boots, so `<RAStackProvider mode="local">` needs no schema wiring at
|
|
71
|
+
// all. An explicit prop (schema.rastack.json object/string, or resource()
|
|
72
|
+
// definitions — array or module namespace) always wins.
|
|
73
|
+
const manifest = useMemo(
|
|
74
|
+
() => toManifest(config.manifest) ?? getGlobalManifest(),
|
|
75
|
+
[config.manifest],
|
|
76
|
+
);
|
|
77
|
+
|
|
60
78
|
const setStatusSafe = (next: RadStatus) => {
|
|
61
79
|
setStatus(next);
|
|
62
80
|
config.onStatusChange?.(next);
|
|
63
81
|
};
|
|
64
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
|
+
|
|
65
95
|
// Locally persisted state is namespaced per identity, so two accounts on
|
|
66
96
|
// one device never share bytes.
|
|
67
97
|
const warehouseDb = withIdentity(
|
|
68
98
|
config.databaseName || "rastack-warehouse",
|
|
69
|
-
|
|
99
|
+
identity,
|
|
70
100
|
);
|
|
71
101
|
|
|
72
102
|
/** Delete everything this identity persisted locally. */
|
|
73
103
|
const purgeLocal = async () => {
|
|
74
104
|
if (cacheStorageRef.current) {
|
|
75
|
-
await purgeIdentityCache(cacheStorageRef.current,
|
|
105
|
+
await purgeIdentityCache(cacheStorageRef.current, identity);
|
|
76
106
|
}
|
|
77
107
|
await deleteIndexedDb(warehouseDb);
|
|
78
108
|
// Replicated rows in the sync engine's store are derived from the same
|
|
@@ -110,20 +140,22 @@ export function RAStackProvider({
|
|
|
110
140
|
}
|
|
111
141
|
|
|
112
142
|
// -- WASM modes (local / s3) --
|
|
113
|
-
if (!
|
|
114
|
-
throw new Error(
|
|
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
|
+
);
|
|
115
147
|
}
|
|
116
148
|
|
|
117
149
|
setStatusSafe("loading");
|
|
118
150
|
const loader = config.loadEngine ?? defaultEngineLoader;
|
|
119
151
|
const mod = await loader();
|
|
120
152
|
if (cancelled) return;
|
|
121
|
-
const engine = createEngine(mod,
|
|
153
|
+
const engine = createEngine(mod, manifest, config.openapi);
|
|
122
154
|
|
|
123
155
|
// Run the in-browser engine as the signed-in identity so owner stamping
|
|
124
156
|
// and row-level security match the server exactly.
|
|
125
|
-
if (
|
|
126
|
-
engine.setAuth?.(
|
|
157
|
+
if (identity) {
|
|
158
|
+
engine.setAuth?.(identity, groups);
|
|
127
159
|
}
|
|
128
160
|
|
|
129
161
|
// Warehouse persistence: IndexedDB for `local`, an S3 sink for `s3`.
|
|
@@ -140,7 +172,7 @@ export function RAStackProvider({
|
|
|
140
172
|
await engine.importWarehouse(existing);
|
|
141
173
|
} else {
|
|
142
174
|
const base = config.warehouseBaseUrl ?? "/data/warehouse/";
|
|
143
|
-
const resources = manifestResources(
|
|
175
|
+
const resources = manifestResources(manifest);
|
|
144
176
|
if (config.cache === false) {
|
|
145
177
|
await seedFromIceberg(engine, base, resources);
|
|
146
178
|
} else {
|
|
@@ -152,7 +184,7 @@ export function RAStackProvider({
|
|
|
152
184
|
const cache = new IcebergFileCache({
|
|
153
185
|
storage,
|
|
154
186
|
source: config.fileSource ?? httpFileSource(base),
|
|
155
|
-
identity
|
|
187
|
+
identity,
|
|
156
188
|
maxOfflineMs: config.maxOfflineMs,
|
|
157
189
|
});
|
|
158
190
|
await cache.seed(engine, resources);
|
|
@@ -177,7 +209,10 @@ export function RAStackProvider({
|
|
|
177
209
|
persistRef.current = persist;
|
|
178
210
|
|
|
179
211
|
engineRef.current = engine;
|
|
180
|
-
configureApi({
|
|
212
|
+
configureApi({
|
|
213
|
+
baseURL: "",
|
|
214
|
+
adapter: createWasmAdapter(engine, persist),
|
|
215
|
+
});
|
|
181
216
|
setStatusSafe("ready");
|
|
182
217
|
}
|
|
183
218
|
|
|
@@ -195,18 +230,21 @@ export function RAStackProvider({
|
|
|
195
230
|
engineRef.current = null;
|
|
196
231
|
};
|
|
197
232
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
198
|
-
}, [config.mode, config.baseURL, config.warehouseBaseUrl,
|
|
233
|
+
}, [config.mode, config.baseURL, config.warehouseBaseUrl, identity]);
|
|
199
234
|
|
|
200
235
|
const client = useMemo<RastackClient>(
|
|
201
236
|
() => ({
|
|
202
237
|
mode: config.mode,
|
|
203
238
|
status,
|
|
239
|
+
auth,
|
|
204
240
|
get engine() {
|
|
205
241
|
return engineRef.current;
|
|
206
242
|
},
|
|
207
243
|
async exportWarehouse() {
|
|
208
244
|
if (!engineRef.current) {
|
|
209
|
-
throw new Error(
|
|
245
|
+
throw new Error(
|
|
246
|
+
"exportWarehouse is only available in local/s3 mode.",
|
|
247
|
+
);
|
|
210
248
|
}
|
|
211
249
|
return engineRef.current.exportWarehouse();
|
|
212
250
|
},
|
|
@@ -225,13 +263,18 @@ export function RAStackProvider({
|
|
|
225
263
|
},
|
|
226
264
|
}),
|
|
227
265
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
228
|
-
[config.mode, status,
|
|
266
|
+
[config.mode, status, identity],
|
|
229
267
|
);
|
|
230
268
|
|
|
231
269
|
return (
|
|
232
270
|
<RastackContext.Provider value={client}>
|
|
233
|
-
<RastackManifestProvider manifest={
|
|
234
|
-
|
|
271
|
+
<RastackManifestProvider manifest={manifest}>
|
|
272
|
+
<RastackAuthProvider
|
|
273
|
+
auth={auth}
|
|
274
|
+
manifest={manifest as ManifestLike | undefined}
|
|
275
|
+
>
|
|
276
|
+
{children}
|
|
277
|
+
</RastackAuthProvider>
|
|
235
278
|
</RastackManifestProvider>
|
|
236
279
|
</RastackContext.Provider>
|
|
237
280
|
);
|
|
@@ -265,7 +308,9 @@ function withIdentity(name: string, identity?: string): string {
|
|
|
265
308
|
export function useRastackClient(): RastackClient {
|
|
266
309
|
const client = useContext(RastackContext);
|
|
267
310
|
if (!client) {
|
|
268
|
-
throw new Error(
|
|
311
|
+
throw new Error(
|
|
312
|
+
"useRastackClient must be used within a <RAStackProvider>.",
|
|
313
|
+
);
|
|
269
314
|
}
|
|
270
315
|
return client;
|
|
271
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
|
|
@@ -98,12 +108,19 @@ export interface RAStackProviderConfig {
|
|
|
98
108
|
|
|
99
109
|
// -- all modes --
|
|
100
110
|
/**
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
111
|
+
* The schema — **usually omitted**: with the `rastack/plugin` dev-server
|
|
112
|
+
* plugin in the bundler, the manifest compiled from your entities is
|
|
113
|
+
* registered globally before the app boots and the provider picks it up
|
|
114
|
+
* automatically. Pass it explicitly to override: the compiled
|
|
115
|
+
* `schema.rastack.json` (object or JSON string), the
|
|
116
|
+
* `virtual:rastack-manifest` module, or `resource()` definitions (an array
|
|
117
|
+
* or a module namespace), from which the provider builds the identical
|
|
118
|
+
* manifest at runtime. One of the two (plugin or prop) is required for
|
|
119
|
+
* WASM modes (the manifest drives the in-browser engine); it is also what
|
|
120
|
+
* lets `useData(Entity)` / `useForm(Entity)` resolve a plain TypeScript
|
|
121
|
+
* type to its resource at runtime.
|
|
105
122
|
*/
|
|
106
|
-
manifest?: object | string;
|
|
123
|
+
manifest?: object | readonly object[] | string;
|
|
107
124
|
/** `openapi.json` — object or JSON string. Optional (`/api/schema/`). */
|
|
108
125
|
openapi?: object | string;
|
|
109
126
|
/**
|
|
@@ -159,6 +176,8 @@ export type RadStatus =
|
|
|
159
176
|
export interface RastackClient {
|
|
160
177
|
mode: RadMode;
|
|
161
178
|
status: RadStatus;
|
|
179
|
+
/** The resolved identity this client runs as (from `auth` or `identity`). */
|
|
180
|
+
auth?: import("../auth").RastackAuth;
|
|
162
181
|
/** The live WASM engine (null in `remote` mode). */
|
|
163
182
|
engine: RastackEngine | null;
|
|
164
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
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
import * as path from "path";
|
|
2
2
|
import * as ts from "typescript";
|
|
3
3
|
import {
|
|
4
|
-
AccessModel,
|
|
5
|
-
AdminModel,
|
|
6
4
|
FieldModel,
|
|
7
5
|
RelationModel,
|
|
8
6
|
ResourceModel,
|
|
9
7
|
ScalarType,
|
|
10
|
-
SyncModel,
|
|
11
8
|
} from "./model";
|
|
12
9
|
import { resourceSourceFiles } from "./program";
|
|
13
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
applyTransitions,
|
|
12
|
+
normaliseAccess,
|
|
13
|
+
normaliseAdmin,
|
|
14
|
+
normaliseSync,
|
|
15
|
+
} from "../define/manifest";
|
|
14
16
|
import type { EntityTypeExport } from "./entities";
|
|
15
17
|
|
|
18
|
+
// The option normalisers live beside the runtime manifest builder
|
|
19
|
+
// (`define/manifest.ts`) and are shared with it, so a manifest built from
|
|
20
|
+
// runtime `resource()` values and one compiled from the same source can
|
|
21
|
+
// never disagree. Re-exported here for existing importers.
|
|
22
|
+
export { applyTransitions } from "../define/manifest";
|
|
23
|
+
|
|
16
24
|
/**
|
|
17
25
|
* Statically analyse resource definitions and produce the canonical model.
|
|
18
26
|
*
|
|
@@ -61,7 +69,9 @@ export function analyzeDsl(
|
|
|
61
69
|
|
|
62
70
|
// Deterministic order: app, then model.
|
|
63
71
|
resources.sort((a, b) =>
|
|
64
|
-
a.app === b.app
|
|
72
|
+
a.app === b.app
|
|
73
|
+
? a.model.localeCompare(b.model)
|
|
74
|
+
: a.app.localeCompare(b.app),
|
|
65
75
|
);
|
|
66
76
|
types.sort((a, b) => a.key.localeCompare(b.key));
|
|
67
77
|
return { resources, types };
|
|
@@ -106,7 +116,12 @@ function analyzeResourceCall(
|
|
|
106
116
|
const app = stringLiteralArg(call.arguments[0]);
|
|
107
117
|
const model = stringLiteralArg(call.arguments[1]);
|
|
108
118
|
const fieldsArg = call.arguments[2];
|
|
109
|
-
if (
|
|
119
|
+
if (
|
|
120
|
+
!app ||
|
|
121
|
+
!model ||
|
|
122
|
+
!fieldsArg ||
|
|
123
|
+
!ts.isObjectLiteralExpression(fieldsArg)
|
|
124
|
+
) {
|
|
110
125
|
return undefined;
|
|
111
126
|
}
|
|
112
127
|
|
|
@@ -147,26 +162,6 @@ function analyzeResourceCall(
|
|
|
147
162
|
return resource;
|
|
148
163
|
}
|
|
149
164
|
|
|
150
|
-
/**
|
|
151
|
-
* Attach a normalised `transitions` block to a resource and derive what the
|
|
152
|
-
* machine implies about its state field: the `states` become the field's
|
|
153
|
-
* allowed-value set (`enum` in OpenAPI, the `member` gate in validation), and
|
|
154
|
-
* `initial` becomes its default when the field doesn't declare one.
|
|
155
|
-
*/
|
|
156
|
-
export function applyTransitions(resource: ResourceModel, raw: unknown): void {
|
|
157
|
-
const transitions = normalizeTransitions(raw);
|
|
158
|
-
if (!transitions) return;
|
|
159
|
-
resource.transitions = transitions;
|
|
160
|
-
|
|
161
|
-
const stateField = resource.fields.find((f) => f.name === transitions.field);
|
|
162
|
-
if (stateField && stateField.type !== "fk") {
|
|
163
|
-
stateField.options = [...transitions.states];
|
|
164
|
-
if (stateField.default === undefined && transitions.initial !== undefined) {
|
|
165
|
-
stateField.default = transitions.initial;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
165
|
/**
|
|
171
166
|
* Resolve a single field from its initializer. FK detection is type-driven:
|
|
172
167
|
* the initializer's *type* is inspected, and a `Ref` type yields a foreign key.
|
|
@@ -190,7 +185,12 @@ function analyzeField(
|
|
|
190
185
|
}
|
|
191
186
|
|
|
192
187
|
// Scalar: the field type carries a `__rastackScalar` string-literal discriminant.
|
|
193
|
-
const scalar = literalTypeProp(
|
|
188
|
+
const scalar = literalTypeProp(
|
|
189
|
+
fieldType,
|
|
190
|
+
"__rastackScalar",
|
|
191
|
+
checker,
|
|
192
|
+
initializer,
|
|
193
|
+
);
|
|
194
194
|
if (!scalar) return undefined;
|
|
195
195
|
|
|
196
196
|
const field: FieldModel = { name, type: scalar as ScalarType };
|
|
@@ -280,41 +280,3 @@ export function literalToValue(node: ts.Expression | undefined): any {
|
|
|
280
280
|
}
|
|
281
281
|
return undefined;
|
|
282
282
|
}
|
|
283
|
-
|
|
284
|
-
function normaliseSync(sync: any): SyncModel | undefined {
|
|
285
|
-
if (!sync || typeof sync !== "object") return undefined;
|
|
286
|
-
const mode = sync.mode === "local" ? "local" : "server";
|
|
287
|
-
if (mode === "server" && !sync.confirmed) {
|
|
288
|
-
// A bare server resource carries no sync semantics.
|
|
289
|
-
return undefined;
|
|
290
|
-
}
|
|
291
|
-
const model: SyncModel = { mode };
|
|
292
|
-
if (typeof sync.scope === "string") model.scope = sync.scope;
|
|
293
|
-
if (Array.isArray(sync.confirmed)) model.confirmed = sync.confirmed;
|
|
294
|
-
if (sync.load === "lazy") model.load = "lazy";
|
|
295
|
-
if (sync.clientIds === true) model.clientIds = true;
|
|
296
|
-
return model;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
function normaliseAccess(access: any): AccessModel | undefined {
|
|
300
|
-
if (!access || typeof access !== "object") return undefined;
|
|
301
|
-
const model: AccessModel = {};
|
|
302
|
-
if (typeof access.ownerField === "string") model.ownerField = access.ownerField;
|
|
303
|
-
if (access.scope === "owner" || access.scope === "shared") {
|
|
304
|
-
model.scope = access.scope;
|
|
305
|
-
}
|
|
306
|
-
if (Array.isArray(access.adminGroups)) {
|
|
307
|
-
model.adminGroups = access.adminGroups.filter(
|
|
308
|
-
(g: unknown) => typeof g === "string",
|
|
309
|
-
);
|
|
310
|
-
}
|
|
311
|
-
return Object.keys(model).length ? model : undefined;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
function normaliseAdmin(admin: any): AdminModel | false | undefined {
|
|
315
|
-
if (admin === false) return false;
|
|
316
|
-
if (!admin || typeof admin !== "object") return undefined;
|
|
317
|
-
const model: AdminModel = {};
|
|
318
|
-
if (Array.isArray(admin.listDisplay)) model.listDisplay = admin.listDisplay;
|
|
319
|
-
return model;
|
|
320
|
-
}
|