@usearete/sdk 0.23.1 → 0.25.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.
package/README.md CHANGED
@@ -335,7 +335,17 @@ Session surface:
335
335
 
336
336
  Prefer these connected paths in application code. `Arete.connect(STACK, ...)` remains available when a direct single-stack client is more convenient.
337
337
 
338
- If multiple stacks package the same program key, the first stack in the session definition owns the promoted alias and the session emits a warning. Both owner-scoped stack paths remain available. An explicitly declared standalone program always owns its top-level key.
338
+ Programs are matched by identity, not by name (`compareProgramIdentity(a, b)` returns `'same' | 'unproven' | 'different'`). A program SDK's identity is its `packageReleaseHash`, the program package release a registry-installed SDK was generated from; local builds have none.
339
+
340
+ - Both have one: equal hashes (or the same object) are the same program. A standalone program that a stack also provides is served by that stack's connected instance: one client, no warning. Different hashes throw `AreteError` with code `PROGRAM_KEY_CONFLICT` before anything connects.
341
+ - At least one has none, with the same `programSpecHash`: the explicitly attached program takes the key, with one `console.warn`. A standalone session program takes `session.programs.<key>`, and `session.stacks.<name>.programs.<key>` keeps the stack's. Two stacks with such copies promote the first stack's.
342
+ - Anything else (a different or missing `programSpecHash`) throws `PROGRAM_KEY_CONFLICT`; use `session.stacks.<name>.programs.<key>` or attach the standalone program under another key. Two stacks bundling different programs under one key both stay reachable through their stacks, and reading `session.programs.<key>` throws `PROGRAM_KEY_CONFLICT` naming them.
343
+
344
+ The same rule applies to `withPrograms`, `ConnectOptions.programs`, a session member's `programs`, and React's `useArete(stack, { programs })`, where the attached program replaces the stack's for that client. `isSameProgramSdk(a, b)` is `compareProgramIdentity(a, b) === 'same'`.
345
+
346
+ `extendProgram`, `extendPrograms`, and `withProgramRead` drop `packageReleaseHash`, because a program changed outside its generated SDK is no longer provably that SDK. Generated entries stamp it last with `withProgramIdentity(program, { packageReleaseHash })`, after the package's own extension.
347
+
348
+ Generated stack and program objects carry their runtime extensions under registry symbols, so `{ ...MY_STACK }` keeps `read`, `flows`, program operations and read descriptors, while `Object.keys` and JSON never list them. `EXTENSION_API_VERSION` (also `arete.extensionApi` in this package's `package.json`) versions that extension contract and changes only on a breaking change.
339
349
 
340
350
  ## Chain Reads
341
351
 
@@ -446,6 +456,28 @@ type RichUpdate<T> =
446
456
 
447
457
  `remove` means an entity left only this query's filter or window. `delete` means the source entity was deleted and is removed from every query for that view.
448
458
 
459
+ A `patch` for a key the client holds no entity for (never received, or evicted by `maxEntriesPerView`) is discarded rather than stored as a partial entity; the entity appears with the server's next full `upsert`. Each discard is reported to `onFrameValidationError` with `reason: 'unknown-key'`. Replayable append-view records (frames with an `offset`) are events and are always applied.
460
+
461
+ ## Testing
462
+
463
+ `@usearete/sdk/testing` provides supported, dependency-light test helpers:
464
+
465
+ - `createWebSocketHarness()` — a scripted WebSocket server; pass `harness.websocketFactory` as `auth.websocketFactory` and answer subscriptions with `frames.*` builders.
466
+ - `createFrameHarness()` — the store engine without a socket.
467
+ - `createFakeTransactionTransport()` — a recording relay that can fail or stall.
468
+ - `createWalletFixture()` — a recording wallet with scripted outcomes; `createTransactionOutcomeFixtures()` covers every status and phase.
469
+ - `createFetchStub(routes)` — a routed, recording `fetch`.
470
+
471
+ ```ts
472
+ import { createWebSocketHarness, createWalletFixture } from '@usearete/sdk/testing';
473
+
474
+ const ws = createWebSocketHarness();
475
+ const client = await Arete.connect(MY_STACK, {
476
+ auth: { websocketFactory: ws.websocketFactory },
477
+ wallet: createWalletFixture(),
478
+ });
479
+ ```
480
+
449
481
  ## Replay cursors
450
482
 
451
483
  Updates from an append view backed by the server's journal carry `cursor`, the