@peers-app/peers-sdk 0.28.0 → 0.28.2
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/contracts/host-contract-client.js +4 -2
- package/dist/data/orm/sql.data-source.js +16 -13
- package/dist/data/orm/table-container.js +6 -3
- package/dist/data/package-table-identity.d.ts +15 -0
- package/dist/data/package-table-identity.js +50 -14
- package/dist/data/package-version-resolver.js +14 -2
- package/dist/define/context.d.ts +12 -0
- package/dist/define/define-package.d.ts +21 -0
- package/dist/define/define-package.js +20 -0
- package/dist/define/index.d.ts +2 -1
- package/dist/define/index.js +2 -1
- package/dist/define/manifest.js +13 -2
- package/dist/define/serve.d.ts +10 -3
- package/dist/define/serve.js +28 -4
- package/dist/define/table-handle.d.ts +12 -3
- package/dist/define/table-handle.js +28 -10
- package/dist/define/table.d.ts +40 -3
- package/dist/define/table.js +32 -5
- package/dist/define/wire-value.d.ts +6 -1
- package/dist/define/wire-value.js +6 -3
- package/dist/logging/console-logger.d.ts +10 -1
- package/dist/logging/console-logger.js +17 -7
- package/dist/package-loader/isolated-artifact.d.ts +14 -0
- package/dist/package-loader/isolated-artifact.js +3 -1
- package/dist/package-loader/isolated-runtime.d.ts +29 -1
- package/dist/package-loader/isolated-runtime.js +13 -0
- package/dist/package-loader/isolated-table-gateway.js +4 -1
- package/dist/package-loader/package-loader.d.ts +9 -0
- package/dist/package-loader/package-loader.js +30 -0
- package/dist/peer-id.d.ts +19 -0
- package/dist/peer-id.js +41 -0
- package/dist/testing/mock-host-contract.js +2 -0
- package/dist/utils.js +13 -32
- package/package.json +1 -1
|
@@ -6,6 +6,7 @@ const user_context_singleton_1 = require("../context/user-context-singleton");
|
|
|
6
6
|
const contract_ref_1 = require("../define/contract-ref");
|
|
7
7
|
const lazy_members_1 = require("../define/lazy-members");
|
|
8
8
|
const table_handle_1 = require("../define/table-handle");
|
|
9
|
+
const wire_value_1 = require("../define/wire-value");
|
|
9
10
|
const observable_1 = require("../observable");
|
|
10
11
|
const contract_proxy_1 = require("./contract-proxy");
|
|
11
12
|
const host_contract_transport_1 = require("./host-contract-transport");
|
|
@@ -265,7 +266,8 @@ function createHostContractClient(ref, options, onDispose) {
|
|
|
265
266
|
const next = await connect({ timeoutMs });
|
|
266
267
|
return untilReplaced(Promise.resolve().then(() => send(next.members)), next.signal);
|
|
267
268
|
};
|
|
268
|
-
|
|
269
|
+
// Arguments and values go as the bridge carries them: a field left undefined is omitted.
|
|
270
|
+
const tools = (0, lazy_members_1.lazyMembers)((toolName) => (...args) => call((members) => members.tool(toolName)(...args.map((arg) => (arg === undefined ? arg : (0, wire_value_1.snapshotPackageValue)(arg))))));
|
|
269
271
|
const tables = (0, lazy_members_1.lazyMembers)((tableName) => ({
|
|
270
272
|
...(0, table_handle_1.packageTableMethods)((operation, args) => call((members) => members.table(tableName)[operation](...args))),
|
|
271
273
|
dataChanged: {
|
|
@@ -317,7 +319,7 @@ function createHostContractClient(ref, options, onDispose) {
|
|
|
317
319
|
if (next.signal.aborted)
|
|
318
320
|
throw next.signal.reason;
|
|
319
321
|
const before = remoteRevision;
|
|
320
|
-
const result = await untilReplaced(next.members.setObservable(observableName, value), next.signal);
|
|
322
|
+
const result = await untilReplaced(next.members.setObservable(observableName, value === undefined ? value : (0, wire_value_1.snapshotPackageValue)(value)), next.signal);
|
|
321
323
|
if (!disposed &&
|
|
322
324
|
connection === next &&
|
|
323
325
|
remoteRevision === before &&
|
|
@@ -10,6 +10,16 @@ const tool_catalog_fields_1 = require("../tool-catalog-fields");
|
|
|
10
10
|
const data_query_1 = require("./data-query");
|
|
11
11
|
const data_query_sqlite_1 = require("./data-query.sqlite");
|
|
12
12
|
const types_1 = require("./types");
|
|
13
|
+
/**
|
|
14
|
+
* The SQLite name {@link SQLDataSource} gives one of a table's indexes.
|
|
15
|
+
*
|
|
16
|
+
* @param tableName - Full (physical) table name.
|
|
17
|
+
* @param index - One entry of `ITableMetaData.indexes`.
|
|
18
|
+
*/
|
|
19
|
+
function sqlIndexName(tableName, index) {
|
|
20
|
+
const fieldNames = index.fields.map((f) => `_${typeof f === "string" ? f : f.name}`).join("");
|
|
21
|
+
return `${tableName}_${fieldNames}_${(0, utils_1.simpleHash)(JSON.stringify(index))}`;
|
|
22
|
+
}
|
|
13
23
|
/** SQLite-backed table storage with additive schema migration and serialized initialization. */
|
|
14
24
|
class SQLDataSource {
|
|
15
25
|
db;
|
|
@@ -79,22 +89,12 @@ class SQLDataSource {
|
|
|
79
89
|
// create indexes if they don't exist
|
|
80
90
|
if (this.metaData.indexes) {
|
|
81
91
|
for (const index of this.metaData.indexes) {
|
|
82
|
-
|
|
92
|
+
// Quoted: a field may be named like an SQL keyword (`order`, `group`).
|
|
83
93
|
const fields = index.fields
|
|
84
|
-
.map((f) => {
|
|
85
|
-
if (typeof f === "string") {
|
|
86
|
-
fieldNames += `_${f}`;
|
|
87
|
-
return f;
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
fieldNames += `_${f.name}`;
|
|
91
|
-
return `${f.name} ${f.order}`;
|
|
92
|
-
}
|
|
93
|
-
})
|
|
94
|
+
.map((f) => (typeof f === "string" ? `"${f}"` : `"${f.name}" ${f.order}`))
|
|
94
95
|
.join(", ");
|
|
95
|
-
const hash = (0, utils_1.simpleHash)(JSON.stringify(index));
|
|
96
96
|
const unique = index.unique ? "UNIQUE" : "";
|
|
97
|
-
const indexName =
|
|
97
|
+
const indexName = sqlIndexName(this.tableName, index);
|
|
98
98
|
indexNames.push(indexName);
|
|
99
99
|
const sql = `CREATE ${unique} INDEX IF NOT EXISTS "${indexName}" ON "${this.tableName}"(${fields});`;
|
|
100
100
|
try {
|
|
@@ -102,6 +102,9 @@ class SQLDataSource {
|
|
|
102
102
|
}
|
|
103
103
|
catch (e) {
|
|
104
104
|
console.error(`Error creating index "${indexName}" on table "${this.tableName}": ${String(e)}`);
|
|
105
|
+
// A package must not accept writes without its declared uniqueness constraint.
|
|
106
|
+
if (this.metaData.packageId && index.unique)
|
|
107
|
+
throw e;
|
|
105
108
|
}
|
|
106
109
|
}
|
|
107
110
|
}
|
|
@@ -160,9 +160,12 @@ class TableContainer {
|
|
|
160
160
|
this.savingDefinitions = true;
|
|
161
161
|
const tableName = (0, utils_1.getFullTableName)(metaData);
|
|
162
162
|
try {
|
|
163
|
-
// Lazy import to avoid circular dependency at module load time
|
|
164
|
-
|
|
165
|
-
|
|
163
|
+
// Lazy import to avoid circular dependency at module load time; it registers the
|
|
164
|
+
// TableDefinitions system table. Use this container's own copy: a definition belongs to
|
|
165
|
+
// the data context the table lives in, not whichever group is active.
|
|
166
|
+
require("../table-definitions-table");
|
|
167
|
+
const definitions = table_definitions_system_1.systemTableDefinitions.TableDefinitions;
|
|
168
|
+
const tableDefsTable = this.getTable(definitions.metaData, definitions.schema);
|
|
166
169
|
const record = {
|
|
167
170
|
tableId: metaData.tableId,
|
|
168
171
|
name: tableName,
|
|
@@ -13,6 +13,21 @@ import type { ITableDefinitionRecord } from "./table-definitions-table";
|
|
|
13
13
|
* @throws When the identifier is empty or contains SQL syntax.
|
|
14
14
|
*/
|
|
15
15
|
export declare function validateIsolatedSqlIdentifier(identifier: string, label?: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Validate a table's index declarations: each lists at least one of the table's fields, in
|
|
18
|
+
* order, by name or as `{ name, order }` with `"ASC"` or `"DESC"`.
|
|
19
|
+
*
|
|
20
|
+
* @param indexes - The declared indexes, if any.
|
|
21
|
+
* @param fieldNames - The table's field names.
|
|
22
|
+
* @param label - Where the indexes come from, for errors.
|
|
23
|
+
* @param allowUnique - Whether an index may be unique. A package may make one unique only on a
|
|
24
|
+
* local-only table: on a synced table two devices can each write a row the other's unique
|
|
25
|
+
* index refuses, and package tables have no conflict resolver. A stored definition may have
|
|
26
|
+
* one anyway (an old-style package's).
|
|
27
|
+
* @throws When an index is malformed, names a field the table lacks, or is unique when not
|
|
28
|
+
* allowed.
|
|
29
|
+
*/
|
|
30
|
+
export declare function validateTableIndexes(indexes: unknown, fieldNames: ReadonlySet<string>, label: string, allowUnique?: boolean): void;
|
|
16
31
|
/**
|
|
17
32
|
* Validate a package-local table name.
|
|
18
33
|
*
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.validateIsolatedSqlIdentifier = validateIsolatedSqlIdentifier;
|
|
4
|
+
exports.validateTableIndexes = validateTableIndexes;
|
|
4
5
|
exports.validatePackageTableName = validatePackageTableName;
|
|
5
6
|
exports.packageScopedTableName = packageScopedTableName;
|
|
6
7
|
exports.packageScopedTableId = packageScopedTableId;
|
|
@@ -29,6 +30,53 @@ function validateIsolatedSqlIdentifier(identifier, label = "Isolated SQL identif
|
|
|
29
30
|
}
|
|
30
31
|
return identifier;
|
|
31
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Validate a table's index declarations: each lists at least one of the table's fields, in
|
|
35
|
+
* order, by name or as `{ name, order }` with `"ASC"` or `"DESC"`.
|
|
36
|
+
*
|
|
37
|
+
* @param indexes - The declared indexes, if any.
|
|
38
|
+
* @param fieldNames - The table's field names.
|
|
39
|
+
* @param label - Where the indexes come from, for errors.
|
|
40
|
+
* @param allowUnique - Whether an index may be unique. A package may make one unique only on a
|
|
41
|
+
* local-only table: on a synced table two devices can each write a row the other's unique
|
|
42
|
+
* index refuses, and package tables have no conflict resolver. A stored definition may have
|
|
43
|
+
* one anyway (an old-style package's).
|
|
44
|
+
* @throws When an index is malformed, names a field the table lacks, or is unique when not
|
|
45
|
+
* allowed.
|
|
46
|
+
*/
|
|
47
|
+
function validateTableIndexes(indexes, fieldNames, label, allowUnique = false) {
|
|
48
|
+
if (indexes === undefined)
|
|
49
|
+
return;
|
|
50
|
+
if (!Array.isArray(indexes))
|
|
51
|
+
throw new Error(`${label} must be an array`);
|
|
52
|
+
for (const [position, index] of indexes.entries()) {
|
|
53
|
+
const at = `${label}[${position}]`;
|
|
54
|
+
const { fields, unique } = (index ?? {});
|
|
55
|
+
if (!index ||
|
|
56
|
+
typeof index !== "object" ||
|
|
57
|
+
Object.keys(index).some((key) => key !== "fields" && key !== "unique") ||
|
|
58
|
+
!Array.isArray(fields) ||
|
|
59
|
+
fields.length === 0) {
|
|
60
|
+
throw new Error(`${at} must be { fields: [...] } with at least one field`);
|
|
61
|
+
}
|
|
62
|
+
if (unique !== undefined && (unique !== true || !allowUnique)) {
|
|
63
|
+
throw new Error(`${at} can be unique only on a local-only table`);
|
|
64
|
+
}
|
|
65
|
+
for (const field of fields) {
|
|
66
|
+
const name = typeof field === "string"
|
|
67
|
+
? field
|
|
68
|
+
: field &&
|
|
69
|
+
typeof field === "object" &&
|
|
70
|
+
Object.keys(field).every((key) => key === "name" || key === "order") &&
|
|
71
|
+
(field.order === "ASC" || field.order === "DESC")
|
|
72
|
+
? field.name
|
|
73
|
+
: undefined;
|
|
74
|
+
if (typeof name !== "string" || !fieldNames.has(name)) {
|
|
75
|
+
throw new Error(`${at} names a field the table doesn't have`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
32
80
|
/**
|
|
33
81
|
* Validate a package-local table name.
|
|
34
82
|
*
|
|
@@ -105,18 +153,6 @@ function validateTableDefinitionRecord(record) {
|
|
|
105
153
|
}
|
|
106
154
|
if (!names.has(meta.primaryKeyName))
|
|
107
155
|
throw new Error("Missing primary key column");
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
for (const index of meta.indexes ?? []) {
|
|
111
|
-
if (!Array.isArray(index.fields) || !index.fields.length)
|
|
112
|
-
throw new Error("Missing index fields");
|
|
113
|
-
for (const field of index.fields) {
|
|
114
|
-
const name = typeof field === "string" ? field : field.name;
|
|
115
|
-
if (!names.has(name))
|
|
116
|
-
throw new Error("Index must name a declared column");
|
|
117
|
-
if (typeof field !== "string" && field.order !== "ASC" && field.order !== "DESC") {
|
|
118
|
-
throw new Error("Invalid index order");
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
156
|
+
// Only what reaches SQL here: an old-style package may keep a unique index on a synced table.
|
|
157
|
+
validateTableIndexes(meta.indexes, names, "Table indexes", true);
|
|
122
158
|
}
|
|
@@ -17,6 +17,7 @@ exports.resolveDevicePackageVersion = resolveDevicePackageVersion;
|
|
|
17
17
|
exports.hasDeviceFollowOverride = hasDeviceFollowOverride;
|
|
18
18
|
exports.readStoredPackageLocalPath = readStoredPackageLocalPath;
|
|
19
19
|
const user_context_singleton_1 = require("../context/user-context-singleton");
|
|
20
|
+
const keys_1 = require("../keys");
|
|
20
21
|
const assistants_1 = require("./assistants");
|
|
21
22
|
const file_types_1 = require("./files/file.types");
|
|
22
23
|
const group_member_roles_1 = require("./group-member-roles");
|
|
@@ -100,12 +101,23 @@ async function assertPackageVersionRunnable(pkg, pv, dataContext, localDev = fal
|
|
|
100
101
|
if (!localDev) {
|
|
101
102
|
const prefs = packagePrefsVar(pkg.packageId, dataContext);
|
|
102
103
|
await prefs.loadingPromise;
|
|
103
|
-
|
|
104
|
+
let selection = prefs();
|
|
105
|
+
const deviceSelected = selection?.activePackageVersionId === pv.packageVersionId;
|
|
106
|
+
if (selection && deviceSelected && !selection.activePackageVersionTag) {
|
|
107
|
+
// A selection saved before 0.28.0 has no tag. Record this version's tag once when this
|
|
108
|
+
// device's own user signed the version (their own build or release), so it keeps running;
|
|
109
|
+
// a version someone else retagged still needs a fresh activation.
|
|
110
|
+
const me = await dataContext.userContext.getMe();
|
|
111
|
+
if (me?.publicKey && (0, keys_1.signedByKey)(pv) === me.publicKey) {
|
|
112
|
+
selection = { ...selection, activePackageVersionTag: pv.versionTag };
|
|
113
|
+
prefs(selection);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
104
116
|
if (!deviceSelected && !(await isPackageRowAdminSigned(pkg, dataContext))) {
|
|
105
117
|
throw new Error("A group package selection needs an Admin's signature before it can run");
|
|
106
118
|
}
|
|
107
119
|
if (pv.versionTag === "dev" &&
|
|
108
|
-
(!deviceSelected ||
|
|
120
|
+
(!deviceSelected || selection?.activePackageVersionTag !== "dev")) {
|
|
109
121
|
throw new Error("Activate this dev version on this device before running it");
|
|
110
122
|
}
|
|
111
123
|
}
|
package/dist/define/context.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { IDataChangedEvent } from "../data/orm/subscribable.data-source";
|
|
1
2
|
import type { PersistentVarScope } from "../data/persistent-var-identity";
|
|
2
3
|
import type { IsolatedContractTableOperation } from "../package-loader/isolated-runtime";
|
|
3
4
|
/**
|
|
@@ -21,6 +22,11 @@ export interface IPackageProviderOptions {
|
|
|
21
22
|
* events, and observables need none; use these directly only for what those don't cover yet.
|
|
22
23
|
*/
|
|
23
24
|
export interface IPackageContext {
|
|
25
|
+
/**
|
|
26
|
+
* The device this worker runs on. It says where the code is running, not who is calling:
|
|
27
|
+
* never use it to decide what a caller may do.
|
|
28
|
+
*/
|
|
29
|
+
readonly deviceId: string;
|
|
24
30
|
/** Run a tool on a contract this package consumes (or provides), as this call's caller. */
|
|
25
31
|
callConsumedTool(contractId: string, version: number, toolName: string, args?: unknown, options?: IPackageProviderOptions): Promise<unknown>;
|
|
26
32
|
/** Run a tool on another device. The remote device authorizes the call. */
|
|
@@ -61,6 +67,12 @@ export interface IPackageContext {
|
|
|
61
67
|
* read it again.
|
|
62
68
|
*/
|
|
63
69
|
watchOwnedPersistentVar(scope: PersistentVarScope, name: string, handler: () => unknown): Promise<PackageUnsubscribe>;
|
|
70
|
+
/**
|
|
71
|
+
* Call `handler` with each change to one of this package's own tables, made here or synced
|
|
72
|
+
* from another device, until the returned function is called or the worker stops. Changes
|
|
73
|
+
* arrive in order, one at a time.
|
|
74
|
+
*/
|
|
75
|
+
watchOwnedTable(tableName: string, handler: (event: IDataChangedEvent<unknown>) => unknown): Promise<PackageUnsubscribe>;
|
|
64
76
|
/**
|
|
65
77
|
* Set the value of an in-memory observable this package provides (one declared without a
|
|
66
78
|
* `persistentVar`). The host checks it against the observable's `valueType`, keeps it for
|
|
@@ -140,12 +140,19 @@ export interface IPackageDefinition {
|
|
|
140
140
|
readonly consumes: readonly IPackageConsumedContract[];
|
|
141
141
|
/** Run when the package's worker starts, in the order they were added. */
|
|
142
142
|
readonly startHooks: readonly PackageStartHook[];
|
|
143
|
+
/** Run when the package's worker stops, in the order they were added. */
|
|
144
|
+
readonly stopHooks: readonly PackageStopHook[];
|
|
143
145
|
}
|
|
144
146
|
/**
|
|
145
147
|
* Background work for when the package's worker starts. Its `ctx` belongs to no call: it can use
|
|
146
148
|
* the package's own tables and variables, but calls to other contracts are refused.
|
|
147
149
|
*/
|
|
148
150
|
export type PackageStartHook = (ctx: IPackageContext) => unknown;
|
|
151
|
+
/**
|
|
152
|
+
* Local cleanup for when the package's worker stops: end child processes, close files. It gets
|
|
153
|
+
* no `ctx`: the worker's calls to Peers are refused by then.
|
|
154
|
+
*/
|
|
155
|
+
export type PackageStopHook = () => unknown;
|
|
149
156
|
/** The builder {@link definePackage} passes its callback. */
|
|
150
157
|
export declare class PackageBuilder {
|
|
151
158
|
/** The package's id, from `newid()`. */
|
|
@@ -163,6 +170,7 @@ export declare class PackageBuilder {
|
|
|
163
170
|
private readonly provided;
|
|
164
171
|
private readonly consumed;
|
|
165
172
|
private readonly starts;
|
|
173
|
+
private readonly stops;
|
|
166
174
|
/**
|
|
167
175
|
* Run `fn` each time the package's worker starts, after the package's variables load and
|
|
168
176
|
* before its in-memory observables publish, so a value set here is the first one readers
|
|
@@ -176,6 +184,19 @@ export declare class PackageBuilder {
|
|
|
176
184
|
* @param fn - Called with the worker's `ctx`.
|
|
177
185
|
*/
|
|
178
186
|
onStart(fn: PackageStartHook): void;
|
|
187
|
+
/**
|
|
188
|
+
* Run `fn` when the package's worker stops: when another version replaces this one, when the
|
|
189
|
+
* package is turned off or removed, and when Peers quits. Use it for local cleanup, such as
|
|
190
|
+
* ending child processes or closing files. It gets no `ctx`, because the worker's calls to
|
|
191
|
+
* Peers (its tables, variables, events) are refused once it is stopping.
|
|
192
|
+
*
|
|
193
|
+
* The worker gets 5 seconds for its stop hooks, then it is ended. A privileged package's child
|
|
194
|
+
* processes end with it, unless they detached from it on purpose (a daemon such as a tmux
|
|
195
|
+
* server); clean those up here. A crash or a forced kill runs no stop hooks.
|
|
196
|
+
*
|
|
197
|
+
* @param fn - Called with no arguments; the worker waits for a returned promise.
|
|
198
|
+
*/
|
|
199
|
+
onStop(fn: PackageStopHook): void;
|
|
179
200
|
/**
|
|
180
201
|
* Provide a contract: this package serves its tools and holds its tables, observables, and
|
|
181
202
|
* events.
|
|
@@ -172,6 +172,7 @@ class PackageBuilder {
|
|
|
172
172
|
provided = [];
|
|
173
173
|
consumed = [];
|
|
174
174
|
starts = [];
|
|
175
|
+
stops = [];
|
|
175
176
|
/**
|
|
176
177
|
* Run `fn` each time the package's worker starts, after the package's variables load and
|
|
177
178
|
* before its in-memory observables publish, so a value set here is the first one readers
|
|
@@ -190,6 +191,24 @@ class PackageBuilder {
|
|
|
190
191
|
}
|
|
191
192
|
this.starts.push(fn);
|
|
192
193
|
}
|
|
194
|
+
/**
|
|
195
|
+
* Run `fn` when the package's worker stops: when another version replaces this one, when the
|
|
196
|
+
* package is turned off or removed, and when Peers quits. Use it for local cleanup, such as
|
|
197
|
+
* ending child processes or closing files. It gets no `ctx`, because the worker's calls to
|
|
198
|
+
* Peers (its tables, variables, events) are refused once it is stopping.
|
|
199
|
+
*
|
|
200
|
+
* The worker gets 5 seconds for its stop hooks, then it is ended. A privileged package's child
|
|
201
|
+
* processes end with it, unless they detached from it on purpose (a daemon such as a tmux
|
|
202
|
+
* server); clean those up here. A crash or a forced kill runs no stop hooks.
|
|
203
|
+
*
|
|
204
|
+
* @param fn - Called with no arguments; the worker waits for a returned promise.
|
|
205
|
+
*/
|
|
206
|
+
onStop(fn) {
|
|
207
|
+
if (typeof fn !== "function") {
|
|
208
|
+
throw new Error("pkg.onStop takes a function");
|
|
209
|
+
}
|
|
210
|
+
this.stops.push(fn);
|
|
211
|
+
}
|
|
193
212
|
/**
|
|
194
213
|
* Provide a contract: this package serves its tools and holds its tables, observables, and
|
|
195
214
|
* events.
|
|
@@ -271,6 +290,7 @@ class PackageBuilder {
|
|
|
271
290
|
provides: Object.freeze(provides),
|
|
272
291
|
consumes: Object.freeze(this.consumed.map((consumed) => Object.freeze({ ...consumed }))),
|
|
273
292
|
startHooks: Object.freeze([...this.starts]),
|
|
293
|
+
stopHooks: Object.freeze([...this.stops]),
|
|
274
294
|
});
|
|
275
295
|
}
|
|
276
296
|
}
|
package/dist/define/index.d.ts
CHANGED
|
@@ -11,7 +11,8 @@ export { AccessLevel } from "../data/access-level";
|
|
|
11
11
|
export type { DataFilter, IDataQueryParams } from "../data/orm/data-query";
|
|
12
12
|
export type { IDataChangedEvent } from "../data/orm/subscribable.data-source";
|
|
13
13
|
export { type Observable, observable, type Subscription } from "../observable";
|
|
14
|
-
export {
|
|
14
|
+
export type { IPackageSaveOptions } from "../package-loader/isolated-runtime";
|
|
15
|
+
export { idFromKey, isid, newid } from "../peer-id";
|
|
15
16
|
export { FieldType, type IField } from "../types/field-type";
|
|
16
17
|
export { zodPeerId } from "../types/zod-types";
|
|
17
18
|
export * from "./context";
|
package/dist/define/index.js
CHANGED
|
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.writePackageVar = exports.userVar = exports.readPackageVar = exports.groupVar = exports.groupUserVar = exports.groupDeviceVar = exports.deviceVar = exports.isImplementedObservable = exports.defineObservable = exports.declareObservable = exports.generatedField = exports.defineEvent = exports.zodPeerId = exports.FieldType = exports.newid = exports.isid = exports.observable = exports.AccessLevel = exports.z = void 0;
|
|
17
|
+
exports.writePackageVar = exports.userVar = exports.readPackageVar = exports.groupVar = exports.groupUserVar = exports.groupDeviceVar = exports.deviceVar = exports.isImplementedObservable = exports.defineObservable = exports.declareObservable = exports.generatedField = exports.defineEvent = exports.zodPeerId = exports.FieldType = exports.newid = exports.isid = exports.idFromKey = exports.observable = exports.AccessLevel = exports.z = void 0;
|
|
18
18
|
/**
|
|
19
19
|
* `@peers-app/peers-sdk/define`: declare an isolated package.
|
|
20
20
|
*
|
|
@@ -30,6 +30,7 @@ Object.defineProperty(exports, "AccessLevel", { enumerable: true, get: function
|
|
|
30
30
|
var observable_1 = require("../observable");
|
|
31
31
|
Object.defineProperty(exports, "observable", { enumerable: true, get: function () { return observable_1.observable; } });
|
|
32
32
|
var peer_id_1 = require("../peer-id");
|
|
33
|
+
Object.defineProperty(exports, "idFromKey", { enumerable: true, get: function () { return peer_id_1.idFromKey; } });
|
|
33
34
|
Object.defineProperty(exports, "isid", { enumerable: true, get: function () { return peer_id_1.isid; } });
|
|
34
35
|
Object.defineProperty(exports, "newid", { enumerable: true, get: function () { return peer_id_1.newid; } });
|
|
35
36
|
var field_type_1 = require("../types/field-type");
|
package/dist/define/manifest.js
CHANGED
|
@@ -116,9 +116,12 @@ function fieldDetails(schema, shape, input, depth, path, event = false, wholeVal
|
|
|
116
116
|
throw new Error(`Field "${path}" uses .nullable(); an empty field is a missing field in Peers, so use .optional() (a null sent for it is removed)`);
|
|
117
117
|
}
|
|
118
118
|
const details = nullable || (wholeValue && optional) ? { optional: true } : {};
|
|
119
|
+
// An object with no listed fields (`z.object({}).passthrough()`) is opaque: it gets no
|
|
120
|
+
// `fields`, as a `generatedField` copy of it has none.
|
|
119
121
|
if (current._def.typeName === "ZodObject" &&
|
|
120
122
|
type === field_type_1.FieldType.object &&
|
|
121
|
-
depth < isolated_artifact_1.ISOLATED_MAX_FIELD_DEPTH
|
|
123
|
+
depth < isolated_artifact_1.ISOLATED_MAX_FIELD_DEPTH &&
|
|
124
|
+
Object.keys(current.shape).length > 0) {
|
|
122
125
|
details.fields = event
|
|
123
126
|
? eventFields(current, depth + 1, path)
|
|
124
127
|
: irFields(current, input, depth + 1, path);
|
|
@@ -501,7 +504,7 @@ function observableValueShape(name, schema) {
|
|
|
501
504
|
return empty ? { ...shape, optional: true } : shape;
|
|
502
505
|
}
|
|
503
506
|
function tableManifest(table) {
|
|
504
|
-
const { name, description, primaryKeyName, schema, versionNumber, localOnly } = table.definition;
|
|
507
|
+
const { name, description, primaryKeyName, schema, versionNumber, localOnly, indexes } = table.definition;
|
|
505
508
|
return {
|
|
506
509
|
name,
|
|
507
510
|
description,
|
|
@@ -509,6 +512,14 @@ function tableManifest(table) {
|
|
|
509
512
|
fields: describing(`Table ${name}`, () => irFields(schema)),
|
|
510
513
|
...(versionNumber === undefined ? {} : { versionNumber }),
|
|
511
514
|
...(localOnly ? { localOnly: true } : {}),
|
|
515
|
+
...(indexes?.length
|
|
516
|
+
? {
|
|
517
|
+
indexes: indexes.map((index) => ({
|
|
518
|
+
fields: index.fields.map((f) => typeof f === "string" ? f : { name: f.name, order: f.order }),
|
|
519
|
+
...(index.unique ? { unique: true } : {}),
|
|
520
|
+
})),
|
|
521
|
+
}
|
|
522
|
+
: {}),
|
|
512
523
|
};
|
|
513
524
|
}
|
|
514
525
|
/** One contract's manifest entry, with name-only references to its tables. */
|
package/dist/define/serve.d.ts
CHANGED
|
@@ -10,12 +10,19 @@ import type { IPackageDefinition } from "./define-package";
|
|
|
10
10
|
* @param ctx - The worker's context, bound to no call.
|
|
11
11
|
*/
|
|
12
12
|
export declare function startPackage(definition: IPackageDefinition, ctx: IPackageContext): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Run a package's `pkg.onStop` hooks in order. A hook that fails is logged, and the rest still
|
|
15
|
+
* run. `servePackage` runs this when the worker stops; call it yourself only in tests.
|
|
16
|
+
*
|
|
17
|
+
* @param definition - The package's default export from `src/package.ts`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function stopPackage(definition: IPackageDefinition): Promise<void>;
|
|
13
20
|
/**
|
|
14
21
|
* Register every provided tool's handler with the Runtime, serve each provided contract's
|
|
15
22
|
* tables, observables, and events from the worker (guest API 3: every consumer call and
|
|
16
|
-
* outgoing message goes through the package),
|
|
17
|
-
* observables when the worker starts, before any call
|
|
18
|
-
* call; packages don't call this themselves.
|
|
23
|
+
* outgoing message goes through the package), start the package's variables, events, and
|
|
24
|
+
* observables when the worker starts, before any call, and run its `pkg.onStop` hooks when the
|
|
25
|
+
* worker stops. `peers-package build` generates the call; packages don't call this themselves.
|
|
19
26
|
*
|
|
20
27
|
* @param definition - The package's default export from `src/package.ts`.
|
|
21
28
|
*/
|
package/dist/define/serve.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.startPackage = startPackage;
|
|
4
|
+
exports.stopPackage = stopPackage;
|
|
4
5
|
exports.servePackage = servePackage;
|
|
5
6
|
const events_1 = require("./events");
|
|
6
7
|
const observables_1 = require("./observables");
|
|
@@ -30,12 +31,28 @@ async function startPackage(definition, ctx) {
|
|
|
30
31
|
}
|
|
31
32
|
await (0, observables_1.servePackageObservables)(definition.provides, ctx);
|
|
32
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Run a package's `pkg.onStop` hooks in order. A hook that fails is logged, and the rest still
|
|
36
|
+
* run. `servePackage` runs this when the worker stops; call it yourself only in tests.
|
|
37
|
+
*
|
|
38
|
+
* @param definition - The package's default export from `src/package.ts`.
|
|
39
|
+
*/
|
|
40
|
+
async function stopPackage(definition) {
|
|
41
|
+
for (const hook of definition.stopHooks) {
|
|
42
|
+
try {
|
|
43
|
+
await hook();
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
console.error("A pkg.onStop hook failed", error);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
33
50
|
/**
|
|
34
51
|
* Register every provided tool's handler with the Runtime, serve each provided contract's
|
|
35
52
|
* tables, observables, and events from the worker (guest API 3: every consumer call and
|
|
36
|
-
* outgoing message goes through the package),
|
|
37
|
-
* observables when the worker starts, before any call
|
|
38
|
-
* call; packages don't call this themselves.
|
|
53
|
+
* outgoing message goes through the package), start the package's variables, events, and
|
|
54
|
+
* observables when the worker starts, before any call, and run its `pkg.onStop` hooks when the
|
|
55
|
+
* worker stops. `peers-package build` generates the call; packages don't call this themselves.
|
|
39
56
|
*
|
|
40
57
|
* @param definition - The package's default export from `src/package.ts`.
|
|
41
58
|
*/
|
|
@@ -43,12 +60,19 @@ function servePackage(definition) {
|
|
|
43
60
|
const worker = globalThis;
|
|
44
61
|
const register = worker.registerContractTool;
|
|
45
62
|
const registerHandler = worker.registerHandler;
|
|
63
|
+
const onWorkerStop = worker.onWorkerStop;
|
|
64
|
+
const stops = definition.stopHooks.length > 0;
|
|
46
65
|
if (typeof register !== "function" ||
|
|
47
66
|
typeof registerHandler !== "function" ||
|
|
48
|
-
typeof worker.onWorkerStart !== "function"
|
|
67
|
+
typeof worker.onWorkerStart !== "function" ||
|
|
68
|
+
// Only a package with stop hooks needs the worker's stop.
|
|
69
|
+
(stops && typeof onWorkerStop !== "function")) {
|
|
49
70
|
throw new Error("servePackage runs inside an isolated package worker");
|
|
50
71
|
}
|
|
51
72
|
worker.onWorkerStart((ctx) => startPackage(definition, ctx));
|
|
73
|
+
if (stops) {
|
|
74
|
+
onWorkerStop?.(() => stopPackage(definition));
|
|
75
|
+
}
|
|
52
76
|
for (const contract of definition.provides) {
|
|
53
77
|
for (const tool of contract.tools) {
|
|
54
78
|
register(contract.contractId, contract.version, tool.definition.name, (args, ctx) => (0, tool_1.runPackageTool)(tool, args, ctx));
|
|
@@ -1,12 +1,21 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type IsolatedContractTableOperation } from "../package-loader/isolated-runtime";
|
|
2
2
|
import type { IPackageTableHandle } from "./table";
|
|
3
|
+
/**
|
|
4
|
+
* A table call's arguments as the bridge carries them. In a filter, a field set to `undefined`
|
|
5
|
+
* matches an empty field, as `null` does, so it's sent as `null`; anywhere else (options, a
|
|
6
|
+
* record) it's left out.
|
|
7
|
+
*
|
|
8
|
+
* @param operation - The table operation.
|
|
9
|
+
* @param args - Its arguments, as the caller passed them.
|
|
10
|
+
*/
|
|
11
|
+
export declare function bridgeTableArgs(operation: IsolatedContractTableOperation, args: unknown[]): unknown[];
|
|
3
12
|
/** Sends one table operation with its arguments and resolves with the host's reply. */
|
|
4
13
|
export type PackageTableCall = (operation: IsolatedContractTableOperation, args: unknown[]) => Promise<unknown>;
|
|
5
14
|
/**
|
|
6
15
|
* A consumer's table methods over one way of calling the host: a consumed contract's
|
|
7
16
|
* (`News(ctx).tables.Feeds`) or a UI client's (`hostContract`). Replies are normalized the same
|
|
8
|
-
* way for both: a missing row is `undefined`, a list is an array.
|
|
9
|
-
*
|
|
17
|
+
* way for both: a missing row is `undefined`, a list is an array. Arguments are copied for the
|
|
18
|
+
* bridge ({@link bridgeTableArgs}); the providing package's table parses a `save`.
|
|
10
19
|
*
|
|
11
20
|
* @param call - Sends an operation to the table.
|
|
12
21
|
*/
|
|
@@ -1,36 +1,54 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.bridgeTableArgs = bridgeTableArgs;
|
|
3
4
|
exports.packageTableMethods = packageTableMethods;
|
|
5
|
+
const isolated_runtime_1 = require("../package-loader/isolated-runtime");
|
|
4
6
|
const wire_value_1 = require("./wire-value");
|
|
7
|
+
/** Operations whose first argument is a filter. */
|
|
8
|
+
const FILTER_OPERATIONS = new Set(["list", "count", "findOne"]);
|
|
9
|
+
/**
|
|
10
|
+
* A table call's arguments as the bridge carries them. In a filter, a field set to `undefined`
|
|
11
|
+
* matches an empty field, as `null` does, so it's sent as `null`; anywhere else (options, a
|
|
12
|
+
* record) it's left out.
|
|
13
|
+
*
|
|
14
|
+
* @param operation - The table operation.
|
|
15
|
+
* @param args - Its arguments, as the caller passed them.
|
|
16
|
+
*/
|
|
17
|
+
function bridgeTableArgs(operation, args) {
|
|
18
|
+
return args.map((arg, index) => (0, wire_value_1.snapshotPackageValue)(arg, {
|
|
19
|
+
undefinedFieldsAsNull: index === 0 && FILTER_OPERATIONS.has(operation),
|
|
20
|
+
}));
|
|
21
|
+
}
|
|
5
22
|
/**
|
|
6
23
|
* A consumer's table methods over one way of calling the host: a consumed contract's
|
|
7
24
|
* (`News(ctx).tables.Feeds`) or a UI client's (`hostContract`). Replies are normalized the same
|
|
8
|
-
* way for both: a missing row is `undefined`, a list is an array.
|
|
9
|
-
*
|
|
25
|
+
* way for both: a missing row is `undefined`, a list is an array. Arguments are copied for the
|
|
26
|
+
* bridge ({@link bridgeTableArgs}); the providing package's table parses a `save`.
|
|
10
27
|
*
|
|
11
28
|
* @param call - Sends an operation to the table.
|
|
12
29
|
*/
|
|
13
30
|
function packageTableMethods(call) {
|
|
31
|
+
const send = (operation, args) => call(operation, bridgeTableArgs(operation, args));
|
|
14
32
|
return {
|
|
15
33
|
async get(recordId) {
|
|
16
|
-
return ((await
|
|
34
|
+
return ((await send("get", [recordId])) ?? undefined);
|
|
17
35
|
},
|
|
18
36
|
async list(filter = {}, options = {}) {
|
|
19
|
-
const rows = await
|
|
37
|
+
const rows = await send("list", [filter, options]);
|
|
20
38
|
return (Array.isArray(rows) ? rows : []);
|
|
21
39
|
},
|
|
22
40
|
async count(filter = {}) {
|
|
23
|
-
return Number(await
|
|
41
|
+
return Number(await send("count", [filter]));
|
|
24
42
|
},
|
|
25
43
|
async findOne(filter = {}) {
|
|
26
|
-
return ((await
|
|
44
|
+
return ((await send("findOne", [filter])) ?? undefined);
|
|
27
45
|
},
|
|
28
|
-
async save(record) {
|
|
29
|
-
|
|
30
|
-
return (await
|
|
46
|
+
async save(record, options) {
|
|
47
|
+
const allowed = (0, isolated_runtime_1.isolatedSaveOptions)(options);
|
|
48
|
+
return (await send("save", allowed ? [record, allowed] : [record]));
|
|
31
49
|
},
|
|
32
50
|
async delete(recordId) {
|
|
33
|
-
await
|
|
51
|
+
await send("delete", [recordId]);
|
|
34
52
|
},
|
|
35
53
|
};
|
|
36
54
|
}
|