@peers-app/peers-sdk 0.23.4 → 0.23.6
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/dist/context/data-context.d.ts +36 -5
- package/dist/context/data-context.js +44 -9
- package/dist/context/user-context.d.ts +44 -0
- package/dist/context/user-context.js +91 -12
- package/dist/contracts/__tests__/contract-provider-router.test.js +26 -0
- package/dist/contracts/__tests__/contract-resolvers.test.js +44 -0
- package/dist/contracts/__tests__/registry.test.js +104 -0
- package/dist/contracts/contract-provider-router.d.ts +21 -0
- package/dist/contracts/contract-provider-router.js +31 -5
- package/dist/contracts/contract-proxy.d.ts +3 -0
- package/dist/contracts/contract-proxy.js +3 -0
- package/dist/contracts/contract-resolvers.d.ts +21 -3
- package/dist/contracts/contract-resolvers.js +105 -32
- package/dist/contracts/index.d.ts +3 -3
- package/dist/contracts/registry.d.ts +59 -0
- package/dist/contracts/registry.js +155 -25
- package/dist/contracts/system-contracts.js +3 -1
- package/dist/data/index.d.ts +1 -0
- package/dist/data/index.js +1 -0
- package/dist/data/orm/types.d.ts +19 -0
- package/dist/data/orm/types.js +11 -0
- package/dist/data/package-table-identity.d.ts +46 -0
- package/dist/data/package-table-identity.js +71 -0
- package/dist/data/package-table-identity.test.d.ts +1 -0
- package/dist/data/package-table-identity.test.js +46 -0
- package/dist/data/package-version-resolver.d.ts +13 -0
- package/dist/data/package-version-resolver.js +142 -6
- package/dist/data/package-version-resolver.test.js +42 -0
- package/dist/data/persistent-var-identity.d.ts +37 -0
- package/dist/data/persistent-var-identity.js +69 -0
- package/dist/data/persistent-vars.d.ts +3 -5
- package/dist/data/persistent-vars.js +25 -13
- package/dist/data/persistent-vars.package-scope.test.d.ts +1 -0
- package/dist/data/persistent-vars.package-scope.test.js +27 -0
- package/dist/data/tools.d.ts +3 -3
- package/dist/package-installer/index.d.ts +1 -0
- package/dist/package-installer/index.js +1 -0
- package/dist/package-installer/package-cloner.d.ts +1 -1
- package/dist/package-installer/package-cloner.js +11 -16
- package/dist/package-installer/package-cloner.test.js +5 -3
- package/dist/package-installer/package-creator.d.ts +4 -3
- package/dist/package-installer/package-creator.js +13 -5
- package/dist/package-installer/package-creator.test.js +19 -0
- package/dist/package-installer/package-dev-tools.d.ts +30 -0
- package/dist/package-installer/package-dev-tools.js +63 -0
- package/dist/package-installer/package-dev-tools.test.d.ts +1 -0
- package/dist/package-installer/package-dev-tools.test.js +64 -0
- package/dist/package-loader/contract-package-loader.js +20 -8
- package/dist/package-loader/contract-package-loader.test.js +16 -0
- package/dist/package-loader/index.d.ts +4 -0
- package/dist/package-loader/index.js +4 -0
- package/dist/package-loader/isolated-artifact.d.ts +233 -0
- package/dist/package-loader/isolated-artifact.js +643 -0
- package/dist/package-loader/isolated-artifact.test.d.ts +1 -0
- package/dist/package-loader/isolated-artifact.test.js +270 -0
- package/dist/package-loader/isolated-persistent-var-gateway.d.ts +20 -0
- package/dist/package-loader/isolated-persistent-var-gateway.js +106 -0
- package/dist/package-loader/isolated-persistent-var-gateway.test.d.ts +1 -0
- package/dist/package-loader/isolated-persistent-var-gateway.test.js +56 -0
- package/dist/package-loader/isolated-runtime.d.ts +229 -0
- package/dist/package-loader/isolated-runtime.js +37 -0
- package/dist/package-loader/isolated-table-gateway.d.ts +16 -0
- package/dist/package-loader/isolated-table-gateway.js +375 -0
- package/dist/package-loader/isolated-table-gateway.test.d.ts +1 -0
- package/dist/package-loader/isolated-table-gateway.test.js +206 -0
- package/dist/package-loader/isolated-value.d.ts +14 -0
- package/dist/package-loader/isolated-value.js +63 -0
- package/dist/package-loader/package-loader.d.ts +123 -4
- package/dist/package-loader/package-loader.js +417 -46
- package/dist/package-loader/package-loader.test.js +478 -12
- package/dist/package-loader/resolve-consumed-contracts.d.ts +3 -3
- package/dist/package-loader/resolve-consumed-contracts.js +18 -10
- package/dist/utils.d.ts +21 -2
- package/dist/utils.js +46 -7
- package/package.json +1 -1
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import type { IContractProviderEndpointRequest } from "../contracts/contract-provider-router";
|
|
2
|
+
import type { IContractCall, IContractProviderEndpoint } from "../contracts/contract-proxy";
|
|
3
|
+
import type { ContractRegistry } from "../contracts/registry";
|
|
4
|
+
import type { PersistentVarScope } from "../data/persistent-vars";
|
|
5
|
+
import type { IField } from "../types/field-type";
|
|
6
|
+
import type { IIsolatedInstallResult, IIsolatedPackageArtifact } from "./isolated-artifact";
|
|
7
|
+
/** Standard table methods exposed through isolated contract-table references. */
|
|
8
|
+
export declare const ISOLATED_CONTRACT_TABLE_OPERATIONS: readonly ["get", "list", "count", "findOne", "save", "insert", "update", "delete"];
|
|
9
|
+
/** Consumer-callable operation on an isolated contract table. */
|
|
10
|
+
export type IsolatedContractTableOperation = (typeof ISOLATED_CONTRACT_TABLE_OPERATIONS)[number];
|
|
11
|
+
/**
|
|
12
|
+
* Trusted caller identity for an isolated tool call. Never read from guest
|
|
13
|
+
* payloads — the host derives this from `DataContext` / workflow state.
|
|
14
|
+
*/
|
|
15
|
+
export interface IIsolatedCallPrincipal {
|
|
16
|
+
userAccessLevel: number;
|
|
17
|
+
/** Omit for a direct user invocation. */
|
|
18
|
+
assistantAccessLevel?: number;
|
|
19
|
+
}
|
|
20
|
+
/** Trusted lifecycle metadata supplied when an isolated artifact is installed. */
|
|
21
|
+
export interface IIsolatedPackageInstallOptions {
|
|
22
|
+
/**
|
|
23
|
+
* Contract maturity derived from the trusted active PackageVersion.
|
|
24
|
+
* Guest artifacts cannot select or change this value.
|
|
25
|
+
*/
|
|
26
|
+
contractLifecycle: "mutable" | "stable";
|
|
27
|
+
/** Trusted selected PackageVersion record ID. */
|
|
28
|
+
packageVersionId?: string;
|
|
29
|
+
/** Trusted semantic version copied from the selected PackageVersion. */
|
|
30
|
+
packageVersion?: string;
|
|
31
|
+
/** Trusted release channel copied from the selected PackageVersion. */
|
|
32
|
+
versionTag?: string;
|
|
33
|
+
}
|
|
34
|
+
/** Options for removing one package from an isolated runtime. */
|
|
35
|
+
export interface IIsolatedPackageUninstallOptions {
|
|
36
|
+
/**
|
|
37
|
+
* Keep the shared registry untouched because the host has already activated
|
|
38
|
+
* a replacement implementation for the same package.
|
|
39
|
+
*/
|
|
40
|
+
preserveRegistry?: boolean;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Host-owned supervisor for isolated package providers.
|
|
44
|
+
*
|
|
45
|
+
* `peers-sdk` defines the interface only. Electron (or a future PWA host)
|
|
46
|
+
* registers a factory that constructs an implementation backed by
|
|
47
|
+
* `peers-isolate` workers. The SDK never depends on Node or SES.
|
|
48
|
+
*/
|
|
49
|
+
export interface IIsolatedPackageRuntime {
|
|
50
|
+
/**
|
|
51
|
+
* Register a validated artifact. Provider source is stored, not evaluated
|
|
52
|
+
* on the host.
|
|
53
|
+
*/
|
|
54
|
+
install(artifact: IIsolatedPackageArtifact, options?: IIsolatedPackageInstallOptions): Promise<IIsolatedInstallResult>;
|
|
55
|
+
/**
|
|
56
|
+
* Atomically replace an installed package version. Implementations stop the
|
|
57
|
+
* previous worker and preserve the old registration if replacement fails.
|
|
58
|
+
*/
|
|
59
|
+
replace(artifact: IIsolatedPackageArtifact, options?: IIsolatedPackageInstallOptions): Promise<IIsolatedInstallResult>;
|
|
60
|
+
/** Stop the package worker (if any) and optionally unregister its contracts. */
|
|
61
|
+
uninstall(packageId: string, options?: IIsolatedPackageUninstallOptions): Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Return a sandbox-backed endpoint when this runtime owns the active
|
|
64
|
+
* provider for the requested contract. Otherwise `undefined` so the
|
|
65
|
+
* caller can fall back to in-process system resolvers.
|
|
66
|
+
*/
|
|
67
|
+
resolveEndpoint(request: IContractProviderEndpointRequest): Promise<IContractProviderEndpoint | undefined>;
|
|
68
|
+
/**
|
|
69
|
+
* True when an isolated package installed here declares this contract
|
|
70
|
+
* version. Registry “active provider” is not required — this runtime can
|
|
71
|
+
* still serve the installed artifact.
|
|
72
|
+
*/
|
|
73
|
+
provides(contractId: string, version: number): boolean;
|
|
74
|
+
/** True when this runtime has installed `packageId`. */
|
|
75
|
+
isInstalled(packageId: string): boolean;
|
|
76
|
+
/** Terminate every worker and drop installed state. */
|
|
77
|
+
dispose(): Promise<void>;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Identity of one isolated package table. The host binds storage to this
|
|
81
|
+
* tuple; guest payloads never choose the owning package.
|
|
82
|
+
*/
|
|
83
|
+
export interface IIsolatedPackageTableRef {
|
|
84
|
+
/** Owning isolated package. Bound by the host, never by guest payload. */
|
|
85
|
+
packageId: string;
|
|
86
|
+
/** Package-local table name from the isolated manifest. */
|
|
87
|
+
tableName: string;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Host-owned declaration used to register an isolated table schema in a
|
|
91
|
+
* data context. Guest code never sees this object.
|
|
92
|
+
*/
|
|
93
|
+
export interface IIsolatedPackageTableDeclaration extends IIsolatedPackageTableRef {
|
|
94
|
+
/** Human-readable table description from the manifest. */
|
|
95
|
+
description: string;
|
|
96
|
+
/** Primary-key field name. Must be an `id` field. */
|
|
97
|
+
primaryKeyName: string;
|
|
98
|
+
/** Column metadata copied from the isolated artifact. */
|
|
99
|
+
fields: IField[];
|
|
100
|
+
/** Schema version for host table-definition updates. */
|
|
101
|
+
versionNumber?: number;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Trusted host capability for isolated-package table persistence.
|
|
105
|
+
*
|
|
106
|
+
* Implementations own `Table` / SQLite access. The isolated worker never
|
|
107
|
+
* receives a `DataContext`, table instance, or SQL handle.
|
|
108
|
+
*/
|
|
109
|
+
export interface IIsolatedPackageTableGateway {
|
|
110
|
+
/**
|
|
111
|
+
* Bind a declared table to its owning package. Data is retained across
|
|
112
|
+
* worker and package unload so a later reinstall can resume it.
|
|
113
|
+
*/
|
|
114
|
+
register(declaration: IIsolatedPackageTableDeclaration): Promise<void>;
|
|
115
|
+
/**
|
|
116
|
+
* Validate and bind a package's complete table set as one registration.
|
|
117
|
+
* Validation failure must not publish any declaration from the batch.
|
|
118
|
+
*/
|
|
119
|
+
registerBatch(declarations: readonly IIsolatedPackageTableDeclaration[]): Promise<void>;
|
|
120
|
+
/** Return a copied record, or `undefined` when it does not exist. */
|
|
121
|
+
get(ref: IIsolatedPackageTableRef, recordId: string): Promise<Record<string, unknown> | undefined>;
|
|
122
|
+
/** Insert or replace a copied record and return the stored copy. */
|
|
123
|
+
save(ref: IIsolatedPackageTableRef, record: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
124
|
+
/**
|
|
125
|
+
* Invoke one allowlisted standard operation for a consumer-facing contract table.
|
|
126
|
+
* The host validates declaration and provider identity before calling the gateway.
|
|
127
|
+
*/
|
|
128
|
+
call(ref: IIsolatedPackageTableRef, operation: IsolatedContractTableOperation, args: unknown[]): Promise<unknown>;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Trusted identity of one package-owned pvar.
|
|
132
|
+
*
|
|
133
|
+
* `packageId` is bound from the worker session. The guest chooses only a
|
|
134
|
+
* package-local logical name and one standard pvar scope.
|
|
135
|
+
*/
|
|
136
|
+
export interface IIsolatedPackagePersistentVarRef {
|
|
137
|
+
/** Owning isolated package. Bound by the host, never by guest payload. */
|
|
138
|
+
packageId: string;
|
|
139
|
+
/** Guest-visible package-local name before host prefixing. */
|
|
140
|
+
name: string;
|
|
141
|
+
/** Existing Peers storage/synchronization scope. */
|
|
142
|
+
scope: PersistentVarScope;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Trusted host capability for isolated package-owned pvars.
|
|
146
|
+
*
|
|
147
|
+
* Implementations derive physical names from trusted package and route
|
|
148
|
+
* identity. No operation can opt out of package prefixing.
|
|
149
|
+
*/
|
|
150
|
+
export interface IIsolatedPackagePersistentVarGateway {
|
|
151
|
+
/** Return a copied JSON value, or `undefined` when unset. */
|
|
152
|
+
get(ref: IIsolatedPackagePersistentVarRef): Promise<unknown>;
|
|
153
|
+
/** Persist a copied JSON value and return the stored copy. */
|
|
154
|
+
set(ref: IIsolatedPackagePersistentVarRef, value: unknown): Promise<unknown>;
|
|
155
|
+
/**
|
|
156
|
+
* Subscribe to canonical value changes, including updates applied by device sync.
|
|
157
|
+
*
|
|
158
|
+
* @returns A callback that detaches the host-side subscription.
|
|
159
|
+
*/
|
|
160
|
+
subscribe(ref: IIsolatedPackagePersistentVarRef, handler: (value: unknown) => void): Promise<() => void>;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Trusted request from an isolated runtime for a host/resolver-backed provider.
|
|
164
|
+
*
|
|
165
|
+
* Both package identities are selected from host runtime state. Guest call
|
|
166
|
+
* payloads cannot choose either identity.
|
|
167
|
+
*/
|
|
168
|
+
export interface IIsolatedContractProviderEndpointRequest extends IContractProviderEndpointRequest {
|
|
169
|
+
/** Installed isolated package making the consumed-contract call. */
|
|
170
|
+
consumerPackageId: string;
|
|
171
|
+
/** Registry-selected provider package. */
|
|
172
|
+
providerPackageId: string;
|
|
173
|
+
/** Optional deadline the gateway should enforce for provider setup/calls. */
|
|
174
|
+
timeoutMs?: number;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Host gateway that lets isolated consumers reach selected non-isolated
|
|
178
|
+
* providers through the same endpoint abstraction used by local and remote routers.
|
|
179
|
+
*/
|
|
180
|
+
export interface IIsolatedContractProviderGateway {
|
|
181
|
+
/**
|
|
182
|
+
* Resolve the selected resolver-backed provider, including subscription
|
|
183
|
+
* support through the request's notification sink.
|
|
184
|
+
*/
|
|
185
|
+
resolveEndpoint(request: IIsolatedContractProviderEndpointRequest): Promise<IContractProviderEndpoint | undefined>;
|
|
186
|
+
}
|
|
187
|
+
/** Options passed to a registered {@link IsolatedPackageRuntimeFactory}. */
|
|
188
|
+
export interface IIsolatedPackageRuntimeFactoryOptions {
|
|
189
|
+
/** Shared per-data-context contract registry from {@link PackageLoader}. */
|
|
190
|
+
registry: ContractRegistry;
|
|
191
|
+
/** Owning data-context id (personal user id or group id). */
|
|
192
|
+
dataContextId: string;
|
|
193
|
+
/**
|
|
194
|
+
* Host-owned table capability for this data context. Required when an
|
|
195
|
+
* isolated package declares tables.
|
|
196
|
+
*/
|
|
197
|
+
tableGateway?: IIsolatedPackageTableGateway;
|
|
198
|
+
/** Host-owned package-prefixed pvar capability for this data context. */
|
|
199
|
+
persistentVarGateway?: IIsolatedPackagePersistentVarGateway;
|
|
200
|
+
/** Gateway for consumed calls to selected host/system providers. */
|
|
201
|
+
providerGateway?: IIsolatedContractProviderGateway;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Host callback that builds an {@link IIsolatedPackageRuntime} for one
|
|
205
|
+
* data context. Registered once at process startup, before any
|
|
206
|
+
* `DataContext` / `PackageLoader` is constructed.
|
|
207
|
+
*/
|
|
208
|
+
export type IsolatedPackageRuntimeFactory = (options: IIsolatedPackageRuntimeFactoryOptions) => IIsolatedPackageRuntime;
|
|
209
|
+
/**
|
|
210
|
+
* Optional hook used by a concrete runtime to derive the host principal for
|
|
211
|
+
* a renderer- or CLI-originated call.
|
|
212
|
+
*/
|
|
213
|
+
export type IsolatedHostPrincipalResolver = (request: IContractProviderEndpointRequest, call: IContractCall) => IIsolatedCallPrincipal | Promise<IIsolatedCallPrincipal>;
|
|
214
|
+
/**
|
|
215
|
+
* Register the process-wide isolated package runtime factory.
|
|
216
|
+
*
|
|
217
|
+
* Call from the Electron main process (or a test) before
|
|
218
|
+
* `initializePeerDevice` constructs any `DataContext`. Pass `undefined` to
|
|
219
|
+
* clear the factory.
|
|
220
|
+
*
|
|
221
|
+
* @param factory - Host factory, or `undefined` to unregister.
|
|
222
|
+
*/
|
|
223
|
+
export declare function setIsolatedPackageRuntimeFactory(factory: IsolatedPackageRuntimeFactory | undefined): void;
|
|
224
|
+
/**
|
|
225
|
+
* Return the registered isolated package runtime factory, if any.
|
|
226
|
+
*
|
|
227
|
+
* @returns The process-wide factory, or `undefined` when isolation is not wired.
|
|
228
|
+
*/
|
|
229
|
+
export declare function getIsolatedPackageRuntimeFactory(): IsolatedPackageRuntimeFactory | undefined;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ISOLATED_CONTRACT_TABLE_OPERATIONS = void 0;
|
|
4
|
+
exports.setIsolatedPackageRuntimeFactory = setIsolatedPackageRuntimeFactory;
|
|
5
|
+
exports.getIsolatedPackageRuntimeFactory = getIsolatedPackageRuntimeFactory;
|
|
6
|
+
/** Standard table methods exposed through isolated contract-table references. */
|
|
7
|
+
exports.ISOLATED_CONTRACT_TABLE_OPERATIONS = [
|
|
8
|
+
"get",
|
|
9
|
+
"list",
|
|
10
|
+
"count",
|
|
11
|
+
"findOne",
|
|
12
|
+
"save",
|
|
13
|
+
"insert",
|
|
14
|
+
"update",
|
|
15
|
+
"delete",
|
|
16
|
+
];
|
|
17
|
+
let isolatedRuntimeFactory;
|
|
18
|
+
/**
|
|
19
|
+
* Register the process-wide isolated package runtime factory.
|
|
20
|
+
*
|
|
21
|
+
* Call from the Electron main process (or a test) before
|
|
22
|
+
* `initializePeerDevice` constructs any `DataContext`. Pass `undefined` to
|
|
23
|
+
* clear the factory.
|
|
24
|
+
*
|
|
25
|
+
* @param factory - Host factory, or `undefined` to unregister.
|
|
26
|
+
*/
|
|
27
|
+
function setIsolatedPackageRuntimeFactory(factory) {
|
|
28
|
+
isolatedRuntimeFactory = factory;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Return the registered isolated package runtime factory, if any.
|
|
32
|
+
*
|
|
33
|
+
* @returns The process-wide factory, or `undefined` when isolation is not wired.
|
|
34
|
+
*/
|
|
35
|
+
function getIsolatedPackageRuntimeFactory() {
|
|
36
|
+
return isolatedRuntimeFactory;
|
|
37
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { DataContext } from "../context/data-context";
|
|
2
|
+
import type { IIsolatedPackageTableGateway } from "./isolated-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* In-memory isolated table gateway for tests. Bindings and rows survive
|
|
5
|
+
* `register` of the same owner so unload/reinstall can resume persisted data.
|
|
6
|
+
*
|
|
7
|
+
* @returns A host-neutral gateway that never touches SQLite.
|
|
8
|
+
*/
|
|
9
|
+
export declare function createMemoryIsolatedPackageTableGateway(): IIsolatedPackageTableGateway;
|
|
10
|
+
/**
|
|
11
|
+
* Create a per-data-context gateway that registers isolated table schemas in
|
|
12
|
+
* `TableContainer` and performs host-owned `get` / `save`.
|
|
13
|
+
*
|
|
14
|
+
* @param dataContext - Owning personal or group data context.
|
|
15
|
+
*/
|
|
16
|
+
export declare function createIsolatedPackageTableGateway(dataContext: DataContext): IIsolatedPackageTableGateway;
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createMemoryIsolatedPackageTableGateway = createMemoryIsolatedPackageTableGateway;
|
|
4
|
+
exports.createIsolatedPackageTableGateway = createIsolatedPackageTableGateway;
|
|
5
|
+
const table_container_1 = require("../data/orm/table-container");
|
|
6
|
+
const types_1 = require("../data/orm/types");
|
|
7
|
+
const package_table_identity_1 = require("../data/package-table-identity");
|
|
8
|
+
const table_definitions_table_1 = require("../data/table-definitions-table");
|
|
9
|
+
const utils_1 = require("../utils");
|
|
10
|
+
const isolated_value_1 = require("./isolated-value");
|
|
11
|
+
function tableKey(ref) {
|
|
12
|
+
return `${ref.packageId}:${ref.tableName}`;
|
|
13
|
+
}
|
|
14
|
+
function versionNumber(declaration) {
|
|
15
|
+
return declaration.versionNumber ?? 1;
|
|
16
|
+
}
|
|
17
|
+
function validateDeclaration(declaration) {
|
|
18
|
+
(0, package_table_identity_1.packageScopedTableId)(declaration.packageId, declaration.tableName);
|
|
19
|
+
(0, package_table_identity_1.validateIsolatedSqlIdentifier)(declaration.primaryKeyName, "Isolated table primary key");
|
|
20
|
+
if (!Number.isInteger(versionNumber(declaration)) || versionNumber(declaration) < 1) {
|
|
21
|
+
throw new Error("Isolated table versionNumber must be a positive integer");
|
|
22
|
+
}
|
|
23
|
+
if (!Array.isArray(declaration.fields) || declaration.fields.length === 0) {
|
|
24
|
+
throw new Error("Isolated table fields must not be empty");
|
|
25
|
+
}
|
|
26
|
+
const fieldNames = new Set();
|
|
27
|
+
for (const field of declaration.fields) {
|
|
28
|
+
(0, package_table_identity_1.validateIsolatedSqlIdentifier)(field.name, "Isolated table field name");
|
|
29
|
+
if (fieldNames.has(field.name)) {
|
|
30
|
+
throw new Error(`Isolated table field "${field.name}" is declared more than once`);
|
|
31
|
+
}
|
|
32
|
+
fieldNames.add(field.name);
|
|
33
|
+
}
|
|
34
|
+
const primaryKey = declaration.fields.find((field) => field.name === declaration.primaryKeyName);
|
|
35
|
+
if (!primaryKey) {
|
|
36
|
+
throw new Error(`Isolated table primary key "${declaration.primaryKeyName}" is not present in fields`);
|
|
37
|
+
}
|
|
38
|
+
if (primaryKey.type !== "id") {
|
|
39
|
+
throw new Error("Isolated table primary key must refer to a field of type id");
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
...declaration,
|
|
43
|
+
fields: declaration.fields.map((field) => ({ ...field })),
|
|
44
|
+
versionNumber: versionNumber(declaration),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function toMetaData(rawDeclaration) {
|
|
48
|
+
const declaration = validateDeclaration(rawDeclaration);
|
|
49
|
+
return {
|
|
50
|
+
tableId: (0, package_table_identity_1.packageScopedTableId)(declaration.packageId, declaration.tableName),
|
|
51
|
+
packageId: declaration.packageId,
|
|
52
|
+
name: declaration.tableName,
|
|
53
|
+
description: declaration.description,
|
|
54
|
+
primaryKeyName: declaration.primaryKeyName,
|
|
55
|
+
fields: declaration.fields,
|
|
56
|
+
versionNumber: versionNumber(declaration),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function normalizedMetaData(metaData) {
|
|
60
|
+
return {
|
|
61
|
+
...metaData,
|
|
62
|
+
fields: metaData.fields.map(({ defaultValue: _defaultValue, ...field }) => field),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function sameDefinition(existing, incoming) {
|
|
66
|
+
return ((0, utils_1.simpleObjectHash)(normalizedMetaData(existing)) ===
|
|
67
|
+
(0, utils_1.simpleObjectHash)(normalizedMetaData(incoming)));
|
|
68
|
+
}
|
|
69
|
+
function requiredPackageId(metaData) {
|
|
70
|
+
if (!metaData.packageId) {
|
|
71
|
+
throw new Error(`Isolated table "${metaData.name}" is missing package identity`);
|
|
72
|
+
}
|
|
73
|
+
return metaData.packageId;
|
|
74
|
+
}
|
|
75
|
+
function requiredTableId(metaData) {
|
|
76
|
+
if (!metaData.tableId) {
|
|
77
|
+
throw new Error(`Isolated table "${metaData.name}" is missing table identity`);
|
|
78
|
+
}
|
|
79
|
+
return metaData.tableId;
|
|
80
|
+
}
|
|
81
|
+
function selectNewerDefinition(existing, incoming) {
|
|
82
|
+
const existingVersion = existing.versionNumber ?? 0;
|
|
83
|
+
const incomingVersion = incoming.versionNumber ?? 0;
|
|
84
|
+
if (existingVersion === incomingVersion && !sameDefinition(existing, incoming)) {
|
|
85
|
+
throw new Error(`Isolated table "${(0, package_table_identity_1.packageScopedTableName)(requiredPackageId(incoming), incoming.name)}" has different schemas at version ${incomingVersion}`);
|
|
86
|
+
}
|
|
87
|
+
const decision = (0, table_container_1.checkVersionedUpdate)(existingVersion, incomingVersion, existing, incoming);
|
|
88
|
+
if (decision === "update") {
|
|
89
|
+
return incoming;
|
|
90
|
+
}
|
|
91
|
+
if (existingVersion > incomingVersion) {
|
|
92
|
+
throw new Error(`Refusing isolated table schema downgrade from version ${existingVersion} to ${incomingVersion}`);
|
|
93
|
+
}
|
|
94
|
+
return existing;
|
|
95
|
+
}
|
|
96
|
+
function declarationFromMetaData(metaData) {
|
|
97
|
+
if (!metaData.packageId) {
|
|
98
|
+
throw new Error("Stored isolated table definition is missing package identity");
|
|
99
|
+
}
|
|
100
|
+
return validateDeclaration({
|
|
101
|
+
packageId: metaData.packageId,
|
|
102
|
+
tableName: metaData.name,
|
|
103
|
+
description: metaData.description,
|
|
104
|
+
primaryKeyName: metaData.primaryKeyName,
|
|
105
|
+
fields: metaData.fields,
|
|
106
|
+
versionNumber: metaData.versionNumber,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
function metaDataFromStoredDefinition(record, incoming) {
|
|
110
|
+
const stored = record.metaData;
|
|
111
|
+
const declaration = declarationFromMetaData({
|
|
112
|
+
...stored,
|
|
113
|
+
versionNumber: record.versionNumber,
|
|
114
|
+
});
|
|
115
|
+
const metaData = toMetaData(declaration);
|
|
116
|
+
const expectedTableId = incoming.tableId;
|
|
117
|
+
const expectedName = (0, package_table_identity_1.packageScopedTableName)(requiredPackageId(incoming), incoming.name);
|
|
118
|
+
if (record.tableId !== expectedTableId ||
|
|
119
|
+
metaData.tableId !== expectedTableId ||
|
|
120
|
+
metaData.packageId !== incoming.packageId ||
|
|
121
|
+
metaData.name !== incoming.name ||
|
|
122
|
+
record.name !== expectedName) {
|
|
123
|
+
throw new Error(`Stored isolated table definition "${record.name}" has an invalid identity`);
|
|
124
|
+
}
|
|
125
|
+
return metaData;
|
|
126
|
+
}
|
|
127
|
+
function registerDeclaration(declarations, byTableId, rawDeclaration) {
|
|
128
|
+
const declaration = validateDeclaration(rawDeclaration);
|
|
129
|
+
const key = tableKey(declaration);
|
|
130
|
+
const tableId = (0, package_table_identity_1.packageScopedTableId)(declaration.packageId, declaration.tableName);
|
|
131
|
+
const existingKey = byTableId.get(tableId);
|
|
132
|
+
if (existingKey && existingKey !== key) {
|
|
133
|
+
const owner = declarations.get(existingKey);
|
|
134
|
+
throw new Error(`Isolated table ${declaration.tableName} collides with package ${owner?.packageId ?? "unknown"}`);
|
|
135
|
+
}
|
|
136
|
+
const current = declarations.get(key);
|
|
137
|
+
if (current) {
|
|
138
|
+
const selected = selectNewerDefinition(toMetaData(current), toMetaData(declaration));
|
|
139
|
+
if (selected.versionNumber === versionNumber(current)) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
declarations.set(key, declaration);
|
|
144
|
+
byTableId.set(tableId, key);
|
|
145
|
+
}
|
|
146
|
+
function asRecord(value, label) {
|
|
147
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
148
|
+
throw new Error(`${label} must be a plain object`);
|
|
149
|
+
}
|
|
150
|
+
return { ...value };
|
|
151
|
+
}
|
|
152
|
+
function copyTableResult(value) {
|
|
153
|
+
const normalizeRecord = (item) => Object.prototype.toString.call(item) === "[object Object]"
|
|
154
|
+
? asRecord(item, "Isolated table result")
|
|
155
|
+
: item;
|
|
156
|
+
const normalized = Array.isArray(value) ? value.map(normalizeRecord) : normalizeRecord(value);
|
|
157
|
+
return (0, isolated_value_1.copyIsolatedValue)(normalized);
|
|
158
|
+
}
|
|
159
|
+
function matchesFilter(row, filter = {}) {
|
|
160
|
+
return Object.entries(filter).every(([name, value]) => row[name] === value);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* In-memory isolated table gateway for tests. Bindings and rows survive
|
|
164
|
+
* `register` of the same owner so unload/reinstall can resume persisted data.
|
|
165
|
+
*
|
|
166
|
+
* @returns A host-neutral gateway that never touches SQLite.
|
|
167
|
+
*/
|
|
168
|
+
function createMemoryIsolatedPackageTableGateway() {
|
|
169
|
+
const declarations = new Map();
|
|
170
|
+
const byTableId = new Map();
|
|
171
|
+
const rows = new Map();
|
|
172
|
+
const registerBatch = async (batch) => {
|
|
173
|
+
const candidateDeclarations = new Map(declarations);
|
|
174
|
+
const candidateByTableId = new Map(byTableId);
|
|
175
|
+
for (const declaration of batch) {
|
|
176
|
+
registerDeclaration(candidateDeclarations, candidateByTableId, declaration);
|
|
177
|
+
}
|
|
178
|
+
declarations.clear();
|
|
179
|
+
byTableId.clear();
|
|
180
|
+
for (const [key, declaration] of candidateDeclarations) {
|
|
181
|
+
declarations.set(key, declaration);
|
|
182
|
+
if (!rows.has(key)) {
|
|
183
|
+
rows.set(key, new Map());
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
for (const [tableId, key] of candidateByTableId) {
|
|
187
|
+
byTableId.set(tableId, key);
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
return {
|
|
191
|
+
async register(declaration) {
|
|
192
|
+
await registerBatch([declaration]);
|
|
193
|
+
},
|
|
194
|
+
registerBatch,
|
|
195
|
+
async get(ref, recordId) {
|
|
196
|
+
const key = tableKey(ref);
|
|
197
|
+
if (!declarations.has(key)) {
|
|
198
|
+
throw new Error(`Undeclared isolated table ${ref.tableName}`);
|
|
199
|
+
}
|
|
200
|
+
return (0, isolated_value_1.copyIsolatedValue)(rows.get(key)?.get(recordId));
|
|
201
|
+
},
|
|
202
|
+
async save(ref, record) {
|
|
203
|
+
const key = tableKey(ref);
|
|
204
|
+
const declaration = declarations.get(key);
|
|
205
|
+
if (!declaration) {
|
|
206
|
+
throw new Error(`Undeclared isolated table ${ref.tableName}`);
|
|
207
|
+
}
|
|
208
|
+
const stored = asRecord((0, isolated_value_1.copyIsolatedValue)(record), "Isolated table record");
|
|
209
|
+
const recordId = stored[declaration.primaryKeyName];
|
|
210
|
+
if (typeof recordId !== "string" || recordId.trim() === "") {
|
|
211
|
+
throw new Error(`Isolated table record is missing primary key "${declaration.primaryKeyName}"`);
|
|
212
|
+
}
|
|
213
|
+
const tableRows = rows.get(key) ?? new Map();
|
|
214
|
+
tableRows.set(recordId, stored);
|
|
215
|
+
rows.set(key, tableRows);
|
|
216
|
+
return (0, isolated_value_1.copyIsolatedValue)(stored);
|
|
217
|
+
},
|
|
218
|
+
async call(ref, operation, args) {
|
|
219
|
+
const key = tableKey(ref);
|
|
220
|
+
const declaration = declarations.get(key);
|
|
221
|
+
const tableRows = rows.get(key);
|
|
222
|
+
if (!declaration || !tableRows) {
|
|
223
|
+
throw new Error(`Undeclared isolated table ${ref.tableName}`);
|
|
224
|
+
}
|
|
225
|
+
const recordId = args[0];
|
|
226
|
+
switch (operation) {
|
|
227
|
+
case "get":
|
|
228
|
+
return typeof recordId === "string"
|
|
229
|
+
? (0, isolated_value_1.copyIsolatedValue)(tableRows.get(recordId))
|
|
230
|
+
: undefined;
|
|
231
|
+
case "list": {
|
|
232
|
+
const filter = (args[0] ?? {});
|
|
233
|
+
const opts = (args[1] ?? {});
|
|
234
|
+
const matching = [...tableRows.values()].filter((row) => matchesFilter(row, filter));
|
|
235
|
+
const pageSize = opts.pageSize ?? matching.length;
|
|
236
|
+
const start = (opts.page ?? 0) * pageSize;
|
|
237
|
+
return (0, isolated_value_1.copyIsolatedValue)(matching.slice(start, start + pageSize));
|
|
238
|
+
}
|
|
239
|
+
case "count": {
|
|
240
|
+
const filter = (args[0] ?? {});
|
|
241
|
+
return [...tableRows.values()].filter((row) => matchesFilter(row, filter)).length;
|
|
242
|
+
}
|
|
243
|
+
case "findOne": {
|
|
244
|
+
const filter = (args[0] ?? {});
|
|
245
|
+
return (0, isolated_value_1.copyIsolatedValue)([...tableRows.values()].find((row) => matchesFilter(row, filter)));
|
|
246
|
+
}
|
|
247
|
+
case "save":
|
|
248
|
+
case "insert":
|
|
249
|
+
case "update": {
|
|
250
|
+
const stored = asRecord(args[0], "Isolated table record");
|
|
251
|
+
const id = stored[declaration.primaryKeyName];
|
|
252
|
+
if (typeof id !== "string" || id.trim() === "") {
|
|
253
|
+
throw new Error(`Isolated table record is missing primary key "${declaration.primaryKeyName}"`);
|
|
254
|
+
}
|
|
255
|
+
if (operation === "insert" && tableRows.has(id)) {
|
|
256
|
+
throw new Error(`Record ${id} already exists`);
|
|
257
|
+
}
|
|
258
|
+
if (operation === "update" && !tableRows.has(id)) {
|
|
259
|
+
throw new Error(`Record ${id} does not exist`);
|
|
260
|
+
}
|
|
261
|
+
tableRows.set(id, stored);
|
|
262
|
+
return (0, isolated_value_1.copyIsolatedValue)(stored);
|
|
263
|
+
}
|
|
264
|
+
case "delete": {
|
|
265
|
+
const id = typeof recordId === "string"
|
|
266
|
+
? recordId
|
|
267
|
+
: asRecord(recordId, "Isolated table record")[declaration.primaryKeyName];
|
|
268
|
+
if (typeof id !== "string") {
|
|
269
|
+
throw new Error(`Isolated table delete requires "${declaration.primaryKeyName}"`);
|
|
270
|
+
}
|
|
271
|
+
tableRows.delete(id);
|
|
272
|
+
return undefined;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Create a per-data-context gateway that registers isolated table schemas in
|
|
280
|
+
* `TableContainer` and performs host-owned `get` / `save`.
|
|
281
|
+
*
|
|
282
|
+
* @param dataContext - Owning personal or group data context.
|
|
283
|
+
*/
|
|
284
|
+
function createIsolatedPackageTableGateway(dataContext) {
|
|
285
|
+
const declarations = new Map();
|
|
286
|
+
const byTableId = new Map();
|
|
287
|
+
let registrationQueue = Promise.resolve();
|
|
288
|
+
const resolve = (ref) => {
|
|
289
|
+
const key = tableKey(ref);
|
|
290
|
+
const declaration = declarations.get(key);
|
|
291
|
+
if (!declaration) {
|
|
292
|
+
throw new Error(`Undeclared isolated table ${ref.tableName}`);
|
|
293
|
+
}
|
|
294
|
+
return declaration;
|
|
295
|
+
};
|
|
296
|
+
const prepareTable = async (declaration) => {
|
|
297
|
+
const incomingMetaData = toMetaData(declaration);
|
|
298
|
+
const existingRecord = await (0, table_definitions_table_1.TableDefinitions)(dataContext).get(requiredTableId(incomingMetaData));
|
|
299
|
+
const activeMetaData = existingRecord
|
|
300
|
+
? selectNewerDefinition(metaDataFromStoredDefinition(existingRecord, incomingMetaData), incomingMetaData)
|
|
301
|
+
: incomingMetaData;
|
|
302
|
+
return { activeMetaData, existingRecord, incomingMetaData };
|
|
303
|
+
};
|
|
304
|
+
const activateTable = async ({ activeMetaData, existingRecord, incomingMetaData, }) => {
|
|
305
|
+
if (!existingRecord || activeMetaData === incomingMetaData) {
|
|
306
|
+
await (0, table_definitions_table_1.TableDefinitions)(dataContext).save({
|
|
307
|
+
tableId: requiredTableId(activeMetaData),
|
|
308
|
+
name: (0, package_table_identity_1.packageScopedTableName)(requiredPackageId(activeMetaData), activeMetaData.name),
|
|
309
|
+
metaData: activeMetaData,
|
|
310
|
+
versionNumber: activeMetaData.versionNumber ?? 0,
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
dataContext.tableContainer.registerTableDefinition({
|
|
314
|
+
metaData: activeMetaData,
|
|
315
|
+
schema: (0, types_1.fieldsToSchema)(activeMetaData.fields),
|
|
316
|
+
}, { overwrite: true });
|
|
317
|
+
return dataContext.tableContainer.getTable(activeMetaData);
|
|
318
|
+
};
|
|
319
|
+
const ensureTable = async (declaration) => activateTable(await prepareTable(declaration));
|
|
320
|
+
const registerBatch = (batch) => {
|
|
321
|
+
const registration = registrationQueue.then(async () => {
|
|
322
|
+
const candidateDeclarations = new Map(declarations);
|
|
323
|
+
const candidateByTableId = new Map(byTableId);
|
|
324
|
+
for (const declaration of batch) {
|
|
325
|
+
registerDeclaration(candidateDeclarations, candidateByTableId, declaration);
|
|
326
|
+
}
|
|
327
|
+
const batchKeys = new Set(batch.map(tableKey));
|
|
328
|
+
const plans = [];
|
|
329
|
+
for (const key of batchKeys) {
|
|
330
|
+
const declaration = candidateDeclarations.get(key);
|
|
331
|
+
if (declaration) {
|
|
332
|
+
plans.push(await prepareTable(declaration));
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
for (const plan of plans) {
|
|
336
|
+
await activateTable(plan);
|
|
337
|
+
}
|
|
338
|
+
declarations.clear();
|
|
339
|
+
byTableId.clear();
|
|
340
|
+
for (const [key, candidate] of candidateDeclarations) {
|
|
341
|
+
declarations.set(key, candidate);
|
|
342
|
+
}
|
|
343
|
+
for (const [tableId, key] of candidateByTableId) {
|
|
344
|
+
byTableId.set(tableId, key);
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
registrationQueue = registration.then(() => undefined, () => undefined);
|
|
348
|
+
return registration;
|
|
349
|
+
};
|
|
350
|
+
return {
|
|
351
|
+
register(declaration) {
|
|
352
|
+
return registerBatch([declaration]);
|
|
353
|
+
},
|
|
354
|
+
registerBatch,
|
|
355
|
+
async get(ref, recordId) {
|
|
356
|
+
await registrationQueue;
|
|
357
|
+
const table = await ensureTable(resolve(ref));
|
|
358
|
+
const row = await table.get(recordId);
|
|
359
|
+
return row ? copyTableResult(row) : undefined;
|
|
360
|
+
},
|
|
361
|
+
async save(ref, record) {
|
|
362
|
+
await registrationQueue;
|
|
363
|
+
const table = await ensureTable(resolve(ref));
|
|
364
|
+
const stored = await table.save(asRecord(record, "Isolated table record"));
|
|
365
|
+
return copyTableResult(stored);
|
|
366
|
+
},
|
|
367
|
+
async call(ref, operation, args) {
|
|
368
|
+
await registrationQueue;
|
|
369
|
+
const table = await ensureTable(resolve(ref));
|
|
370
|
+
const method = table[operation];
|
|
371
|
+
const result = await method.apply(table, (0, isolated_value_1.copyIsolatedValue)(args));
|
|
372
|
+
return copyTableResult(result);
|
|
373
|
+
},
|
|
374
|
+
};
|
|
375
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|