@peers-app/peers-sdk 0.28.2 → 0.28.4

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.
@@ -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
  };
@@ -3,7 +3,7 @@ import type { Table } from "../data/orm/table";
3
3
  import type { ITool, IToolInstance } from "../data/tools";
4
4
  import type { Event, ISubscriptionResult } from "../events";
5
5
  import { type Observable } from "../observable";
6
- import type { IIsolatedCallPrincipal } from "../package-loader/isolated-runtime";
6
+ import { type IIsolatedCallPrincipal } from "../package-loader/isolated-runtime";
7
7
  import type { IWorkflowRunContext } from "../types/workflow-run-context";
8
8
  import { type IContractTransport } from "./contract-transport";
9
9
  import type { IContractDefinition, IRemoteContractConsumer } from "./types";
@@ -12,6 +12,7 @@ const access_level_1 = require("../data/access-level");
12
12
  const group_permissions_1 = require("../data/group-permissions");
13
13
  const decorators_1 = require("../data/orm/decorators");
14
14
  const observable_1 = require("../observable");
15
+ const isolated_runtime_1 = require("../package-loader/isolated-runtime");
15
16
  const utils_1 = require("../utils");
16
17
  const action_dispatch_1 = require("./action-dispatch");
17
18
  const contract_transport_1 = require("./contract-transport");
@@ -584,7 +585,8 @@ function createContractProvider(resolution, opts = {}) {
584
585
  !(0, decorators_1.isContractRemoteTableMethod)(method)) {
585
586
  throw new Error(`Table method '${call.name}.${call.operation}' is not remotely callable`);
586
587
  }
587
- return impl.apply(table, call.args);
588
+ (0, decorators_1.checkRemoteTableSaveOptions)(call.operation, call.args);
589
+ return impl.apply(table, (0, isolated_runtime_1.isolatedTableArgs)(call.operation, call.args));
588
590
  }
589
591
  case "tool": {
590
592
  if (call.operation !== "run") {
@@ -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), snapshot);
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);
@@ -116,7 +116,8 @@ let GroupsTable = (() => {
116
116
  ...group,
117
117
  ...(await (0, write_checks_1.signingStamp)(this, group)),
118
118
  });
119
- return this.save(group, opts);
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`, now or
61
- * just after the row on record when that was signed by a clock running ahead, so the new
62
- * signature counts as the later one (and, for a package table, after the deleted row's
63
- * retained stamp); for a package table also the context the approval is for.
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`, now or
60
- * just after the row on record when that was signed by a clock running ahead, so the new
61
- * signature counts as the later one (and, for a package table, after the deleted row's
62
- * retained stamp); for a package table also the context the approval is for.
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
- return this.save(packageVersion, opts);
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) {
@@ -127,7 +127,8 @@ let PackagesTable = (() => {
127
127
  ...packageObj,
128
128
  ...(await (0, write_checks_1.signingStamp)(this, packageObj)),
129
129
  });
130
- return this.save(packageObj, opts);
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;
@@ -12,17 +12,33 @@ export declare const ISOLATED_CONTRACT_TABLE_OPERATIONS: readonly ["get", "list"
12
12
  export type IsolatedContractTableOperation = (typeof ISOLATED_CONTRACT_TABLE_OPERATIONS)[number];
13
13
  /** Options a package may pass with a table `save`. */
14
14
  export interface IPackageSaveOptions {
15
+ /** Seed a new row so any real write wins, even one not yet synced. Has no effect on updates. */
16
+ weakInsert?: boolean;
17
+ /** Create if missing; otherwise leave the row untouched and return it. Use with weakInsert for defaults. */
18
+ insertOnly?: boolean;
19
+ /** Reset the whole row, replacing even unseen earlier edits from other devices. Later writes still win. */
20
+ saveAsSnapshot?: boolean;
15
21
  /** Save a row whose id was deleted before. Without it, that save is refused. */
16
22
  restoreIfDeleted?: boolean;
17
23
  }
18
24
  /**
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.
25
+ * Copy every host save option except `writtenAt`, accepting only values exactly equal to `true`.
26
+ * `writtenAt` stays host-only: it orders signed rows by their signing time, and accepting it
27
+ * from a package would let that package backdate writes. Unknown keys are dropped.
21
28
  *
22
29
  * @param options - A table `save`'s second argument, as received.
23
30
  * @returns The allowed options, or `undefined` when none are set.
24
31
  */
25
32
  export declare function isolatedSaveOptions(options: unknown): IPackageSaveOptions | undefined;
33
+ /**
34
+ * Limit a package or contract table write to its public arguments. Only `save` accepts
35
+ * options; `insert` and `update` accept only a row. Other operations keep their arguments.
36
+ *
37
+ * @param operation - The table method being called.
38
+ * @param args - Its arguments, as received across the bridge.
39
+ * @returns Arguments with save options filtered by {@link isolatedSaveOptions}.
40
+ */
41
+ export declare function isolatedTableArgs(operation: string, args: unknown[]): unknown[];
26
42
  /** One line a package worker wrote to its stdout or stderr. */
27
43
  export interface IIsolatedWorkerOutput {
28
44
  /** The worker's package. */
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ISOLATED_CONTRACT_TABLE_EVENTS = exports.ISOLATED_CONTRACT_TABLE_OPERATIONS = void 0;
4
4
  exports.isolatedSaveOptions = isolatedSaveOptions;
5
+ exports.isolatedTableArgs = isolatedTableArgs;
5
6
  exports.setIsolatedPackageRuntimeFactory = setIsolatedPackageRuntimeFactory;
6
7
  exports.getIsolatedPackageRuntimeFactory = getIsolatedPackageRuntimeFactory;
7
8
  /** Standard table methods exposed through isolated contract-table references. */
@@ -16,16 +17,36 @@ exports.ISOLATED_CONTRACT_TABLE_OPERATIONS = [
16
17
  "delete",
17
18
  ];
18
19
  /**
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.
20
+ * Copy every host save option except `writtenAt`, accepting only values exactly equal to `true`.
21
+ * `writtenAt` stays host-only: it orders signed rows by their signing time, and accepting it
22
+ * from a package would let that package backdate writes. Unknown keys are dropped.
21
23
  *
22
24
  * @param options - A table `save`'s second argument, as received.
23
25
  * @returns The allowed options, or `undefined` when none are set.
24
26
  */
25
27
  function isolatedSaveOptions(options) {
26
- return options?.restoreIfDeleted === true
27
- ? { restoreIfDeleted: true }
28
- : undefined;
28
+ const supplied = options;
29
+ const allowed = {};
30
+ for (const key of ["weakInsert", "insertOnly", "saveAsSnapshot", "restoreIfDeleted"]) {
31
+ if (supplied?.[key] === true)
32
+ allowed[key] = true;
33
+ }
34
+ return Object.keys(allowed).length ? allowed : undefined;
35
+ }
36
+ /**
37
+ * Limit a package or contract table write to its public arguments. Only `save` accepts
38
+ * options; `insert` and `update` accept only a row. Other operations keep their arguments.
39
+ *
40
+ * @param operation - The table method being called.
41
+ * @param args - Its arguments, as received across the bridge.
42
+ * @returns Arguments with save options filtered by {@link isolatedSaveOptions}.
43
+ */
44
+ function isolatedTableArgs(operation, args) {
45
+ if (operation === "save") {
46
+ const options = isolatedSaveOptions(args[1]);
47
+ return options ? [args[0], options] : args.slice(0, 1);
48
+ }
49
+ return operation === "insert" || operation === "update" ? args.slice(0, 1) : args;
29
50
  }
30
51
  /** Table events consumers may subscribe to on an isolated contract table. */
31
52
  exports.ISOLATED_CONTRACT_TABLE_EVENTS = ["dataChanged"];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peers-app/peers-sdk",
3
- "version": "0.28.2",
3
+ "version": "0.28.4",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/peers-app/peers-sdk.git"