@peers-app/peers-sdk 0.19.13 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/contracts/__tests__/builder.test.js +24 -0
  2. package/dist/contracts/__tests__/contract-events.test.d.ts +1 -0
  3. package/dist/contracts/__tests__/contract-events.test.js +199 -0
  4. package/dist/contracts/__tests__/contract-observables.test.d.ts +1 -0
  5. package/dist/contracts/__tests__/contract-observables.test.js +290 -0
  6. package/dist/contracts/__tests__/contract-provider-router.test.d.ts +1 -0
  7. package/dist/contracts/__tests__/contract-provider-router.test.js +193 -0
  8. package/dist/contracts/__tests__/contract-proxy.test.d.ts +1 -0
  9. package/dist/contracts/__tests__/contract-proxy.test.js +424 -0
  10. package/dist/contracts/__tests__/contract-resolvers.test.d.ts +1 -0
  11. package/dist/contracts/__tests__/contract-resolvers.test.js +148 -0
  12. package/dist/contracts/__tests__/contract-subscription-lifecycle.test.d.ts +1 -0
  13. package/dist/contracts/__tests__/contract-subscription-lifecycle.test.js +353 -0
  14. package/dist/contracts/__tests__/contract-table-events.test.d.ts +1 -0
  15. package/dist/contracts/__tests__/contract-table-events.test.js +209 -0
  16. package/dist/contracts/__tests__/contract-transport.test.d.ts +1 -0
  17. package/dist/contracts/__tests__/contract-transport.test.js +163 -0
  18. package/dist/contracts/__tests__/persistent-registry.test.d.ts +1 -0
  19. package/dist/contracts/__tests__/persistent-registry.test.js +109 -0
  20. package/dist/contracts/__tests__/playground.test.d.ts +1 -0
  21. package/dist/contracts/__tests__/playground.test.js +213 -0
  22. package/dist/contracts/__tests__/validate.test.js +122 -0
  23. package/dist/contracts/builder.d.ts +9 -1
  24. package/dist/contracts/builder.js +13 -0
  25. package/dist/contracts/contract-provider-router.d.ts +72 -0
  26. package/dist/contracts/contract-provider-router.js +164 -0
  27. package/dist/contracts/contract-proxy.d.ts +333 -0
  28. package/dist/contracts/contract-proxy.js +663 -0
  29. package/dist/contracts/contract-resolvers.d.ts +66 -0
  30. package/dist/contracts/contract-resolvers.js +138 -0
  31. package/dist/contracts/contract-transport.d.ts +94 -0
  32. package/dist/contracts/contract-transport.js +159 -0
  33. package/dist/contracts/contracts.table.d.ts +1 -1
  34. package/dist/contracts/contracts.table.js +2 -2
  35. package/dist/contracts/index.d.ts +6 -1
  36. package/dist/contracts/index.js +25 -1
  37. package/dist/contracts/persistent-registry.js +3 -1
  38. package/dist/contracts/system/logs-contract.d.ts +24 -0
  39. package/dist/contracts/system/logs-contract.js +60 -0
  40. package/dist/contracts/types.d.ts +27 -4
  41. package/dist/contracts/validate.d.ts +2 -2
  42. package/dist/contracts/validate.js +44 -2
  43. package/dist/data/orm/decorators.d.ts +6 -0
  44. package/dist/data/orm/decorators.js +21 -0
  45. package/dist/data/user-trust-levels.d.ts +2 -0
  46. package/dist/data/user-trust-levels.js +2 -1
  47. package/dist/data/user-trust-levels.test.d.ts +1 -0
  48. package/dist/data/user-trust-levels.test.js +37 -0
  49. package/dist/device/binary-peer-connection-v2.d.ts +2 -0
  50. package/dist/device/binary-peer-connection-v2.js +12 -2
  51. package/dist/device/binary-peer-connection-v2.test.js +23 -0
  52. package/dist/device/get-trust-level-fn.d.ts +6 -0
  53. package/dist/device/get-trust-level-fn.js +12 -7
  54. package/dist/device/get-trust-level-fn.test.js +16 -2
  55. package/dist/logging/console-logs.table.d.ts +3 -0
  56. package/dist/logging/console-logs.table.js +2 -1
  57. package/dist/system-ids.d.ts +11 -0
  58. package/dist/system-ids.js +12 -1
  59. package/package.json +1 -1
@@ -0,0 +1,66 @@
1
+ import type { DataContext } from "../context/data-context";
2
+ import type { UserContext } from "../context/user-context";
3
+ import type { IDeviceInfo } from "../data/devices";
4
+ import { type IContractProviderRouter } from "./contract-provider-router";
5
+ import { type IContractCall, type IContractResolution } from "./contract-proxy";
6
+ import type { IContractTransport } from "./contract-transport";
7
+ /**
8
+ * Produces the live {@link IContractResolution} for a contract, scoped to a data context.
9
+ * System contracts register a resolver on import so the provider side can map a contract
10
+ * call to real tables / tools / observables at execution time.
11
+ */
12
+ export type ContractResolver = (ctx: {
13
+ dataContext: DataContext;
14
+ }) => IContractResolution | Promise<IContractResolution>;
15
+ /** Connection-authenticated identity fields used to authorize remote contract calls. */
16
+ export type ContractRemoteIdentity = Pick<IDeviceInfo, "deviceId" | "userId" | "publicKey" | "publicBoxKey">;
17
+ /**
18
+ * Register the resolver that provides the live implementation for a contract version.
19
+ * Later registrations for the same contract+version replace earlier ones (matching the
20
+ * registry's "latest provider wins for dev contracts" behavior).
21
+ */
22
+ export declare function registerContractResolver(contractId: string, version: number, resolver: ContractResolver): void;
23
+ /** Look up the resolver for a contract version, if one is registered. */
24
+ export declare function getContractResolver(contractId: string, version: number): ContractResolver | undefined;
25
+ /**
26
+ * Check whether a connection-authenticated remote identity has full contract authority.
27
+ *
28
+ * The identity must match a valid self-signed Users record in the provider's personal or
29
+ * shared-group data, and the provider's personal context must assign the caller exactly
30
+ * {@link TrustLevel.Self}. Unsigned discovery stubs and key mismatches fail closed.
31
+ */
32
+ export declare function isSelfTrustedContractCaller(userContext: UserContext, remoteIdentity: ContractRemoteIdentity): Promise<boolean>;
33
+ /**
34
+ * Create the local-object provider router for one verified device connection.
35
+ *
36
+ * The router derives caller identity from the connection owner supplied by the host,
37
+ * authorizes every call before invoking a registered resolver, normalizes omitted data
38
+ * contexts to the local user's personal context, and caches stateful endpoints per
39
+ * contract/version/data-context route. The caller must match a valid signed Users record
40
+ * and have exact Self trust in the provider's personal context. Explicit data-context ids
41
+ * retain their existing behavior after authorization.
42
+ *
43
+ * @param transport Contract transport backed by one verified connection.
44
+ * @param opts.userContext Local user context that owns provider data.
45
+ * @param opts.remoteIdentity Identity derived from the verified remote connection.
46
+ */
47
+ export declare function createUserContextContractProviderRouter(transport: IContractTransport, opts: {
48
+ userContext: UserContext;
49
+ remoteIdentity: ContractRemoteIdentity;
50
+ }): IContractProviderRouter;
51
+ /**
52
+ * Provider-side request/response glue: apply the signed-identity Self-trust host gate,
53
+ * resolve the contract for the authorized data context, build a stateless provider, and
54
+ * execute the call.
55
+ *
56
+ * Used by transports (e.g. the connection `CONTRACT_CALL_RPC` handler) to run an inbound
57
+ * {@link IContractCall} against local state.
58
+ *
59
+ * @param call The inbound contract call.
60
+ * @param opts.userContext The local user context executing the call.
61
+ * @param opts.remoteIdentity Identity derived from the verified remote connection.
62
+ */
63
+ export declare function handleContractCall(call: IContractCall, opts: {
64
+ userContext: UserContext;
65
+ remoteIdentity: ContractRemoteIdentity;
66
+ }): Promise<any>;
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerContractResolver = registerContractResolver;
4
+ exports.getContractResolver = getContractResolver;
5
+ exports.isSelfTrustedContractCaller = isSelfTrustedContractCaller;
6
+ exports.createUserContextContractProviderRouter = createUserContextContractProviderRouter;
7
+ exports.handleContractCall = handleContractCall;
8
+ const user_permissions_1 = require("../data/user-permissions");
9
+ const user_trust_levels_1 = require("../data/user-trust-levels");
10
+ const users_1 = require("../data/users");
11
+ const socket_type_1 = require("../device/socket.type");
12
+ const contract_provider_router_1 = require("./contract-provider-router");
13
+ const contract_proxy_1 = require("./contract-proxy");
14
+ const types_1 = require("./types");
15
+ const resolvers = new Map();
16
+ /**
17
+ * Register the resolver that provides the live implementation for a contract version.
18
+ * Later registrations for the same contract+version replace earlier ones (matching the
19
+ * registry's "latest provider wins for dev contracts" behavior).
20
+ */
21
+ function registerContractResolver(contractId, version, resolver) {
22
+ resolvers.set((0, types_1.contractKey)(contractId, version), resolver);
23
+ }
24
+ /** Look up the resolver for a contract version, if one is registered. */
25
+ function getContractResolver(contractId, version) {
26
+ return resolvers.get((0, types_1.contractKey)(contractId, version));
27
+ }
28
+ /**
29
+ * Check whether a connection-authenticated remote identity has full contract authority.
30
+ *
31
+ * The identity must match a valid self-signed Users record in the provider's personal or
32
+ * shared-group data, and the provider's personal context must assign the caller exactly
33
+ * {@link TrustLevel.Self}. Unsigned discovery stubs and key mismatches fail closed.
34
+ */
35
+ async function isSelfTrustedContractCaller(userContext, remoteIdentity) {
36
+ const identityContexts = [userContext.userDataContext, ...userContext.groupDataContexts.values()];
37
+ let hasVerifiedIdentity = false;
38
+ for (const dataContext of identityContexts) {
39
+ const user = await (0, users_1.Users)(dataContext).get(remoteIdentity.userId);
40
+ if (!user?.signature ||
41
+ user.userId !== remoteIdentity.userId ||
42
+ user.publicKey !== remoteIdentity.publicKey ||
43
+ user.publicBoxKey !== remoteIdentity.publicBoxKey) {
44
+ continue;
45
+ }
46
+ try {
47
+ (0, user_permissions_1.verifyUserSignature)(user);
48
+ hasVerifiedIdentity = true;
49
+ break;
50
+ }
51
+ catch {
52
+ // A different context may contain the valid signed record.
53
+ }
54
+ }
55
+ if (!hasVerifiedIdentity) {
56
+ return false;
57
+ }
58
+ const trustLevel = remoteIdentity.userId === userContext.userId
59
+ ? socket_type_1.TrustLevel.Self
60
+ : ((await (0, user_trust_levels_1.UserTrustLevels)(userContext.userDataContext).get(remoteIdentity.userId))
61
+ ?.trustLevel ?? socket_type_1.TrustLevel.Unknown);
62
+ return trustLevel === socket_type_1.TrustLevel.Self;
63
+ }
64
+ async function authorizeUserContextCall(call, userContext, remoteIdentity) {
65
+ const context = {
66
+ callerUserId: remoteIdentity.userId,
67
+ localUserId: userContext.userId,
68
+ };
69
+ if (!(await isSelfTrustedContractCaller(userContext, remoteIdentity))) {
70
+ throw new Error(`Permission denied: ${call.member} '${call.name}'.${call.operation} on contract ${call.contractId}`);
71
+ }
72
+ return {
73
+ dataContextId: call.dataContextId || userContext.userId,
74
+ context,
75
+ };
76
+ }
77
+ /**
78
+ * Create the local-object provider router for one verified device connection.
79
+ *
80
+ * The router derives caller identity from the connection owner supplied by the host,
81
+ * authorizes every call before invoking a registered resolver, normalizes omitted data
82
+ * contexts to the local user's personal context, and caches stateful endpoints per
83
+ * contract/version/data-context route. The caller must match a valid signed Users record
84
+ * and have exact Self trust in the provider's personal context. Explicit data-context ids
85
+ * retain their existing behavior after authorization.
86
+ *
87
+ * @param transport Contract transport backed by one verified connection.
88
+ * @param opts.userContext Local user context that owns provider data.
89
+ * @param opts.remoteIdentity Identity derived from the verified remote connection.
90
+ */
91
+ function createUserContextContractProviderRouter(transport, opts) {
92
+ const { userContext, remoteIdentity } = opts;
93
+ const context = {
94
+ callerUserId: remoteIdentity.userId,
95
+ localUserId: userContext.userId,
96
+ };
97
+ return (0, contract_provider_router_1.createContractProviderRouter)(transport, {
98
+ context,
99
+ authorizeCall: (call) => authorizeUserContextCall(call, userContext, remoteIdentity),
100
+ resolveEndpoint: async ({ contractId, version, dataContextId, notify }) => {
101
+ const resolver = getContractResolver(contractId, version);
102
+ if (!resolver) {
103
+ throw new Error(`No contract provider registered for ${contractId} v${version}`);
104
+ }
105
+ const dataContext = userContext.getDataContext(dataContextId || userContext.userId);
106
+ const resolution = await resolver({ dataContext });
107
+ return (0, contract_proxy_1.createContractProviderEndpoint)(resolution, { notify });
108
+ },
109
+ });
110
+ }
111
+ /**
112
+ * Provider-side request/response glue: apply the signed-identity Self-trust host gate,
113
+ * resolve the contract for the authorized data context, build a stateless provider, and
114
+ * execute the call.
115
+ *
116
+ * Used by transports (e.g. the connection `CONTRACT_CALL_RPC` handler) to run an inbound
117
+ * {@link IContractCall} against local state.
118
+ *
119
+ * @param call The inbound contract call.
120
+ * @param opts.userContext The local user context executing the call.
121
+ * @param opts.remoteIdentity Identity derived from the verified remote connection.
122
+ */
123
+ async function handleContractCall(call, opts) {
124
+ const { userContext, remoteIdentity } = opts;
125
+ const authorization = await authorizeUserContextCall(call, userContext, remoteIdentity);
126
+ const resolver = getContractResolver(call.contractId, call.version);
127
+ if (!resolver) {
128
+ throw new Error(`No contract provider registered for ${call.contractId} v${call.version}`);
129
+ }
130
+ const routedCall = {
131
+ ...call,
132
+ dataContextId: authorization.dataContextId,
133
+ };
134
+ const dataContext = userContext.getDataContext(authorization.dataContextId || userContext.userId);
135
+ const resolution = await resolver({ dataContext });
136
+ const provider = (0, contract_proxy_1.createContractProvider)(resolution);
137
+ return provider(routedCall, authorization.context);
138
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Transport-agnostic bidirectional channel that every contract proxy sits on.
3
+ *
4
+ * The transport is a dumb, symmetric duplex: `emit(channel, ...args)` sends on a
5
+ * named channel and resolves with the peer handler's return value (the ack), and
6
+ * `on(channel, handler)` registers a handler whose resolved value becomes that ack.
7
+ * All contract-specific routing (which contract, which member, subscriptions, …)
8
+ * lives *above* this layer in the payload; the transport itself is unaware of it.
9
+ *
10
+ * This is deliberately the common denominator of the primitives we already have:
11
+ * a device {@link IDuplexEmitter} ({@link Connection}), a socket.io `Socket`, and the
12
+ * `ISocket` abstraction all expose `emit(name, ...) -> Promise<ack>` + `on(name, handler)`
13
+ * + listener registration. Adapters below wrap each of those (plus an in-process pair for tests) into
14
+ * the same {@link IContractTransport} shape so proxies work unchanged whether the provider
15
+ * runs in the same process, another iframe, the UI↔server socket, or a remote device.
16
+ */
17
+ /**
18
+ * A minimal, symmetric, bidirectional channel for contract traffic.
19
+ *
20
+ * Both `emit` and `on` are available on each end, so provider→consumer pushes use the
21
+ * exact same code path as consumer→provider requests (the pusher just ignores the
22
+ * returned promise).
23
+ */
24
+ export interface IContractTransport {
25
+ /**
26
+ * Send `args` on the named `channel`; resolves with the peer handler's returned
27
+ * value (the ack), or rejects if the peer handler throws / rejects.
28
+ */
29
+ emit(channel: string, ...args: any[]): Promise<any>;
30
+ /**
31
+ * Register a `handler` for the named `channel`. The handler's resolved value is
32
+ * returned to the emitter as the ack. Returns an unsubscribe function that removes
33
+ * this handler.
34
+ */
35
+ on(channel: string, handler: (...args: any[]) => any | Promise<any>): () => void;
36
+ }
37
+ /**
38
+ * The structural shape of a duplex RPC emitter (notably a device {@link Connection})
39
+ * that {@link connectionContractTransport} wraps.
40
+ *
41
+ * Declared structurally so `peers-sdk`'s contract layer does not import `Connection`
42
+ * (avoiding an import cycle) yet a `Connection` — and anything else exposing the same
43
+ * `emit`/`on` surface — satisfies it.
44
+ */
45
+ export interface IDuplexEmitter {
46
+ /** Send an RPC and resolve with the remote handler's result. */
47
+ emit(eventName: string, ...args: any[]): Promise<any>;
48
+ /** Register a handler invoked with the emitter's positional args. */
49
+ on(eventName: string, handler: (...args: any[]) => any): void;
50
+ }
51
+ /**
52
+ * Fixed channel for consumer→provider request/response traffic (tool runs, table
53
+ * methods, observable get/set, and subscribe/unsubscribe). Value matches
54
+ * the legacy `CONTRACT_CALL_RPC` event name so existing provider registrations keep
55
+ * working unchanged.
56
+ */
57
+ export declare const CONTRACT_REQUEST_CHANNEL = "contractCall";
58
+ /**
59
+ * Fixed channel for provider→consumer event, table `dataChanged`, and observable
60
+ * notifications. Subscription ids in each payload let many consumers share one
61
+ * underlying connection.
62
+ */
63
+ export declare const CONTRACT_NOTIFY_CHANNEL = "contractNotify";
64
+ /**
65
+ * Wrap a duplex RPC emitter (e.g. a device {@link Connection}) as an
66
+ * {@link IContractTransport}. `emit` delegates directly. `on` uses one adapter-owned
67
+ * dispatcher per emitter/channel and returns an unsubscribe that removes only the caller's
68
+ * handler from that dispatcher; it never uses a coarse `removeAllListeners`.
69
+ *
70
+ * Multiple notify listeners may share one connection and dispose independently. The request
71
+ * channel intentionally accepts one handler only: a connection-wide provider router owns
72
+ * multiplexing contracts and data contexts. Stacking fixed-resolution provider sessions on
73
+ * one request channel is unsupported.
74
+ *
75
+ * ```ts
76
+ * const logs = createContractConsumer(logsContractDefinition, connectionContractTransport(connection));
77
+ * await logs.tables.ConsoleLogs.list({ level: "error" }, { pageSize: 20 });
78
+ * ```
79
+ *
80
+ * @param emitter The underlying duplex emitter (Connection-like) to delegate to.
81
+ */
82
+ export declare function connectionContractTransport(emitter: IDuplexEmitter): IContractTransport;
83
+ /**
84
+ * Create a cross-wired pair of {@link IContractTransport}s that talk to each other in the
85
+ * same process. Whatever one end `emit`s on a channel is delivered to the other end's
86
+ * handler for that channel; the handler's resolved value is returned to the emitter as
87
+ * the ack, and a thrown/rejected handler rejects the emit. Both ends are fully symmetric,
88
+ * so either side can act as consumer, provider, or both.
89
+ *
90
+ * Intended for tests and same-process contract wiring (no real network transport).
91
+ *
92
+ * @returns `[a, b]` — two transports wired to each other.
93
+ */
94
+ export declare function inProcessTransportPair(): [IContractTransport, IContractTransport];
@@ -0,0 +1,159 @@
1
+ "use strict";
2
+ /**
3
+ * Transport-agnostic bidirectional channel that every contract proxy sits on.
4
+ *
5
+ * The transport is a dumb, symmetric duplex: `emit(channel, ...args)` sends on a
6
+ * named channel and resolves with the peer handler's return value (the ack), and
7
+ * `on(channel, handler)` registers a handler whose resolved value becomes that ack.
8
+ * All contract-specific routing (which contract, which member, subscriptions, …)
9
+ * lives *above* this layer in the payload; the transport itself is unaware of it.
10
+ *
11
+ * This is deliberately the common denominator of the primitives we already have:
12
+ * a device {@link IDuplexEmitter} ({@link Connection}), a socket.io `Socket`, and the
13
+ * `ISocket` abstraction all expose `emit(name, ...) -> Promise<ack>` + `on(name, handler)`
14
+ * + listener registration. Adapters below wrap each of those (plus an in-process pair for tests) into
15
+ * the same {@link IContractTransport} shape so proxies work unchanged whether the provider
16
+ * runs in the same process, another iframe, the UI↔server socket, or a remote device.
17
+ */
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.CONTRACT_NOTIFY_CHANNEL = exports.CONTRACT_REQUEST_CHANNEL = void 0;
20
+ exports.connectionContractTransport = connectionContractTransport;
21
+ exports.inProcessTransportPair = inProcessTransportPair;
22
+ /**
23
+ * Fixed channel for consumer→provider request/response traffic (tool runs, table
24
+ * methods, observable get/set, and subscribe/unsubscribe). Value matches
25
+ * the legacy `CONTRACT_CALL_RPC` event name so existing provider registrations keep
26
+ * working unchanged.
27
+ */
28
+ exports.CONTRACT_REQUEST_CHANNEL = "contractCall";
29
+ /**
30
+ * Fixed channel for provider→consumer event, table `dataChanged`, and observable
31
+ * notifications. Subscription ids in each payload let many consumers share one
32
+ * underlying connection.
33
+ */
34
+ exports.CONTRACT_NOTIFY_CHANNEL = "contractNotify";
35
+ const connectionDispatcherStates = new WeakMap();
36
+ /**
37
+ * Wrap a duplex RPC emitter (e.g. a device {@link Connection}) as an
38
+ * {@link IContractTransport}. `emit` delegates directly. `on` uses one adapter-owned
39
+ * dispatcher per emitter/channel and returns an unsubscribe that removes only the caller's
40
+ * handler from that dispatcher; it never uses a coarse `removeAllListeners`.
41
+ *
42
+ * Multiple notify listeners may share one connection and dispose independently. The request
43
+ * channel intentionally accepts one handler only: a connection-wide provider router owns
44
+ * multiplexing contracts and data contexts. Stacking fixed-resolution provider sessions on
45
+ * one request channel is unsupported.
46
+ *
47
+ * ```ts
48
+ * const logs = createContractConsumer(logsContractDefinition, connectionContractTransport(connection));
49
+ * await logs.tables.ConsoleLogs.list({ level: "error" }, { pageSize: 20 });
50
+ * ```
51
+ *
52
+ * @param emitter The underlying duplex emitter (Connection-like) to delegate to.
53
+ */
54
+ function connectionContractTransport(emitter) {
55
+ return {
56
+ emit: (channel, ...args) => emitter.emit(channel, ...args),
57
+ on: (channel, handler) => {
58
+ let channelStates = connectionDispatcherStates.get(emitter);
59
+ if (!channelStates) {
60
+ channelStates = new Map();
61
+ connectionDispatcherStates.set(emitter, channelStates);
62
+ }
63
+ let state = channelStates.get(channel);
64
+ if (!state) {
65
+ const handlers = new Set();
66
+ const dispatcher = async (...args) => {
67
+ const results = await Promise.all([...handlers].map((registered) => {
68
+ try {
69
+ return Promise.resolve(registered(...args));
70
+ }
71
+ catch (error) {
72
+ return Promise.reject(error);
73
+ }
74
+ }));
75
+ return channel === exports.CONTRACT_REQUEST_CHANNEL ? results[0] : undefined;
76
+ };
77
+ state = { handlers, dispatcher };
78
+ channelStates.set(channel, state);
79
+ emitter.on(channel, dispatcher);
80
+ }
81
+ if (channel === exports.CONTRACT_REQUEST_CHANNEL && state.handlers.size > 0) {
82
+ throw new Error("Contract request channel already has a handler; use one connection-wide provider router");
83
+ }
84
+ state.handlers.add(handler);
85
+ let active = true;
86
+ return () => {
87
+ if (!active) {
88
+ return;
89
+ }
90
+ active = false;
91
+ state?.handlers.delete(handler);
92
+ };
93
+ },
94
+ };
95
+ }
96
+ /**
97
+ * Build one end of an in-process transport pair. `on` registers into `localHandlers`;
98
+ * `emit` invokes the peer's handlers concurrently and awaits them. A single handler's
99
+ * result is returned as its ack; multi-listener notification channels resolve `undefined`.
100
+ * The request channel enforces a single handler so tests cannot accidentally hide missing
101
+ * provider routing behind last-handler-wins behavior.
102
+ */
103
+ function makeInProcessTransport(localHandlers, remoteHandlers) {
104
+ return {
105
+ emit: async (channel, ...args) => {
106
+ const handlers = remoteHandlers.get(channel);
107
+ if (!handlers || handlers.size === 0) {
108
+ return undefined;
109
+ }
110
+ const results = await Promise.all([...handlers].map((handler) => {
111
+ try {
112
+ return Promise.resolve(handler(...args));
113
+ }
114
+ catch (error) {
115
+ return Promise.reject(error);
116
+ }
117
+ }));
118
+ return results.length === 1 ? results[0] : undefined;
119
+ },
120
+ on: (channel, handler) => {
121
+ let set = localHandlers.get(channel);
122
+ if (!set) {
123
+ set = new Set();
124
+ localHandlers.set(channel, set);
125
+ }
126
+ if (channel === exports.CONTRACT_REQUEST_CHANNEL && set.size > 0) {
127
+ throw new Error("Contract request channel already has a handler; use one provider router");
128
+ }
129
+ set.add(handler);
130
+ let active = true;
131
+ return () => {
132
+ if (!active) {
133
+ return;
134
+ }
135
+ active = false;
136
+ set?.delete(handler);
137
+ };
138
+ },
139
+ };
140
+ }
141
+ /**
142
+ * Create a cross-wired pair of {@link IContractTransport}s that talk to each other in the
143
+ * same process. Whatever one end `emit`s on a channel is delivered to the other end's
144
+ * handler for that channel; the handler's resolved value is returned to the emitter as
145
+ * the ack, and a thrown/rejected handler rejects the emit. Both ends are fully symmetric,
146
+ * so either side can act as consumer, provider, or both.
147
+ *
148
+ * Intended for tests and same-process contract wiring (no real network transport).
149
+ *
150
+ * @returns `[a, b]` — two transports wired to each other.
151
+ */
152
+ function inProcessTransportPair() {
153
+ const handlersA = new Map();
154
+ const handlersB = new Map();
155
+ return [
156
+ makeInProcessTransport(handlersA, handlersB),
157
+ makeInProcessTransport(handlersB, handlersA),
158
+ ];
159
+ }
@@ -4,7 +4,7 @@ import { Table } from "../data/orm/table";
4
4
  import { type ITableMetaData } from "../data/orm/types";
5
5
  /**
6
6
  * Zod schema for persisted contracts.
7
- * The `shape` field stores the serialized `{ tables, tools, observables }` JSON
7
+ * The `shape` field stores the serialized `{ tables, tools, observables, events }` JSON
8
8
  * so the full `IContractDefinition` can be rehydrated without re-extracting from
9
9
  * live package code.
10
10
  */
@@ -10,7 +10,7 @@ const types_1 = require("../data/orm/types");
10
10
  const zod_types_1 = require("../types/zod-types");
11
11
  /**
12
12
  * Zod schema for persisted contracts.
13
- * The `shape` field stores the serialized `{ tables, tools, observables }` JSON
13
+ * The `shape` field stores the serialized `{ tables, tools, observables, events }` JSON
14
14
  * so the full `IContractDefinition` can be rehydrated without re-extracting from
15
15
  * live package code.
16
16
  */
@@ -20,7 +20,7 @@ const schema = zod_1.z.object({
20
20
  devTag: zod_1.z.string().optional().describe("'dev' if the contract shape is still mutable"),
21
21
  name: zod_1.z.string().describe("Human-readable contract name"),
22
22
  description: zod_1.z.string().describe("Contract description"),
23
- shape: zod_1.z.string().describe("JSON-serialized { tables, tools, observables }"),
23
+ shape: zod_1.z.string().describe("JSON-serialized { tables, tools, observables, events }"),
24
24
  registeredAt: zod_1.z.string().describe("ISO 8601 timestamp of first registration"),
25
25
  });
26
26
  exports.contractsSchema = schema;
@@ -1,9 +1,14 @@
1
1
  export { ContractBuilder, definePackage, PackageBuilder } from "./builder";
2
+ export { type ContractCallAuthorizer, type ContractProviderEndpointResolver, createContractProviderRouter, type IAuthorizedContractRoute, type IContractProviderEndpointRequest, type IContractProviderRouter, type IContractProviderRouterOptions, } from "./contract-provider-router";
2
3
  export { ContractProviders, ContractProvidersTable, contractProvidersMetaData, contractProvidersSchema, type IContractProviderRecord, } from "./contract-providers.table";
4
+ export { CONTRACT_CALL_RPC, type ContractMemberType, type ContractNotificationSink, type ContractPermissionCheck, createContractConsumer, createContractProvider, createContractProviderEndpoint, createContractProviderSession, type IContractCall, type IContractCallContext, type IContractConsumer, type IContractNotification, type IContractProviderEndpoint, type IContractProviderEndpointOptions, type IContractProviderSession, type IContractProviderSessionOptions, type IContractResolution, sameUserContractPermissionCheck, } from "./contract-proxy";
5
+ export { type ContractRemoteIdentity, type ContractResolver, createUserContextContractProviderRouter, getContractResolver, handleContractCall, isSelfTrustedContractCaller, registerContractResolver, } from "./contract-resolvers";
6
+ export { CONTRACT_NOTIFY_CHANNEL, CONTRACT_REQUEST_CHANNEL, connectionContractTransport, type IContractTransport, type IDuplexEmitter, inProcessTransportPair, } from "./contract-transport";
3
7
  export { Contracts, ContractsTable, contractsMetaData, contractsSchema, type IContractRecord, } from "./contracts.table";
4
8
  export { extractTableShape, extractToolShape, type IExtractableTableDef, type IExtractableToolInstance, setSchemaToFieldsFn, } from "./extract";
5
9
  export { PersistentContractRegistry } from "./persistent-registry";
6
10
  export { ContractRegistry } from "./registry";
7
- export type { IAlsoImplementsDeclaration, IConsumedContract, IContractDefinition, IContractObservable, IContractTable, IContractTool, IPackageDefinitionResult, IResolvedContract, IValidationError, IValidationResult, } from "./types";
11
+ export { getLogsContractDefinition, LOGS_CONTRACT_VERSION, resolveLogsContract, } from "./system/logs-contract";
12
+ export type { IAlsoImplementsDeclaration, IConsumedContract, IContractDefinition, IContractEvent, IContractObservable, IContractTable, IContractTool, IPackageDefinitionResult, IResolvedContract, IValidationError, IValidationResult, } from "./types";
8
13
  export { contractKey } from "./types";
9
14
  export { validateAlsoImplements, validateImmutability, validateProviderSatisfiesContract, } from "./validate";
@@ -1,15 +1,35 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.validateProviderSatisfiesContract = exports.validateImmutability = exports.validateAlsoImplements = exports.contractKey = exports.ContractRegistry = exports.PersistentContractRegistry = exports.setSchemaToFieldsFn = exports.extractToolShape = exports.extractTableShape = exports.contractsSchema = exports.contractsMetaData = exports.ContractsTable = exports.Contracts = exports.contractProvidersSchema = exports.contractProvidersMetaData = exports.ContractProvidersTable = exports.ContractProviders = exports.PackageBuilder = exports.definePackage = exports.ContractBuilder = void 0;
3
+ exports.validateProviderSatisfiesContract = exports.validateImmutability = exports.validateAlsoImplements = exports.contractKey = exports.resolveLogsContract = exports.LOGS_CONTRACT_VERSION = exports.getLogsContractDefinition = exports.ContractRegistry = exports.PersistentContractRegistry = exports.setSchemaToFieldsFn = exports.extractToolShape = exports.extractTableShape = exports.contractsSchema = exports.contractsMetaData = exports.ContractsTable = exports.Contracts = exports.inProcessTransportPair = exports.connectionContractTransport = exports.CONTRACT_REQUEST_CHANNEL = exports.CONTRACT_NOTIFY_CHANNEL = exports.registerContractResolver = exports.isSelfTrustedContractCaller = exports.handleContractCall = exports.getContractResolver = exports.createUserContextContractProviderRouter = exports.sameUserContractPermissionCheck = 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;
4
4
  var builder_1 = require("./builder");
5
5
  Object.defineProperty(exports, "ContractBuilder", { enumerable: true, get: function () { return builder_1.ContractBuilder; } });
6
6
  Object.defineProperty(exports, "definePackage", { enumerable: true, get: function () { return builder_1.definePackage; } });
7
7
  Object.defineProperty(exports, "PackageBuilder", { enumerable: true, get: function () { return builder_1.PackageBuilder; } });
8
+ var contract_provider_router_1 = require("./contract-provider-router");
9
+ Object.defineProperty(exports, "createContractProviderRouter", { enumerable: true, get: function () { return contract_provider_router_1.createContractProviderRouter; } });
8
10
  var contract_providers_table_1 = require("./contract-providers.table");
9
11
  Object.defineProperty(exports, "ContractProviders", { enumerable: true, get: function () { return contract_providers_table_1.ContractProviders; } });
10
12
  Object.defineProperty(exports, "ContractProvidersTable", { enumerable: true, get: function () { return contract_providers_table_1.ContractProvidersTable; } });
11
13
  Object.defineProperty(exports, "contractProvidersMetaData", { enumerable: true, get: function () { return contract_providers_table_1.contractProvidersMetaData; } });
12
14
  Object.defineProperty(exports, "contractProvidersSchema", { enumerable: true, get: function () { return contract_providers_table_1.contractProvidersSchema; } });
15
+ var contract_proxy_1 = require("./contract-proxy");
16
+ Object.defineProperty(exports, "CONTRACT_CALL_RPC", { enumerable: true, get: function () { return contract_proxy_1.CONTRACT_CALL_RPC; } });
17
+ Object.defineProperty(exports, "createContractConsumer", { enumerable: true, get: function () { return contract_proxy_1.createContractConsumer; } });
18
+ Object.defineProperty(exports, "createContractProvider", { enumerable: true, get: function () { return contract_proxy_1.createContractProvider; } });
19
+ Object.defineProperty(exports, "createContractProviderEndpoint", { enumerable: true, get: function () { return contract_proxy_1.createContractProviderEndpoint; } });
20
+ Object.defineProperty(exports, "createContractProviderSession", { enumerable: true, get: function () { return contract_proxy_1.createContractProviderSession; } });
21
+ Object.defineProperty(exports, "sameUserContractPermissionCheck", { enumerable: true, get: function () { return contract_proxy_1.sameUserContractPermissionCheck; } });
22
+ var contract_resolvers_1 = require("./contract-resolvers");
23
+ Object.defineProperty(exports, "createUserContextContractProviderRouter", { enumerable: true, get: function () { return contract_resolvers_1.createUserContextContractProviderRouter; } });
24
+ Object.defineProperty(exports, "getContractResolver", { enumerable: true, get: function () { return contract_resolvers_1.getContractResolver; } });
25
+ Object.defineProperty(exports, "handleContractCall", { enumerable: true, get: function () { return contract_resolvers_1.handleContractCall; } });
26
+ Object.defineProperty(exports, "isSelfTrustedContractCaller", { enumerable: true, get: function () { return contract_resolvers_1.isSelfTrustedContractCaller; } });
27
+ Object.defineProperty(exports, "registerContractResolver", { enumerable: true, get: function () { return contract_resolvers_1.registerContractResolver; } });
28
+ var contract_transport_1 = require("./contract-transport");
29
+ Object.defineProperty(exports, "CONTRACT_NOTIFY_CHANNEL", { enumerable: true, get: function () { return contract_transport_1.CONTRACT_NOTIFY_CHANNEL; } });
30
+ Object.defineProperty(exports, "CONTRACT_REQUEST_CHANNEL", { enumerable: true, get: function () { return contract_transport_1.CONTRACT_REQUEST_CHANNEL; } });
31
+ Object.defineProperty(exports, "connectionContractTransport", { enumerable: true, get: function () { return contract_transport_1.connectionContractTransport; } });
32
+ Object.defineProperty(exports, "inProcessTransportPair", { enumerable: true, get: function () { return contract_transport_1.inProcessTransportPair; } });
13
33
  var contracts_table_1 = require("./contracts.table");
14
34
  Object.defineProperty(exports, "Contracts", { enumerable: true, get: function () { return contracts_table_1.Contracts; } });
15
35
  Object.defineProperty(exports, "ContractsTable", { enumerable: true, get: function () { return contracts_table_1.ContractsTable; } });
@@ -23,6 +43,10 @@ var persistent_registry_1 = require("./persistent-registry");
23
43
  Object.defineProperty(exports, "PersistentContractRegistry", { enumerable: true, get: function () { return persistent_registry_1.PersistentContractRegistry; } });
24
44
  var registry_1 = require("./registry");
25
45
  Object.defineProperty(exports, "ContractRegistry", { enumerable: true, get: function () { return registry_1.ContractRegistry; } });
46
+ var logs_contract_1 = require("./system/logs-contract");
47
+ Object.defineProperty(exports, "getLogsContractDefinition", { enumerable: true, get: function () { return logs_contract_1.getLogsContractDefinition; } });
48
+ Object.defineProperty(exports, "LOGS_CONTRACT_VERSION", { enumerable: true, get: function () { return logs_contract_1.LOGS_CONTRACT_VERSION; } });
49
+ Object.defineProperty(exports, "resolveLogsContract", { enumerable: true, get: function () { return logs_contract_1.resolveLogsContract; } });
26
50
  var types_1 = require("./types");
27
51
  Object.defineProperty(exports, "contractKey", { enumerable: true, get: function () { return types_1.contractKey; } });
28
52
  var validate_1 = require("./validate");
@@ -30,6 +30,7 @@ class PersistentContractRegistry {
30
30
  tables: definition.tables,
31
31
  tools: definition.tools,
32
32
  observables: definition.observables,
33
+ events: definition.events ?? [],
33
34
  });
34
35
  if (existingContract) {
35
36
  await contractsTable.save({
@@ -122,7 +123,7 @@ class PersistentContractRegistry {
122
123
  }
123
124
  /** Convert a persisted contract record back to an `IContractDefinition`. */
124
125
  recordToDefinition(record) {
125
- const { tables, tools, observables } = JSON.parse(record.shape);
126
+ const { tables, tools, observables, events } = JSON.parse(record.shape);
126
127
  return {
127
128
  contractId: record.contractId,
128
129
  version: record.version,
@@ -132,6 +133,7 @@ class PersistentContractRegistry {
132
133
  tables,
133
134
  tools,
134
135
  observables,
136
+ events: events ?? [],
135
137
  };
136
138
  }
137
139
  }
@@ -0,0 +1,24 @@
1
+ import type { DataContext } from "../../context/data-context";
2
+ import type { IContractResolution } from "../contract-proxy";
3
+ import type { IContractDefinition } from "../types";
4
+ /** Version of the built-in System Logs contract. */
5
+ export declare const LOGS_CONTRACT_VERSION = 1;
6
+ /**
7
+ * The first built-in **system contract**: read and write the local device's console logs.
8
+ *
9
+ * It declares only the `ConsoleLogs` table — the table's own methods (`list`, `count`,
10
+ * `deleteOldLogs`, …) are the entire surface we need, so no bespoke tools are required.
11
+ * The table shape is derived from {@link consoleLogSchema} so the contract stays accurate.
12
+ *
13
+ * Built lazily and memoized: reading `consoleLogsMetaData` / `consoleLogSchema` at module
14
+ * load would race the import cycle between the logging table and the SDK barrel, so we defer
15
+ * construction until first use (well after all modules have finished initializing).
16
+ */
17
+ export declare function getLogsContractDefinition(): IContractDefinition;
18
+ /**
19
+ * Resolve the logs contract to its live implementation for a data context: the
20
+ * `ConsoleLogs` table backing that context.
21
+ */
22
+ export declare function resolveLogsContract(ctx: {
23
+ dataContext: DataContext;
24
+ }): IContractResolution;