@peers-app/peers-sdk 0.28.1 → 0.28.3
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/user-context-singleton.js +2 -0
- package/dist/contracts/contract-proxy.js +1 -0
- package/dist/contracts/host-contract-client.js +4 -2
- package/dist/data/group-members.js +2 -1
- package/dist/data/group-resign.js +4 -1
- package/dist/data/groups.js +2 -1
- package/dist/data/orm/client-proxy.data-source.d.ts +1 -0
- package/dist/data/orm/client-proxy.data-source.js +3 -0
- package/dist/data/orm/data-query.d.ts +20 -0
- package/dist/data/orm/data-query.js +5 -0
- package/dist/data/orm/decorators.d.ts +2 -0
- package/dist/data/orm/decorators.js +11 -0
- package/dist/data/orm/write-checks.d.ts +6 -4
- package/dist/data/orm/write-checks.js +12 -5
- package/dist/data/package-versions.js +6 -1
- package/dist/data/packages.js +2 -1
- package/dist/data/table-definitions-table.d.ts +3 -3
- package/dist/data/tool-access-approvals.d.ts +3 -3
- 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 +1 -0
- package/dist/define/manifest.js +4 -1
- 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 +25 -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-runtime.d.ts +28 -0
- package/dist/package-loader/isolated-runtime.js +15 -0
- package/dist/package-loader/package-loader.d.ts +9 -0
- package/dist/package-loader/package-loader.js +30 -0
- package/dist/testing/mock-host-contract.js +2 -0
- package/package.json +1 -1
|
@@ -9,6 +9,7 @@ exports.getTableContainer = getTableContainer;
|
|
|
9
9
|
exports.setDefaultClientUserContext = setDefaultClientUserContext;
|
|
10
10
|
exports.getAllTables = getAllTables;
|
|
11
11
|
const client_proxy_data_source_1 = require("../data/orm/client-proxy.data-source");
|
|
12
|
+
const decorators_1 = require("../data/orm/decorators");
|
|
12
13
|
const rpc_types_1 = require("../rpc-types");
|
|
13
14
|
const utils_1 = require("../utils");
|
|
14
15
|
const user_context_1 = require("./user-context");
|
|
@@ -170,6 +171,7 @@ if (!rpc_types_1.isClient) {
|
|
|
170
171
|
if (typeof method !== "function") {
|
|
171
172
|
throw new Error(`Method ${methodName} not found on table ${tableName}`);
|
|
172
173
|
}
|
|
174
|
+
(0, decorators_1.checkRemoteTableSaveOptions)(methodName, args);
|
|
173
175
|
// @ts-expect-error - call this way to ensure 'this' is correct
|
|
174
176
|
return table[methodName](...args);
|
|
175
177
|
};
|
|
@@ -584,6 +584,7 @@ function createContractProvider(resolution, opts = {}) {
|
|
|
584
584
|
!(0, decorators_1.isContractRemoteTableMethod)(method)) {
|
|
585
585
|
throw new Error(`Table method '${call.name}.${call.operation}' is not remotely callable`);
|
|
586
586
|
}
|
|
587
|
+
(0, decorators_1.checkRemoteTableSaveOptions)(call.operation, call.args);
|
|
587
588
|
return impl.apply(table, call.args);
|
|
588
589
|
}
|
|
589
590
|
case "tool": {
|
|
@@ -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 &&
|
|
@@ -131,9 +131,10 @@ let GroupMembersTable = (() => {
|
|
|
131
131
|
...(await (0, write_checks_1.signingStamp)(this, groupMember, current)),
|
|
132
132
|
});
|
|
133
133
|
return this.save(groupMember, {
|
|
134
|
-
saveAsSnapshot: true,
|
|
135
134
|
restoreIfDeleted: true,
|
|
136
135
|
...(opts || {}),
|
|
136
|
+
saveAsSnapshot: true,
|
|
137
|
+
writtenAt: groupMember.signedAt,
|
|
137
138
|
});
|
|
138
139
|
}
|
|
139
140
|
async delete(groupMemberId) {
|
|
@@ -39,7 +39,10 @@ async function resignGroupAuthority(dataContext, opts = {}) {
|
|
|
39
39
|
try {
|
|
40
40
|
if (opts.secretKey) {
|
|
41
41
|
const stamp = await (0, write_checks_1.signingStamp)(table, row, row);
|
|
42
|
-
await table.save((0, keys_1.addSignatureToObject)({ ...row, ...stamp }, opts.secretKey),
|
|
42
|
+
await table.save((0, keys_1.addSignatureToObject)({ ...row, ...stamp }, opts.secretKey), {
|
|
43
|
+
...snapshot,
|
|
44
|
+
writtenAt: stamp.signedAt,
|
|
45
|
+
});
|
|
43
46
|
}
|
|
44
47
|
else {
|
|
45
48
|
await table.signAndSave(prepare(row), snapshot);
|
package/dist/data/groups.js
CHANGED
|
@@ -116,7 +116,8 @@ let GroupsTable = (() => {
|
|
|
116
116
|
...group,
|
|
117
117
|
...(await (0, write_checks_1.signingStamp)(this, group)),
|
|
118
118
|
});
|
|
119
|
-
|
|
119
|
+
// Field patches from concurrent writes could combine into a row nobody signed.
|
|
120
|
+
return this.save(group, { ...opts, saveAsSnapshot: true, writtenAt: group.signedAt });
|
|
120
121
|
}
|
|
121
122
|
/**
|
|
122
123
|
* Re-sign this group's authority rows (group row, memberships, packages, package versions)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { z } from "zod";
|
|
2
2
|
import { type DataFilter, type ICursorIterable, type IDataQueryParams, type IDataSource, type ISaveOptions } from "./data-query";
|
|
3
3
|
import { type ITableMetaData } from "./types";
|
|
4
|
+
/** A renderer's table data source, forwarding operations to the host over RPC. */
|
|
4
5
|
export declare class ClientProxyDataSource<T extends {
|
|
5
6
|
[key: string]: any;
|
|
6
7
|
}> implements IDataSource<T> {
|
|
@@ -4,7 +4,9 @@ exports.ClientProxyDataSource = void 0;
|
|
|
4
4
|
const rpc_types_1 = require("../../rpc-types");
|
|
5
5
|
const utils_1 = require("../../utils");
|
|
6
6
|
const data_query_1 = require("./data-query");
|
|
7
|
+
const decorators_1 = require("./decorators");
|
|
7
8
|
const types_1 = require("./types");
|
|
9
|
+
/** A renderer's table data source, forwarding operations to the host over RPC. */
|
|
8
10
|
class ClientProxyDataSource {
|
|
9
11
|
metaData;
|
|
10
12
|
schema;
|
|
@@ -38,6 +40,7 @@ class ClientProxyDataSource {
|
|
|
38
40
|
return rpc_types_1.rpcServerCalls.tableMethodCall(this.dataContextId, this.tableName, "findOne", filter, opts);
|
|
39
41
|
}
|
|
40
42
|
async save(record, opts) {
|
|
43
|
+
(0, decorators_1.checkRemoteTableSaveOptions)("save", [record, opts]);
|
|
41
44
|
return rpc_types_1.rpcServerCalls.tableMethodCall(this.dataContextId, this.tableName, "save", record, opts);
|
|
42
45
|
}
|
|
43
46
|
async insert(record) {
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
/** Options controlling how a host table save is recorded. */
|
|
1
2
|
export interface ISaveOptions {
|
|
2
3
|
restoreIfDeleted?: boolean;
|
|
4
|
+
/** @internal Host-only change time for signed rows; must match their signedAt. */
|
|
5
|
+
writtenAt?: number;
|
|
3
6
|
saveAsSnapshot?: boolean;
|
|
4
7
|
weakInsert?: boolean;
|
|
5
8
|
insertOnly?: boolean;
|
|
6
9
|
}
|
|
10
|
+
/** Persistence operations for one table. */
|
|
7
11
|
export interface IDataSource<T extends {
|
|
8
12
|
[key: string]: any;
|
|
9
13
|
}> {
|
|
@@ -18,6 +22,7 @@ export interface IDataSource<T extends {
|
|
|
18
22
|
update: (record: T) => Promise<T>;
|
|
19
23
|
delete: (data: string | T) => Promise<void>;
|
|
20
24
|
}
|
|
25
|
+
/** Table operations needed to execute a data query. */
|
|
21
26
|
export interface ITable<T extends Record<string, any>> {
|
|
22
27
|
tableName: string;
|
|
23
28
|
metaData: {
|
|
@@ -34,6 +39,7 @@ export interface ITable<T extends Record<string, any>> {
|
|
|
34
39
|
textSearch?: string;
|
|
35
40
|
}) => Promise<T[]>;
|
|
36
41
|
}
|
|
42
|
+
/** Filtering, ordering and pagination for table reads. */
|
|
37
43
|
export interface IDataQueryParams<T extends Record<string, any>> {
|
|
38
44
|
page?: number;
|
|
39
45
|
pageSize?: number;
|
|
@@ -43,6 +49,7 @@ export interface IDataQueryParams<T extends Record<string, any>> {
|
|
|
43
49
|
textSearchExact?: boolean;
|
|
44
50
|
skipPast?: T;
|
|
45
51
|
}
|
|
52
|
+
/** A table query that can be listed or consumed through a cursor. */
|
|
46
53
|
export declare class DataQuery<T extends Record<string, any>> {
|
|
47
54
|
readonly table: ITable<T>;
|
|
48
55
|
constructor(table: ITable<T>, { page, pageSize, sortBy, filter, textSearch, textSearchExact }?: IDataQueryParams<T>);
|
|
@@ -55,18 +62,23 @@ export declare class DataQuery<T extends Record<string, any>> {
|
|
|
55
62
|
cursor(): ICursorIterable<T>;
|
|
56
63
|
list(): Promise<T[]>;
|
|
57
64
|
}
|
|
65
|
+
/** A cursor positioned at a row, with optional seek support. */
|
|
58
66
|
export interface ICursor<T> {
|
|
59
67
|
value: T | null;
|
|
60
68
|
next: (skipPast?: T) => Promise<T | null>;
|
|
61
69
|
}
|
|
70
|
+
/** A cursor that also supports asynchronous iteration. */
|
|
62
71
|
export type ICursorIterable<T> = ICursor<T> & AsyncIterable<T>;
|
|
72
|
+
/** Adds asynchronous iteration to a cursor. */
|
|
63
73
|
export declare function iterableCursor<T>(cursor: ICursor<T>): ICursorIterable<T>;
|
|
74
|
+
/** Reads a data source in pages through an asynchronous cursor. */
|
|
64
75
|
export declare function dataSourceCursor<T extends {
|
|
65
76
|
[key: string]: any;
|
|
66
77
|
}>(dataSource: {
|
|
67
78
|
list: (filter?: DataFilter<T>, opts?: IDataQueryParams<T>) => Promise<T[]>;
|
|
68
79
|
primaryKeyName: string;
|
|
69
80
|
}, filter?: DataFilter<T>, opts?: IDataQueryParams<T>): ICursorIterable<T>;
|
|
81
|
+
/** Counts matching rows, falling back to a cursor when native count is unavailable. */
|
|
70
82
|
export declare function dataSourceCount<T extends {
|
|
71
83
|
[key: string]: any;
|
|
72
84
|
}>(dataSource: {
|
|
@@ -74,11 +86,15 @@ export declare function dataSourceCount<T extends {
|
|
|
74
86
|
list: (filter?: DataFilter<T>, opts?: IDataQueryParams<T>) => Promise<T[]>;
|
|
75
87
|
primaryKeyName: string;
|
|
76
88
|
}, filter?: DataFilter<T>): Promise<number>;
|
|
89
|
+
/** Ordered fields; a leading minus selects descending order. */
|
|
77
90
|
export type SortBy<T extends {
|
|
78
91
|
[key: string]: any;
|
|
79
92
|
}> = (keyof T extends string | number ? keyof T | `-${keyof T}` : never)[];
|
|
93
|
+
/** Scalar values accepted by data filters. */
|
|
80
94
|
export type DataFieldScalar = boolean | number | string | Date | null;
|
|
95
|
+
/** Comparison operators accepted in a field filter. */
|
|
81
96
|
export type DataFilterValueOperator = "$eq" | "$ne" | "$gt" | "$gte" | "$lt" | "$lte" | "$exists" | "$matchWords" | "$substr" | "$expr" | "$in";
|
|
97
|
+
/** A field value or comparison used in a data filter. */
|
|
82
98
|
export type DataFilterValue = DataFieldScalar | DataFieldScalar[] | {
|
|
83
99
|
[key in Exclude<DataFilterValueOperator, "$in">]?: DataFieldScalar;
|
|
84
100
|
} | {
|
|
@@ -86,13 +102,17 @@ export type DataFilterValue = DataFieldScalar | DataFieldScalar[] | {
|
|
|
86
102
|
} | {
|
|
87
103
|
$in: DataFieldScalar[];
|
|
88
104
|
};
|
|
105
|
+
/** Field predicates combined with AND. */
|
|
89
106
|
export type DataFilterAnd<T> = {
|
|
90
107
|
[key in keyof T]?: DataFilterValue;
|
|
91
108
|
};
|
|
109
|
+
/** Alternative groups of field predicates combined with OR. */
|
|
92
110
|
export type DataFilterOr<T> = DataFilterAnd<T>[] | {
|
|
93
111
|
$or: DataFilterAnd<T>[];
|
|
94
112
|
};
|
|
113
|
+
/** A table filter composed of field predicates and logical groups. */
|
|
95
114
|
export type DataFilter<T> = DataFilterAnd<T> | DataFilterOr<T> | {
|
|
96
115
|
$and: DataFilter<T>[];
|
|
97
116
|
};
|
|
117
|
+
/** Adds predicates to resume an ordered query after the supplied row. */
|
|
98
118
|
export declare function filterToSkipPastValue(filter: DataFilter<unknown>, sortBy: string[], skipPast: any): DataFilter<unknown>;
|
|
@@ -5,6 +5,7 @@ exports.iterableCursor = iterableCursor;
|
|
|
5
5
|
exports.dataSourceCursor = dataSourceCursor;
|
|
6
6
|
exports.dataSourceCount = dataSourceCount;
|
|
7
7
|
exports.filterToSkipPastValue = filterToSkipPastValue;
|
|
8
|
+
/** A table query that can be listed or consumed through a cursor. */
|
|
8
9
|
class DataQuery {
|
|
9
10
|
table;
|
|
10
11
|
constructor(table, { page, pageSize, sortBy, filter, textSearch, textSearchExact } = {}) {
|
|
@@ -85,6 +86,7 @@ class DataQuery {
|
|
|
85
86
|
}
|
|
86
87
|
}
|
|
87
88
|
exports.DataQuery = DataQuery;
|
|
89
|
+
/** Adds asynchronous iteration to a cursor. */
|
|
88
90
|
function iterableCursor(cursor) {
|
|
89
91
|
// @ts-expect-error - we're explicitly extending the cursor object to have this symbol
|
|
90
92
|
cursor[Symbol.asyncIterator] = () => {
|
|
@@ -97,6 +99,7 @@ function iterableCursor(cursor) {
|
|
|
97
99
|
};
|
|
98
100
|
return cursor;
|
|
99
101
|
}
|
|
102
|
+
/** Reads a data source in pages through an asynchronous cursor. */
|
|
100
103
|
function dataSourceCursor(dataSource, filter = {}, opts = {}) {
|
|
101
104
|
// if (dataSource.cursor) {
|
|
102
105
|
// return dataSource.cursor(filter, opts);
|
|
@@ -157,6 +160,7 @@ function dataSourceCursor(dataSource, filter = {}, opts = {}) {
|
|
|
157
160
|
};
|
|
158
161
|
return iterableCursor(cursor);
|
|
159
162
|
}
|
|
163
|
+
/** Counts matching rows, falling back to a cursor when native count is unavailable. */
|
|
160
164
|
async function dataSourceCount(dataSource, filter = {}) {
|
|
161
165
|
if (dataSource.count) {
|
|
162
166
|
return dataSource.count(filter);
|
|
@@ -169,6 +173,7 @@ async function dataSourceCount(dataSource, filter = {}) {
|
|
|
169
173
|
}
|
|
170
174
|
return count;
|
|
171
175
|
}
|
|
176
|
+
/** Adds predicates to resume an ordered query after the supplied row. */
|
|
172
177
|
function filterToSkipPastValue(filter, sortBy, skipPast) {
|
|
173
178
|
const sortByFilters = [];
|
|
174
179
|
for (let i = 0; i < sortBy.length; i++) {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** Refuse host-only signed write times supplied through a table RPC or contract call. */
|
|
2
|
+
export declare function checkRemoteTableSaveOptions(methodName: string, args: readonly unknown[]): void;
|
|
1
3
|
/**
|
|
2
4
|
* Return whether a table method was explicitly marked for remote invocation by
|
|
3
5
|
* {@link ProxyClientTableMethodCalls}. Client proxies are recognized through their
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkRemoteTableSaveOptions = checkRemoteTableSaveOptions;
|
|
3
4
|
exports.isContractRemoteTableMethod = isContractRemoteTableMethod;
|
|
4
5
|
exports.ProxyClientTableMethodCalls = ProxyClientTableMethodCalls;
|
|
5
6
|
const rpc_types_1 = require("../../rpc-types");
|
|
6
7
|
const contractRemoteTableMethod = Symbol("contractRemoteTableMethod");
|
|
8
|
+
/** Refuse host-only signed write times supplied through a table RPC or contract call. */
|
|
9
|
+
function checkRemoteTableSaveOptions(methodName, args) {
|
|
10
|
+
if (!["save", "insert", "update", "signAndSave"].includes(methodName))
|
|
11
|
+
return;
|
|
12
|
+
const options = args[1];
|
|
13
|
+
if (options && typeof options === "object" && "writtenAt" in options) {
|
|
14
|
+
throw new Error("writtenAt is host-only; use signAndSave to sign a new change");
|
|
15
|
+
}
|
|
16
|
+
}
|
|
7
17
|
function markContractRemoteTableMethod(method) {
|
|
8
18
|
Object.defineProperty(method, contractRemoteTableMethod, { value: true });
|
|
9
19
|
}
|
|
@@ -37,6 +47,7 @@ function ProxyClientTableMethodCalls() {
|
|
|
37
47
|
const methodName = context.name;
|
|
38
48
|
// WARNING: Using typescript's magical `this` param
|
|
39
49
|
const proxyFn = function (...args) {
|
|
50
|
+
checkRemoteTableSaveOptions(methodName, args);
|
|
40
51
|
const dataContextId = this.dataSource.dataContextId || this.groupId || "";
|
|
41
52
|
return rpc_types_1.rpcServerCalls.tableMethodCall(dataContextId, this.tableName, methodName, ...args);
|
|
42
53
|
};
|
|
@@ -57,10 +57,12 @@ export declare function checkLocalSave<T extends {
|
|
|
57
57
|
*/
|
|
58
58
|
export declare const packageApprovalTables: ReadonlySet<string>;
|
|
59
59
|
/**
|
|
60
|
-
* What a table's `signAndSave` (and a re-sign) puts under the signature: `signedAt`,
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
60
|
+
* What a table's `signAndSave` (and a re-sign) puts under the signature: `signedAt`, a tick of
|
|
61
|
+
* the context's write clock (wall time without a tracked source) or just after the row on record
|
|
62
|
+
* when that was signed by a clock running ahead, so the new signature counts as the later one
|
|
63
|
+
* (and, for a package table, after the deleted row's retained stamp); for a package table also
|
|
64
|
+
* the context the approval is for. The save then records the change at this same time
|
|
65
|
+
* (`writtenAt`), so sync and the signed order agree on which concurrent write is later.
|
|
64
66
|
*
|
|
65
67
|
* @param table - The table being signed for.
|
|
66
68
|
* @param row - The row being signed.
|
|
@@ -56,10 +56,12 @@ async function checkLocalSave(table, row, check) {
|
|
|
56
56
|
*/
|
|
57
57
|
exports.packageApprovalTables = new Set(["Packages", "PackageVersions"]);
|
|
58
58
|
/**
|
|
59
|
-
* What a table's `signAndSave` (and a re-sign) puts under the signature: `signedAt`,
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
59
|
+
* What a table's `signAndSave` (and a re-sign) puts under the signature: `signedAt`, a tick of
|
|
60
|
+
* the context's write clock (wall time without a tracked source) or just after the row on record
|
|
61
|
+
* when that was signed by a clock running ahead, so the new signature counts as the later one
|
|
62
|
+
* (and, for a package table, after the deleted row's retained stamp); for a package table also
|
|
63
|
+
* the context the approval is for. The save then records the change at this same time
|
|
64
|
+
* (`writtenAt`), so sync and the signed order agree on which concurrent write is later.
|
|
63
65
|
*
|
|
64
66
|
* @param table - The table being signed for.
|
|
65
67
|
* @param row - The row being signed.
|
|
@@ -76,8 +78,13 @@ async function signingStamp(table, row, current) {
|
|
|
76
78
|
const dataContext = (await getUserContext()).getDataContext(groupId);
|
|
77
79
|
floor = await deletedSignedAt(tableName, { dataContext, recordId });
|
|
78
80
|
}
|
|
81
|
+
// Duck typing also works when the data source comes from another installed SDK copy.
|
|
82
|
+
let source = table.dataSource;
|
|
83
|
+
while (source && !source.getWriteClock)
|
|
84
|
+
source = source.dataSource;
|
|
85
|
+
const clock = await source?.getWriteClock?.();
|
|
79
86
|
return {
|
|
80
|
-
signedAt: Math.max(Date.now(), floor + 1),
|
|
87
|
+
signedAt: Math.max(clock ? Math.ceil(clock.tick()) : Date.now(), floor + 1),
|
|
81
88
|
...(packageTable ? { signedContextId: groupId } : {}),
|
|
82
89
|
};
|
|
83
90
|
}
|
|
@@ -137,7 +137,12 @@ let PackageVersionsTable = (() => {
|
|
|
137
137
|
...packageVersion,
|
|
138
138
|
...(await (0, write_checks_1.signingStamp)(this, packageVersion)),
|
|
139
139
|
});
|
|
140
|
-
|
|
140
|
+
// Field patches from concurrent writes could combine into a row nobody signed.
|
|
141
|
+
return this.save(packageVersion, {
|
|
142
|
+
...opts,
|
|
143
|
+
saveAsSnapshot: true,
|
|
144
|
+
writtenAt: packageVersion.signedAt,
|
|
145
|
+
});
|
|
141
146
|
}
|
|
142
147
|
/** Registers the signer for all SDK copies in this JavaScript realm. Replaces any prior signer. */
|
|
143
148
|
static enablePackageVersionSigning(fn) {
|
package/dist/data/packages.js
CHANGED
|
@@ -127,7 +127,8 @@ let PackagesTable = (() => {
|
|
|
127
127
|
...packageObj,
|
|
128
128
|
...(await (0, write_checks_1.signingStamp)(this, packageObj)),
|
|
129
129
|
});
|
|
130
|
-
|
|
130
|
+
// Field patches from concurrent writes could combine into a row nobody signed.
|
|
131
|
+
return this.save(packageObj, { ...opts, saveAsSnapshot: true, writtenAt: packageObj.signedAt });
|
|
131
132
|
}
|
|
132
133
|
/** Registers the signer for all SDK copies in this JavaScript realm. Replaces any prior signer. */
|
|
133
134
|
static enablePackageSigning(fn) {
|
|
@@ -9,18 +9,18 @@ export declare const tableDefinitionRecordSchema: z.ZodObject<{
|
|
|
9
9
|
deleted: z.ZodOptional<z.ZodDate>;
|
|
10
10
|
}, "strip", z.ZodTypeAny, {
|
|
11
11
|
name: string;
|
|
12
|
+
tableId: string;
|
|
12
13
|
metaData: {} & {
|
|
13
14
|
[k: string]: any;
|
|
14
15
|
};
|
|
15
|
-
tableId: string;
|
|
16
16
|
versionNumber: number;
|
|
17
17
|
deleted?: Date | undefined;
|
|
18
18
|
}, {
|
|
19
19
|
name: string;
|
|
20
|
+
tableId: string;
|
|
20
21
|
metaData: {} & {
|
|
21
22
|
[k: string]: any;
|
|
22
23
|
};
|
|
23
|
-
tableId: string;
|
|
24
24
|
deleted?: Date | undefined;
|
|
25
25
|
versionNumber?: number | undefined;
|
|
26
26
|
}>;
|
|
@@ -36,10 +36,10 @@ export type ITableDefinitionRecord = z.infer<typeof tableDefinitionRecordSchema>
|
|
|
36
36
|
*/
|
|
37
37
|
export declare function TableDefinitions(dataContext?: DataContext): import("..").Table<{
|
|
38
38
|
name: string;
|
|
39
|
+
tableId: string;
|
|
39
40
|
metaData: {} & {
|
|
40
41
|
[k: string]: any;
|
|
41
42
|
};
|
|
42
|
-
tableId: string;
|
|
43
43
|
versionNumber: number;
|
|
44
44
|
deleted?: Date | undefined;
|
|
45
45
|
}>;
|
|
@@ -46,9 +46,9 @@ export declare const toolAccessApprovalSchema: z.ZodObject<{
|
|
|
46
46
|
}, "strip", z.ZodTypeAny, {
|
|
47
47
|
nonce: string;
|
|
48
48
|
status: ToolAccessApprovalStatus;
|
|
49
|
-
userId: string;
|
|
50
49
|
toolName: string;
|
|
51
50
|
toolId: string;
|
|
51
|
+
userId: string;
|
|
52
52
|
scope: ToolAccessApprovalScope;
|
|
53
53
|
createdAt: Date;
|
|
54
54
|
dataContextId: string;
|
|
@@ -69,9 +69,9 @@ export declare const toolAccessApprovalSchema: z.ZodObject<{
|
|
|
69
69
|
resultSummary?: string | undefined;
|
|
70
70
|
}, {
|
|
71
71
|
nonce: string;
|
|
72
|
-
userId: string;
|
|
73
72
|
toolName: string;
|
|
74
73
|
toolId: string;
|
|
74
|
+
userId: string;
|
|
75
75
|
dataContextId: string;
|
|
76
76
|
expiresAt: Date;
|
|
77
77
|
toolAccessApprovalId: string;
|
|
@@ -102,9 +102,9 @@ export type IToolAccessApproval = z.infer<typeof toolAccessApprovalSchema>;
|
|
|
102
102
|
export declare function ToolAccessApprovals(dataContext?: DataContext): import("..").Table<{
|
|
103
103
|
nonce: string;
|
|
104
104
|
status: ToolAccessApprovalStatus;
|
|
105
|
-
userId: string;
|
|
106
105
|
toolName: string;
|
|
107
106
|
toolId: string;
|
|
107
|
+
userId: string;
|
|
108
108
|
scope: ToolAccessApprovalScope;
|
|
109
109
|
createdAt: Date;
|
|
110
110
|
dataContextId: string;
|
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,6 +11,7 @@ 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 type { IPackageSaveOptions } from "../package-loader/isolated-runtime";
|
|
14
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";
|
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);
|
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
|
}
|
package/dist/define/table.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { z } from "zod";
|
|
2
2
|
import type { DataFilter, IDataQueryParams } from "../data/orm/data-query";
|
|
3
3
|
import type { IDataChangedEvent } from "../data/orm/subscribable.data-source";
|
|
4
|
-
import type
|
|
4
|
+
import { type IPackageSaveOptions } from "../package-loader/isolated-runtime";
|
|
5
|
+
import type { IPackageContext, PackageUnsubscribe } from "./context";
|
|
5
6
|
/** What {@link defineTable} takes. */
|
|
6
7
|
export interface IPackageTableDefinition<S extends z.AnyZodObject = z.AnyZodObject> {
|
|
7
8
|
/** Table name, unique in the package. Other packages reach it only through a contract. */
|
|
@@ -53,8 +54,10 @@ export interface IPackageTableHandle<T extends Record<string, any>, TInput = T>
|
|
|
53
54
|
/**
|
|
54
55
|
* Insert or replace a row. It reaches the providing package's table, which parses it with the
|
|
55
56
|
* schema first (so defaults and transforms apply), through any override its class makes.
|
|
57
|
+
* Pass `{ restoreIfDeleted: true }` to save a row whose id was deleted before; without it,
|
|
58
|
+
* that save is refused.
|
|
56
59
|
*/
|
|
57
|
-
save(record: TInput): Promise<T>;
|
|
60
|
+
save(record: TInput, options?: IPackageSaveOptions): Promise<T>;
|
|
58
61
|
/** Delete a row by id. */
|
|
59
62
|
delete(recordId: string): Promise<void>;
|
|
60
63
|
}
|
|
@@ -103,10 +106,29 @@ export declare class PackageTable<S extends z.AnyZodObject = z.AnyZodObject> imp
|
|
|
103
106
|
* Insert or replace a row, parsed with the schema first so defaults and transforms apply. An
|
|
104
107
|
* optional field that is `null` or `undefined` is left out, so it's stored unset; a field with
|
|
105
108
|
* a default gets the default. Resolves to the row as stored.
|
|
109
|
+
*
|
|
110
|
+
* @param record - The row.
|
|
111
|
+
* @param options - `{ restoreIfDeleted: true }` saves a row whose id was deleted before;
|
|
112
|
+
* without it, that save is refused. An override that calls `super.save` passes it on.
|
|
106
113
|
*/
|
|
107
|
-
save(record: z.input<S
|
|
114
|
+
save(record: z.input<S>, options?: IPackageSaveOptions): Promise<z.infer<S>>;
|
|
108
115
|
/** Delete a row by id. */
|
|
109
116
|
delete(recordId: string): Promise<void>;
|
|
117
|
+
/**
|
|
118
|
+
* Call `handler` with each change to this table: inserts, updates, and deletes, made by this
|
|
119
|
+
* package, by a consumer, or synced from another device. Changes arrive in order, one at a
|
|
120
|
+
* time; the package's own writes from `handler` arrive too. A handler gets a tool call's
|
|
121
|
+
* deadline; past it, later changes stop waiting for it. Subscribe in `pkg.onStart`: it lasts
|
|
122
|
+
* until the returned function is called or the worker stops, and nothing is replayed from
|
|
123
|
+
* while the worker was stopped, so subscribe first, then read what's already there.
|
|
124
|
+
*
|
|
125
|
+
* Only the package itself can subscribe this way; consumers subscribe to `dataChanged`
|
|
126
|
+
* through a contract.
|
|
127
|
+
*
|
|
128
|
+
* @param handler - Gets each change, with the row as `list` returns it.
|
|
129
|
+
* @returns Stops the subscription.
|
|
130
|
+
*/
|
|
131
|
+
subscribe(handler: (event: IDataChangedEvent<z.infer<S>>) => unknown): Promise<PackageUnsubscribe>;
|
|
110
132
|
/**
|
|
111
133
|
* One change on its way to one subscriber of the table's `dataChanged`, with `this.ctx`
|
|
112
134
|
* carrying that subscriber. Return it to send it, a changed copy to send that instead, or
|
package/dist/define/table.js
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.PackageTable = void 0;
|
|
4
4
|
exports.defineTable = defineTable;
|
|
5
|
+
const isolated_runtime_1 = require("../package-loader/isolated-runtime");
|
|
5
6
|
const empty_fields_1 = require("./empty-fields");
|
|
6
|
-
const
|
|
7
|
+
const table_handle_1 = require("./table-handle");
|
|
7
8
|
/**
|
|
8
9
|
* One of the package's tables, for one call: what `Feeds(ctx)` returns, and what answers a
|
|
9
10
|
* consumer's call through a contract (from a UI or another package). It runs in the provider's
|
|
@@ -40,7 +41,7 @@ class PackageTable {
|
|
|
40
41
|
}
|
|
41
42
|
/** Send one operation to storage and clean rows before provider code receives them. */
|
|
42
43
|
async store(operation, args) {
|
|
43
|
-
const result = await this.ctx.callOwnedTable(this.definition.name, operation, args);
|
|
44
|
+
const result = await this.ctx.callOwnedTable(this.definition.name, operation, (0, table_handle_1.bridgeTableArgs)(operation, args));
|
|
44
45
|
const { schema } = this.definition;
|
|
45
46
|
if (operation === "list" && Array.isArray(result)) {
|
|
46
47
|
return result.map((row) => (0, empty_fields_1.withoutEmptySchemaFields)(schema, row));
|
|
@@ -71,16 +72,42 @@ class PackageTable {
|
|
|
71
72
|
* Insert or replace a row, parsed with the schema first so defaults and transforms apply. An
|
|
72
73
|
* optional field that is `null` or `undefined` is left out, so it's stored unset; a field with
|
|
73
74
|
* a default gets the default. Resolves to the row as stored.
|
|
75
|
+
*
|
|
76
|
+
* @param record - The row.
|
|
77
|
+
* @param options - `{ restoreIfDeleted: true }` saves a row whose id was deleted before;
|
|
78
|
+
* without it, that save is refused. An override that calls `super.save` passes it on.
|
|
74
79
|
*/
|
|
75
|
-
async save(record) {
|
|
80
|
+
async save(record, options) {
|
|
76
81
|
const { schema } = this.definition;
|
|
77
|
-
const row =
|
|
78
|
-
|
|
82
|
+
const row = schema.parse((0, empty_fields_1.withoutEmptySchemaFields)(schema, record));
|
|
83
|
+
const allowed = (0, isolated_runtime_1.isolatedSaveOptions)(options);
|
|
84
|
+
return (await this.store("save", allowed ? [row, allowed] : [row]));
|
|
79
85
|
}
|
|
80
86
|
/** Delete a row by id. */
|
|
81
87
|
async delete(recordId) {
|
|
82
88
|
await this.store("delete", [recordId]);
|
|
83
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Call `handler` with each change to this table: inserts, updates, and deletes, made by this
|
|
92
|
+
* package, by a consumer, or synced from another device. Changes arrive in order, one at a
|
|
93
|
+
* time; the package's own writes from `handler` arrive too. A handler gets a tool call's
|
|
94
|
+
* deadline; past it, later changes stop waiting for it. Subscribe in `pkg.onStart`: it lasts
|
|
95
|
+
* until the returned function is called or the worker stops, and nothing is replayed from
|
|
96
|
+
* while the worker was stopped, so subscribe first, then read what's already there.
|
|
97
|
+
*
|
|
98
|
+
* Only the package itself can subscribe this way; consumers subscribe to `dataChanged`
|
|
99
|
+
* through a contract.
|
|
100
|
+
*
|
|
101
|
+
* @param handler - Gets each change, with the row as `list` returns it.
|
|
102
|
+
* @returns Stops the subscription.
|
|
103
|
+
*/
|
|
104
|
+
async subscribe(handler) {
|
|
105
|
+
const { name, schema } = this.definition;
|
|
106
|
+
return this.ctx.watchOwnedTable(name, (event) => handler({
|
|
107
|
+
...event,
|
|
108
|
+
dataObject: (0, empty_fields_1.withoutEmptySchemaFields)(schema, event.dataObject),
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
84
111
|
/**
|
|
85
112
|
* One change on its way to one subscriber of the table's `dataChanged`, with `this.ctx`
|
|
86
113
|
* carrying that subscriber. Return it to send it, a changed copy to send that instead, or
|
|
@@ -8,5 +8,10 @@
|
|
|
8
8
|
* environments (jsdom) lack.
|
|
9
9
|
*
|
|
10
10
|
* @param value - The value to send: arguments, a result, a row, an event, or an observable value.
|
|
11
|
+
* @param options - `undefinedFieldsAsNull` sends a missing field as `null` instead of leaving it
|
|
12
|
+
* out, for a table filter: there `{ tag: undefined }` matches an empty field, as `null` does,
|
|
13
|
+
* and leaving it out would match every row.
|
|
11
14
|
*/
|
|
12
|
-
export declare function snapshotPackageValue(value: unknown
|
|
15
|
+
export declare function snapshotPackageValue(value: unknown, options?: {
|
|
16
|
+
undefinedFieldsAsNull?: boolean;
|
|
17
|
+
}): unknown;
|
|
@@ -11,8 +11,11 @@ exports.snapshotPackageValue = snapshotPackageValue;
|
|
|
11
11
|
* environments (jsdom) lack.
|
|
12
12
|
*
|
|
13
13
|
* @param value - The value to send: arguments, a result, a row, an event, or an observable value.
|
|
14
|
+
* @param options - `undefinedFieldsAsNull` sends a missing field as `null` instead of leaving it
|
|
15
|
+
* out, for a table filter: there `{ tag: undefined }` matches an empty field, as `null` does,
|
|
16
|
+
* and leaving it out would match every row.
|
|
14
17
|
*/
|
|
15
|
-
function snapshotPackageValue(value) {
|
|
18
|
+
function snapshotPackageValue(value, options = {}) {
|
|
16
19
|
const ancestors = new Set();
|
|
17
20
|
const copy = (current, path) => {
|
|
18
21
|
if (current === null || typeof current === "string" || typeof current === "boolean") {
|
|
@@ -56,10 +59,10 @@ function snapshotPackageValue(value) {
|
|
|
56
59
|
}
|
|
57
60
|
const record = {};
|
|
58
61
|
for (const [key, field] of Object.entries(current)) {
|
|
59
|
-
if (field !== undefined) {
|
|
62
|
+
if (field !== undefined || options.undefinedFieldsAsNull) {
|
|
60
63
|
// Define rather than assign, so a "__proto__" key stays a field.
|
|
61
64
|
Object.defineProperty(record, key, {
|
|
62
|
-
value: copy(field, `${path}.${key}`),
|
|
65
|
+
value: field === undefined ? null : copy(field, `${path}.${key}`),
|
|
63
66
|
enumerable: true,
|
|
64
67
|
writable: true,
|
|
65
68
|
configurable: true,
|
|
@@ -27,9 +27,18 @@ export declare function extractCoreMessage(message: string): string;
|
|
|
27
27
|
*/
|
|
28
28
|
export declare class Logger {
|
|
29
29
|
private source;
|
|
30
|
+
private process;
|
|
30
31
|
private pendingLogs;
|
|
31
32
|
private waitingForConsoleLogsTable;
|
|
32
|
-
|
|
33
|
+
/**
|
|
34
|
+
* @param source - What the lines are about, such as a module or package name. Defaults to the
|
|
35
|
+
* caller's file.
|
|
36
|
+
* @param options - `process` records the lines under another process's name (such as
|
|
37
|
+
* `"worker"` for a package worker's output) instead of this one's.
|
|
38
|
+
*/
|
|
39
|
+
constructor(source?: string, options?: {
|
|
40
|
+
process?: string;
|
|
41
|
+
});
|
|
33
42
|
/**
|
|
34
43
|
* Log a debug message
|
|
35
44
|
*/
|
|
@@ -199,9 +199,11 @@ function extractSource(stackTrace) {
|
|
|
199
199
|
return undefined;
|
|
200
200
|
}
|
|
201
201
|
/**
|
|
202
|
-
* Write a log entry to the ConsoleLogs table with a specific source
|
|
202
|
+
* Write a log entry to the ConsoleLogs table with a specific source. `processName` names another
|
|
203
|
+
* process the line came from (such as a package worker); its stack is not this one's, so an
|
|
204
|
+
* error gets none.
|
|
203
205
|
*/
|
|
204
|
-
async function writeLogToDatabaseWithSource(level, source, args) {
|
|
206
|
+
async function writeLogToDatabaseWithSource(level, source, args, processName) {
|
|
205
207
|
try {
|
|
206
208
|
const timestamp = (0, utils_1.getTimestamp)();
|
|
207
209
|
// Format message from arguments
|
|
@@ -223,12 +225,12 @@ async function writeLogToDatabaseWithSource(level, source, args) {
|
|
|
223
225
|
}
|
|
224
226
|
const context = args.length === 0 ? undefined : args.length === 1 ? args[0] : args;
|
|
225
227
|
// Get stack trace for errors
|
|
226
|
-
const stackTrace = level === "error" ? getStackTrace() : undefined;
|
|
228
|
+
const stackTrace = level === "error" && !processName ? getStackTrace() : undefined;
|
|
227
229
|
const logRecord = {
|
|
228
230
|
logId: (0, utils_1.newid)(),
|
|
229
231
|
timestamp,
|
|
230
232
|
level,
|
|
231
|
-
process: currentProcessName,
|
|
233
|
+
process: processName ?? currentProcessName,
|
|
232
234
|
processInstanceId: currentProcessInstanceId,
|
|
233
235
|
source,
|
|
234
236
|
message,
|
|
@@ -254,15 +256,23 @@ async function writeLogToDatabaseWithSource(level, source, args) {
|
|
|
254
256
|
*/
|
|
255
257
|
class Logger {
|
|
256
258
|
source;
|
|
259
|
+
process;
|
|
257
260
|
pendingLogs = [];
|
|
258
261
|
waitingForConsoleLogsTable = true;
|
|
259
|
-
|
|
262
|
+
/**
|
|
263
|
+
* @param source - What the lines are about, such as a module or package name. Defaults to the
|
|
264
|
+
* caller's file.
|
|
265
|
+
* @param options - `process` records the lines under another process's name (such as
|
|
266
|
+
* `"worker"` for a package worker's output) instead of this one's.
|
|
267
|
+
*/
|
|
268
|
+
constructor(source, options = {}) {
|
|
260
269
|
// If no source provided, try to extract from caller's location
|
|
261
270
|
if (!source) {
|
|
262
271
|
const stack = new Error().stack;
|
|
263
272
|
source = extractSource(stack) || "unknown";
|
|
264
273
|
}
|
|
265
274
|
this.source = source;
|
|
275
|
+
this.process = options.process;
|
|
266
276
|
(0, console_logs_table_1.ConsoleLogs)().then(() => {
|
|
267
277
|
this.waitingForConsoleLogsTable = false;
|
|
268
278
|
this.flushPendingLogs();
|
|
@@ -321,7 +331,7 @@ class Logger {
|
|
|
321
331
|
return;
|
|
322
332
|
}
|
|
323
333
|
// System is initialized, write directly
|
|
324
|
-
writeLogToDatabaseWithSource(level, this.source, args).catch((err) => {
|
|
334
|
+
writeLogToDatabaseWithSource(level, this.source, args, this.process).catch((err) => {
|
|
325
335
|
originalConsole.error("[console-logger] Failed to write log:", err);
|
|
326
336
|
});
|
|
327
337
|
}
|
|
@@ -330,7 +340,7 @@ class Logger {
|
|
|
330
340
|
this.pendingLogs.length = 0;
|
|
331
341
|
for (const { level, args } of _pendingLogs) {
|
|
332
342
|
await (0, utils_1.sleep)(10); // small delay to avoid stampede
|
|
333
|
-
await writeLogToDatabaseWithSource(level, this.source, args).catch((err) => {
|
|
343
|
+
await writeLogToDatabaseWithSource(level, this.source, args, this.process).catch((err) => {
|
|
334
344
|
originalConsole.error("[console-logger] Failed to flush pending log:", err);
|
|
335
345
|
});
|
|
336
346
|
}
|
|
@@ -10,6 +10,30 @@ import type { IPackageWorkerHealth } from "./package-runtime-state";
|
|
|
10
10
|
export declare const ISOLATED_CONTRACT_TABLE_OPERATIONS: readonly ["get", "list", "count", "findOne", "save", "insert", "update", "delete"];
|
|
11
11
|
/** Consumer-callable operation on an isolated contract table. */
|
|
12
12
|
export type IsolatedContractTableOperation = (typeof ISOLATED_CONTRACT_TABLE_OPERATIONS)[number];
|
|
13
|
+
/** Options a package may pass with a table `save`. */
|
|
14
|
+
export interface IPackageSaveOptions {
|
|
15
|
+
/** Save a row whose id was deleted before. Without it, that save is refused. */
|
|
16
|
+
restoreIfDeleted?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The save options a package may use, from whatever a caller passed: only `restoreIfDeleted`.
|
|
20
|
+
* The host's other save options (snapshots, weak inserts) stay out of package hands.
|
|
21
|
+
* `writtenAt` must stay host-only even if this allowlist expands: it orders signed rows,
|
|
22
|
+
* and accepting it from a package would let that package backdate writes.
|
|
23
|
+
*
|
|
24
|
+
* @param options - A table `save`'s second argument, as received.
|
|
25
|
+
* @returns The allowed options, or `undefined` when none are set.
|
|
26
|
+
*/
|
|
27
|
+
export declare function isolatedSaveOptions(options: unknown): IPackageSaveOptions | undefined;
|
|
28
|
+
/** One line a package worker wrote to its stdout or stderr. */
|
|
29
|
+
export interface IIsolatedWorkerOutput {
|
|
30
|
+
/** The worker's package. */
|
|
31
|
+
packageId: string;
|
|
32
|
+
/** Which stream the line came from. */
|
|
33
|
+
stream: "stdout" | "stderr";
|
|
34
|
+
/** The line, without its newline. */
|
|
35
|
+
line: string;
|
|
36
|
+
}
|
|
13
37
|
/**
|
|
14
38
|
* Trusted caller identity for an isolated tool call. Never read from guest
|
|
15
39
|
* payloads — the host derives this from `DataContext` / workflow state.
|
|
@@ -303,6 +327,10 @@ export interface IIsolatedPackageRuntimeFactoryOptions {
|
|
|
303
327
|
persistentVarGateway?: IIsolatedPackagePersistentVarGateway;
|
|
304
328
|
/** Gateway for consumed calls to selected host/system providers. */
|
|
305
329
|
providerGateway?: IIsolatedContractProviderGateway;
|
|
330
|
+
/** Receives each line package workers write; without it, lines go to this process's output. */
|
|
331
|
+
onWorkerOutput?: (output: IIsolatedWorkerOutput) => void;
|
|
332
|
+
/** This device's id, which package code reads as `ctx.deviceId`. */
|
|
333
|
+
getDeviceId?: () => string | undefined;
|
|
306
334
|
}
|
|
307
335
|
/**
|
|
308
336
|
* Host callback that builds an {@link IIsolatedPackageRuntime} for one
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ISOLATED_CONTRACT_TABLE_EVENTS = exports.ISOLATED_CONTRACT_TABLE_OPERATIONS = void 0;
|
|
4
|
+
exports.isolatedSaveOptions = isolatedSaveOptions;
|
|
4
5
|
exports.setIsolatedPackageRuntimeFactory = setIsolatedPackageRuntimeFactory;
|
|
5
6
|
exports.getIsolatedPackageRuntimeFactory = getIsolatedPackageRuntimeFactory;
|
|
6
7
|
/** Standard table methods exposed through isolated contract-table references. */
|
|
@@ -14,6 +15,20 @@ exports.ISOLATED_CONTRACT_TABLE_OPERATIONS = [
|
|
|
14
15
|
"update",
|
|
15
16
|
"delete",
|
|
16
17
|
];
|
|
18
|
+
/**
|
|
19
|
+
* The save options a package may use, from whatever a caller passed: only `restoreIfDeleted`.
|
|
20
|
+
* The host's other save options (snapshots, weak inserts) stay out of package hands.
|
|
21
|
+
* `writtenAt` must stay host-only even if this allowlist expands: it orders signed rows,
|
|
22
|
+
* and accepting it from a package would let that package backdate writes.
|
|
23
|
+
*
|
|
24
|
+
* @param options - A table `save`'s second argument, as received.
|
|
25
|
+
* @returns The allowed options, or `undefined` when none are set.
|
|
26
|
+
*/
|
|
27
|
+
function isolatedSaveOptions(options) {
|
|
28
|
+
return options?.restoreIfDeleted === true
|
|
29
|
+
? { restoreIfDeleted: true }
|
|
30
|
+
: undefined;
|
|
31
|
+
}
|
|
17
32
|
/** Table events consumers may subscribe to on an isolated contract table. */
|
|
18
33
|
exports.ISOLATED_CONTRACT_TABLE_EVENTS = ["dataChanged"];
|
|
19
34
|
let isolatedRuntimeFactory;
|
|
@@ -48,6 +48,10 @@ export declare class PackageLoader {
|
|
|
48
48
|
private contractInstalledPackages;
|
|
49
49
|
/** Packages installed through the isolated runtime (no host eval). */
|
|
50
50
|
private isolatedPackageIds;
|
|
51
|
+
/** Each isolated package's name, for its worker's log lines. */
|
|
52
|
+
private isolatedPackageNames;
|
|
53
|
+
/** One logger per package whose worker wrote output, writing as process `worker`. */
|
|
54
|
+
private workerLoggers;
|
|
51
55
|
/** Per-package counter bumped on every runtime-state change; lets slow unloads yield. */
|
|
52
56
|
private runtimeStateSequences;
|
|
53
57
|
/** Hash of the bundle each loaded package was built from, for `onlyIfChanged` loads. */
|
|
@@ -189,6 +193,11 @@ export declare class PackageLoader {
|
|
|
189
193
|
* Dispose the isolated runtime for this data context.
|
|
190
194
|
*/
|
|
191
195
|
dispose(): Promise<void>;
|
|
196
|
+
/**
|
|
197
|
+
* Record one line a package worker wrote, under process `worker` with the package's name as
|
|
198
|
+
* its source. It still reaches this process's output too.
|
|
199
|
+
*/
|
|
200
|
+
private logWorkerOutput;
|
|
192
201
|
private retireHostEndpoints;
|
|
193
202
|
/**
|
|
194
203
|
* Resolve and load every non-disabled package in this data context.
|
|
@@ -16,6 +16,7 @@ const package_version_resolver_1 = require("../data/package-version-resolver");
|
|
|
16
16
|
const package_versions_1 = require("../data/package-versions");
|
|
17
17
|
const packages_1 = require("../data/packages");
|
|
18
18
|
const keys_1 = require("../keys");
|
|
19
|
+
const console_logger_1 = require("../logging/console-logger");
|
|
19
20
|
const observable_1 = require("../observable");
|
|
20
21
|
const contract_package_loader_1 = require("./contract-package-loader");
|
|
21
22
|
const isolated_artifact_1 = require("./isolated-artifact");
|
|
@@ -63,6 +64,10 @@ class PackageLoader {
|
|
|
63
64
|
contractInstalledPackages = new Set();
|
|
64
65
|
/** Packages installed through the isolated runtime (no host eval). */
|
|
65
66
|
isolatedPackageIds = new Set();
|
|
67
|
+
/** Each isolated package's name, for its worker's log lines. */
|
|
68
|
+
isolatedPackageNames = new Map();
|
|
69
|
+
/** One logger per package whose worker wrote output, writing as process `worker`. */
|
|
70
|
+
workerLoggers = new Map();
|
|
66
71
|
/** Per-package counter bumped on every runtime-state change; lets slow unloads yield. */
|
|
67
72
|
runtimeStateSequences = new Map();
|
|
68
73
|
/** Hash of the bundle each loaded package was built from, for `onlyIfChanged` loads. */
|
|
@@ -120,6 +125,8 @@ class PackageLoader {
|
|
|
120
125
|
resolverOnly: true,
|
|
121
126
|
}),
|
|
122
127
|
},
|
|
128
|
+
onWorkerOutput: (output) => this.logWorkerOutput(output),
|
|
129
|
+
getDeviceId: () => this.dataContext.userContext?.deviceId(),
|
|
123
130
|
});
|
|
124
131
|
this.unsubscribePackageHealth = this.isolatedRuntime.subscribePackageHealth?.((packageId) => {
|
|
125
132
|
const previous = this.runtimeStates()[packageId];
|
|
@@ -623,9 +630,28 @@ class PackageLoader {
|
|
|
623
630
|
this.contractInstalledPackages.clear();
|
|
624
631
|
this.packageInstances = {};
|
|
625
632
|
this.loadedBundleHashes.clear();
|
|
633
|
+
this.workerLoggers.clear();
|
|
634
|
+
this.isolatedPackageNames.clear();
|
|
626
635
|
this.runtimeStateSequences.clear();
|
|
627
636
|
this.runtimeStates({});
|
|
628
637
|
}
|
|
638
|
+
/**
|
|
639
|
+
* Record one line a package worker wrote, under process `worker` with the package's name as
|
|
640
|
+
* its source. It still reaches this process's output too.
|
|
641
|
+
*/
|
|
642
|
+
logWorkerOutput({ packageId, stream, line }) {
|
|
643
|
+
let logger = this.workerLoggers.get(packageId);
|
|
644
|
+
if (!logger) {
|
|
645
|
+
logger = new console_logger_1.Logger(this.isolatedPackageNames.get(packageId) ?? packageId, {
|
|
646
|
+
process: "worker",
|
|
647
|
+
});
|
|
648
|
+
this.workerLoggers.set(packageId, logger);
|
|
649
|
+
}
|
|
650
|
+
if (stream === "stderr")
|
|
651
|
+
logger.error(line);
|
|
652
|
+
else
|
|
653
|
+
logger.log(line);
|
|
654
|
+
}
|
|
629
655
|
retireHostEndpoints(packageId) {
|
|
630
656
|
this.hostActivationGenerations.set(packageId, (this.hostActivationGenerations.get(packageId) ?? 0) + 1);
|
|
631
657
|
for (const router of this.consumedContractRouters)
|
|
@@ -935,6 +961,10 @@ class PackageLoader {
|
|
|
935
961
|
return undefined;
|
|
936
962
|
}
|
|
937
963
|
assertCurrent();
|
|
964
|
+
if (this.isolatedPackageNames.get(pkg.packageId) !== pkg.name) {
|
|
965
|
+
this.workerLoggers.delete(pkg.packageId);
|
|
966
|
+
}
|
|
967
|
+
this.isolatedPackageNames.set(pkg.packageId, pkg.name);
|
|
938
968
|
const installOptions = { ...isolatedContractLifecycle(selectedPackageVersion), assertCurrent };
|
|
939
969
|
let installed;
|
|
940
970
|
if (this.isolatedRuntime.isInstalled(pkg.packageId)) {
|
|
@@ -237,6 +237,8 @@ function mockProviderContext(contractId, tables) {
|
|
|
237
237
|
get(_target, property) {
|
|
238
238
|
if (property === "callOwnedTable")
|
|
239
239
|
return callOwnedTable;
|
|
240
|
+
if (property === "deviceId")
|
|
241
|
+
return "mock-host-device";
|
|
240
242
|
if (typeof property !== "string" || property === "then")
|
|
241
243
|
return undefined;
|
|
242
244
|
return () => {
|