@unconfirmed/sui-effect 0.1.0 → 0.1.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 (82) hide show
  1. package/AGENTS.md +47 -14
  2. package/CHANGELOG.md +86 -0
  3. package/LLMS.md +2889 -1726
  4. package/README.md +517 -11
  5. package/dist/domain/bcs.d.ts +44 -0
  6. package/dist/domain/bcs.d.ts.map +1 -1
  7. package/dist/domain/bcs.js +64 -0
  8. package/dist/domain/bcs.js.map +1 -1
  9. package/dist/domain/errors.d.ts +164 -26
  10. package/dist/domain/errors.d.ts.map +1 -1
  11. package/dist/domain/errors.js +193 -15
  12. package/dist/domain/errors.js.map +1 -1
  13. package/dist/domain/executed.d.ts +123 -18
  14. package/dist/domain/executed.d.ts.map +1 -1
  15. package/dist/domain/executed.js +196 -5
  16. package/dist/domain/executed.js.map +1 -1
  17. package/dist/domain/journal-entry.d.ts +6 -2
  18. package/dist/domain/journal-entry.d.ts.map +1 -1
  19. package/dist/domain/schemas.d.ts +234 -67
  20. package/dist/domain/schemas.d.ts.map +1 -1
  21. package/dist/domain/schemas.js +82 -4
  22. package/dist/domain/schemas.js.map +1 -1
  23. package/dist/domain/sui-schema.d.ts +3 -2
  24. package/dist/domain/sui-schema.d.ts.map +1 -1
  25. package/dist/domain/sui-schema.js +3 -2
  26. package/dist/domain/sui-schema.js.map +1 -1
  27. package/dist/extension.d.ts +1 -1
  28. package/dist/extension.d.ts.map +1 -1
  29. package/dist/extension.js +1 -1
  30. package/dist/extension.js.map +1 -1
  31. package/dist/index.d.ts +1 -1
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +1 -1
  34. package/dist/index.js.map +1 -1
  35. package/dist/script.d.ts +1 -1
  36. package/dist/script.d.ts.map +1 -1
  37. package/dist/script.js +1 -1
  38. package/dist/script.js.map +1 -1
  39. package/dist/services/Script.d.ts +42 -0
  40. package/dist/services/Script.d.ts.map +1 -1
  41. package/dist/services/Script.js +69 -2
  42. package/dist/services/Script.js.map +1 -1
  43. package/dist/services/Signer.d.ts +32 -7
  44. package/dist/services/Signer.d.ts.map +1 -1
  45. package/dist/services/Signer.js +69 -10
  46. package/dist/services/Signer.js.map +1 -1
  47. package/dist/services/Sui.d.ts +42 -1
  48. package/dist/services/Sui.d.ts.map +1 -1
  49. package/dist/services/Sui.js +18 -4
  50. package/dist/services/Sui.js.map +1 -1
  51. package/dist/services/SuiCore.d.ts +12 -0
  52. package/dist/services/SuiCore.d.ts.map +1 -1
  53. package/dist/services/SuiCore.js +124 -0
  54. package/dist/services/SuiCore.js.map +1 -1
  55. package/dist/services/SuiCoreFake.d.ts +59 -4
  56. package/dist/services/SuiCoreFake.d.ts.map +1 -1
  57. package/dist/services/SuiCoreFake.js +199 -23
  58. package/dist/services/SuiCoreFake.js.map +1 -1
  59. package/dist/services/SuiExtension.d.ts +127 -14
  60. package/dist/services/SuiExtension.d.ts.map +1 -1
  61. package/dist/services/SuiExtension.js +125 -28
  62. package/dist/services/SuiExtension.js.map +1 -1
  63. package/dist/services/SuiGraphQL.d.ts +13 -0
  64. package/dist/services/SuiGraphQL.d.ts.map +1 -1
  65. package/dist/services/SuiGraphQL.js +13 -0
  66. package/dist/services/SuiGraphQL.js.map +1 -1
  67. package/dist/services/Tx.d.ts +630 -12
  68. package/dist/services/Tx.d.ts.map +1 -1
  69. package/dist/services/Tx.js +187 -8
  70. package/dist/services/Tx.js.map +1 -1
  71. package/dist/testing.d.ts +22 -3
  72. package/dist/testing.d.ts.map +1 -1
  73. package/dist/testing.js +39 -3
  74. package/dist/testing.js.map +1 -1
  75. package/dist/tx.d.ts +1 -1
  76. package/dist/tx.d.ts.map +1 -1
  77. package/dist/tx.js +1 -1
  78. package/dist/tx.js.map +1 -1
  79. package/docs/extensions.md +871 -33
  80. package/examples/extension-template/src/Escrow.ts +1 -1
  81. package/examples/extension-template/test/escrow.test.ts +91 -2
  82. package/package.json +3 -2
package/AGENTS.md CHANGED
@@ -26,7 +26,15 @@ the work plan. Where this file and the spec disagree, fix this file.
26
26
  edges: the Promise facade of `SuiExtension.fromService`, and `Script.run`,
27
27
  which is a process entrypoint and whose whole job is to fork the root fiber,
28
28
  await its `Exit` and exit.
29
- - No `any`. No `unknown` in an error channel. No `console.log` in `src/`.
29
+ - No `any`. No `unknown` in an error channel. No `console.log` in `src/`. The
30
+ one `console.warn` is `SuiCore.mapSdkError`'s duplicate-SDK warning, which
31
+ fires at most once per process and is documented where it lives. **Nothing in
32
+ `src/` writes to stderr otherwise** — `test/release-0-1-2.test.ts` captures it
33
+ around a full `Tx.run` on the fake and asserts it is empty.
34
+ - **A branded id from a shorthand spelling** comes from `SuiAddress.normalize`
35
+ / `ObjectId.normalize` (decode, then brand); `.make` validates without
36
+ decoding and is for the padded form only. Neither is for a value that came
37
+ from outside: that goes through `Schema.decodeUnknownEffect`.
30
38
  - **One Move type rule.** `typeMatches(expected, actual)` (`src/domain/bcs.ts`)
31
39
  is the only comparison: a bare expected tag matches every instantiation of the
32
40
  generic, a parameterized one is compared in full after normalization. The
@@ -67,7 +75,7 @@ the work plan. Where this file and the spec disagree, fix this file.
67
75
  | Tier | What it is | When to use it |
68
76
  |---|---|---|
69
77
  | `SuiCore` | A 1:1 Effect wrap of `ClientWithCoreApi`. One member per `SuiClientTypes.TransportMethods` key plus `getObject`, `getDynamicObjectField`, `waitForTransaction`, `signAndExecuteTransaction` and `use`. `Include` generics preserved. | Reaching a field or method `Sui` does not expose, and inside extension implementations. |
70
- | `Sui` | The opinionated tier over `SuiCore`: fixed include sets, decoded BCS content, `Option` where absence is normal, chunked and integrity-checked batch reads (`getObjects` per-item `Result`, `getObjectsOrFail` first-error-wins), `Stream` pagination, one sender lock. | Almost all application and extension code. |
78
+ | `Sui` | The opinionated tier over `SuiCore`: fixed include sets, decoded BCS content, `Option` where absence is normal, chunked and integrity-checked batch reads (`getObjects` per-item `Result`, `getObjectsStrict` first-error-wins — `getObjectsOrFail` is the deprecated alias), `Stream` pagination, one sender lock. | Almost all application and extension code. |
71
79
 
72
80
  Reads go through `Sui`; writes go through `Tx`. An extension never calls
73
81
  `SuiCore.executeTransaction` directly. `Sui` exposes the `SuiCore` it was built
@@ -79,11 +87,11 @@ and nothing else.
79
87
  | Name | What it is |
80
88
  |---|---|
81
89
  | `Signer` | A credential as a **value**, never a service: `{ address, scheme, signTransaction, signPersonalMessage }`. One process may hold two. Secret material never reaches the value. |
82
- | `Tx.build/sign/cosign/sponsored/submit/reconcile/run/reconcileAll` | The lifecycle as functions, each with a closed error union, all `R = Sui`. |
90
+ | `Tx.build/sign/cosign/sponsored/submit/submitVia/reconcile/recorded/run/reconcileAll` | The lifecycle as functions, each with a closed error union, all `R = Sui`. `submitVia` is `submit` for a submission somebody else makes; `recorded(digest)` reads one journal entry, which is what `reconcileAll` — unresolved entries only — cannot answer. `Tx.run` takes `onSigned`, the hook between the last signature and the first send. |
83
91
  | `SubmitConfig` | A `Context.Reference` holding expiration policy, the optional `validFor` wall-clock bound, the gas-budget ceiling, `preflight`, the sender lock, the resubmit schedule, attempts, timeout and expiry margin, plus `expiryEvidence`, `reconcileRecheck`, `awaitVisibility`, `visibilityTimeout` and `nonce`. |
84
92
  | `Journal` | A `Context.Reference` with an in-memory default. `@unconfirmed/sui-effect/journal` swaps in a durable one over `KeyValueStore`; `Tx.reconcileAll()` is the explicit startup call. |
85
93
  | `Script` | `{ sui, core, signer, network }` plus `Script.run` and `Script.exitCode`. `ScriptReadOnly` is the signer-less variant, a separate key on purpose. |
86
- | `SuiExtension.fromService` | The Promise face of an Effect service, and the only place in `src/` allowed to run Effects. Options: `sui` (chain pinning), `warm` (build the runtime synchronously in `register`). The face carries `$ready()` and `$dispose()`; a synchronous member called before the runtime exists fails with `ExtensionNotReady`, while `Effect` and `Stream` members work cold. Every registration on one client shares one base `Sui`/`SuiCore` — one chain-id read and one sender-lock map — reference counted, so `$dispose()` releases it only when the last registration does. |
94
+ | `SuiExtension.fromService` | The Promise face of an Effect service, and the only place in `src/` allowed to run Effects. Options: `sui` (chain pinning), `warm` (build the runtime synchronously in `register`, which throws **any** layer failure out of `$extend`). The face carries `$ready()` and `$dispose()`; a synchronous member called before the runtime exists fails with `ExtensionNotReady`, while `Effect` and `Stream` members work cold — a cold call is a real `Promise` subclass that is also an `AsyncIterable`, with its rejection pre-handled. `PromiseFace` recurses by **type**, so an interface-typed namespace is mapped; the leaves are functions, arrays, `Uint8Array`, `Date`, `Promise`, BCS codecs and anything marked `SuiExtension.Leaf<T>` / `SuiExtension.leaf(value)`. Every registration on one client shares one base `Sui`/`SuiCore` — one chain-id read and one sender-lock map — reference counted, so `$dispose()` releases it only when the last registration does; a `warm` registration re-warms on the next use after `$dispose()`. |
87
95
  | `SuiGraphQL` | A bare tag over the SDK's `SuiGraphQLClient` (`layer`, `layerConfig`, `layerUnavailable`). @unconfirmed/sui-effect wraps no GraphQL API; the tag exists so extensions share one client. |
88
96
 
89
97
  ## Extensions
@@ -99,7 +107,12 @@ copyable package every block of that guide is quoted from, and
99
107
  `Tx.submit` journals `Signed` before the first execute, re-sends the identical
100
108
  bytes (never a rebuild) on a retryable `TransportError` or a timeout, and
101
109
  reconciles when the retries run out. A `TransportError` never escapes once bytes
102
- may have been sent: it becomes `SubmissionUnknown`, which carries them.
110
+ may have been sent: it becomes `SubmissionUnknown`, which carries them. **The
111
+ one exception is a gRPC `INVALID_ARGUMENT`** (`REFUSED_OUTRIGHT` in `Tx.ts`):
112
+ the node refused the request, nothing executed, and reconciling would ask
113
+ whether a never-sent transaction is on chain — a question a lagging or scripted
114
+ node can answer yes. That error escapes as itself, which is why `SubmitError`
115
+ includes `TransportError`.
103
116
  `JournalError` escapes only from the `Signed` write, before anything is sent;
104
117
  after the network has answered, a failed journal write is logged and the answer
105
118
  stands.
@@ -151,7 +164,7 @@ Every failure is one flat tag; there is no error inheritance.
151
164
  | `ObjectNotFound` / `ObjectDeleted` / `ObjectUnavailable` | The three `ObjectError.reason` values. |
152
165
  | `TransactionNotFound` | No transaction with that digest is known. |
153
166
  | `NetworkMismatch` | The node is on another chain than the layer was built for. |
154
- | `DecodeError` | BCS content or a schema boundary did not decode. |
167
+ | `DecodeError` | BCS content or a schema boundary did not decode. `kind` is `"type"` (tag mismatch, nothing parsed), `"bytes"` (BCS parse or trailing bytes) or `"shape"` (a domain schema). Every producer sets it; consumers branch on it, never on `issue`. |
155
168
  | `SimulationFailed` | Simulation reported an execution failure. No gas charged. |
156
169
  | `ExecutionFailed` | Applied on chain and failed. Gas charged. |
157
170
  | `SubmissionUnknown` | Bytes may have been sent; the outcome is unknown. Carries the signed bytes, unless it came from reconciling a bare digest. |
@@ -164,21 +177,33 @@ Every failure is one flat tag; there is no error inheritance.
164
177
  `"not_applied"` for every other tag in the taxonomy, and `"unknown"` for
165
178
  anything that is neither one of those tags nor declares an `outcome`.
166
179
  `Script.exitCode` exits 1 for that last case rather than 3. An extension error
167
- may declare its own `outcome`, and should. `SuiError.isRetryable`, `SuiError.describe` (one actionable line) and
168
- `SuiError.toJson` round it out.
180
+ may declare its own `outcome`, and should. `SuiError.outcome` also takes `{ phase: "pre-submit" }`, which changes only the
181
+ unclassified answer (`"not_applied"` instead of `"unknown"`, true by
182
+ construction before a send). `SuiError.isRetryable`, `SuiError.isTaxonomy`,
183
+ `SuiError.describe` (one actionable line, and total over foreign errors) and
184
+ `SuiError.toJson` round it out. **Every error class without a `message` schema
185
+ field carries `override get message()` returning `describe(this)`**, so
186
+ `.message` is never empty; it is a getter, so it stays out of the encoding — and `toJson` adds `outcome` from the instance
187
+ when the error declares one, which is almost always a class field rather than a
188
+ schema field.
169
189
 
170
190
  ## Testing
171
191
 
172
192
  `@unconfirmed/sui-effect/testing` ships `SuiCoreFake.layer(script)`, `layerTest(script)`
173
- (the real `Sui` over the fake `SuiCore`), `layerExtensionTest(layer, script)`
174
- (an extension's own layer over that) and `SuiTest` (`putObject`, `bumpVersion`,
193
+ (the real `Sui` over the fake `SuiCore`), `layerExtensionTest(layer, script, { extra })`
194
+ (an extension's own layer over that, with `SuiGraphQL.layerUnavailable` provided
195
+ by default and `extra` for any other dependency the layer requires) and `SuiTest` (`putObject`, `bumpVersion`,
175
196
  `recordTransaction`, `deleteObject`, `setClock`, `setEpoch`, `scriptExecute`,
176
197
  `scriptSimulate`, `scriptGetTransaction`, `calls`), which is the whole harness
177
198
  an extension's tests need. Call recording is reached through `SuiTest.calls`,
178
199
  not off the fake handle. The fake serves in-memory objects with
179
200
  BCS content, the Clock object `0x6`, and scripted outcomes
180
- (`FakeOutcome.succeed`, `failWith`, `transportError`, `notFound`, `timeoutThen`)
181
- for simulate, execute, `getTransaction` and the resolver's budget simulation
201
+ (`FakeOutcome.succeed`, `failWith` which takes the SDK's wire `ExecutionError`
202
+ **or** sui-effect's decoded `ExecutionReason`, encoding the second and throwing
203
+ on anything else — `transportError`, `notFound`, `timeoutThen`)
204
+ for simulate, execute, `getObject` (read-failure injection), `getTransaction` —
205
+ which is also what drives every
206
+ `waitForTransaction` outcome — `coinMetadata`, and the resolver's budget simulation
182
207
  (`buildSimulate`, which is how a test makes `Tx.build` fail with
183
208
  `SimulationFailed`). It records every call so a test can
184
209
  assert the include set that was sent. It also implements
@@ -204,6 +229,14 @@ rather than failing a `Simulation` decode.
204
229
  The fake enforces the invariants the lifecycle depends on: a known digest
205
230
  executes idempotently, gas selection excludes object inputs, the coin set
206
231
  evolves (deleted, mutated with `FakeChange.balance`, gas-bumped, created), a
207
- submission with fewer signatures than the bytes name signers is refused, and a
232
+ submission whose signatures do not cover the addresses the bytes name by count
233
+ **and** by the addresses recovered from the signatures — is refused the way a
234
+ validator refuses it (an `RpcError` carrying `INVALID_ARGUMENT`, so `Tx.submit`
235
+ fails fast instead of reconciling into a scripted success), and a
208
236
  version history is served through `tryGetPastObject`, which is what
209
- `SuiCore.getObjectAtVersion` reads.
237
+ `SuiCore.getObjectAtVersion` reads. The resolver's budget simulate is recorded
238
+ as a `simulateTransaction` call (`resolver: true`) and answered by
239
+ `FakeScript.buildSimulate` when there is one and by the ordered `simulate`
240
+ script otherwise, so "building always simulates" is observable on the harness.
241
+ `getBalance` and `listBalances` are keyed by owner and coin type; a `FakeBalance`
242
+ with no `owner` answers for everyone.
package/CHANGELOG.md ADDED
@@ -0,0 +1,86 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@unconfirmed/sui-effect`. The format is one line per
4
+ change, newest release first.
5
+
6
+ ## 0.1.2
7
+
8
+ Two defects from the first three downstream conversions, and the surface they
9
+ exposed. Nothing was removed. Two shapes changed: `Tx.reconcileAll` now returns
10
+ a tagged union, and `Tx.submit`'s error union gained `TransportError` for the
11
+ one case where a node refuses a submission outright.
12
+
13
+ ### Fixed
14
+
15
+ - **`Tx.submit` no longer reconciles a submission the node refused.** A gRPC `INVALID_ARGUMENT` — malformed bytes, or a sponsored transaction carrying only the sender's signature — is the node answering that it did not execute anything, so it escapes as the `TransportError` it is. Reconciling it asked "is this digest on chain?" about a transaction that was never sent, and a lagging node (or a scripted `getTransaction`) could answer yes: an app's sponsored, under-signed submission was reported as a **successful `Executed`**. `SubmitError` therefore includes `TransportError`; every other transport failure still becomes `SubmissionUnknown`.
16
+ - **`SuiCoreFake` refuses an under-signed or wrongly-signed submission the way a validator does**, with an `RpcError` carrying `INVALID_ARGUMENT` rather than a plain `Error` that `mapSdkError` classified as a *retryable* transport failure. It checks the signature count and, when the signatures parse, the addresses they were made by. A sponsored submit against the fake needs `Tx.cosign` (or `Tx.run`'s `sponsor`) first, exactly as it does against a node.
17
+ - **The fake's resolver simulate is recorded**, so `SuiTest.calls("simulateTransaction")` sees the simulate that `Tx.build` promises, and it is answered by `FakeScript.buildSimulate` when there is one and by the ordered `simulate` script otherwise — `simulate: [FakeOutcome.failWith(...)]` now surfaces through `Tx.build` and `Tx.run` as `SimulationFailed` instead of vanishing.
18
+ - **Nothing in `src/` writes to stderr** (the one documented `console.warn` aside). A test captures stderr around a full `Tx.run` on the fake, with `bigint`s in the script, and asserts it is empty: the reported `console.error("DEBUG …", JSON.stringify(outcomes))` lines are in no published 0.1.1 artefact, but a `JSON.stringify` over a scripted `bigint` throws, and the throw surfaced later as an unrelated `FakeUnimplemented`.
19
+ - **Every error class has a real `.message`.** `Schema.TaggedError` leaves it empty, so anything surfacing `error.message` showed nothing; the classes without a `message` schema field now carry `override get message()` returning `SuiError.describe(this)`. It is a getter, so `SuiError.toJson`'s encoding is unchanged.
20
+ - **`SuiError.describe` accepts a foreign error** instead of returning `undefined` from an exhaustive `switch` while its signature promised a `string`. An extension's error, or any `{ _tag }`, gets its tag and message.
21
+ - **`SuiCoreFake.getBalance` is keyed by owner**, not by coin type alone. A `FakeBalance` may carry an `owner`; one without answers for every owner, which is what a pre-0.1.2 script meant.
22
+ - **`FakeOutcome.failWith` accepts sui-effect's decoded `ExecutionReason`** as well as the SDK's wire `ExecutionError`, encoding the first into the second. A fixture in the wrong shape used to decode-fail several calls later, on a different method, after the outcome cursor had already moved; it now throws where the fixture is written, naming both shapes.
23
+
24
+ ### Added
25
+
26
+ - **`DecodeError.kind`**: `"type"` (the Move type was not the expected one — nothing was parsed, and the one a consumer may answer with a 404), `"bytes"` (the BCS parse failed or left trailing bytes — never safe to swallow), `"shape"` (a domain schema refused a parsed or JSON value). Every producer sets it, `SuiError.describe` prints it, and it defaults to `"shape"`, so an extension constructing a `DecodeError` without one still compiles. Branch on `kind`, never on `issue`.
27
+ - **`Tx.reconcileAll` returns a `Schema.TaggedUnion`**: `{ _tag: "Executed", executed }`, `{ _tag: "ExecutionFailed", error }`, `{ _tag: "NotApplied", error }`, `{ _tag: "SubmissionUnknown", error }`. The old bare union — an `Executed` with no discriminator beside three errors that had one — is exported as the deprecated type `ReconciledOutcome`.
28
+ - **`Tx.recorded(digest)`**: the journal entry for one digest, as an `Option`. `reconcileAll` returns **only** the entries that were unresolved, and this is how to ask about one that already settled.
29
+ - **`Tx.submitVia(signed, send)`**: the submission lifecycle when a relay or a sponsorship service does the sending. Journals `Signed` before calling `send`, calls it exactly once, decodes whatever it answers (an SDK `TransactionResult`, a reduced envelope, a bare digest, or nothing — in which case it asks the chain), reconciles an ambiguous failure with the full evidence rules, and journals the terminal answer. A `send` error whose instance declares `outcome: "not_applied"` fails straight through without a reconcile.
30
+ - **`Tx.run`'s `onSigned` hook**: called with the signed bytes after the last signature and before the first `executeTransaction`, inside the sender lock, for the record the journal does not hold. Its failure is a `JournalError` and fails the run with nothing sent.
31
+ - **`Executed.fromPartial(envelope)`**: an `Executed` from a reduced relay or sponsor envelope. `changedObjects` entries need only `objectId` and `idOperation`; what the envelope did not say stays `Unknown` rather than being guessed, and the accessors read `Unknown` as "not said" so `created()` and `deleted()` still classify. JSON spellings are accepted (`bcs` as base64 or a byte array, every `u64` as a number or a `bigint`). It cannot invent the `objectTypes` join, so the type-filtered accessors match nothing without it, and `gasUsedTotal` is `0n` for an envelope that reported no gas.
32
+ - **`Executed.fromTransactionResult(result)`** is public: the strict constructor, for an SDK `TransactionResult` read with the full include set.
33
+ - **`Event.json`**: kept when the source carried one, which in practice means a relay envelope whose events have no BCS. Never populated from a gRPC execute.
34
+ - **`SuiError.outcome(error, { phase: "pre-submit" })`** answers `"not_applied"` rather than `"unknown"` for an unclassified error, which is true by construction before anything is sent. The default is `"post-submit"`, the 0.1.1 behaviour. **`SuiError.isTaxonomy(error)`** answers whether an error is one of the tags this package owns.
35
+ - **`Sui.getObjectsStrict`**: `getObjectsOrFail` under a name that reads correctly in isolation. The old name stays as a deprecated alias of the same function.
36
+ - **`Signer.fromConfig` accepts a 32-byte hex seed** (64 hex characters, `0x` optional, read as Ed25519) as well as a Bech32 `suiprivkey`, so a raw seed from a secret manager no longer needs `fromHex` in application code.
37
+ - **`Signer.fromSdkSigner` validates its argument** and throws a `TypeError` naming the missing member. A double without `getKeyScheme` produced `scheme: undefined` and nothing complained until a validator did. Its JSDoc now states that `toSuiAddress()` and `getKeyScheme()` are read at construction, and that clear-signing inputs are the SDK signer's own concern.
38
+ - **`SuiLayerOptions.retry`**: a `Schedule` for the one `getChainIdentifier` a `Sui` layer makes. A `ManagedRuntime` memoizes its layer build, failure included, so one unlucky read at boot otherwise poisons a browser tab or a Worker isolate for its whole life.
39
+ - **`FakeScript.getObject`** (and `SuiTest.scriptGetObject`): scripted outcomes for a **read**, so a test can inject a transport failure, a miss or a timeout into `getObject`. An absent or exhausted script serves the object map as before.
40
+ - **`Script.report(exit, { stderr?, journal? })`**: the diagnostic lines and the unresolved-entry printing `Script.run` does at the end, returning the exit code, for a CLI that owns its own argv parser and process and cannot hand the entrypoint over.
41
+ - **`Script.run` prints each unresolved journal entry encoded through the `JournalEntry` schema** — the same JSON a durable journal stores, bytes base64 — beside the two human lines it already printed.
42
+
43
+ ### Documentation
44
+
45
+ - The extension guide gains three sections: **"Application consumers"** (a module-level `ManagedRuntime` over `Sui.layerNoDepsWith({ chainId })` and `SuiCore.layerFromClient`, HMR disposal, the memoized-failed-build trap, the in-memory journal in a browser, mapping `outcome` onto UI states with the pre-submit caveat, signing with an external cosigner, and a `layerTest`-backed double for an app's own `runSui`), **"Workers and Durable Objects"** (no `process`, `ConfigProvider.fromEnvRecord(env)`, one runtime per isolate, the sender lock not crossing isolates and address-balance gas, DO alarms versus `Schedule` sleeps, and the whole `KeyValueStore.makeStringOnly` adapter over DO storage), and **"Relay and sponsor envelopes"** (`Executed.fromPartial`, what it cannot invent, and `Tx.submitVia`).
46
+ - A **"Sponsored by an external service"** recipe in the signers section: build with `Tx.sponsored`, sign, hand over the base64 bytes and the serialized signature, and reconcile by the digest — which a co-signature does not change.
47
+ - The testing section gains: `getTransaction` is what a submit test's reconcile reads (script `notFound` unless the test means "this landed"), a sponsored submit needs `Tx.cosign` and should assert the signature count on `SuiTest.calls("executeTransaction")`, which script slot answers the build's simulate, that `layerTest` asserts the built-in chain id for `mainnet` and `testnet`, how to inject a read failure, and why an isolated-consumer fixture in the package `tsconfig`'s `include` typechecks your code against the tarball it last installed.
48
+ - Error guidance: tag strings are namespaced by whoever defined them and must be copied from the installed package; `DecodeError.kind` replaces branching on `issue`; a wrapper error must copy the `outcome` and digest of what it wrapped, because `Script.exitCode` honours `outcome` first.
49
+ - Migration-table rows for a hand-rolled idempotent submitter, `sui.core.getTransaction` for a transaction that may have failed, a predecessor `ready()` genesis check mapping to `Sui.layerNoDepsWith({ chainId })`, and a removed standalone read export; plus the `.find(... type?.includes(...))`-then-throw grep pattern that `Executed.expectCreated` replaces, and the rule that a consumer-edit table is regenerated from the published `exports` map rather than from facade call sites.
50
+ - README: the `INVALID_ARGUMENT` rule, `onSigned`, `submitVia`, the `Reconciled` shape and `Tx.recorded`, `DecodeError.kind`, the error `.message` getter, `outcome`'s phase, relay envelopes and `Executed.events`' exact type, `bigint` at the JSON boundary, the `SubmitConfig` override idiom with `maxGasBudget`'s 50 SUI default and when `lockSender: false` is correct, and a section on applications, Workers and Durable Objects.
51
+
52
+ ## 0.1.1
53
+
54
+ Seventeen fixes from the first downstream conversion. Nothing in the public API
55
+ was removed or renamed: an extension built against 0.1.0 compiles unchanged, and
56
+ the type-level workarounds it had to carry are now unnecessary.
57
+
58
+ ### Fixed
59
+
60
+ - `PromiseFace<S>` recurses into **interface-typed** members, not only those assignable to `Record<string, unknown>`, so an interface-typed namespace (`readonly protocol: ProtocolService`) is mapped in the type the way the runtime has always mapped it — before this the face type said `Effect` where the value was a `Promise`.
61
+ - `SuiError.toJson` includes `outcome` when the error instance declares one, including the usual case where it is a class field rather than a schema field, so a log line carries the same axis `SuiError.outcome` and `Script.exitCode` read.
62
+ - The placeholder a **cold** member call returns is a real `Promise` subclass implementing `Symbol.asyncIterator`, so `instanceof Promise` holds and `expect(...).rejects` recognises it; its rejection is pre-handled, so a cold call nobody awaits no longer aborts the process with an unhandled `ExtensionNotReady`.
63
+ - A `warm` registration re-runs its warm build on the next use after `$dispose()` instead of degrading to a cold registration whose every synchronous member throws `ExtensionNotReady`.
64
+ - `mapSdkError` duck-types the SDK's error classes when `instanceof` fails — `reason` plus `objectId` for `ObjectNotFound` / `ObjectDeleted` / `ObjectUnavailable`, `reason` plus `digest` for `TransactionNotFound`, the name plus `executionError` for `SimulationFailed` — so two copies of `@mysten/sui` in one process no longer turn every one of those tags into `TransportError { status: "notFound" }`, and the first such failure logs one warning naming the real problem.
65
+
66
+ ### Added
67
+
68
+ - `SuiExtension.Leaf<T>` and `SuiExtension.leaf(value)`: the marker an extension puts on a class-instance member the Promise face must pass through whole rather than recurse into. `Uint8Array`, `Date`, `Promise`, arrays and BCS codecs (`parse` plus `serialize`) are recognised without it.
69
+ - `SuiAddress.normalize(input)` and `ObjectId.normalize(input)`: decode-and-brand from any spelling the SDK accepts, which is what `"0x1"` needs and what `.make` (validate without decoding) refuses.
70
+ - `SuiSchema.decodeWith(bcsType, expectedType, map)`: a BCS layout plus a throwing mapper into a domain value, as one codec, so an extension stops hand-rolling `Effect.try` around `Schema.decodeUnknownEffect` and losing the `DecodeError`.
71
+ - `layerExtensionTest(layer, script, { extra })`: extra dependency layers for an extension whose layer requires more than the client could provide, with `SuiGraphQL.layerUnavailable` merged in by default.
72
+ - `FakeScript.coinMetadata`, so `getCoinMetadata` is testable on the harness; an unscripted coin type answers `{ coinMetadata: null }` the way a node does.
73
+ - `CHANGELOG.md`, shipped in the package's `files`.
74
+
75
+ ### Documentation
76
+
77
+ - The extension guide gains: the sanctioned idiom for a standalone function that needs a sibling extension's service, a worked generic-Move-type example on the fake, the `bun install --force` step after re-packing a vendored tarball with an unchanged filename, that `waitForTransaction` outcomes are driven by `FakeScript.getTransaction`, that a `warm` registration throws **any** layer failure synchronously out of `$extend`, and what a cold placeholder is now that it is a real `Promise`.
78
+ - `SuiGraphQL`'s JSDoc states that `yield* SuiGraphQL` yields the `SuiGraphQLClient` itself and that the service type is `SuiGraphQL["Service"]`.
79
+
80
+ ## 0.1.0
81
+
82
+ Initial release: two client tiers (`SuiCore`, `Sui`), a closed error taxonomy,
83
+ the transaction lifecycle as functions with typed outcomes (`Tx`), the journal,
84
+ the `Script` preset, the BCS bridge and branded schemas, `SuiExtension.fromService`
85
+ for the Promise face of an Effect service, `SuiGraphQL` as a bare tag, and the
86
+ in-memory fake with its test layers.