@enbox/agent 0.7.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/browser.mjs +11 -11
  2. package/dist/browser.mjs.map +4 -4
  3. package/dist/esm/agent-session.js +16 -0
  4. package/dist/esm/agent-session.js.map +1 -0
  5. package/dist/esm/dwn-api.js +5 -4
  6. package/dist/esm/dwn-api.js.map +1 -1
  7. package/dist/esm/dwn-encryption.js +11 -4
  8. package/dist/esm/dwn-encryption.js.map +1 -1
  9. package/dist/esm/enbox-connect-protocol.js +376 -207
  10. package/dist/esm/enbox-connect-protocol.js.map +1 -1
  11. package/dist/esm/index.js +2 -1
  12. package/dist/esm/index.js.map +1 -1
  13. package/dist/esm/local-key-manager.js.map +1 -1
  14. package/dist/esm/protocol-utils.js +19 -6
  15. package/dist/esm/protocol-utils.js.map +1 -1
  16. package/dist/esm/store-data.js.map +1 -1
  17. package/dist/esm/store-key.js +1 -1
  18. package/dist/esm/store-key.js.map +1 -1
  19. package/dist/esm/sync-engine-level.js +18 -5
  20. package/dist/esm/sync-engine-level.js.map +1 -1
  21. package/dist/esm/types/dwn.js.map +1 -1
  22. package/dist/esm/utils.js +0 -12
  23. package/dist/esm/utils.js.map +1 -1
  24. package/dist/types/agent-session.d.ts +59 -0
  25. package/dist/types/agent-session.d.ts.map +1 -0
  26. package/dist/types/dwn-api.d.ts.map +1 -1
  27. package/dist/types/dwn-encryption.d.ts.map +1 -1
  28. package/dist/types/enbox-connect-protocol.d.ts +34 -3
  29. package/dist/types/enbox-connect-protocol.d.ts.map +1 -1
  30. package/dist/types/index.d.ts +2 -1
  31. package/dist/types/index.d.ts.map +1 -1
  32. package/dist/types/local-key-manager.d.ts.map +1 -1
  33. package/dist/types/protocol-utils.d.ts.map +1 -1
  34. package/dist/types/sync-engine-level.d.ts +6 -1
  35. package/dist/types/sync-engine-level.d.ts.map +1 -1
  36. package/dist/types/types/dwn.d.ts +10 -1
  37. package/dist/types/types/dwn.d.ts.map +1 -1
  38. package/dist/types/utils.d.ts +0 -2
  39. package/dist/types/utils.d.ts.map +1 -1
  40. package/package.json +6 -6
  41. package/src/agent-session.ts +78 -0
  42. package/src/dwn-api.ts +5 -4
  43. package/src/dwn-encryption.ts +14 -6
  44. package/src/enbox-connect-protocol.ts +466 -256
  45. package/src/index.ts +2 -1
  46. package/src/local-key-manager.ts +7 -3
  47. package/src/protocol-utils.ts +19 -8
  48. package/src/store-data.ts +1 -1
  49. package/src/store-key.ts +1 -1
  50. package/src/sync-engine-level.ts +19 -6
  51. package/src/types/dwn.ts +21 -12
  52. package/src/utils.ts +3 -18
package/src/index.ts CHANGED
@@ -13,6 +13,7 @@ export { evaluateClosure, evaluateClosureBatch } from './sync-closure-resolver.j
13
13
  export type * from './types/vc.js';
14
14
 
15
15
  export * from './agent-did-resolver-cache.js';
16
+ export * from './agent-session.js';
16
17
  export * from './anonymous-dwn-api.js';
17
18
  export * from './bearer-identity.js';
18
19
  export * from './crypto-api.js';
@@ -42,4 +43,4 @@ export * from './test-harness.js';
42
43
  export * from './utils.js';
43
44
  export * from './enbox-connect-protocol.js';
44
45
  export * from './enbox-user-agent.js';
45
- export { KeyDeliveryProtocolDefinition } from './store-data-protocols.js';
46
+ export { IdentityProtocolDefinition, JwkProtocolDefinition, KeyDeliveryProtocolDefinition } from './store-data-protocols.js';
@@ -770,9 +770,13 @@ export class LocalKeyManager implements AgentKeyManager {
770
770
  // agent DID's X25519 key, so looking up that key via the DwnKeyStore would
771
771
  // cause infinite recursion (decrypt → getPrivateKey → DwnKeyStore.get → decrypt…).
772
772
  try {
773
- const agentKeyManager = this.agent?.agentDid?.keyManager;
774
- if (agentKeyManager && typeof (agentKeyManager as any).exportKey === 'function') {
775
- const agentKey = await (agentKeyManager as any).exportKey({ keyUri });
773
+ // The base `KeyManager` interface doesn't declare `exportKey` — only
774
+ // some implementations support raw-key export. Narrowly-typed probe
775
+ // (no `any`) so the call below uses the structurally correct type.
776
+ type ExportableKeyManager = { exportKey: (params: { keyUri: string }) => Promise<unknown> };
777
+ const agentKeyManager = this.agent?.agentDid?.keyManager as Partial<ExportableKeyManager> | undefined;
778
+ if (agentKeyManager && typeof agentKeyManager.exportKey === 'function') {
779
+ const agentKey = await agentKeyManager.exportKey({ keyUri });
776
780
  if (agentKey && isPrivateJwk(agentKey)) {
777
781
  return agentKey;
778
782
  }
@@ -10,12 +10,17 @@ export function getRuleSetAtPath(
10
10
  protocolDefinition: ProtocolDefinition,
11
11
  protocolPath: string,
12
12
  ): ProtocolRuleSet | undefined {
13
- const segments = protocolPath.split('/');
14
- let ruleSet: ProtocolRuleSet | undefined =
15
- protocolDefinition.structure as unknown as ProtocolRuleSet;
16
- for (const segment of segments) {
17
- ruleSet = ruleSet[segment] as ProtocolRuleSet | undefined;
13
+ const [first, ...rest] = protocolPath.split('/');
14
+ // Top-level lookup uses the structure's declared `{ [key: string]:
15
+ // ProtocolRuleSet }` index signature directly — no top-level cast.
16
+ let ruleSet: ProtocolRuleSet | undefined = protocolDefinition.structure[first];
17
+ for (const segment of rest) {
18
18
  if (!ruleSet) { return undefined; }
19
+ // Nested rule sets index into a `ProtocolRuleSet`'s child map.
20
+ // `ProtocolRuleSet` has typed `$encryption`/`$actions`/etc. fields
21
+ // alongside the child index signature, so the index access here
22
+ // returns a union; narrow it back to `ProtocolRuleSet | undefined`.
23
+ ruleSet = ruleSet[segment] as ProtocolRuleSet | undefined;
19
24
  }
20
25
  return ruleSet;
21
26
  }
@@ -91,8 +96,6 @@ export function hasRelationalReadAccess(
91
96
  ofPath: string,
92
97
  protocolDefinition: ProtocolDefinition,
93
98
  ): boolean {
94
- const structure = protocolDefinition.structure as unknown as ProtocolRuleSet;
95
-
96
99
  function walkRuleSet(rs: ProtocolRuleSet): boolean {
97
100
  // Check $actions on this node
98
101
  if (rs.$actions) {
@@ -120,7 +123,15 @@ export function hasRelationalReadAccess(
120
123
  return false;
121
124
  }
122
125
 
123
- return walkRuleSet(structure);
126
+ // Walk every top-level type. The structure is typed as
127
+ // `{ [key: string]: ProtocolRuleSet }`, so each child is directly a
128
+ // ProtocolRuleSet — no top-level cast needed.
129
+ for (const key in protocolDefinition.structure) {
130
+ if (walkRuleSet(protocolDefinition.structure[key])) {
131
+ return true;
132
+ }
133
+ }
134
+ return false;
124
135
  }
125
136
 
126
137
  /**
package/src/store-data.ts CHANGED
@@ -337,7 +337,7 @@ export class DwnDataStore<TStoreObject extends Record<string, any> = Jwk> implem
337
337
  }
338
338
 
339
339
  // If the record was found, convert back to store object format.
340
- const storeObject = await Stream.consumeToJson({ readableStream: readReply.entry.data }) as TStoreObject;
340
+ const storeObject = await Stream.consumeToJson<TStoreObject>({ readableStream: readReply.entry.data });
341
341
 
342
342
  // If caching is enabled, add the store object to the cache.
343
343
  if (useCache) {
package/src/store-key.ts CHANGED
@@ -81,7 +81,7 @@ export class DwnKeyStore extends DwnDataStore<Jwk> implements AgentDataStore<Jwk
81
81
  throw new Error(`${this.name}: Failed to read encrypted key record: ${record.recordId}`);
82
82
  }
83
83
 
84
- storedKey = await Stream.consumeToJson({ readableStream: readResult.entry.data }) as Jwk;
84
+ storedKey = await Stream.consumeToJson<Jwk>({ readableStream: readResult.entry.data });
85
85
  } else {
86
86
  // Unencrypted record (legacy or non-encrypted store) — read inline.
87
87
  if (!record.encodedData) {
@@ -6,12 +6,13 @@ import type { GenericMessage, MessageEvent, MessagesSubscribeReply, MessagesSync
6
6
  import ms from 'ms';
7
7
 
8
8
  import { Level } from 'level';
9
+ import { sleep } from '@enbox/common';
9
10
  import { Encoder, hashToHex, initDefaultHashes, Message } from '@enbox/dwn-sdk-js';
10
11
 
11
12
  import type { ClosureEvaluationContext } from './sync-closure-types.js';
13
+ import type { EnboxPlatformAgent } from './types/agent.js';
12
14
  import type { PermissionsApi } from './types/permissions.js';
13
15
  import type { DeadLetterCategory, DeadLetterEntry, PushResult, ReplicationLinkState, StartSyncParams, SyncConnectivityState, SyncEngine, SyncEvent, SyncEventListener, SyncHealthSummary, SyncIdentityOptions, SyncMode, SyncScope } from './types/sync.js';
14
- import type { EnboxAgent, EnboxPlatformAgent } from './types/agent.js';
15
16
 
16
17
  import { evaluateClosure } from './sync-closure-resolver.js';
17
18
  import { MAX_PENDING_TOKENS } from './types/sync.js';
@@ -313,7 +314,7 @@ export class SyncEngineLevel implements SyncEngine {
313
314
 
314
315
  constructor({ agent, dataPath, db }: SyncEngineLevelParams) {
315
316
  this._agent = agent;
316
- this._permissionsApi = new AgentPermissionsApi({ agent: agent as EnboxAgent });
317
+ this._permissionsApi = new AgentPermissionsApi({ agent });
317
318
  this._db = (db) ? db : new Level<string, string>(dataPath ?? 'DATA/AGENT/SYNC_STORE');
318
319
  }
319
320
 
@@ -346,7 +347,7 @@ export class SyncEngineLevel implements SyncEngine {
346
347
 
347
348
  set agent(agent: EnboxPlatformAgent) {
348
349
  this._agent = agent;
349
- this._permissionsApi = new AgentPermissionsApi({ agent: agent as EnboxAgent });
350
+ this._permissionsApi = new AgentPermissionsApi({ agent });
350
351
  // Cached sync targets were resolved through the previous agent's
351
352
  // DID resolver / endpoint lookup — invalidate so the next sync
352
353
  // tick re-resolves through the new agent.
@@ -597,18 +598,30 @@ export class SyncEngineLevel implements SyncEngine {
597
598
  /**
598
599
  * stopSync awaits the completion of the current sync operation before stopping the sync interval
599
600
  * and tearing down any live subscriptions.
601
+ *
602
+ * @param timeout - Maximum milliseconds to wait for an in-progress
603
+ * sync cycle to finish. Non-finite values (`NaN`, `Infinity`) are
604
+ * coerced to the default to avoid a tight poll loop or never-exit
605
+ * condition.
600
606
  */
601
607
  public async stopSync(timeout: number = 2000): Promise<void> {
608
+ // Coerce non-finite timeouts (NaN, Infinity) to the default. NaN
609
+ // comparisons are always false, so `elapsedTimeout >= NaN` would
610
+ // never trip the timeout exit; `Math.min(NaN, 100)` is NaN and
611
+ // `setTimeout(_, NaN)` clamps to 0, spinning the poll loop. Both
612
+ // are footguns for callers passing a computed timeout that
613
+ // accidentally evaluates to NaN.
614
+ const safeTimeout = Number.isFinite(timeout) ? timeout : 2000;
602
615
  this._engineGeneration++;
603
616
  let elapsedTimeout = 0;
604
617
 
605
618
  while (this._syncLock) {
606
- if (elapsedTimeout >= timeout) {
607
- throw new Error(`SyncEngineLevel: Existing sync operation did not complete within ${timeout} milliseconds.`);
619
+ if (elapsedTimeout >= safeTimeout) {
620
+ throw new Error(`SyncEngineLevel: Existing sync operation did not complete within ${safeTimeout} milliseconds.`);
608
621
  }
609
622
 
610
623
  elapsedTimeout += 100;
611
- await new Promise((resolve): void => { setTimeout(resolve, timeout < 100 ? timeout : 100); });
624
+ await sleep(Math.min(safeTimeout, 100));
612
625
  }
613
626
 
614
627
  if (this._syncIntervalId) {
package/src/types/dwn.ts CHANGED
@@ -202,24 +202,33 @@ export type DwnResponse<T extends DwnInterface> = {
202
202
  reply: DwnMessageReply[T];
203
203
  };
204
204
 
205
+ /**
206
+ * Per-DWN-interface message factory. Only the static `create` and `parse`
207
+ * methods are part of the contract — the underlying classes have private
208
+ * or protected constructors (factory pattern), so the interface
209
+ * intentionally does not declare `new ()`. Omitting `new ()` lets the
210
+ * mapped-type table below assign class values directly without casts:
211
+ * a class with a private constructor still satisfies a structural type
212
+ * that doesn't require constructability, as long as the static side
213
+ * (`create` / `parse`) lines up.
214
+ */
205
215
  export interface DwnMessageConstructor<T extends DwnInterface> {
206
- new (): DwnMessageInstance[T];
207
216
  create(params: DwnMessageParams[T]): Promise<DwnMessageInstance[T]>;
208
217
  parse(rawMessage: DwnMessage[T]): Promise<DwnMessageInstance[T]>;
209
218
  }
210
219
 
211
220
  export const dwnMessageConstructors: { [T in DwnInterface]: DwnMessageConstructor<T> } = {
212
- [DwnInterface.MessagesRead] : MessagesRead as any,
213
- [DwnInterface.MessagesSubscribe] : MessagesSubscribe as any,
214
- [DwnInterface.MessagesSync] : MessagesSync as any,
215
- [DwnInterface.ProtocolsConfigure] : ProtocolsConfigure as any,
216
- [DwnInterface.ProtocolsQuery] : ProtocolsQuery as any,
217
- [DwnInterface.RecordsDelete] : RecordsDelete as any,
218
- [DwnInterface.RecordsQuery] : RecordsQuery as any,
219
- [DwnInterface.RecordsRead] : RecordsRead as any,
220
- [DwnInterface.RecordsSubscribe] : RecordsSubscribe as any,
221
- [DwnInterface.RecordsWrite] : RecordsWrite as any,
222
- } as const;
221
+ [DwnInterface.MessagesRead] : MessagesRead,
222
+ [DwnInterface.MessagesSubscribe] : MessagesSubscribe,
223
+ [DwnInterface.MessagesSync] : MessagesSync,
224
+ [DwnInterface.ProtocolsConfigure] : ProtocolsConfigure,
225
+ [DwnInterface.ProtocolsQuery] : ProtocolsQuery,
226
+ [DwnInterface.RecordsDelete] : RecordsDelete,
227
+ [DwnInterface.RecordsQuery] : RecordsQuery,
228
+ [DwnInterface.RecordsRead] : RecordsRead,
229
+ [DwnInterface.RecordsSubscribe] : RecordsSubscribe,
230
+ [DwnInterface.RecordsWrite] : RecordsWrite,
231
+ };
223
232
 
224
233
  export interface DwnMessageInstance {
225
234
  [DwnInterface.MessagesRead] : MessagesRead;
package/src/utils.ts CHANGED
@@ -155,21 +155,6 @@ export function pollWithTtl(
155
155
  });
156
156
  }
157
157
 
158
- /** Concatenates a base URL and a path ensuring that there is exactly one slash between them */
159
- export function concatenateUrl(baseUrl: string, path: string): string {
160
- // Remove trailing slash from baseUrl if it exists
161
- if (baseUrl.endsWith('/')) {
162
- baseUrl = baseUrl.slice(0, -1);
163
- }
164
-
165
- // Remove leading slash from path if it exists
166
- if (path.startsWith('/')) {
167
- path = path.slice(1);
168
- }
169
-
170
- return `${baseUrl}/${path}`;
171
- }
172
-
173
158
  /**
174
159
  * Map over an array with bounded concurrency, preserving input order in the
175
160
  * output array. Uses a sliding-window pool of workers so the next item is
@@ -229,12 +214,12 @@ export async function mapConcurrentSettled<T, R>(
229
214
  concurrency: number,
230
215
  fn: (item: T, index: number) => Promise<R>,
231
216
  ): Promise<PromiseSettledResult<R>[]> {
232
- return mapConcurrent(items, concurrency, async (item, index) => {
217
+ return mapConcurrent<T, PromiseSettledResult<R>>(items, concurrency, async (item, index) => {
233
218
  try {
234
219
  const value = await fn(item, index);
235
- return { status: 'fulfilled', value } as PromiseFulfilledResult<R>;
220
+ return { status: 'fulfilled', value };
236
221
  } catch (reason) {
237
- return { status: 'rejected', reason } as PromiseRejectedResult;
222
+ return { status: 'rejected', reason };
238
223
  }
239
224
  });
240
225
  }