@peers-app/peers-sdk 0.26.5 → 0.27.0
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/README.md +3 -1
- package/bin/peers-package.js +13 -0
- package/define/package.json +4 -0
- package/dist/contracts/__tests__/action-dispatch.test.js +56 -76
- package/dist/contracts/__tests__/also-implements.test.d.ts +1 -0
- package/dist/contracts/__tests__/also-implements.test.js +110 -0
- package/dist/contracts/__tests__/builder.test.js +45 -0
- package/dist/contracts/__tests__/contract-resolvers.test.js +134 -4
- package/dist/contracts/__tests__/frozen-contracts.test.d.ts +1 -0
- package/dist/contracts/__tests__/frozen-contracts.test.js +191 -0
- package/dist/contracts/__tests__/generic-tool-dispatch.test.js +13 -56
- package/dist/contracts/__tests__/host-contract-client.test.d.ts +1 -0
- package/dist/contracts/__tests__/host-contract-client.test.js +793 -0
- package/dist/contracts/__tests__/host-contract-transport.test.js +15 -0
- package/dist/contracts/__tests__/registry.test.js +68 -0
- package/dist/contracts/__tests__/target-dispatch.d.ts +11 -0
- package/dist/contracts/__tests__/target-dispatch.js +24 -0
- package/dist/contracts/__tests__/tool-catalog-service.test.js +37 -1
- package/dist/contracts/__tests__/tool-catalog.test.js +12 -3
- package/dist/contracts/__tests__/validate.test.js +177 -7
- package/dist/contracts/__tests__/workflow-context.test.js +0 -11
- package/dist/contracts/builder.d.ts +16 -2
- package/dist/contracts/builder.js +44 -3
- package/dist/contracts/contract-definition-schema.d.ts +251 -0
- package/dist/contracts/contract-definition-schema.js +48 -0
- package/dist/contracts/contract-provider-router.d.ts +2 -0
- package/dist/contracts/contract-provider-router.js +5 -2
- package/dist/contracts/contract-proxy.d.ts +52 -18
- package/dist/contracts/contract-proxy.js +144 -203
- package/dist/contracts/contract-resolvers.d.ts +6 -4
- package/dist/contracts/contract-resolvers.js +32 -9
- package/dist/contracts/frozen-contracts.d.ts +59 -0
- package/dist/contracts/frozen-contracts.js +86 -0
- package/dist/contracts/host-contract-client.d.ts +134 -0
- package/dist/contracts/host-contract-client.js +396 -0
- package/dist/contracts/host-contract-transport.d.ts +2 -0
- package/dist/contracts/host-contract-transport.js +8 -0
- package/dist/contracts/index.d.ts +6 -4
- package/dist/contracts/index.js +11 -5
- package/dist/contracts/registry.d.ts +31 -1
- package/dist/contracts/registry.js +100 -6
- package/dist/contracts/system/contract-catalog-contract.d.ts +61 -6
- package/dist/contracts/system/contract-catalog-contract.js +17 -5
- package/dist/contracts/tool-catalog-service.js +26 -6
- package/dist/contracts/tool-catalog.d.ts +1 -1
- package/dist/contracts/tool-catalog.js +4 -1
- package/dist/contracts/types.d.ts +32 -2
- package/dist/contracts/validate.d.ts +16 -1
- package/dist/contracts/validate.js +139 -25
- package/dist/data/change-tracking.d.ts +17 -5
- package/dist/data/change-tracking.js +22 -6
- package/dist/data/change-tracking.test.d.ts +1 -0
- package/dist/data/change-tracking.test.js +62 -0
- package/dist/data/group-members.d.ts +6 -1
- package/dist/data/group-members.js +12 -4
- package/dist/data/groups.d.ts +6 -1
- package/dist/data/groups.js +12 -4
- package/dist/data/orm/sql.data-source.d.ts +9 -0
- package/dist/data/orm/sql.data-source.js +47 -2
- package/dist/data/orm/sql.data-source.test.js +142 -0
- package/dist/data/orm/table-container.js +9 -9
- package/dist/data/orm/table-container.test.js +44 -0
- package/dist/data/orm/types.d.ts +41 -1
- package/dist/data/orm/types.js +159 -28
- package/dist/data/orm/types.test.js +129 -0
- package/dist/data/package-versions.d.ts +5 -1
- package/dist/data/package-versions.js +11 -4
- package/dist/data/packages.d.ts +7 -1
- package/dist/data/packages.js +13 -4
- package/dist/data/peer-types.d.ts +8 -62
- package/dist/data/persistent-vars.d.ts +4 -1
- package/dist/data/persistent-vars.js +7 -2
- package/dist/data/table-signing.test.d.ts +1 -0
- package/dist/data/table-signing.test.js +118 -0
- package/dist/data/tools.d.ts +29 -221
- package/dist/data/users.d.ts +1 -2
- package/dist/data/users.js +8 -5
- package/dist/define/also-implements.test.d.ts +1 -0
- package/dist/define/also-implements.test.js +123 -0
- package/dist/define/context.d.ts +77 -0
- package/dist/define/context.js +2 -0
- package/dist/define/contract-ref.d.ts +177 -0
- package/dist/define/contract-ref.js +67 -0
- package/dist/define/declare-contract.d.ts +167 -0
- package/dist/define/declare-contract.js +220 -0
- package/dist/define/declare-contract.test.d.ts +1 -0
- package/dist/define/declare-contract.test.js +573 -0
- package/dist/define/define-package.d.ts +203 -0
- package/dist/define/define-package.js +289 -0
- package/dist/define/define.test.d.ts +1 -0
- package/dist/define/define.test.js +1010 -0
- package/dist/define/empty-fields.d.ts +15 -0
- package/dist/define/empty-fields.js +134 -0
- package/dist/define/empty-fields.test.d.ts +1 -0
- package/dist/define/empty-fields.test.js +55 -0
- package/dist/define/events-observables.test.d.ts +1 -0
- package/dist/define/events-observables.test.js +1013 -0
- package/dist/define/events.d.ts +81 -0
- package/dist/define/events.js +81 -0
- package/dist/define/generated-field.d.ts +20 -0
- package/dist/define/generated-field.js +25 -0
- package/dist/define/index.d.ts +28 -0
- package/dist/define/index.js +62 -0
- package/dist/define/lazy-members.d.ts +8 -0
- package/dist/define/lazy-members.js +38 -0
- package/dist/define/manifest.d.ts +63 -0
- package/dist/define/manifest.js +653 -0
- package/dist/define/observables.d.ts +146 -0
- package/dist/define/observables.js +187 -0
- package/dist/define/serve-members.d.ts +21 -0
- package/dist/define/serve-members.js +152 -0
- package/dist/define/serve-members.test.d.ts +1 -0
- package/dist/define/serve-members.test.js +630 -0
- package/dist/define/serve.d.ts +22 -0
- package/dist/define/serve.js +58 -0
- package/dist/define/system-contracts.d.ts +41 -0
- package/dist/define/system-contracts.js +21 -0
- package/dist/define/table-handle.d.ts +13 -0
- package/dist/define/table-handle.js +36 -0
- package/dist/define/table.d.ts +128 -0
- package/dist/define/table.js +122 -0
- package/dist/define/tool.d.ts +129 -0
- package/dist/define/tool.js +91 -0
- package/dist/define/vars.d.ts +82 -0
- package/dist/define/vars.js +256 -0
- package/dist/define/vars.test.d.ts +1 -0
- package/dist/define/vars.test.js +39 -0
- package/dist/define/wire-value.d.ts +12 -0
- package/dist/define/wire-value.js +76 -0
- package/dist/define/worker-state.d.ts +15 -0
- package/dist/define/worker-state.js +45 -0
- package/dist/device/tx-encoding.test.js +10 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +5 -1
- package/dist/observable.d.ts +42 -2
- package/dist/observable.js +25 -7
- package/dist/package-build/build-package.d.ts +60 -0
- package/dist/package-build/build-package.js +312 -0
- package/dist/package-build/build-package.test.d.ts +1 -0
- package/dist/package-build/build-package.test.js +204 -0
- package/dist/package-build/cli.d.ts +7 -0
- package/dist/package-build/cli.js +218 -0
- package/dist/package-build/contract-add.d.ts +35 -0
- package/dist/package-build/contract-add.js +97 -0
- package/dist/package-build/contract-add.test.d.ts +1 -0
- package/dist/package-build/contract-add.test.js +467 -0
- package/dist/package-build/contract-source.d.ts +59 -0
- package/dist/package-build/contract-source.js +471 -0
- package/dist/package-build/freeze-contracts.d.ts +56 -0
- package/dist/package-build/freeze-contracts.js +198 -0
- package/dist/package-build/freeze-contracts.test.d.ts +1 -0
- package/dist/package-build/freeze-contracts.test.js +355 -0
- package/dist/package-installer/package-creator.d.ts +4 -0
- package/dist/package-installer/package-creator.js +57 -16
- package/dist/package-installer/package-creator.test.js +86 -0
- package/dist/package-loader/contract-members.d.ts +53 -0
- package/dist/package-loader/contract-members.js +31 -0
- package/dist/package-loader/contract-package-loader.js +5 -1
- package/dist/package-loader/contract-package-loader.test.js +11 -0
- package/dist/package-loader/host-authority-loading.test.js +9 -5
- package/dist/package-loader/index.d.ts +1 -0
- package/dist/package-loader/index.js +1 -0
- package/dist/package-loader/isolated-artifact.d.ts +80 -17
- package/dist/package-loader/isolated-artifact.js +257 -51
- package/dist/package-loader/isolated-artifact.test.js +294 -21
- package/dist/package-loader/isolated-runtime.d.ts +22 -7
- package/dist/package-loader/isolated-table-gateway.js +30 -11
- package/dist/package-loader/isolated-table-gateway.test.js +243 -0
- package/dist/package-loader/package-loader.d.ts +8 -1
- package/dist/package-loader/package-loader.js +39 -20
- package/dist/package-loader/package-loader.test.js +47 -0
- package/dist/peer-id.d.ts +25 -0
- package/dist/peer-id.js +99 -0
- package/dist/serial-json.compat.test.d.ts +1 -0
- package/dist/serial-json.compat.test.js +369 -0
- package/dist/serial-json.d.ts +41 -5
- package/dist/serial-json.js +216 -157
- package/dist/serial-json.oracle.d.ts +21 -0
- package/dist/serial-json.oracle.js +222 -0
- package/dist/serial-json.test.js +315 -0
- package/dist/testing/index.d.ts +4 -0
- package/dist/testing/index.js +8 -0
- package/dist/testing/mock-host-contract.d.ts +123 -0
- package/dist/testing/mock-host-contract.js +434 -0
- package/dist/testing/mock-host-contract.test.d.ts +1 -0
- package/dist/testing/mock-host-contract.test.js +782 -0
- package/dist/types/field-type.d.ts +35 -18
- package/dist/types/field-type.js +4 -1
- package/dist/types/zod-types.d.ts +9 -0
- package/dist/types/zod-types.js +8 -2
- package/dist/utils.d.ts +1 -25
- package/dist/utils.js +10 -90
- package/package.json +9 -2
- package/testing/package.json +4 -0
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.validateProviderSatisfiesContract = validateProviderSatisfiesContract;
|
|
4
|
+
exports.contractShapeChanges = contractShapeChanges;
|
|
4
5
|
exports.validateImmutability = validateImmutability;
|
|
5
6
|
exports.validateAlsoImplements = validateAlsoImplements;
|
|
7
|
+
const lodash_1 = require("lodash");
|
|
6
8
|
const types_1 = require("./types");
|
|
7
9
|
function ok() {
|
|
8
10
|
return { valid: true, errors: [] };
|
|
@@ -15,12 +17,13 @@ function merge(results) {
|
|
|
15
17
|
return { valid: errors.length === 0, errors };
|
|
16
18
|
}
|
|
17
19
|
/**
|
|
18
|
-
* Check whether `implField` satisfies the contract's `contractField
|
|
19
|
-
* - Types must match exactly
|
|
20
|
-
* -
|
|
21
|
-
* -
|
|
20
|
+
* Check whether `implField` satisfies the contract's `contractField` for its callers.
|
|
21
|
+
* - Types must match exactly, and so must `isArray`.
|
|
22
|
+
* - What callers receive: a field the contract requires must be required.
|
|
23
|
+
* - What callers send: a field the contract makes optional must be optional, since callers may
|
|
24
|
+
* leave it out; the implementation may make a required one optional.
|
|
22
25
|
*/
|
|
23
|
-
function isFieldCompatible(contractField, implField) {
|
|
26
|
+
function isFieldCompatible(contractField, implField, direction) {
|
|
24
27
|
if (contractField.type !== implField.type) {
|
|
25
28
|
return `type mismatch: contract requires '${contractField.type}', implementation has '${implField.type}'`;
|
|
26
29
|
}
|
|
@@ -30,9 +33,12 @@ function isFieldCompatible(contractField, implField) {
|
|
|
30
33
|
if (!contractField.isArray && implField.isArray) {
|
|
31
34
|
return "contract requires scalar, implementation is an array";
|
|
32
35
|
}
|
|
33
|
-
if (!contractField.optional && implField.optional) {
|
|
36
|
+
if (direction !== "input" && !contractField.optional && implField.optional) {
|
|
34
37
|
return "contract requires a required field, implementation marks it optional";
|
|
35
38
|
}
|
|
39
|
+
if (direction !== "output" && contractField.optional && !implField.optional) {
|
|
40
|
+
return "contract marks the field optional, implementation requires it";
|
|
41
|
+
}
|
|
36
42
|
return null;
|
|
37
43
|
}
|
|
38
44
|
function validateTableFields(contractTable, implTable) {
|
|
@@ -50,7 +56,7 @@ function validateTableFields(contractTable, implTable) {
|
|
|
50
56
|
}
|
|
51
57
|
continue;
|
|
52
58
|
}
|
|
53
|
-
const compat = isFieldCompatible(contractField, implField);
|
|
59
|
+
const compat = isFieldCompatible(contractField, implField, "both");
|
|
54
60
|
if (compat) {
|
|
55
61
|
errors.push({
|
|
56
62
|
kind: "field",
|
|
@@ -89,7 +95,7 @@ function validateToolFields(contractTool, implTool, direction) {
|
|
|
89
95
|
}
|
|
90
96
|
continue;
|
|
91
97
|
}
|
|
92
|
-
const compat = isFieldCompatible(contractField, implField);
|
|
98
|
+
const compat = isFieldCompatible(contractField, implField, direction);
|
|
93
99
|
if (compat) {
|
|
94
100
|
errors.push({
|
|
95
101
|
kind: "field",
|
|
@@ -129,7 +135,7 @@ function validateEventFields(contractEvent, implEvent) {
|
|
|
129
135
|
}
|
|
130
136
|
continue;
|
|
131
137
|
}
|
|
132
|
-
const compat = isFieldCompatible(contractField, implField);
|
|
138
|
+
const compat = isFieldCompatible(contractField, implField, "output");
|
|
133
139
|
if (compat) {
|
|
134
140
|
errors.push({
|
|
135
141
|
kind: "field",
|
|
@@ -226,12 +232,132 @@ function validateProviderSatisfiesContract(impl, contract) {
|
|
|
226
232
|
}
|
|
227
233
|
return errors.length === 0 ? ok() : fail(errors);
|
|
228
234
|
}
|
|
235
|
+
/**
|
|
236
|
+
* Keys the frozen comparison ignores: a stable version's descriptions may change, and so may a
|
|
237
|
+
* tool's policy (`remoteAccessLevel`, `requiresWorkflowRun`), which isn't part of its shape.
|
|
238
|
+
*/
|
|
239
|
+
const IGNORED_KEYS = new Set([
|
|
240
|
+
"description",
|
|
241
|
+
"usageDescription",
|
|
242
|
+
"remoteAccessLevel",
|
|
243
|
+
"requiresWorkflowRun",
|
|
244
|
+
]);
|
|
245
|
+
/**
|
|
246
|
+
* IR keys added after contracts were first frozen. A shape that has one matches a shape that
|
|
247
|
+
* doesn't (a provider built with an older SDK); where both have it, they must agree.
|
|
248
|
+
*/
|
|
249
|
+
const LATER_IR_KEYS = new Set(["fields", "enumValues", "value"]);
|
|
250
|
+
function isRecord(value) {
|
|
251
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
252
|
+
}
|
|
253
|
+
function uniqueNames(items) {
|
|
254
|
+
const byName = new Map();
|
|
255
|
+
for (const item of items) {
|
|
256
|
+
if (!isRecord(item) || typeof item.name !== "string" || byName.has(item.name)) {
|
|
257
|
+
return undefined;
|
|
258
|
+
}
|
|
259
|
+
byName.set(item.name, item);
|
|
260
|
+
}
|
|
261
|
+
return byName;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Structural equality for the frozen comparison. Members and fields match by name in any order,
|
|
265
|
+
* `enumValues` as sets, and keys in any order; descriptions and tool policy are ignored, and so is
|
|
266
|
+
* a later IR key only one side has. A `defaultValue` must be equal as data.
|
|
267
|
+
*/
|
|
268
|
+
function sameShape(a, b, key) {
|
|
269
|
+
if (key === "defaultValue") {
|
|
270
|
+
// Legacy schemaToFields retains Zod's default factories. Compare only their wire data,
|
|
271
|
+
// as the previous JSON comparison did, without executing or comparing factory identities.
|
|
272
|
+
return (0, lodash_1.isEqual)(JSON.parse(JSON.stringify({ value: a })), JSON.parse(JSON.stringify({ value: b })));
|
|
273
|
+
}
|
|
274
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
275
|
+
if (key === "enumValues") {
|
|
276
|
+
const values = new Set(b);
|
|
277
|
+
return new Set(a).size === values.size && a.every((value) => values.has(value));
|
|
278
|
+
}
|
|
279
|
+
if (a.length !== b.length) {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
const aByName = uniqueNames(a);
|
|
283
|
+
const bByName = uniqueNames(b);
|
|
284
|
+
if (aByName && bByName) {
|
|
285
|
+
return [...aByName].every(([name, item]) => bByName.has(name) && sameShape(item, bByName.get(name)));
|
|
286
|
+
}
|
|
287
|
+
return a.every((item, index) => sameShape(item, b[index]));
|
|
288
|
+
}
|
|
289
|
+
if (isRecord(a) && isRecord(b)) {
|
|
290
|
+
for (const name of new Set([...Object.keys(a), ...Object.keys(b)])) {
|
|
291
|
+
const left = a[name];
|
|
292
|
+
const right = b[name];
|
|
293
|
+
if (IGNORED_KEYS.has(name) ||
|
|
294
|
+
(left === undefined && right === undefined) ||
|
|
295
|
+
(LATER_IR_KEYS.has(name) && (left === undefined || right === undefined))) {
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
if (!sameShape(left, right, name)) {
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return true;
|
|
303
|
+
}
|
|
304
|
+
return a === b;
|
|
305
|
+
}
|
|
306
|
+
/** A contract's member lists, with what to call one member of each in a change. */
|
|
307
|
+
const MEMBER_KINDS = [
|
|
308
|
+
["tables", "table"],
|
|
309
|
+
["tools", "tool"],
|
|
310
|
+
["observables", "observable"],
|
|
311
|
+
["events", "event"],
|
|
312
|
+
];
|
|
313
|
+
/**
|
|
314
|
+
* What changed between two shapes of one contract version, member by member: a table, tool,
|
|
315
|
+
* observable, or event that was added, removed, or changed. Empty when the shapes match.
|
|
316
|
+
* Descriptions and tool policy are ignored, as in {@link validateImmutability}.
|
|
317
|
+
*
|
|
318
|
+
* @param existing - The frozen shape.
|
|
319
|
+
* @param incoming - The shape to compare with it.
|
|
320
|
+
* @returns One line per changed member, such as `tool add-item changed`.
|
|
321
|
+
*/
|
|
322
|
+
function contractShapeChanges(existing, incoming) {
|
|
323
|
+
const changes = [];
|
|
324
|
+
for (const [kind, label] of MEMBER_KINDS) {
|
|
325
|
+
const beforeMembers = existing[kind] ?? [];
|
|
326
|
+
const afterMembers = incoming[kind] ?? [];
|
|
327
|
+
// Do not collapse duplicate names: host-backed registrations can reach this comparison
|
|
328
|
+
// without isolated-artifact validation. Preserve the list comparison for such inputs.
|
|
329
|
+
if (!uniqueNames(beforeMembers) || !uniqueNames(afterMembers)) {
|
|
330
|
+
if (!sameShape(beforeMembers, afterMembers)) {
|
|
331
|
+
changes.push(`${label} list changed (duplicate member names)`);
|
|
332
|
+
}
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
const before = new Map(beforeMembers.map((member) => [member.name, member]));
|
|
336
|
+
const after = new Map(afterMembers.map((member) => [member.name, member]));
|
|
337
|
+
for (const [name, member] of before) {
|
|
338
|
+
if (!after.has(name))
|
|
339
|
+
changes.push(`${label} ${name} was removed`);
|
|
340
|
+
else if (!sameShape(member, after.get(name)))
|
|
341
|
+
changes.push(`${label} ${name} changed`);
|
|
342
|
+
}
|
|
343
|
+
for (const name of after.keys()) {
|
|
344
|
+
if (!before.has(name))
|
|
345
|
+
changes.push(`${label} ${name} was added`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return changes;
|
|
349
|
+
}
|
|
229
350
|
/**
|
|
230
351
|
* Validate that a frozen (stable) contract definition has not been modified.
|
|
231
352
|
* If the existing definition has no `devTag` it is frozen; the incoming
|
|
232
|
-
* definition must
|
|
353
|
+
* definition must have the same shape and also be stable.
|
|
233
354
|
* Dev contracts (`devTag: "dev"`) may change freely but cannot be registered
|
|
234
355
|
* once a stable version exists for that contract+version.
|
|
356
|
+
*
|
|
357
|
+
* Descriptions may change on a frozen version (to point callers at a newer one, say), and so
|
|
358
|
+
* may a tool's policy (`remoteAccessLevel`, `requiresWorkflowRun`); member, field, and key order
|
|
359
|
+
* don't matter. IR keys added later (`fields`, `enumValues`, and an
|
|
360
|
+
* observable's `value`) are compared only when both definitions have them.
|
|
235
361
|
*/
|
|
236
362
|
function validateImmutability(existing, incoming) {
|
|
237
363
|
if (existing.devTag === "dev") {
|
|
@@ -247,25 +373,13 @@ function validateImmutability(existing, incoming) {
|
|
|
247
373
|
},
|
|
248
374
|
]);
|
|
249
375
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
tables: existing.tables,
|
|
253
|
-
tools: existing.tools,
|
|
254
|
-
observables: existing.observables,
|
|
255
|
-
events: existing.events ?? [],
|
|
256
|
-
});
|
|
257
|
-
const incomingShape = JSON.stringify({
|
|
258
|
-
tables: incoming.tables,
|
|
259
|
-
tools: incoming.tools,
|
|
260
|
-
observables: incoming.observables,
|
|
261
|
-
events: incoming.events ?? [],
|
|
262
|
-
});
|
|
263
|
-
if (existingShape !== incomingShape) {
|
|
376
|
+
const changes = contractShapeChanges(existing, incoming);
|
|
377
|
+
if (changes.length > 0) {
|
|
264
378
|
return fail([
|
|
265
379
|
{
|
|
266
380
|
kind: "contract",
|
|
267
381
|
name: (0, types_1.contractKey)(existing.contractId, existing.version),
|
|
268
|
-
message: `Stable contract ${existing.name} v${existing.version} is frozen and cannot be modified`,
|
|
382
|
+
message: `Stable contract ${existing.name} v${existing.version} is frozen and cannot be modified (${changes.join("; ")})`,
|
|
269
383
|
},
|
|
270
384
|
]);
|
|
271
385
|
}
|
|
@@ -59,7 +59,12 @@ export type IChangeRecord = z.infer<typeof changeRecordSchema>;
|
|
|
59
59
|
* - `appliedClock` stamps `appliedAt` at insert time (per-device sync watermark)
|
|
60
60
|
*/
|
|
61
61
|
export declare class ChangeTrackingTable extends SQLDataSource<IChangeRecord> {
|
|
62
|
+
/**
|
|
63
|
+
* Writers fire this after storing change rows, whether written on this device, applied from a
|
|
64
|
+
* peer, or backfilled. Sync listens to advertise its latest `appliedAt`.
|
|
65
|
+
*/
|
|
62
66
|
readonly dataChangedEmitter: Emitter<IChangeRecord>;
|
|
67
|
+
/** Subscribe side of {@link dataChangedEmitter}. */
|
|
63
68
|
readonly dataChanged: Event<IChangeRecord>;
|
|
64
69
|
/**
|
|
65
70
|
* Hybrid logical clock for author write times (`createdAt`, `supersededAt`).
|
|
@@ -112,15 +117,16 @@ export declare class ChangeTrackingTable extends SQLDataSource<IChangeRecord> {
|
|
|
112
117
|
*/
|
|
113
118
|
filterToDeletedIds(recordIds: string[]): Promise<string[]>;
|
|
114
119
|
/**
|
|
115
|
-
* Find recordIds that have no change records
|
|
120
|
+
* Find recordIds that have no change records, in any table or only in `tableName`.
|
|
116
121
|
*
|
|
117
122
|
* This is useful for identifying records that were never synced
|
|
118
123
|
* or have been completely cleaned up.
|
|
119
124
|
*
|
|
120
125
|
* @param recordIds - Array of recordIds to check
|
|
126
|
+
* @param tableName - Limit the lookup to this table; omitted checks across all tables
|
|
121
127
|
* @returns Array of recordIds with no change records
|
|
122
128
|
*/
|
|
123
|
-
filterToMissingIds(recordIds: string[]): Promise<string[]>;
|
|
129
|
+
filterToMissingIds(recordIds: string[], tableName?: string): Promise<string[]>;
|
|
124
130
|
/**
|
|
125
131
|
* Find changeIds that we do not have
|
|
126
132
|
*
|
|
@@ -214,12 +220,18 @@ export declare class ChangeTrackingTable extends SQLDataSource<IChangeRecord> {
|
|
|
214
220
|
*/
|
|
215
221
|
deleteSupersededChangesOlderThan(tableName: string, beforeTimestamp: number): Promise<void>;
|
|
216
222
|
/**
|
|
217
|
-
* Bulk delete changes
|
|
223
|
+
* Bulk delete changes by ID.
|
|
218
224
|
*
|
|
219
|
-
* @param
|
|
220
|
-
* @param beforeTimestamp - Delete changes superseded before this timestamp
|
|
225
|
+
* @param changeIds - IDs of the change rows to delete
|
|
221
226
|
*/
|
|
222
227
|
deleteChanges(changeIds: string[]): Promise<void>;
|
|
228
|
+
/**
|
|
229
|
+
* Delete every change row for one table, active or superseded. Used when a table is deleted or
|
|
230
|
+
* becomes local-only: its rows stop syncing until a later schema enables sync again.
|
|
231
|
+
*
|
|
232
|
+
* @param tableName - Full name of the tracked table, as stored in each row's `tableName`
|
|
233
|
+
*/
|
|
234
|
+
deleteTableChanges(tableName: string): Promise<void>;
|
|
223
235
|
/**
|
|
224
236
|
* Clear inline values on insert changes (op='set', path='/') for a specific table.
|
|
225
237
|
* These values are no longer needed since they are resolved from the source table at query time.
|
|
@@ -63,7 +63,12 @@ const changeTrackingV2TableMetaData = {
|
|
|
63
63
|
* - `appliedClock` stamps `appliedAt` at insert time (per-device sync watermark)
|
|
64
64
|
*/
|
|
65
65
|
class ChangeTrackingTable extends orm_1.SQLDataSource {
|
|
66
|
+
/**
|
|
67
|
+
* Writers fire this after storing change rows, whether written on this device, applied from a
|
|
68
|
+
* peer, or backfilled. Sync listens to advertise its latest `appliedAt`.
|
|
69
|
+
*/
|
|
66
70
|
dataChangedEmitter;
|
|
71
|
+
/** Subscribe side of {@link dataChangedEmitter}. */
|
|
67
72
|
dataChanged;
|
|
68
73
|
/**
|
|
69
74
|
* Hybrid logical clock for author write times (`createdAt`, `supersededAt`).
|
|
@@ -171,15 +176,16 @@ class ChangeTrackingTable extends orm_1.SQLDataSource {
|
|
|
171
176
|
return deletedRecordsResult.map((d) => d.recordId);
|
|
172
177
|
}
|
|
173
178
|
/**
|
|
174
|
-
* Find recordIds that have no change records
|
|
179
|
+
* Find recordIds that have no change records, in any table or only in `tableName`.
|
|
175
180
|
*
|
|
176
181
|
* This is useful for identifying records that were never synced
|
|
177
182
|
* or have been completely cleaned up.
|
|
178
183
|
*
|
|
179
184
|
* @param recordIds - Array of recordIds to check
|
|
185
|
+
* @param tableName - Limit the lookup to this table; omitted checks across all tables
|
|
180
186
|
* @returns Array of recordIds with no change records
|
|
181
187
|
*/
|
|
182
|
-
async filterToMissingIds(recordIds) {
|
|
188
|
+
async filterToMissingIds(recordIds, tableName) {
|
|
183
189
|
if (recordIds.length === 0)
|
|
184
190
|
return [];
|
|
185
191
|
await this.initTable();
|
|
@@ -193,8 +199,9 @@ class ChangeTrackingTable extends orm_1.SQLDataSource {
|
|
|
193
199
|
FROM InputRecordIds AS i
|
|
194
200
|
LEFT JOIN "${this.tableName}" AS existing
|
|
195
201
|
ON i.recordId = existing.recordId
|
|
202
|
+
${tableName === undefined ? "" : "AND existing.tableName = ?"}
|
|
196
203
|
WHERE existing.recordId IS NULL
|
|
197
|
-
`, recordIds);
|
|
204
|
+
`, tableName === undefined ? recordIds : [...recordIds, tableName]);
|
|
198
205
|
return missingRecordsResult.map((d) => d.recordId);
|
|
199
206
|
}
|
|
200
207
|
/**
|
|
@@ -443,10 +450,9 @@ class ChangeTrackingTable extends orm_1.SQLDataSource {
|
|
|
443
450
|
`, [tableName, beforeTimestamp]);
|
|
444
451
|
}
|
|
445
452
|
/**
|
|
446
|
-
* Bulk delete changes
|
|
453
|
+
* Bulk delete changes by ID.
|
|
447
454
|
*
|
|
448
|
-
* @param
|
|
449
|
-
* @param beforeTimestamp - Delete changes superseded before this timestamp
|
|
455
|
+
* @param changeIds - IDs of the change rows to delete
|
|
450
456
|
*/
|
|
451
457
|
async deleteChanges(changeIds) {
|
|
452
458
|
await this.initTable();
|
|
@@ -455,6 +461,16 @@ class ChangeTrackingTable extends orm_1.SQLDataSource {
|
|
|
455
461
|
WHERE changeId IN (${changeIds.map((_c) => "?").join()})
|
|
456
462
|
`, changeIds);
|
|
457
463
|
}
|
|
464
|
+
/**
|
|
465
|
+
* Delete every change row for one table, active or superseded. Used when a table is deleted or
|
|
466
|
+
* becomes local-only: its rows stop syncing until a later schema enables sync again.
|
|
467
|
+
*
|
|
468
|
+
* @param tableName - Full name of the tracked table, as stored in each row's `tableName`
|
|
469
|
+
*/
|
|
470
|
+
async deleteTableChanges(tableName) {
|
|
471
|
+
await this.initTable();
|
|
472
|
+
await this.db.exec(`DELETE FROM "${this.tableName}" WHERE tableName = ?`, [tableName]);
|
|
473
|
+
}
|
|
458
474
|
/**
|
|
459
475
|
* Clear inline values on insert changes (op='set', path='/') for a specific table.
|
|
460
476
|
* These values are no longer needed since they are resolved from the source table at query time.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const SQLiteDB = require("better-sqlite3");
|
|
4
|
+
const utils_1 = require("../utils");
|
|
5
|
+
const change_tracking_1 = require("./change-tracking");
|
|
6
|
+
describe("ChangeTrackingTable table-scoped history", () => {
|
|
7
|
+
let sqlite;
|
|
8
|
+
let changes;
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
sqlite = new SQLiteDB(":memory:");
|
|
11
|
+
const db = {
|
|
12
|
+
async get(sql, params = []) {
|
|
13
|
+
return sqlite.prepare(sql).get(params);
|
|
14
|
+
},
|
|
15
|
+
async all(sql, params = []) {
|
|
16
|
+
return sqlite.prepare(sql).all(params);
|
|
17
|
+
},
|
|
18
|
+
async exec(sql, params = []) {
|
|
19
|
+
return { changes: sqlite.prepare(sql).run(params).changes };
|
|
20
|
+
},
|
|
21
|
+
async close() {
|
|
22
|
+
sqlite.close();
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
changes = new change_tracking_1.ChangeTrackingTable({ db });
|
|
26
|
+
});
|
|
27
|
+
afterEach(() => sqlite.close());
|
|
28
|
+
const change = (tableName, recordId) => ({
|
|
29
|
+
changeId: (0, utils_1.newid)(),
|
|
30
|
+
tableName,
|
|
31
|
+
recordId,
|
|
32
|
+
op: "set",
|
|
33
|
+
path: "/",
|
|
34
|
+
value: null,
|
|
35
|
+
createdAt: Date.now(),
|
|
36
|
+
appliedAt: 0,
|
|
37
|
+
});
|
|
38
|
+
it("checks missing IDs within the requested table, preserving unscoped lookups", async () => {
|
|
39
|
+
const sharedId = (0, utils_1.newid)();
|
|
40
|
+
const trackedId = (0, utils_1.newid)();
|
|
41
|
+
const missingId = (0, utils_1.newid)();
|
|
42
|
+
await changes.insert(change("Other", sharedId));
|
|
43
|
+
await changes.insert(change("State", trackedId));
|
|
44
|
+
await expect(changes.filterToMissingIds([sharedId, trackedId, missingId], "State")).resolves.toEqual([sharedId, missingId]);
|
|
45
|
+
await expect(changes.filterToMissingIds([sharedId, trackedId, missingId])).resolves.toEqual([
|
|
46
|
+
missingId,
|
|
47
|
+
]);
|
|
48
|
+
await expect(changes.filterToMissingIds([], "State")).resolves.toEqual([]);
|
|
49
|
+
});
|
|
50
|
+
it("deletes active, superseded, and deleted history only for the named table", async () => {
|
|
51
|
+
const tableName = "State'with-quote";
|
|
52
|
+
const recordId = (0, utils_1.newid)();
|
|
53
|
+
const other = change("Other", recordId);
|
|
54
|
+
await changes.insert(change(tableName, recordId));
|
|
55
|
+
await changes.insert({ ...change(tableName, recordId), supersededAt: Date.now() });
|
|
56
|
+
await changes.insert({ ...change(tableName, recordId), op: "delete" });
|
|
57
|
+
await changes.insert(other);
|
|
58
|
+
await changes.deleteTableChanges(tableName);
|
|
59
|
+
await changes.deleteTableChanges(tableName);
|
|
60
|
+
expect(await changes.list()).toEqual([expect.objectContaining({ changeId: other.changeId })]);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -3,6 +3,7 @@ import type { DataContext } from "../context/data-context";
|
|
|
3
3
|
import { GroupMemberRole } from "./group-member-roles";
|
|
4
4
|
import type { ISaveOptions } from "./orm/data-query";
|
|
5
5
|
import { Table } from "./orm/table";
|
|
6
|
+
/** Schema for a signed group membership, including its role and soft deletion state. */
|
|
6
7
|
export declare const groupMemberSchema: z.ZodObject<{
|
|
7
8
|
groupMemberId: z.ZodEffects<z.ZodString, string, string>;
|
|
8
9
|
groupId: z.ZodEffects<z.ZodString, string, string>;
|
|
@@ -25,18 +26,22 @@ export declare const groupMemberSchema: z.ZodObject<{
|
|
|
25
26
|
groupMemberId: string;
|
|
26
27
|
deleted?: boolean | undefined;
|
|
27
28
|
}>;
|
|
29
|
+
/** A signed association between a user and a group with an assigned role. */
|
|
28
30
|
export type IGroupMember = z.infer<typeof groupMemberSchema>;
|
|
31
|
+
/** System table name for group membership records. */
|
|
29
32
|
export declare const groupMembersTableName = "GroupMembers";
|
|
33
|
+
/** Group membership table with signature checks and signed soft deletion. */
|
|
30
34
|
export declare class GroupMembersTable extends Table<IGroupMember> {
|
|
31
35
|
static isPassthrough: boolean;
|
|
32
36
|
save(groupMember: IGroupMember, opts?: ISaveOptions): Promise<IGroupMember>;
|
|
33
37
|
signAndSave(groupMember: IGroupMember, opts?: ISaveOptions): Promise<IGroupMember>;
|
|
34
38
|
delete(groupMemberId: string): Promise<void>;
|
|
35
|
-
|
|
39
|
+
/** Registers the signer for all SDK copies in this JavaScript realm. Replaces any prior signer. */
|
|
36
40
|
static enableGroupMemberSigning(fn: (groupMember: IGroupMember) => IGroupMember): void;
|
|
37
41
|
/** @deprecated Forbidden on GroupsTable; use save() */
|
|
38
42
|
insert(..._args: Parameters<Table<IGroupMember>["insert"]>): never;
|
|
39
43
|
/** @deprecated Forbidden on GroupsTable; use save() */
|
|
40
44
|
update(..._args: Parameters<Table<IGroupMember>["update"]>): never;
|
|
41
45
|
}
|
|
46
|
+
/** Returns the GroupMembers table for the given context or the current default context. */
|
|
42
47
|
export declare function GroupMembers(dataContext?: DataContext): GroupMembersTable;
|
|
@@ -46,6 +46,7 @@ const table_1 = require("./orm/table");
|
|
|
46
46
|
const table_definitions_system_1 = require("./orm/table-definitions.system");
|
|
47
47
|
const types_1 = require("./orm/types");
|
|
48
48
|
const users_1 = require("./users");
|
|
49
|
+
/** Schema for a signed group membership, including its role and soft deletion state. */
|
|
49
50
|
exports.groupMemberSchema = zod_1.z.object({
|
|
50
51
|
groupMemberId: zod_types_1.zodPeerId,
|
|
51
52
|
groupId: zod_types_1.zodPeerId.describe("The id of the group"),
|
|
@@ -59,6 +60,7 @@ exports.groupMemberSchema = zod_1.z.object({
|
|
|
59
60
|
.optional()
|
|
60
61
|
.describe("Whether this membership has been deleted (soft delete)"),
|
|
61
62
|
});
|
|
63
|
+
/** System table name for group membership records. */
|
|
62
64
|
exports.groupMembersTableName = "GroupMembers";
|
|
63
65
|
const metaData = {
|
|
64
66
|
name: exports.groupMembersTableName,
|
|
@@ -67,6 +69,10 @@ const metaData = {
|
|
|
67
69
|
fields: (0, types_1.schemaToFields)(exports.groupMemberSchema),
|
|
68
70
|
indexes: [{ fields: ["groupId", "userId"], unique: true }],
|
|
69
71
|
};
|
|
72
|
+
// Symbol.for keeps signer registration shared by separately installed SDK copies.
|
|
73
|
+
const signingKey = Symbol.for("@peers-app/peers-sdk/group-member-signing");
|
|
74
|
+
const signingGlobals = globalThis;
|
|
75
|
+
/** Group membership table with signature checks and signed soft deletion. */
|
|
70
76
|
let GroupMembersTable = (() => {
|
|
71
77
|
let _classSuper = table_1.Table;
|
|
72
78
|
let _instanceExtraInitializers = [];
|
|
@@ -96,10 +102,11 @@ let GroupMembersTable = (() => {
|
|
|
96
102
|
throw new Error("Group member signature is not valid");
|
|
97
103
|
}
|
|
98
104
|
async signAndSave(groupMember, opts) {
|
|
99
|
-
|
|
105
|
+
const sign = signingGlobals[signingKey];
|
|
106
|
+
if (!sign) {
|
|
100
107
|
throw new Error("Group member signing must be enabled to sign and save group members. Call GroupMembersTable.enableGroupMemberSigning(fn) to enable it.");
|
|
101
108
|
}
|
|
102
|
-
groupMember =
|
|
109
|
+
groupMember = sign.call(GroupMembersTable, groupMember);
|
|
103
110
|
return this.save(groupMember, {
|
|
104
111
|
saveAsSnapshot: true,
|
|
105
112
|
restoreIfDeleted: true,
|
|
@@ -118,9 +125,9 @@ let GroupMembersTable = (() => {
|
|
|
118
125
|
await this.signAndSave(groupMember);
|
|
119
126
|
}
|
|
120
127
|
}
|
|
121
|
-
|
|
128
|
+
/** Registers the signer for all SDK copies in this JavaScript realm. Replaces any prior signer. */
|
|
122
129
|
static enableGroupMemberSigning(fn) {
|
|
123
|
-
|
|
130
|
+
signingGlobals[signingKey] = fn;
|
|
124
131
|
}
|
|
125
132
|
/** @deprecated Forbidden on GroupsTable; use save() */
|
|
126
133
|
insert(..._args) {
|
|
@@ -138,6 +145,7 @@ let GroupMembersTable = (() => {
|
|
|
138
145
|
})();
|
|
139
146
|
exports.GroupMembersTable = GroupMembersTable;
|
|
140
147
|
(0, table_definitions_system_1.registerSystemTableDefinition)(metaData, exports.groupMemberSchema, GroupMembersTable);
|
|
148
|
+
/** Returns the GroupMembers table for the given context or the current default context. */
|
|
141
149
|
function GroupMembers(dataContext) {
|
|
142
150
|
return (0, user_context_singleton_1.getTableContainer)(dataContext).getTable(metaData, exports.groupMemberSchema, GroupMembersTable);
|
|
143
151
|
}
|
package/dist/data/groups.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { DataContext } from "../context/data-context";
|
|
|
3
3
|
import { GroupMemberRole } from "./group-member-roles";
|
|
4
4
|
import type { ISaveOptions } from "./orm/data-query";
|
|
5
5
|
import { Table } from "./orm/table";
|
|
6
|
+
/** Schema for a signed group record, including membership policy and group public keys. */
|
|
6
7
|
export declare const groupSchema: z.ZodObject<{
|
|
7
8
|
groupId: z.ZodEffects<z.ZodString, string, string>;
|
|
8
9
|
name: z.ZodString;
|
|
@@ -37,18 +38,22 @@ export declare const groupSchema: z.ZodObject<{
|
|
|
37
38
|
iconClassName?: string | undefined;
|
|
38
39
|
publicRole?: GroupMemberRole | undefined;
|
|
39
40
|
}>;
|
|
41
|
+
/** A signed group record shared between personal and group contexts. */
|
|
40
42
|
export type IGroup = z.infer<typeof groupSchema>;
|
|
43
|
+
/** System table name for group records. */
|
|
41
44
|
export declare const groupsTableName = "Groups";
|
|
45
|
+
/** Group table with signature enforcement outside the personal context. */
|
|
42
46
|
export declare class GroupsTable extends Table<IGroup> {
|
|
43
47
|
static isPassthrough: boolean;
|
|
44
48
|
save(group: IGroup, opts?: ISaveOptions): Promise<IGroup>;
|
|
45
49
|
signAndSave(group: IGroup, opts?: ISaveOptions): Promise<IGroup>;
|
|
46
50
|
delete(groupId: string): Promise<void>;
|
|
47
|
-
|
|
51
|
+
/** Registers the signer for all SDK copies in this JavaScript realm. Replaces any prior signer. */
|
|
48
52
|
static enableGroupSigning(fn: (group: IGroup) => IGroup): void;
|
|
49
53
|
/** @deprecated Direct inserts forbidden; use save() */
|
|
50
54
|
insert(..._args: Parameters<Table<IGroup>["insert"]>): never;
|
|
51
55
|
/** @deprecated Direct updates forbidden; use save() */
|
|
52
56
|
update(..._args: Parameters<Table<IGroup>["update"]>): never;
|
|
53
57
|
}
|
|
58
|
+
/** Returns the Groups table for the given context or the current default context. */
|
|
54
59
|
export declare function Groups(dataContext?: DataContext): GroupsTable;
|
package/dist/data/groups.js
CHANGED
|
@@ -45,6 +45,7 @@ const decorators_1 = require("./orm/decorators");
|
|
|
45
45
|
const table_1 = require("./orm/table");
|
|
46
46
|
const table_definitions_system_1 = require("./orm/table-definitions.system");
|
|
47
47
|
const types_1 = require("./orm/types");
|
|
48
|
+
/** Schema for a signed group record, including membership policy and group public keys. */
|
|
48
49
|
exports.groupSchema = zod_1.z.object({
|
|
49
50
|
groupId: zod_types_1.zodPeerId,
|
|
50
51
|
name: zod_1.z.string(),
|
|
@@ -67,6 +68,7 @@ exports.groupSchema = zod_1.z.object({
|
|
|
67
68
|
.string()
|
|
68
69
|
.describe("The public key used to encrypt data that only group members with writer permissions or higher can decrypt"),
|
|
69
70
|
});
|
|
71
|
+
/** System table name for group records. */
|
|
70
72
|
exports.groupsTableName = "Groups";
|
|
71
73
|
const metaData = {
|
|
72
74
|
name: exports.groupsTableName,
|
|
@@ -76,6 +78,10 @@ const metaData = {
|
|
|
76
78
|
iconClassName: "bi bi-people-fill",
|
|
77
79
|
indexes: [{ fields: ["name"] }],
|
|
78
80
|
};
|
|
81
|
+
// Symbol.for keeps signer registration shared by separately installed SDK copies.
|
|
82
|
+
const signingKey = Symbol.for("@peers-app/peers-sdk/group-signing");
|
|
83
|
+
const signingGlobals = globalThis;
|
|
84
|
+
/** Group table with signature enforcement outside the personal context. */
|
|
79
85
|
let GroupsTable = (() => {
|
|
80
86
|
let _classSuper = table_1.Table;
|
|
81
87
|
let _instanceExtraInitializers = [];
|
|
@@ -103,10 +109,11 @@ let GroupsTable = (() => {
|
|
|
103
109
|
return super.save(group, opts);
|
|
104
110
|
}
|
|
105
111
|
async signAndSave(group, opts) {
|
|
106
|
-
|
|
112
|
+
const sign = signingGlobals[signingKey];
|
|
113
|
+
if (!sign) {
|
|
107
114
|
throw new Error("Group signing must be enabled to sign and save groups. Call GroupsTable.enableGroupSigning(fn) to enable it.");
|
|
108
115
|
}
|
|
109
|
-
group =
|
|
116
|
+
group = sign.call(GroupsTable, group);
|
|
110
117
|
return this.save(group, opts);
|
|
111
118
|
}
|
|
112
119
|
async delete(groupId) {
|
|
@@ -117,9 +124,9 @@ let GroupsTable = (() => {
|
|
|
117
124
|
}
|
|
118
125
|
throw new Error("Only deleting groups from the user context Groups table is allowed");
|
|
119
126
|
}
|
|
120
|
-
|
|
127
|
+
/** Registers the signer for all SDK copies in this JavaScript realm. Replaces any prior signer. */
|
|
121
128
|
static enableGroupSigning(fn) {
|
|
122
|
-
|
|
129
|
+
signingGlobals[signingKey] = fn;
|
|
123
130
|
}
|
|
124
131
|
/** @deprecated Direct inserts forbidden; use save() */
|
|
125
132
|
insert(..._args) {
|
|
@@ -137,6 +144,7 @@ let GroupsTable = (() => {
|
|
|
137
144
|
})();
|
|
138
145
|
exports.GroupsTable = GroupsTable;
|
|
139
146
|
(0, table_definitions_system_1.registerSystemTableDefinition)(metaData, exports.groupSchema, GroupsTable);
|
|
147
|
+
/** Returns the Groups table for the given context or the current default context. */
|
|
140
148
|
function Groups(dataContext) {
|
|
141
149
|
return (0, user_context_singleton_1.getTableContainer)(dataContext).getTable(metaData, exports.groupSchema, GroupsTable);
|
|
142
150
|
}
|
|
@@ -42,6 +42,15 @@ export declare class SQLDataSource<T extends {
|
|
|
42
42
|
* columns (for example `accessLevel`) are written on insert.
|
|
43
43
|
*/
|
|
44
44
|
private applySchemaDefaults;
|
|
45
|
+
/**
|
|
46
|
+
* Remove `record`'s empty fields in place, so a row is written and returned as it's stored:
|
|
47
|
+
* an optional field that is `null` or `undefined` is left out, and so is an empty field with a
|
|
48
|
+
* default, which gets the default. Returns the defaulted fields' names. A required field
|
|
49
|
+
* without a default keeps its value for validation to refuse.
|
|
50
|
+
*/
|
|
51
|
+
private removeEmptyFields;
|
|
52
|
+
/** Copy the parsed defaults requested by {@link removeEmptyFields} onto `record`. */
|
|
53
|
+
private applyDefaults;
|
|
45
54
|
/**
|
|
46
55
|
* Columns to SET on update. Required fields that are missing are omitted so
|
|
47
56
|
* older peers without a new column do not overwrite a local value with NULL.
|