@peers-app/peers-sdk 0.25.9 → 0.25.10

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.
@@ -426,6 +426,32 @@ describe("contract proxy — error handling", () => {
426
426
  const consumer = consumerVia(contract, provider);
427
427
  await expect(consumer.tables.Widgets.list()).rejects.toThrow(/does not provide table 'Widgets'/);
428
428
  });
429
+ it("forwards callerPackageId when the resolution opts into call context", async () => {
430
+ const seen = [];
431
+ const provider = (0, contract_proxy_1.createContractProvider)({
432
+ contractId: CONTRACT_ID,
433
+ version: 1,
434
+ forwardCallContext: true,
435
+ tools: {
436
+ listGrants: {
437
+ tool: { toolId: (0, utils_1.newid)(), name: "listGrants", accessLevel: 0 },
438
+ toolFn: async (host) => {
439
+ seen.push(host.callerPackageId ?? null);
440
+ return "ok";
441
+ },
442
+ },
443
+ },
444
+ });
445
+ await provider({
446
+ contractId: CONTRACT_ID,
447
+ version: 1,
448
+ member: "tool",
449
+ name: "listGrants",
450
+ operation: "run",
451
+ args: [],
452
+ }, { callerPackageId: "00pkg00000000000000000001" });
453
+ expect(seen).toEqual(["00pkg00000000000000000001"]);
454
+ });
429
455
  it.each([
430
456
  ["tool", "missingTool"],
431
457
  ["observable", "missingObservable"],
@@ -520,6 +546,52 @@ describe("contract proxy — permission checks", () => {
520
546
  expect(denied.table.save).not.toHaveBeenCalled();
521
547
  });
522
548
  });
549
+ describe("contract provider endpoint event filter", () => {
550
+ it("delivers a generic event only to subscribers the filter allows", async () => {
551
+ const handlers = new Set();
552
+ const changed = {
553
+ eventName: () => "changed",
554
+ subscribe: (handler) => {
555
+ handlers.add(handler);
556
+ return {
557
+ unsubscribe: () => {
558
+ handlers.delete(handler);
559
+ return true;
560
+ },
561
+ };
562
+ },
563
+ };
564
+ const resolution = {
565
+ contractId: CONTRACT_ID,
566
+ version: 1,
567
+ events: { changed: changed },
568
+ eventFilter: (_eventName, payload, context) => payload.owner === context.callerPackageId,
569
+ };
570
+ const delivered = [];
571
+ const endpoint = (0, contract_proxy_1.createContractProviderEndpoint)(resolution, {
572
+ notify: async (notification) => {
573
+ delivered.push(notification.subscriptionId);
574
+ },
575
+ });
576
+ const subscribe = (subscriptionId, callerPackageId) => endpoint.handleCall({
577
+ contractId: CONTRACT_ID,
578
+ version: 1,
579
+ member: "event",
580
+ name: "changed",
581
+ operation: "subscribe",
582
+ subscriptionId,
583
+ args: [],
584
+ }, { callerPackageId });
585
+ await subscribe("allowed", "pkg-a");
586
+ await subscribe("denied", "pkg-b");
587
+ for (const handler of handlers) {
588
+ handler({ owner: "pkg-a" });
589
+ }
590
+ await new Promise((resolve) => setImmediate(resolve));
591
+ expect(delivered).toEqual(["allowed"]);
592
+ endpoint.dispose();
593
+ });
594
+ });
523
595
  describe("connectionContractTransport", () => {
524
596
  it("emits on the request channel with the call and returns the response", async () => {
525
597
  const emit = jest.fn(async (_event, _call) => ["ok"]);
@@ -220,6 +220,11 @@ export interface IContractToolContext {
220
220
  * by the host and returned only to the provider-side tool implementation.
221
221
  */
222
222
  getVariable(variableName: string, requestingToolId: string): Promise<string>;
223
+ /**
224
+ * Isolated package that originated this call, when the host runtime supplied one.
225
+ * Absent for trusted local callers (the signed-in UI session and the CLI).
226
+ */
227
+ callerPackageId?: string;
223
228
  }
224
229
  /**
225
230
  * Build the **consumer** side of a contract proxy from a contract definition and a
@@ -293,6 +298,13 @@ export interface IContractResolution {
293
298
  * Omit this for remote or otherwise untrusted provider routes.
294
299
  */
295
300
  toolContext?: IContractToolContext;
301
+ /**
302
+ * When true, provider tool functions receive an {@link IContractToolContext} as their
303
+ * final argument even if {@link toolContext} was not set, including
304
+ * {@link IContractToolContext.callerPackageId} from the current call. Host contracts
305
+ * that authorize by calling package set this.
306
+ */
307
+ forwardCallContext?: boolean;
296
308
  observables?: Record<string, Observable<any>>;
297
309
  /**
298
310
  * Provider-owned write policy for live observables. A `set` is allowed only when the
@@ -306,6 +318,20 @@ export interface IContractResolution {
306
318
  * their payloads to its configured notification sink.
307
319
  */
308
320
  events?: Record<string, Event<any>>;
321
+ /**
322
+ * Optional per-subscriber gate for generic contract events.
323
+ *
324
+ * Table and observable streams are not filtered. The endpoint invokes this once
325
+ * per subscriber with the {@link IContractCallContext} captured at subscribe time.
326
+ * Return false, or reject, to drop that notification for that subscriber only.
327
+ * A shared endpoint can therefore hide one caller's events from another.
328
+ *
329
+ * @param eventName Contract event name, such as `changed`.
330
+ * @param payload Payload about to be delivered.
331
+ * @param context Caller identity from the subscribe call.
332
+ * @returns Whether this subscriber may receive the payload.
333
+ */
334
+ eventFilter?: (eventName: string, payload: unknown, context: IContractCallContext) => boolean | Promise<boolean>;
309
335
  }
310
336
  /** Identity of the caller, supplied by the transport layer for permission checks. */
311
337
  export interface IContractCallContext {
@@ -319,6 +345,11 @@ export interface IContractCallContext {
319
345
  * host router before dispatch.
320
346
  */
321
347
  callerDeviceId?: string;
348
+ /**
349
+ * Isolated package that originated this call. Set by the isolate runtime from host
350
+ * state, never from a guest payload. Absent for trusted local UI and CLI callers.
351
+ */
352
+ callerPackageId?: string;
322
353
  /**
323
354
  * Provider-side data context used to resolve the caller's group/personal role.
324
355
  * In-process only — never serialized across a connection.
@@ -418,7 +449,9 @@ export interface IContractProviderEndpoint {
418
449
  * or an {@link Observable} (`resolution.observables[name]`) — is only subscribed to while at
419
450
  * least one consumer holds a subscription (refcounted by a resolved key), and each payload is
420
451
  * fanned out through the supplied {@link ContractNotificationSink} as an
421
- * {@link IContractNotification}. The endpoint owns no request listener, which lets a
452
+ * {@link IContractNotification}. Generic events pass through
453
+ * {@link IContractResolution.eventFilter} once per subscriber, using the caller context
454
+ * captured when that subscriber subscribed. The endpoint owns no request listener, which lets a
422
455
  * connection-wide router multiplex many endpoints through one request channel and later
423
456
  * substitute sandbox-backed endpoint implementations.
424
457
  *
@@ -534,9 +534,19 @@ function createContractProvider(resolution, opts = {}) {
534
534
  throw new Error(`Contract ${call.contractId} does not provide tool '${call.name}'`);
535
535
  }
536
536
  await assertContractToolAccess(tool, call, context);
537
- return resolution.toolContext
538
- ? tool.toolFn(...call.args, resolution.toolContext)
539
- : tool.toolFn(...call.args);
537
+ if (resolution.toolContext || resolution.forwardCallContext) {
538
+ const base = resolution.toolContext ?? {
539
+ dataContextId: context.dataContext?.dataContextId ?? "",
540
+ getVariable: async () => {
541
+ throw new Error("Persistent variables are not available to this contract tool");
542
+ },
543
+ };
544
+ return tool.toolFn(...call.args, {
545
+ ...base,
546
+ callerPackageId: context.callerPackageId,
547
+ });
548
+ }
549
+ return tool.toolFn(...call.args);
540
550
  }
541
551
  case "observable": {
542
552
  const obs = resolution.observables?.[call.name];
@@ -571,7 +581,9 @@ function createContractProvider(resolution, opts = {}) {
571
581
  * or an {@link Observable} (`resolution.observables[name]`) — is only subscribed to while at
572
582
  * least one consumer holds a subscription (refcounted by a resolved key), and each payload is
573
583
  * fanned out through the supplied {@link ContractNotificationSink} as an
574
- * {@link IContractNotification}. The endpoint owns no request listener, which lets a
584
+ * {@link IContractNotification}. Generic events pass through
585
+ * {@link IContractResolution.eventFilter} once per subscriber, using the caller context
586
+ * captured when that subscriber subscribed. The endpoint owns no request listener, which lets a
575
587
  * connection-wide router multiplex many endpoints through one request channel and later
576
588
  * substitute sandbox-backed endpoint implementations.
577
589
  *
@@ -581,9 +593,10 @@ function createContractProvider(resolution, opts = {}) {
581
593
  function createContractProviderEndpoint(resolution, opts) {
582
594
  const { notify, permissionCheck, context: defaultContext } = opts;
583
595
  const dispatch = createContractProvider(resolution, { permissionCheck });
584
- // One live subscription per resolved subscription key, refcounted by the set of
585
- // consumer subscription ids currently attached to it, plus a reverse index for
586
- // unsubscribe. Keys namespace generic events (`event:<name>`), table events
596
+ // One live subscription per resolved subscription key, refcounted by the subscribers
597
+ // currently attached to it, plus a reverse index for unsubscribe. Each subscriber keeps
598
+ // the caller context from its subscribe call so event filters can differ per consumer.
599
+ // Keys namespace generic events (`event:<name>`), table events
587
600
  // (`table:<name>:<event>`), and observable streams (`observable:<name>`) so the same
588
601
  // machinery serves all three.
589
602
  const byKey = new Map();
@@ -651,29 +664,51 @@ function createContractProviderEndpoint(resolution, opts) {
651
664
  },
652
665
  };
653
666
  };
654
- const subscribe = (key, attach, subscriptionId) => {
667
+ const reportNotifyFailure = (subscriptionId, err) => {
668
+ console.error(`Contract notify failed for subscription '${subscriptionId}'`, err);
669
+ };
670
+ const deliver = (key, subscriptionId, payload, context) => {
671
+ // Start each notification independently. A slow or rejected destination must not
672
+ // block sibling subscribers, and every rejection is handled because Observable
673
+ // callbacks do not await returned promises. Without a filter, attach the catch
674
+ // directly so a rejected notify is logged on the same turn as before.
675
+ if (!(key.startsWith("event:") && resolution.eventFilter)) {
676
+ if (!subIdToKey.has(subscriptionId)) {
677
+ return;
678
+ }
679
+ void notifySink(subscriptionId, payload, key).catch((err) => {
680
+ reportNotifyFailure(subscriptionId, err);
681
+ });
682
+ return;
683
+ }
684
+ void (async () => {
685
+ const allowed = await resolution.eventFilter?.(key.slice("event:".length), payload, context);
686
+ if (!allowed || !subIdToKey.has(subscriptionId)) {
687
+ return;
688
+ }
689
+ await notifySink(subscriptionId, payload, key);
690
+ })().catch((err) => {
691
+ reportNotifyFailure(subscriptionId, err);
692
+ });
693
+ };
694
+ const subscribe = (key, attach, subscriptionId, context) => {
655
695
  if (subIdToKey.has(subscriptionId)) {
656
696
  throw new Error(`Duplicate contract subscriptionId '${subscriptionId}'`);
657
697
  }
658
698
  let entry = byKey.get(key);
659
699
  if (!entry) {
660
700
  // First subscriber for this key: attach the single live subscription.
661
- const subIds = new Set();
701
+ const subscribers = new Map();
662
702
  const detach = attach((payload) => {
663
- // Start each notification independently. A slow or rejected destination must not
664
- // block sibling subscribers, and every rejection is handled because Observable
665
- // callbacks do not await returned promises.
666
- for (const sid of [...subIds]) {
667
- void notifySink(sid, payload, key).catch((err) => {
668
- console.error(`Contract notify failed for subscription '${sid}'`, err);
669
- });
703
+ for (const [sid, subscriberContext] of [...subscribers]) {
704
+ deliver(key, sid, payload, subscriberContext);
670
705
  }
671
706
  });
672
- entry = { liveUnsub: detach, subIds };
707
+ entry = { liveUnsub: detach, subscribers };
673
708
  byKey.set(key, entry);
674
709
  }
675
710
  subIdToKey.set(subscriptionId, key);
676
- entry.subIds.add(subscriptionId);
711
+ entry.subscribers.set(subscriptionId, context);
677
712
  };
678
713
  const unsubscribe = (subscriptionId) => {
679
714
  const key = subIdToKey.get(subscriptionId);
@@ -685,8 +720,8 @@ function createContractProviderEndpoint(resolution, opts) {
685
720
  if (!entry) {
686
721
  return;
687
722
  }
688
- entry.subIds.delete(subscriptionId);
689
- if (entry.subIds.size === 0) {
723
+ entry.subscribers.delete(subscriptionId);
724
+ if (entry.subscribers.size === 0) {
690
725
  // Last subscriber left: detach the live subscription (nothing more is pushed).
691
726
  entry.liveUnsub();
692
727
  byKey.delete(key);
@@ -731,7 +766,7 @@ function createContractProviderEndpoint(resolution, opts) {
731
766
  throw new Error(`Contract provider endpoint ${resolution.contractId} v${resolution.version} is disposed`);
732
767
  }
733
768
  const { key, attach } = resolveSubscription(call);
734
- subscribe(key, attach, call.subscriptionId);
769
+ subscribe(key, attach, call.subscriptionId, context);
735
770
  }
736
771
  finally {
737
772
  pendingSubIds.delete(call.subscriptionId);
@@ -149,6 +149,7 @@ const permissionDenied = (call, reason) => new Error(`Permission denied: ${call.
149
149
  const localOnlyContractIds = new Set([
150
150
  system_ids_1.identityContractId,
151
151
  system_ids_1.invitesContractId,
152
+ system_ids_1.localFoldersContractId,
152
153
  system_ids_1.toolAccessControlContractId,
153
154
  ]);
154
155
  /**
@@ -15,6 +15,7 @@ export { createRemoteContractEndpoint, getRemoteContractTransportFactory, type I
15
15
  export { DEVICE_OPERATIONS_CONTRACT_VERSION, DEVICE_OPERATIONS_MAX_QUERY_LENGTH, DEVICE_OPERATIONS_MAX_RESULT_BYTES, DEVICE_OPERATIONS_MAX_ROWS, DEVICE_OPERATIONS_MAX_TOOLS, type DeviceOperationCapability, deviceOperationsProviderPackageId, deviceOperationsToolIds, getDeviceOperationsContractDefinition, type IDeviceDatabaseQueryInput, type IDeviceDatabaseQueryResult, type IDeviceOperationStatus, type IDeviceToolListInput, type IDeviceToolLookupInput, type IDeviceToolLookupResult, type IDeviceToolRunInput, } from "./system/device-operations-contract";
16
16
  export { getIdentityContractDefinition, IDENTITY_CONTRACT_VERSION, type IIdentityConsumer, type IIdentitySessionInfo, } from "./system/identity-contract";
17
17
  export { getInvitesContractDefinition, type IAcceptInviteResult, type ICreatedInvite, type IInvitePreview, type IInvitesConsumer, INVITES_CONTRACT_VERSION, } from "./system/invites-contract";
18
+ export { getLocalFoldersContractDefinition, type ILocalFolderChange, type ILocalFolderChangedEvent, type ILocalFolderEntry, type ILocalFolderFileBlocks, type ILocalFolderFileBlocksInput, type ILocalFolderGrantInfo, type ILocalFolderIndexInput, type ILocalFolderJobFinishedEvent, type ILocalFolderJobHandle, type ILocalFolderJobInfo, type ILocalFolderJobProgressEvent, type ILocalFolderListInput, type ILocalFolderMoveInput, type ILocalFolderPathInput, type ILocalFolderPurgeTrashInput, type ILocalFolderReadRangeInput, type ILocalFolderReadRangeResult, type ILocalFolderRequestGrantInput, type ILocalFolderStatInput, LOCAL_FOLDERS_CHUNK_AVG, LOCAL_FOLDERS_CHUNK_MAX, LOCAL_FOLDERS_CHUNK_MIN, LOCAL_FOLDERS_CONTRACT_VERSION, LOCAL_FOLDERS_JOB_TTL_MS, LOCAL_FOLDERS_READ_RANGE_MAX, LOCAL_FOLDERS_RESERVED_DIR, type LocalFolderChangeKind, type LocalFolderEntryKind, type LocalFolderMode, localFoldersProviderPackageId, } from "./system/local-folders-contract";
18
19
  export { getLogsContractDefinition, type ILogsConsumer, LOGS_CONTRACT_VERSION, resolveLogsContract, } from "./system/logs-contract";
19
20
  export * from "./system/network-contracts";
20
21
  export { getToolAccessControlContractDefinition, type IToolAccessControlConsumer, TOOL_ACCESS_CONTROL_CONTRACT_VERSION, } from "./system/tool-access-control-contract";
@@ -15,7 +15,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  exports.DEVICE_OPERATIONS_MAX_ROWS = exports.DEVICE_OPERATIONS_MAX_RESULT_BYTES = exports.DEVICE_OPERATIONS_MAX_QUERY_LENGTH = exports.DEVICE_OPERATIONS_CONTRACT_VERSION = exports.setRemoteContractTransportFactory = exports.resolveRemoteTargetEndpoint = exports.getRemoteContractTransportFactory = exports.createRemoteContractEndpoint = exports.ContractRegistry = exports.PersistentContractRegistry = exports.waitForHostContractTransport = exports.subscribeHostContractTransport = exports.registerHostContractTransport = exports.getHostContractTransport = exports.hostContractConsumer = exports.setSchemaToFieldsFn = exports.extractToolShape = exports.extractTableShape = exports.createLocalContractConsumer = exports.contractsSchema = exports.contractsMetaData = exports.ContractsTable = exports.Contracts = exports.inProcessTransportPair = exports.connectionContractTransport = exports.CONTRACT_REQUEST_CHANNEL = exports.CONTRACT_NOTIFY_CHANNEL = exports.verifyRemoteContractIdentity = exports.registerContractResolver = exports.isSelfTrustedContractCaller = exports.handleContractCall = exports.getContractResolver = exports.createUserContextContractProviderRouter = exports.createLocalUserContextContractProviderRouter = exports.authorizeRemoteContractCall = exports.sameUserContractPermissionCheck = exports.createRemoteContractConsumer = exports.createContractProviderSession = exports.createContractProviderEndpoint = exports.createContractProvider = exports.createContractConsumer = exports.CONTRACT_CALL_RPC = exports.contractProvidersSchema = exports.contractProvidersMetaData = exports.ContractProvidersTable = exports.ContractProviders = exports.createContractProviderRouter = exports.PackageBuilder = exports.definePackage = exports.ContractBuilder = void 0;
18
- exports.validateProviderSatisfiesContract = exports.validateImmutability = exports.validateAlsoImplements = exports.contractKey = exports.registerSystemContract = exports.listSystemContracts = exports.installSystemContracts = exports.TOOL_ACCESS_CONTROL_CONTRACT_VERSION = exports.getToolAccessControlContractDefinition = exports.resolveLogsContract = exports.LOGS_CONTRACT_VERSION = exports.getLogsContractDefinition = exports.INVITES_CONTRACT_VERSION = exports.getInvitesContractDefinition = exports.IDENTITY_CONTRACT_VERSION = exports.getIdentityContractDefinition = exports.getDeviceOperationsContractDefinition = exports.deviceOperationsToolIds = exports.deviceOperationsProviderPackageId = exports.DEVICE_OPERATIONS_MAX_TOOLS = void 0;
18
+ exports.validateProviderSatisfiesContract = exports.validateImmutability = exports.validateAlsoImplements = exports.contractKey = exports.registerSystemContract = exports.listSystemContracts = exports.installSystemContracts = exports.TOOL_ACCESS_CONTROL_CONTRACT_VERSION = exports.getToolAccessControlContractDefinition = exports.resolveLogsContract = exports.LOGS_CONTRACT_VERSION = exports.getLogsContractDefinition = exports.localFoldersProviderPackageId = exports.LOCAL_FOLDERS_RESERVED_DIR = exports.LOCAL_FOLDERS_READ_RANGE_MAX = exports.LOCAL_FOLDERS_JOB_TTL_MS = exports.LOCAL_FOLDERS_CONTRACT_VERSION = exports.LOCAL_FOLDERS_CHUNK_MIN = exports.LOCAL_FOLDERS_CHUNK_MAX = exports.LOCAL_FOLDERS_CHUNK_AVG = exports.getLocalFoldersContractDefinition = exports.INVITES_CONTRACT_VERSION = exports.getInvitesContractDefinition = exports.IDENTITY_CONTRACT_VERSION = exports.getIdentityContractDefinition = exports.getDeviceOperationsContractDefinition = exports.deviceOperationsToolIds = exports.deviceOperationsProviderPackageId = exports.DEVICE_OPERATIONS_MAX_TOOLS = void 0;
19
19
  var builder_1 = require("./builder");
20
20
  Object.defineProperty(exports, "ContractBuilder", { enumerable: true, get: function () { return builder_1.ContractBuilder; } });
21
21
  Object.defineProperty(exports, "definePackage", { enumerable: true, get: function () { return builder_1.definePackage; } });
@@ -91,6 +91,16 @@ Object.defineProperty(exports, "IDENTITY_CONTRACT_VERSION", { enumerable: true,
91
91
  var invites_contract_1 = require("./system/invites-contract");
92
92
  Object.defineProperty(exports, "getInvitesContractDefinition", { enumerable: true, get: function () { return invites_contract_1.getInvitesContractDefinition; } });
93
93
  Object.defineProperty(exports, "INVITES_CONTRACT_VERSION", { enumerable: true, get: function () { return invites_contract_1.INVITES_CONTRACT_VERSION; } });
94
+ var local_folders_contract_1 = require("./system/local-folders-contract");
95
+ Object.defineProperty(exports, "getLocalFoldersContractDefinition", { enumerable: true, get: function () { return local_folders_contract_1.getLocalFoldersContractDefinition; } });
96
+ Object.defineProperty(exports, "LOCAL_FOLDERS_CHUNK_AVG", { enumerable: true, get: function () { return local_folders_contract_1.LOCAL_FOLDERS_CHUNK_AVG; } });
97
+ Object.defineProperty(exports, "LOCAL_FOLDERS_CHUNK_MAX", { enumerable: true, get: function () { return local_folders_contract_1.LOCAL_FOLDERS_CHUNK_MAX; } });
98
+ Object.defineProperty(exports, "LOCAL_FOLDERS_CHUNK_MIN", { enumerable: true, get: function () { return local_folders_contract_1.LOCAL_FOLDERS_CHUNK_MIN; } });
99
+ Object.defineProperty(exports, "LOCAL_FOLDERS_CONTRACT_VERSION", { enumerable: true, get: function () { return local_folders_contract_1.LOCAL_FOLDERS_CONTRACT_VERSION; } });
100
+ Object.defineProperty(exports, "LOCAL_FOLDERS_JOB_TTL_MS", { enumerable: true, get: function () { return local_folders_contract_1.LOCAL_FOLDERS_JOB_TTL_MS; } });
101
+ Object.defineProperty(exports, "LOCAL_FOLDERS_READ_RANGE_MAX", { enumerable: true, get: function () { return local_folders_contract_1.LOCAL_FOLDERS_READ_RANGE_MAX; } });
102
+ Object.defineProperty(exports, "LOCAL_FOLDERS_RESERVED_DIR", { enumerable: true, get: function () { return local_folders_contract_1.LOCAL_FOLDERS_RESERVED_DIR; } });
103
+ Object.defineProperty(exports, "localFoldersProviderPackageId", { enumerable: true, get: function () { return local_folders_contract_1.localFoldersProviderPackageId; } });
94
104
  var logs_contract_1 = require("./system/logs-contract");
95
105
  Object.defineProperty(exports, "getLogsContractDefinition", { enumerable: true, get: function () { return logs_contract_1.getLogsContractDefinition; } });
96
106
  Object.defineProperty(exports, "LOGS_CONTRACT_VERSION", { enumerable: true, get: function () { return logs_contract_1.LOGS_CONTRACT_VERSION; } });
@@ -0,0 +1,163 @@
1
+ import type { IContractDefinition } from "../types";
2
+ /** Version of the built-in System Local Folders contract. */
3
+ export declare const LOCAL_FOLDERS_CONTRACT_VERSION = 1;
4
+ /** Maximum byte length accepted by `readRange` in one call. */
5
+ export declare const LOCAL_FOLDERS_READ_RANGE_MAX: number;
6
+ /** FastCDC minimum block size. Files at or below this size are one block. */
7
+ export declare const LOCAL_FOLDERS_CHUNK_MIN: number;
8
+ /** FastCDC target average block size. */
9
+ export declare const LOCAL_FOLDERS_CHUNK_AVG: number;
10
+ /** FastCDC maximum block size. */
11
+ export declare const LOCAL_FOLDERS_CHUNK_MAX: number;
12
+ /** Directory name reserved inside every grant for trash and in-flight files. */
13
+ export declare const LOCAL_FOLDERS_RESERVED_DIR = ".peers";
14
+ /** How long finished index jobs stay readable via `getJob`. */
15
+ export declare const LOCAL_FOLDERS_JOB_TTL_MS: number;
16
+ /** Package ID used by live System Local Folders tool instances. */
17
+ export declare const localFoldersProviderPackageId = "000peers0system0000000001";
18
+ /** Grant mode accepted by `requestGrant`. */
19
+ export type LocalFolderMode = "read" | "readwrite";
20
+ /** Filesystem entry kind returned by `list` and `stat`. */
21
+ export type LocalFolderEntryKind = "file" | "dir" | "symlink";
22
+ /** Watcher change kind delivered on the `changed` event. */
23
+ export type LocalFolderChangeKind = "created" | "modified" | "deleted" | "renamed";
24
+ /** Input for {@link ILocalFoldersConsumer tools.requestGrant}. */
25
+ export interface ILocalFolderRequestGrantInput {
26
+ groupId: string;
27
+ mode: LocalFolderMode;
28
+ /** Operator-only. Ignored when an isolated package is the caller. */
29
+ packageId?: string;
30
+ /**
31
+ * Operator-only absolute path. Isolated callers cannot set this.
32
+ * Omit it on a desktop host to open the folder picker.
33
+ */
34
+ absolutePath?: string;
35
+ }
36
+ /** One active grant returned by `requestGrant` and `listGrants`. */
37
+ export interface ILocalFolderGrantInfo {
38
+ grantId: string;
39
+ groupId: string;
40
+ absolutePath: string;
41
+ mode: LocalFolderMode;
42
+ packageId?: string;
43
+ createdAt: number;
44
+ }
45
+ /** Input for listing a directory inside a grant. */
46
+ export interface ILocalFolderListInput {
47
+ grantId: string;
48
+ /** Relative path. Empty lists the grant root. */
49
+ relPath?: string;
50
+ recursive?: boolean;
51
+ }
52
+ /** One directory entry. */
53
+ export interface ILocalFolderEntry {
54
+ relPath: string;
55
+ name: string;
56
+ kind: LocalFolderEntryKind;
57
+ size: number;
58
+ mtimeMs: number;
59
+ inode: string;
60
+ }
61
+ /** Input for `stat`. */
62
+ export interface ILocalFolderStatInput {
63
+ grantId: string;
64
+ relPath: string;
65
+ }
66
+ /** Input for a bounded read. `length` is capped at {@link LOCAL_FOLDERS_READ_RANGE_MAX}. */
67
+ export interface ILocalFolderReadRangeInput {
68
+ grantId: string;
69
+ relPath: string;
70
+ offset: number;
71
+ length: number;
72
+ }
73
+ /** Bytes returned by `readRange`. `data` is a base64 string so it survives JSON tool results. */
74
+ export interface ILocalFolderReadRangeResult {
75
+ dataBase64: string;
76
+ bytesRead: number;
77
+ }
78
+ /** Input for starting an index job. */
79
+ export interface ILocalFolderIndexInput {
80
+ grantId: string;
81
+ /** Relative directory or file. Empty indexes the whole grant. */
82
+ relPath?: string;
83
+ }
84
+ /** Handle returned by `index`. */
85
+ export interface ILocalFolderJobHandle {
86
+ jobId: string;
87
+ }
88
+ /** Input for `getFileBlocks`. */
89
+ export interface ILocalFolderFileBlocksInput {
90
+ grantId: string;
91
+ /** Relative file path. */
92
+ relPath: string;
93
+ }
94
+ /** Indexed blocks for one file. `indexed` is false when the path has no index row. */
95
+ export interface ILocalFolderFileBlocks {
96
+ indexed: boolean;
97
+ size?: number;
98
+ mtimeMs?: number;
99
+ blocks: {
100
+ hash: string;
101
+ size: number;
102
+ }[];
103
+ }
104
+ /** Index job snapshot returned by `getJob`. */
105
+ export interface ILocalFolderJobInfo {
106
+ jobId: string;
107
+ kind: "index";
108
+ status: "running" | "done" | "error" | "cancelled";
109
+ done: number;
110
+ total: number;
111
+ error?: string;
112
+ result?: {
113
+ files: number;
114
+ rehashed: number;
115
+ skipped: number;
116
+ removed: number;
117
+ };
118
+ }
119
+ /** Input for `move`. */
120
+ export interface ILocalFolderMoveInput {
121
+ grantId: string;
122
+ from: string;
123
+ to: string;
124
+ }
125
+ /** Input for `trash` and single-path tools. */
126
+ export interface ILocalFolderPathInput {
127
+ grantId: string;
128
+ relPath: string;
129
+ }
130
+ /** Input for `purgeTrash`. */
131
+ export interface ILocalFolderPurgeTrashInput {
132
+ grantId: string;
133
+ olderThanDays: number;
134
+ }
135
+ /** One path change inside a grant. */
136
+ export interface ILocalFolderChange {
137
+ relPath: string;
138
+ kind: LocalFolderChangeKind;
139
+ }
140
+ /** Payload of the `changed` event. */
141
+ export interface ILocalFolderChangedEvent {
142
+ grantId: string;
143
+ changes: ILocalFolderChange[];
144
+ }
145
+ /** Payload of the `jobProgress` event. */
146
+ export interface ILocalFolderJobProgressEvent {
147
+ jobId: string;
148
+ done: number;
149
+ total: number;
150
+ }
151
+ /** Payload of the `jobFinished` event. */
152
+ export interface ILocalFolderJobFinishedEvent {
153
+ jobId: string;
154
+ status: "done" | "error" | "cancelled";
155
+ error?: string;
156
+ }
157
+ /**
158
+ * Return the local-only System Local Folders contract shape.
159
+ *
160
+ * The host checks every path against an operator grant. Remote devices cannot call
161
+ * this contract; bytes between devices move through block exchange, not these tools.
162
+ */
163
+ export declare function getLocalFoldersContractDefinition(): IContractDefinition;
@@ -0,0 +1,235 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.localFoldersProviderPackageId = exports.LOCAL_FOLDERS_JOB_TTL_MS = exports.LOCAL_FOLDERS_RESERVED_DIR = exports.LOCAL_FOLDERS_CHUNK_MAX = exports.LOCAL_FOLDERS_CHUNK_AVG = exports.LOCAL_FOLDERS_CHUNK_MIN = exports.LOCAL_FOLDERS_READ_RANGE_MAX = exports.LOCAL_FOLDERS_CONTRACT_VERSION = void 0;
4
+ exports.getLocalFoldersContractDefinition = getLocalFoldersContractDefinition;
5
+ const system_ids_1 = require("../../system-ids");
6
+ const field_type_1 = require("../../types/field-type");
7
+ const system_contracts_1 = require("../system-contracts");
8
+ /** Version of the built-in System Local Folders contract. */
9
+ exports.LOCAL_FOLDERS_CONTRACT_VERSION = 1;
10
+ /** Maximum byte length accepted by `readRange` in one call. */
11
+ exports.LOCAL_FOLDERS_READ_RANGE_MAX = 256 * 1024;
12
+ /** FastCDC minimum block size. Files at or below this size are one block. */
13
+ exports.LOCAL_FOLDERS_CHUNK_MIN = 256 * 1024;
14
+ /** FastCDC target average block size. */
15
+ exports.LOCAL_FOLDERS_CHUNK_AVG = 1024 * 1024;
16
+ /** FastCDC maximum block size. */
17
+ exports.LOCAL_FOLDERS_CHUNK_MAX = 4 * 1024 * 1024;
18
+ /** Directory name reserved inside every grant for trash and in-flight files. */
19
+ exports.LOCAL_FOLDERS_RESERVED_DIR = ".peers";
20
+ /** How long finished index jobs stay readable via `getJob`. */
21
+ exports.LOCAL_FOLDERS_JOB_TTL_MS = 60 * 60 * 1000;
22
+ /** Package ID used by live System Local Folders tool instances. */
23
+ exports.localFoldersProviderPackageId = system_ids_1.peersSystemPackageId;
24
+ const field = (name, type, description, opts = {}) => ({ name, type, description, ...opts });
25
+ const grantIdField = field("grantId", field_type_1.FieldType.id, "Folder grant id.");
26
+ let definition;
27
+ /**
28
+ * Return the local-only System Local Folders contract shape.
29
+ *
30
+ * The host checks every path against an operator grant. Remote devices cannot call
31
+ * this contract; bytes between devices move through block exchange, not these tools.
32
+ */
33
+ function getLocalFoldersContractDefinition() {
34
+ definition ??= {
35
+ contractId: system_ids_1.localFoldersContractId,
36
+ version: exports.LOCAL_FOLDERS_CONTRACT_VERSION,
37
+ devTag: "dev",
38
+ name: "System Local Folders",
39
+ description: "Scoped access to operator-granted folders on this device: listing, bounded reads, indexing, move, and trash.",
40
+ tables: [],
41
+ tools: [
42
+ {
43
+ name: "requestGrant",
44
+ usageDescription: "Grant a local folder. Omit absolutePath on desktop to open the folder picker. Isolated packages cannot create grants.",
45
+ inputFields: [
46
+ field("groupId", field_type_1.FieldType.id, "Group this folder is associated with."),
47
+ field("mode", field_type_1.FieldType.string, 'Either "read" or "readwrite".'),
48
+ field("packageId", field_type_1.FieldType.id, "Package allowed to use the grant.", {
49
+ optional: true,
50
+ }),
51
+ field("absolutePath", field_type_1.FieldType.string, "Absolute directory path. Operator callers only. Omit to open the picker.", { optional: true }),
52
+ ],
53
+ outputFields: [
54
+ grantIdField,
55
+ field("groupId", field_type_1.FieldType.id, "Group id stored on the grant."),
56
+ field("absolutePath", field_type_1.FieldType.string, "Absolute path of the granted root."),
57
+ field("mode", field_type_1.FieldType.string, "Grant mode."),
58
+ field("packageId", field_type_1.FieldType.id, "Package the grant is scoped to.", { optional: true }),
59
+ field("createdAt", field_type_1.FieldType.number, "Creation time in unix epoch milliseconds."),
60
+ ],
61
+ },
62
+ {
63
+ name: "listGrants",
64
+ usageDescription: "List active grants. Isolated callers see only grants scoped to their package.",
65
+ inputFields: [],
66
+ outputFields: [
67
+ field("grants", field_type_1.FieldType.object, "Active grants on this device.", { isArray: true }),
68
+ ],
69
+ },
70
+ {
71
+ name: "revokeGrant",
72
+ usageDescription: "Revoke a grant. Access stops immediately. Isolated callers can revoke only their own grants.",
73
+ inputFields: [grantIdField],
74
+ outputFields: [field("revoked", field_type_1.FieldType.boolean, "Whether the grant is now revoked.")],
75
+ },
76
+ {
77
+ name: "list",
78
+ usageDescription: "List entries under a relative path inside a grant. Skips .peers.",
79
+ inputFields: [
80
+ grantIdField,
81
+ field("relPath", field_type_1.FieldType.string, "Relative directory. Empty lists the root.", {
82
+ optional: true,
83
+ }),
84
+ field("recursive", field_type_1.FieldType.boolean, "List the subtree.", { optional: true }),
85
+ ],
86
+ outputFields: [field("entries", field_type_1.FieldType.object, "Directory entries.", { isArray: true })],
87
+ },
88
+ {
89
+ name: "stat",
90
+ usageDescription: "Stat one relative path inside a grant.",
91
+ inputFields: [
92
+ grantIdField,
93
+ field("relPath", field_type_1.FieldType.string, "Relative path inside the grant."),
94
+ ],
95
+ outputFields: [
96
+ field("relPath", field_type_1.FieldType.string, "Relative path."),
97
+ field("name", field_type_1.FieldType.string, "Final path segment."),
98
+ field("kind", field_type_1.FieldType.string, "file, dir, or symlink."),
99
+ field("size", field_type_1.FieldType.number, "Size in bytes."),
100
+ field("mtimeMs", field_type_1.FieldType.number, "mtime in unix epoch milliseconds."),
101
+ field("inode", field_type_1.FieldType.string, "File identity string."),
102
+ ],
103
+ },
104
+ {
105
+ name: "readRange",
106
+ usageDescription: `Read up to ${exports.LOCAL_FOLDERS_READ_RANGE_MAX} bytes at an offset. Returns base64.`,
107
+ inputFields: [
108
+ grantIdField,
109
+ field("relPath", field_type_1.FieldType.string, "Relative file path."),
110
+ field("offset", field_type_1.FieldType.number, "Byte offset."),
111
+ field("length", field_type_1.FieldType.number, "Requested length, capped by the host."),
112
+ ],
113
+ outputFields: [
114
+ field("dataBase64", field_type_1.FieldType.string, "File bytes encoded as base64."),
115
+ field("bytesRead", field_type_1.FieldType.number, "Number of bytes read."),
116
+ ],
117
+ },
118
+ {
119
+ name: "index",
120
+ usageDescription: "Start a host job that content-defines and hashes files into the local block index. Requires a readwrite grant.",
121
+ inputFields: [
122
+ grantIdField,
123
+ field("relPath", field_type_1.FieldType.string, "Relative file or directory. Empty indexes the grant.", {
124
+ optional: true,
125
+ }),
126
+ ],
127
+ outputFields: [field("jobId", field_type_1.FieldType.id, "Index job id.")],
128
+ },
129
+ {
130
+ name: "getFileBlocks",
131
+ usageDescription: "Return the indexed content-defined blocks for one file. Read mode is enough. indexed is false when the file has not been indexed.",
132
+ inputFields: [
133
+ grantIdField,
134
+ field("relPath", field_type_1.FieldType.string, "Relative file path inside the grant."),
135
+ ],
136
+ outputFields: [
137
+ field("indexed", field_type_1.FieldType.boolean, "Whether an index row exists for this path."),
138
+ field("size", field_type_1.FieldType.number, "File size in bytes at index time.", { optional: true }),
139
+ field("mtimeMs", field_type_1.FieldType.number, "mtime in unix epoch milliseconds.", {
140
+ optional: true,
141
+ }),
142
+ field("blocks", field_type_1.FieldType.object, "Ordered blocks with hash and size.", {
143
+ isArray: true,
144
+ }),
145
+ ],
146
+ },
147
+ {
148
+ name: "getJob",
149
+ usageDescription: "Return the status of an index job, including after it finishes.",
150
+ inputFields: [field("jobId", field_type_1.FieldType.id, "Job id returned by index.")],
151
+ outputFields: [
152
+ field("jobId", field_type_1.FieldType.id, "Job id."),
153
+ field("kind", field_type_1.FieldType.string, "Job kind. v1 is index."),
154
+ field("status", field_type_1.FieldType.string, "running, done, error, or cancelled."),
155
+ field("done", field_type_1.FieldType.number, "Units completed."),
156
+ field("total", field_type_1.FieldType.number, "Units discovered."),
157
+ field("error", field_type_1.FieldType.string, "Failure message.", { optional: true }),
158
+ field("result", field_type_1.FieldType.object, "Index counts when status is done.", {
159
+ optional: true,
160
+ }),
161
+ ],
162
+ },
163
+ {
164
+ name: "cancelJob",
165
+ usageDescription: "Cancel a running index job.",
166
+ inputFields: [field("jobId", field_type_1.FieldType.id, "Job id returned by index.")],
167
+ outputFields: [field("cancelled", field_type_1.FieldType.boolean, "Whether the job was cancelled.")],
168
+ },
169
+ {
170
+ name: "move",
171
+ usageDescription: "Move a path inside a readwrite grant. Atomic where the OS allows.",
172
+ inputFields: [
173
+ grantIdField,
174
+ field("from", field_type_1.FieldType.string, "Relative source path."),
175
+ field("to", field_type_1.FieldType.string, "Relative destination path."),
176
+ ],
177
+ outputFields: [field("moved", field_type_1.FieldType.boolean, "Whether the move completed.")],
178
+ },
179
+ {
180
+ name: "trash",
181
+ usageDescription: "Move a path to .peers/trash/<date>/ inside a readwrite grant.",
182
+ inputFields: [grantIdField, field("relPath", field_type_1.FieldType.string, "Relative path to trash.")],
183
+ outputFields: [
184
+ field("trashed", field_type_1.FieldType.boolean, "Whether the path was moved to trash."),
185
+ field("trashPath", field_type_1.FieldType.string, "Relative path inside the grant trash."),
186
+ ],
187
+ },
188
+ {
189
+ name: "purgeTrash",
190
+ usageDescription: "Delete trash entries older than the given number of days.",
191
+ inputFields: [
192
+ grantIdField,
193
+ field("olderThanDays", field_type_1.FieldType.number, "Delete trash dated strictly before this many days ago."),
194
+ ],
195
+ outputFields: [field("removed", field_type_1.FieldType.number, "Number of trash entries removed.")],
196
+ },
197
+ ],
198
+ observables: [],
199
+ events: [
200
+ {
201
+ name: "changed",
202
+ description: "Debounced filesystem changes inside a grant. The reserved .peers directory is omitted. v1 hosts emit created, modified, and deleted. renamed is reserved for a later host.",
203
+ payloadFields: [
204
+ grantIdField,
205
+ field("changes", field_type_1.FieldType.object, "Changes with relPath and kind. v1 hosts emit created, modified, or deleted. renamed is reserved.", { isArray: true }),
206
+ ],
207
+ },
208
+ {
209
+ name: "jobProgress",
210
+ description: "Progress for a host index job.",
211
+ payloadFields: [
212
+ field("jobId", field_type_1.FieldType.id, "Job id."),
213
+ field("done", field_type_1.FieldType.number, "Units completed."),
214
+ field("total", field_type_1.FieldType.number, "Units discovered."),
215
+ ],
216
+ },
217
+ {
218
+ name: "jobFinished",
219
+ description: "An index job finished, failed, or was cancelled.",
220
+ payloadFields: [
221
+ field("jobId", field_type_1.FieldType.id, "Job id."),
222
+ field("status", field_type_1.FieldType.string, "done, error, or cancelled."),
223
+ field("error", field_type_1.FieldType.string, "Failure message.", { optional: true }),
224
+ ],
225
+ },
226
+ ],
227
+ };
228
+ return definition;
229
+ }
230
+ (0, system_contracts_1.registerSystemContract)({
231
+ providerPackageId: system_ids_1.peersSystemPackageId,
232
+ contractId: system_ids_1.localFoldersContractId,
233
+ version: exports.LOCAL_FOLDERS_CONTRACT_VERSION,
234
+ getDefinition: getLocalFoldersContractDefinition,
235
+ });
@@ -14,6 +14,7 @@ export * from "./group-secret";
14
14
  export * from "./group-share";
15
15
  export * from "./groups";
16
16
  export * from "./invites";
17
+ export * from "./local-folders";
17
18
  export * from "./messages";
18
19
  export * from "./package-table-identity";
19
20
  export * from "./package-version-resolver";
@@ -30,6 +30,7 @@ __exportStar(require("./group-secret"), exports);
30
30
  __exportStar(require("./group-share"), exports);
31
31
  __exportStar(require("./groups"), exports);
32
32
  __exportStar(require("./invites"), exports);
33
+ __exportStar(require("./local-folders"), exports);
33
34
  __exportStar(require("./messages"), exports);
34
35
  __exportStar(require("./package-table-identity"), exports);
35
36
  __exportStar(require("./package-version-resolver"), exports);
@@ -0,0 +1,176 @@
1
+ import { z } from "zod";
2
+ import type { DataContext } from "../context/data-context";
3
+ import { type ITableMetaData } from "./orm/types";
4
+ /** Content-defined block descriptor stored on an indexed file. */
5
+ export declare const localFolderBlockSchema: z.ZodObject<{
6
+ hash: z.ZodString;
7
+ size: z.ZodNumber;
8
+ }, "strip", z.ZodTypeAny, {
9
+ size: number;
10
+ hash: string;
11
+ }, {
12
+ size: number;
13
+ hash: string;
14
+ }>;
15
+ /** One content-defined block: hash and length. */
16
+ export type ILocalFolderBlock = z.infer<typeof localFolderBlockSchema>;
17
+ /** Access mode recorded on a folder grant. */
18
+ export declare const localFolderGrantModeSchema: z.ZodEnum<["read", "readwrite"]>;
19
+ /** `read` lists and reads; `readwrite` may also move, trash, and index. */
20
+ export type LocalFolderGrantMode = z.infer<typeof localFolderGrantModeSchema>;
21
+ /**
22
+ * Operator grant of one absolute folder to an optional package.
23
+ * Stored only on this device, in the personal database.
24
+ */
25
+ export declare const localFolderGrantSchema: z.ZodObject<{
26
+ grantId: z.ZodEffects<z.ZodString, string, string>;
27
+ packageId: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
28
+ groupId: z.ZodEffects<z.ZodString, string, string>;
29
+ absolutePath: z.ZodString;
30
+ mode: z.ZodEnum<["read", "readwrite"]>;
31
+ createdAt: z.ZodNumber;
32
+ revokedAt: z.ZodOptional<z.ZodNumber>;
33
+ }, "strip", z.ZodTypeAny, {
34
+ groupId: string;
35
+ createdAt: number;
36
+ mode: "readwrite" | "read";
37
+ grantId: string;
38
+ absolutePath: string;
39
+ packageId?: string | undefined;
40
+ revokedAt?: number | undefined;
41
+ }, {
42
+ groupId: string;
43
+ createdAt: number;
44
+ mode: "readwrite" | "read";
45
+ grantId: string;
46
+ absolutePath: string;
47
+ packageId?: string | undefined;
48
+ revokedAt?: number | undefined;
49
+ }>;
50
+ /** A device-local folder grant. */
51
+ export type ILocalFolderGrant = z.infer<typeof localFolderGrantSchema>;
52
+ /** Indexed file under a grant. One row per `(grantId, relPath)`. */
53
+ export declare const localFolderFileSchema: z.ZodObject<{
54
+ fileIndexId: z.ZodEffects<z.ZodString, string, string>;
55
+ grantId: z.ZodEffects<z.ZodString, string, string>;
56
+ relPath: z.ZodString;
57
+ size: z.ZodNumber;
58
+ mtimeMs: z.ZodNumber;
59
+ inode: z.ZodString;
60
+ blocks: z.ZodArray<z.ZodObject<{
61
+ hash: z.ZodString;
62
+ size: z.ZodNumber;
63
+ }, "strip", z.ZodTypeAny, {
64
+ size: number;
65
+ hash: string;
66
+ }, {
67
+ size: number;
68
+ hash: string;
69
+ }>, "many">;
70
+ }, "strip", z.ZodTypeAny, {
71
+ size: number;
72
+ grantId: string;
73
+ relPath: string;
74
+ mtimeMs: number;
75
+ inode: string;
76
+ blocks: {
77
+ size: number;
78
+ hash: string;
79
+ }[];
80
+ fileIndexId: string;
81
+ }, {
82
+ size: number;
83
+ grantId: string;
84
+ relPath: string;
85
+ mtimeMs: number;
86
+ inode: string;
87
+ blocks: {
88
+ size: number;
89
+ hash: string;
90
+ }[];
91
+ fileIndexId: string;
92
+ }>;
93
+ /** A local index row for one file. */
94
+ export type ILocalFolderFile = z.infer<typeof localFolderFileSchema>;
95
+ /**
96
+ * One servable block location. The same hash may appear in several files;
97
+ * `blockHash` is indexed, not unique.
98
+ */
99
+ export declare const localFolderBlockRowSchema: z.ZodObject<{
100
+ blockRowId: z.ZodEffects<z.ZodString, string, string>;
101
+ blockHash: z.ZodString;
102
+ grantId: z.ZodEffects<z.ZodString, string, string>;
103
+ relPath: z.ZodString;
104
+ offset: z.ZodNumber;
105
+ length: z.ZodNumber;
106
+ mtimeMs: z.ZodNumber;
107
+ }, "strip", z.ZodTypeAny, {
108
+ length: number;
109
+ offset: number;
110
+ grantId: string;
111
+ relPath: string;
112
+ mtimeMs: number;
113
+ blockRowId: string;
114
+ blockHash: string;
115
+ }, {
116
+ length: number;
117
+ offset: number;
118
+ grantId: string;
119
+ relPath: string;
120
+ mtimeMs: number;
121
+ blockRowId: string;
122
+ blockHash: string;
123
+ }>;
124
+ /** A local index row mapping a block hash to a byte range in a real file. */
125
+ export type ILocalFolderBlockRow = z.infer<typeof localFolderBlockRowSchema>;
126
+ /** Table metadata for {@link LocalFolderGrants}. */
127
+ export declare const localFolderGrantsMetaData: ITableMetaData;
128
+ /** Table metadata for {@link LocalFolderFiles}. */
129
+ export declare const localFolderFilesMetaData: ITableMetaData;
130
+ /** Table metadata for {@link LocalFolderBlocks}. */
131
+ export declare const localFolderBlocksMetaData: ITableMetaData;
132
+ /**
133
+ * Device-local folder grants. Pass the personal data context; these rows do not sync.
134
+ *
135
+ * @param dataContext Context whose database holds the grants. Defaults to the current context.
136
+ */
137
+ export declare function LocalFolderGrants(dataContext?: DataContext): import("..").Table<{
138
+ groupId: string;
139
+ createdAt: number;
140
+ mode: "readwrite" | "read";
141
+ grantId: string;
142
+ absolutePath: string;
143
+ packageId?: string | undefined;
144
+ revokedAt?: number | undefined;
145
+ }>;
146
+ /**
147
+ * Device-local indexed files. Pass the personal data context; these rows do not sync.
148
+ *
149
+ * @param dataContext Context whose database holds the index. Defaults to the current context.
150
+ */
151
+ export declare function LocalFolderFiles(dataContext?: DataContext): import("..").Table<{
152
+ size: number;
153
+ grantId: string;
154
+ relPath: string;
155
+ mtimeMs: number;
156
+ inode: string;
157
+ blocks: {
158
+ size: number;
159
+ hash: string;
160
+ }[];
161
+ fileIndexId: string;
162
+ }>;
163
+ /**
164
+ * Device-local block locations. Pass the personal data context; these rows do not sync.
165
+ *
166
+ * @param dataContext Context whose database holds the index. Defaults to the current context.
167
+ */
168
+ export declare function LocalFolderBlocks(dataContext?: DataContext): import("..").Table<{
169
+ length: number;
170
+ offset: number;
171
+ grantId: string;
172
+ relPath: string;
173
+ mtimeMs: number;
174
+ blockRowId: string;
175
+ blockHash: string;
176
+ }>;
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.localFolderBlocksMetaData = exports.localFolderFilesMetaData = exports.localFolderGrantsMetaData = exports.localFolderBlockRowSchema = exports.localFolderFileSchema = exports.localFolderGrantSchema = exports.localFolderGrantModeSchema = exports.localFolderBlockSchema = void 0;
4
+ exports.LocalFolderGrants = LocalFolderGrants;
5
+ exports.LocalFolderFiles = LocalFolderFiles;
6
+ exports.LocalFolderBlocks = LocalFolderBlocks;
7
+ const zod_1 = require("zod");
8
+ const user_context_singleton_1 = require("../context/user-context-singleton");
9
+ const zod_types_1 = require("../types/zod-types");
10
+ const table_definitions_system_1 = require("./orm/table-definitions.system");
11
+ const types_1 = require("./orm/types");
12
+ /** Content-defined block descriptor stored on an indexed file. */
13
+ exports.localFolderBlockSchema = zod_1.z.object({
14
+ hash: zod_1.z.string().describe("SHA-256 hex digest of the block bytes."),
15
+ size: zod_1.z.number().describe("Block length in bytes."),
16
+ });
17
+ /** Access mode recorded on a folder grant. */
18
+ exports.localFolderGrantModeSchema = zod_1.z.enum(["read", "readwrite"]);
19
+ /**
20
+ * Operator grant of one absolute folder to an optional package.
21
+ * Stored only on this device, in the personal database.
22
+ */
23
+ exports.localFolderGrantSchema = zod_1.z.object({
24
+ grantId: zod_types_1.zodPeerId.describe("Grant id."),
25
+ packageId: zod_types_1.zodPeerId
26
+ .optional()
27
+ .describe("Isolated package allowed to use this grant. Absent for operator-owned grants."),
28
+ groupId: zod_types_1.zodPeerId.describe("Group the folder is associated with."),
29
+ absolutePath: zod_1.z.string().describe("Absolute filesystem path of the granted root."),
30
+ mode: exports.localFolderGrantModeSchema.describe("Whether the grant allows writes."),
31
+ createdAt: zod_1.z.number().describe("Unix epoch milliseconds when the grant was created."),
32
+ revokedAt: zod_1.z
33
+ .number()
34
+ .optional()
35
+ .describe("Unix epoch milliseconds when the grant was revoked. Absent while active."),
36
+ });
37
+ /** Indexed file under a grant. One row per `(grantId, relPath)`. */
38
+ exports.localFolderFileSchema = zod_1.z.object({
39
+ fileIndexId: zod_types_1.zodPeerId.describe("Index row id."),
40
+ grantId: zod_types_1.zodPeerId.describe("Grant this file belongs to."),
41
+ relPath: zod_1.z.string().describe("Path relative to the grant root, `/`-separated, NFC."),
42
+ size: zod_1.z.number().describe("File size in bytes at index time."),
43
+ mtimeMs: zod_1.z.number().describe("File mtime in milliseconds at index time."),
44
+ inode: zod_1.z.string().describe("File identity string (`stat.ino`) at index time."),
45
+ blocks: zod_1.z.array(exports.localFolderBlockSchema).describe("Ordered content-defined blocks."),
46
+ });
47
+ /**
48
+ * One servable block location. The same hash may appear in several files;
49
+ * `blockHash` is indexed, not unique.
50
+ */
51
+ exports.localFolderBlockRowSchema = zod_1.z.object({
52
+ blockRowId: zod_types_1.zodPeerId.describe("Block-location row id."),
53
+ blockHash: zod_1.z.string().describe("SHA-256 hex digest of the block bytes."),
54
+ grantId: zod_types_1.zodPeerId.describe("Grant whose file contains the block."),
55
+ relPath: zod_1.z.string().describe("File path relative to the grant root."),
56
+ offset: zod_1.z.number().describe("Byte offset of the block within the file."),
57
+ length: zod_1.z.number().describe("Block length in bytes."),
58
+ mtimeMs: zod_1.z.number().describe("File mtime in milliseconds when the block was indexed."),
59
+ });
60
+ const grantMetaData = {
61
+ name: "LocalFolderGrants",
62
+ description: "Device-local scoped folder grants for System Local Folders.",
63
+ primaryKeyName: "grantId",
64
+ fields: (0, types_1.schemaToFields)(exports.localFolderGrantSchema),
65
+ localOnly: true,
66
+ indexes: [{ fields: ["packageId"] }, { fields: ["groupId"] }],
67
+ };
68
+ const fileMetaData = {
69
+ name: "LocalFolderFiles",
70
+ description: "Device-local file index for System Local Folders.",
71
+ primaryKeyName: "fileIndexId",
72
+ fields: (0, types_1.schemaToFields)(exports.localFolderFileSchema),
73
+ localOnly: true,
74
+ indexes: [{ fields: ["grantId", "relPath"], unique: true }, { fields: ["grantId"] }],
75
+ };
76
+ const blockMetaData = {
77
+ name: "LocalFolderBlocks",
78
+ description: "Device-local block locations for System Local Folders.",
79
+ primaryKeyName: "blockRowId",
80
+ fields: (0, types_1.schemaToFields)(exports.localFolderBlockRowSchema),
81
+ localOnly: true,
82
+ indexes: [{ fields: ["blockHash"] }, { fields: ["grantId", "relPath"] }],
83
+ };
84
+ (0, table_definitions_system_1.registerSystemTableDefinition)(grantMetaData, exports.localFolderGrantSchema);
85
+ (0, table_definitions_system_1.registerSystemTableDefinition)(fileMetaData, exports.localFolderFileSchema);
86
+ (0, table_definitions_system_1.registerSystemTableDefinition)(blockMetaData, exports.localFolderBlockRowSchema);
87
+ /** Table metadata for {@link LocalFolderGrants}. */
88
+ exports.localFolderGrantsMetaData = grantMetaData;
89
+ /** Table metadata for {@link LocalFolderFiles}. */
90
+ exports.localFolderFilesMetaData = fileMetaData;
91
+ /** Table metadata for {@link LocalFolderBlocks}. */
92
+ exports.localFolderBlocksMetaData = blockMetaData;
93
+ /**
94
+ * Device-local folder grants. Pass the personal data context; these rows do not sync.
95
+ *
96
+ * @param dataContext Context whose database holds the grants. Defaults to the current context.
97
+ */
98
+ function LocalFolderGrants(dataContext) {
99
+ return (0, user_context_singleton_1.getTableContainer)(dataContext).getTable(grantMetaData, exports.localFolderGrantSchema);
100
+ }
101
+ /**
102
+ * Device-local indexed files. Pass the personal data context; these rows do not sync.
103
+ *
104
+ * @param dataContext Context whose database holds the index. Defaults to the current context.
105
+ */
106
+ function LocalFolderFiles(dataContext) {
107
+ return (0, user_context_singleton_1.getTableContainer)(dataContext).getTable(fileMetaData, exports.localFolderFileSchema);
108
+ }
109
+ /**
110
+ * Device-local block locations. Pass the personal data context; these rows do not sync.
111
+ *
112
+ * @param dataContext Context whose database holds the index. Defaults to the current context.
113
+ */
114
+ function LocalFolderBlocks(dataContext) {
115
+ return (0, user_context_singleton_1.getTableContainer)(dataContext).getTable(blockMetaData, exports.localFolderBlockRowSchema);
116
+ }
@@ -93,3 +93,25 @@ export declare const identityToolIds: {
93
93
  readonly signOut: "00muc9mlzxwdm2yj5p5wkjvz7";
94
94
  readonly registerWithPeersServices: "00muc9mlzx07q98l3f9x49o10";
95
95
  };
96
+ /**
97
+ * Contract id for the local-only System Local Folders surface (`system.fs`).
98
+ * Scoped folder grants, directory listing, and host-side indexing. Not available
99
+ * over device-to-device connections; block exchange is a separate path.
100
+ */
101
+ export declare const localFoldersContractId = "00mukk9dwb1mjtd845kftuazm";
102
+ /** Stable IDs for tools in System Local Folders v1. */
103
+ export declare const localFoldersToolIds: {
104
+ readonly requestGrant: "00mukk9dwdcv4f99ydwxao97f";
105
+ readonly listGrants: "00mukk9dwdq119xmvz4ap8aum";
106
+ readonly revokeGrant: "00mukk9dwdezt5sbjviq1n1sp";
107
+ readonly list: "00mukk9dwd6mxscrv9eeaanai";
108
+ readonly stat: "00mukk9dwdon2y2487clcg67k";
109
+ readonly readRange: "00mukk9dwdih6mh8rrofph80o";
110
+ readonly index: "00mukk9dwdm01bxg3hrby6pli";
111
+ readonly getFileBlocks: "00mumw1pmmf3y2ujfdc99h4ib";
112
+ readonly getJob: "00mukk9dwd1c1f51xjysssgtv";
113
+ readonly cancelJob: "00mukk9dweizvif4ghp6kfa92";
114
+ readonly move: "00mukk9dwer8wsw5eiwis8tzm";
115
+ readonly trash: "00mukk9dwejbuehqzszpyxefx";
116
+ readonly purgeTrash: "00mukk9dwe6co8gvuy44kmuzd";
117
+ };
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.identityToolIds = exports.identityContractId = exports.invitesToolIds = exports.invitesContractId = exports.toolAccessControlToolIds = exports.toolAccessControlContractId = exports.networkControlContractId = exports.networkDiagnosticsContractId = exports.deviceOperationsContractId = exports.logsContractId = exports.peersSystemPackageId = exports.peersCorePublishPublicKey = exports.peersCorePackageRepoUrl = exports.peersCorePackageId = exports.createGroupToolId = exports.queryDatabaseToolId = exports.runWorkflowToolId = exports.defaultSendMessageToolId = exports.defaultAssistantRunnerToolId = exports.adminAssistantId = exports.openCodeAssistantId = exports.defaultAssistantId = exports.peersRootUserId = void 0;
3
+ exports.localFoldersToolIds = exports.localFoldersContractId = exports.identityToolIds = exports.identityContractId = exports.invitesToolIds = exports.invitesContractId = exports.toolAccessControlToolIds = exports.toolAccessControlContractId = exports.networkControlContractId = exports.networkDiagnosticsContractId = exports.deviceOperationsContractId = exports.logsContractId = exports.peersSystemPackageId = exports.peersCorePublishPublicKey = exports.peersCorePackageRepoUrl = exports.peersCorePackageId = exports.createGroupToolId = exports.queryDatabaseToolId = exports.runWorkflowToolId = exports.defaultSendMessageToolId = exports.defaultAssistantRunnerToolId = exports.adminAssistantId = exports.openCodeAssistantId = exports.defaultAssistantId = exports.peersRootUserId = void 0;
4
4
  /** Stable user ID that owns built-in Peers system records. */
5
5
  exports.peersRootUserId = "000peers0user000000000001";
6
6
  /** Default assistant used when no user-selected assistant is available. */
@@ -97,3 +97,25 @@ exports.identityToolIds = {
97
97
  signOut: "00muc9mlzxwdm2yj5p5wkjvz7",
98
98
  registerWithPeersServices: "00muc9mlzx07q98l3f9x49o10",
99
99
  };
100
+ /**
101
+ * Contract id for the local-only System Local Folders surface (`system.fs`).
102
+ * Scoped folder grants, directory listing, and host-side indexing. Not available
103
+ * over device-to-device connections; block exchange is a separate path.
104
+ */
105
+ exports.localFoldersContractId = "00mukk9dwb1mjtd845kftuazm";
106
+ /** Stable IDs for tools in System Local Folders v1. */
107
+ exports.localFoldersToolIds = {
108
+ requestGrant: "00mukk9dwdcv4f99ydwxao97f",
109
+ listGrants: "00mukk9dwdq119xmvz4ap8aum",
110
+ revokeGrant: "00mukk9dwdezt5sbjviq1n1sp",
111
+ list: "00mukk9dwd6mxscrv9eeaanai",
112
+ stat: "00mukk9dwdon2y2487clcg67k",
113
+ readRange: "00mukk9dwdih6mh8rrofph80o",
114
+ index: "00mukk9dwdm01bxg3hrby6pli",
115
+ getFileBlocks: "00mumw1pmmf3y2ujfdc99h4ib",
116
+ getJob: "00mukk9dwd1c1f51xjysssgtv",
117
+ cancelJob: "00mukk9dweizvif4ghp6kfa92",
118
+ move: "00mukk9dwer8wsw5eiwis8tzm",
119
+ trash: "00mukk9dwejbuehqzszpyxefx",
120
+ purgeTrash: "00mukk9dwe6co8gvuy44kmuzd",
121
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peers-app/peers-sdk",
3
- "version": "0.25.9",
3
+ "version": "0.25.10",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/peers-app/peers-sdk.git"