sui-effect 0.1.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 (115) hide show
  1. package/AGENTS.md +209 -0
  2. package/LICENSE +21 -0
  3. package/LLMS.md +6707 -0
  4. package/README.md +300 -0
  5. package/dist/domain/bcs.d.ts +101 -0
  6. package/dist/domain/bcs.d.ts.map +1 -0
  7. package/dist/domain/bcs.js +183 -0
  8. package/dist/domain/bcs.js.map +1 -0
  9. package/dist/domain/errors.d.ts +595 -0
  10. package/dist/domain/errors.d.ts.map +1 -0
  11. package/dist/domain/errors.js +476 -0
  12. package/dist/domain/errors.js.map +1 -0
  13. package/dist/domain/executed.d.ts +325 -0
  14. package/dist/domain/executed.d.ts.map +1 -0
  15. package/dist/domain/executed.js +253 -0
  16. package/dist/domain/executed.js.map +1 -0
  17. package/dist/domain/journal-entry.d.ts +468 -0
  18. package/dist/domain/journal-entry.d.ts.map +1 -0
  19. package/dist/domain/journal-entry.js +63 -0
  20. package/dist/domain/journal-entry.js.map +1 -0
  21. package/dist/domain/schemas.d.ts +950 -0
  22. package/dist/domain/schemas.d.ts.map +1 -0
  23. package/dist/domain/schemas.js +571 -0
  24. package/dist/domain/schemas.js.map +1 -0
  25. package/dist/domain/sui-schema.d.ts +12 -0
  26. package/dist/domain/sui-schema.d.ts.map +1 -0
  27. package/dist/domain/sui-schema.js +12 -0
  28. package/dist/domain/sui-schema.js.map +1 -0
  29. package/dist/extension.d.ts +7 -0
  30. package/dist/extension.d.ts.map +1 -0
  31. package/dist/extension.js +7 -0
  32. package/dist/extension.js.map +1 -0
  33. package/dist/index.d.ts +31 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +31 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/internal.d.ts +18 -0
  38. package/dist/internal.d.ts.map +1 -0
  39. package/dist/internal.js +18 -0
  40. package/dist/internal.js.map +1 -0
  41. package/dist/journal.d.ts +51 -0
  42. package/dist/journal.d.ts.map +1 -0
  43. package/dist/journal.js +18 -0
  44. package/dist/journal.js.map +1 -0
  45. package/dist/script.d.ts +7 -0
  46. package/dist/script.d.ts.map +1 -0
  47. package/dist/script.js +7 -0
  48. package/dist/script.js.map +1 -0
  49. package/dist/services/Journal.d.ts +76 -0
  50. package/dist/services/Journal.d.ts.map +1 -0
  51. package/dist/services/Journal.js +70 -0
  52. package/dist/services/Journal.js.map +1 -0
  53. package/dist/services/JournalKeyValueStore.d.ts +73 -0
  54. package/dist/services/JournalKeyValueStore.d.ts.map +1 -0
  55. package/dist/services/JournalKeyValueStore.js +155 -0
  56. package/dist/services/JournalKeyValueStore.js.map +1 -0
  57. package/dist/services/Script.d.ts +210 -0
  58. package/dist/services/Script.d.ts.map +1 -0
  59. package/dist/services/Script.js +441 -0
  60. package/dist/services/Script.js.map +1 -0
  61. package/dist/services/Signer.d.ts +132 -0
  62. package/dist/services/Signer.d.ts.map +1 -0
  63. package/dist/services/Signer.js +158 -0
  64. package/dist/services/Signer.js.map +1 -0
  65. package/dist/services/SubmitConfig.d.ts +175 -0
  66. package/dist/services/SubmitConfig.d.ts.map +1 -0
  67. package/dist/services/SubmitConfig.js +52 -0
  68. package/dist/services/SubmitConfig.js.map +1 -0
  69. package/dist/services/Sui.d.ts +295 -0
  70. package/dist/services/Sui.d.ts.map +1 -0
  71. package/dist/services/Sui.js +414 -0
  72. package/dist/services/Sui.js.map +1 -0
  73. package/dist/services/SuiCore.d.ts +227 -0
  74. package/dist/services/SuiCore.d.ts.map +1 -0
  75. package/dist/services/SuiCore.js +400 -0
  76. package/dist/services/SuiCore.js.map +1 -0
  77. package/dist/services/SuiCoreFake.d.ts +278 -0
  78. package/dist/services/SuiCoreFake.d.ts.map +1 -0
  79. package/dist/services/SuiCoreFake.js +1003 -0
  80. package/dist/services/SuiCoreFake.js.map +1 -0
  81. package/dist/services/SuiExtension.d.ts +208 -0
  82. package/dist/services/SuiExtension.d.ts.map +1 -0
  83. package/dist/services/SuiExtension.js +355 -0
  84. package/dist/services/SuiExtension.js.map +1 -0
  85. package/dist/services/SuiGraphQL.d.ts +98 -0
  86. package/dist/services/SuiGraphQL.d.ts.map +1 -0
  87. package/dist/services/SuiGraphQL.js +118 -0
  88. package/dist/services/SuiGraphQL.js.map +1 -0
  89. package/dist/services/Tx.d.ts +687 -0
  90. package/dist/services/Tx.d.ts.map +1 -0
  91. package/dist/services/Tx.js +1224 -0
  92. package/dist/services/Tx.js.map +1 -0
  93. package/dist/testing.d.ts +88 -0
  94. package/dist/testing.d.ts.map +1 -0
  95. package/dist/testing.js +163 -0
  96. package/dist/testing.js.map +1 -0
  97. package/dist/tx.d.ts +24 -0
  98. package/dist/tx.d.ts.map +1 -0
  99. package/dist/tx.js +24 -0
  100. package/dist/tx.js.map +1 -0
  101. package/docs/extensions.md +1685 -0
  102. package/examples/extension-template/README.md +170 -0
  103. package/examples/extension-template/package.json +44 -0
  104. package/examples/extension-template/scripts/check-package.ts +145 -0
  105. package/examples/extension-template/src/Escrow.ts +445 -0
  106. package/examples/extension-template/src/Platform.ts +136 -0
  107. package/examples/extension-template/src/errors.ts +61 -0
  108. package/examples/extension-template/src/extension.ts +63 -0
  109. package/examples/extension-template/src/index.ts +34 -0
  110. package/examples/extension-template/src/schema.ts +158 -0
  111. package/examples/extension-template/src/upstream.ts +66 -0
  112. package/examples/extension-template/test/escrow.test.ts +559 -0
  113. package/examples/extension-template/tsconfig.build.json +26 -0
  114. package/examples/extension-template/tsconfig.json +35 -0
  115. package/package.json +87 -0
package/README.md ADDED
@@ -0,0 +1,300 @@
1
+ # sui-effect
2
+
3
+ An opinionated [Effect](https://effect.website) v4 layer over
4
+ [`@mysten/sui`](https://www.npmjs.com/package/@mysten/sui) for building safe
5
+ TypeScript applications and on-demand agent scripts on Sui.
6
+
7
+ The SDK keeps doing BCS, transaction building, signing and transport.
8
+ sui-effect owns the shape of the program around it: two client tiers, closed
9
+ error unions on every function, the transaction lifecycle as functions with
10
+ typed outcomes, crash-safe submission, and time, retry and interruption through
11
+ Effect so tests can drive them.
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ bun add sui-effect
17
+ bun add -d effect@4.0.0-rc.112 @mysten/sui@2.30.0
18
+ ```
19
+
20
+ `effect` and `@mysten/sui` are peer dependencies: one copy of each per process,
21
+ or `Context.Service` identities and `instanceof` checks stop matching. The
22
+ `effect` peer is pinned **exactly** to `4.0.0-rc.112`, because rc.113 renamed
23
+ `Config.nonEmptyString`, `Config.string` and `Config.redacted` to
24
+ `Config.NonEmptyString`, `Config.String` and `Config.Redacted`, and neighbouring
25
+ release candidates are not interchangeable.
26
+
27
+ A **library** built on sui-effect puts all three in its own
28
+ `peerDependencies` **and** `devDependencies`, never in `dependencies`; see
29
+ `examples/extension-template/README.md`.
30
+
31
+ ## A complete script
32
+
33
+ ```ts
34
+ import { bcs } from "@mysten/sui/bcs"
35
+ import { Config, Console, Effect } from "effect"
36
+ import { ObjectId, SuiSchema } from "sui-effect"
37
+ import { Script } from "sui-effect/script"
38
+ import { Tx } from "sui-effect/tx"
39
+
40
+ const PKG = "0x…"
41
+
42
+ const Escrow = SuiSchema.bcs(
43
+ bcs.struct("Escrow", { id: bcs.Address, amount: bcs.u64() }),
44
+ `${PKG}::escrow::Escrow`
45
+ )
46
+
47
+ export const program = Effect.gen(function*() {
48
+ const { signer, sui } = yield* Script
49
+ const id = yield* Config.schema(ObjectId, "ESCROW_ID")
50
+ const escrow = yield* sui.getObject(id, { schema: Escrow })
51
+ const executed = yield* Tx.run((tx) => {
52
+ tx.moveCall({
53
+ target: `${PKG}::escrow::claim`,
54
+ arguments: [tx.object(id), tx.pure.u64(escrow.content.amount)]
55
+ })
56
+ }, { signer })
57
+ const receipt = yield* executed.expectCreated(`${PKG}::escrow::Receipt`)
58
+ yield* Console.log(receipt.id)
59
+ })
60
+
61
+ if (import.meta.main) {
62
+ await Script.run(program)
63
+ }
64
+ ```
65
+
66
+ That is `examples/script-claim.ts`, which a test runs against the in-memory
67
+ fake. Every failure it can produce is in the generator's inferred error type and
68
+ has its own exit code, with no handling lines anywhere.
69
+
70
+ ## The two tiers
71
+
72
+ `SuiCore` is the mechanical tier: a one-to-one Effect wrap of the SDK's
73
+ `ClientWithCoreApi`, with every transport method present, the SDK's `Include`
74
+ generics preserved, every call cancellable through the Effect's `AbortSignal`,
75
+ every failure mapped into one tagged error by one mapper, a span per method, and
76
+ retries on transient transport failures for reads only. Reach for it when you
77
+ need a field or a method the tier above does not expose, and through
78
+ `core.use(client => …)` when an upstream package wants the client object itself.
79
+
80
+ `Sui` is the opinionated tier over it, and is what application and extension
81
+ code reads through: fixed include sets, BCS content decoded through `Schema`
82
+ with the object's Move type checked first, `Option` where absence is normal,
83
+ batch reads chunked to 50 and checked for missing or duplicated ids — per-item
84
+ `Result` from `getObjects`, first-error-wins from `getObjectsOrFail` — pagination
85
+ as `Stream`, one lock per sender so two transactions from one address cannot
86
+ pick the same gas coin, and the chain's own clock. A Move type with no type
87
+ arguments matches every instantiation of it, wherever a type is compared, so one
88
+ codec covers a generic Move type and the object keeps its own instantiated type. It carries the `SuiCore` it
89
+ was built over as `sui.core`, which is why every `Tx.*` function needs only
90
+ `Sui`.
91
+
92
+ ## The transaction lifecycle
93
+
94
+ `Tx` is the lifecycle as functions — `build`, `sign`, `cosign`, `sponsored`,
95
+ `submit`, `reconcile`, `run`, `reconcileAll` — each with a closed error union
96
+ and `R = Sui`. `Tx.run` holds the sender lock from build through submit, builds
97
+ (which always simulates before anything is signed — the SDK's resolver does it
98
+ when there is anything to resolve, and `Tx.build` runs one explicitly when
99
+ there is not, so a transaction that would abort never gets signed),
100
+ signs, journals the signed bytes before the first execute, re-sends the
101
+ identical bytes — never a rebuild — on a retryable transport failure or a
102
+ timeout, waits for the execution to be visible to reads before it releases the
103
+ lock, and if it still does not know what happened, reconciles: `Executed`,
104
+ `ExecutionFailed`, `NotApplied { evidence }`, or `SubmissionUnknown` carrying
105
+ the bytes. A `TransportError` never escapes once bytes may have been sent —
106
+ from `submit`, and from `reconcile` and `reconcileAll` too. `Signer` is a
107
+ value, not a service, so one process can hold two credentials; `SubmitConfig`
108
+ and `Journal` are `Context.Reference`s with working defaults, so none of this
109
+ needs wiring, and `sui-effect/journal` swaps the memory journal for a durable
110
+ one over `KeyValueStore`.
111
+
112
+ **`NotApplied` is hard to earn, on purpose.** Saying a transaction never applied
113
+ tells the documented retry idiom to send the caller's intent again, so there are
114
+ exactly two kinds of evidence and both are checked twice over. `"expired"`
115
+ requires the epoch (or timestamp) bound to be observed as passed, then a
116
+ `getTransaction` miss, then — after `SubmitConfig.reconcileRecheck` — both
117
+ again; a single observation is `SubmissionUnknown`, and
118
+ `SubmitConfig.expiryEvidence: "never"` turns the rule off for a deployment
119
+ behind a mixed-node load balancer. `"inputConsumed"` requires a *different*
120
+ transaction's **own effects** to report that it took a pinned object at exactly
121
+ the version the bytes pinned: reconcile follows the live object's
122
+ `previousTransaction` — which names the latest mutation, not the consumer of the
123
+ version in question — to that transaction and reads `inputVersion` off its
124
+ `changedObjects`. There is no "the object at version `v + 1`" rule, because Sui
125
+ stamps every output with the transaction's **Lamport version**, `max(input
126
+ versions) + 1`, so a coin read alongside a newer gas object jumps from version 4
127
+ to 6,436,928 and version 5 never existed. Every pinned reference — owned inputs
128
+ and gas coins alike — is tried before reconcile gives up.
129
+
130
+ **In practice that means `SubmissionUnknown`, not `NotApplied`, for almost
131
+ every stuck submission** whose PTB touched a shared object or an owned object
132
+ older than the gas coin. Plan an operator path or a `reconcileAll` at startup;
133
+ do not build a retry loop that expects `NotApplied { inputConsumed }`. Before
134
+ any of this, reconcile compares the chain the bytes were built for with
135
+ `sui.chainId` and refuses to reason across chains.
136
+
137
+ **A sponsored `Tx.run` needs both signatures.** When the gas owner is not the
138
+ sender, pass `sponsor`: `Tx.run(recipe, { signer, gasOwner, sponsor })`. Without
139
+ it the run fails with `SigningError` before anything is built, because one
140
+ signature on sponsored bytes is something a validator rejects outright. Two
141
+ parties that cannot both sign in one process use `build`, `sign`, `cosign` and
142
+ `submit` directly.
143
+
144
+ When the recipe sets no expiration, `Tx.build` sets one: `ValidDuring` bounded
145
+ to the current epoch and the next, carrying the chain identifier as a replay
146
+ guard. Two epochs is what the validator rule allows for a transaction with no
147
+ address-owned inputs — a PTB over shared objects, or any sponsored transaction —
148
+ and the chain field is enforced, so bytes signed for testnet cannot land on
149
+ mainnet. There is deliberately no wall-clock bound: every Sui network refuses a
150
+ transaction that carries one today. `SubmitConfig.validFor` adds one for the day
151
+ that changes.
152
+
153
+ ## Extensions
154
+
155
+ A downstream SDK is an extension: an Effect service built on `Sui` and `Tx`
156
+ whose layer requires `Sui` and nothing it could have built itself, whose
157
+ contributions to a transaction are recipe fragments consumers compose into one
158
+ programmable transaction, whose signers are parameters, and whose Promise face
159
+ is derived — not hand-maintained — by `SuiExtension.fromService`, so a consumer
160
+ with an SDK client writes `client.$extend(escrow(options))` and then plain
161
+ `await`s, with the same tagged error instances on rejection.
162
+ **[`docs/extensions.md`](docs/extensions.md) is the contract**, and
163
+ `examples/extension-template/` is a copyable package that implements it — it
164
+ ships inside the published package, so
165
+ `node_modules/sui-effect/examples/extension-template/` is there to copy without
166
+ a checkout.
167
+
168
+ Four things an extension author should know before reading the guide. A layer
169
+ may require `Sui | SuiCore` and must provide everything else itself, including
170
+ another extension's service — the guide's "composing extensions" section is that
171
+ pattern. A Promise face's **synchronous** members (recipe builders, a package
172
+ id) are real only once the runtime exists, so either `await client.<name>.$ready()`
173
+ once or register with `warm`; calling one before that fails with
174
+ `ExtensionNotReady` rather than returning a Promise the type does not mention
175
+ (`Effect` and `Stream` members work cold, as Promises and as async iterables).
176
+ Every registration on one client **shares one `Sui`**, and therefore one
177
+ sender-lock map, so two extensions never select gas for the same address at
178
+ once; `$dispose()` releases that shared base only when the last registration on
179
+ the client is disposed, and it is not final — the next call builds a fresh one.
180
+ And `SuiGraphQL` is sui-effect's tag over the SDK's `SuiGraphQLClient` — one
181
+ client shared by every extension that reads GraphQL; sui-effect wraps no GraphQL
182
+ API of its own.
183
+
184
+ ## Errors
185
+
186
+ Every failure is one of these, every one is a `Schema.TaggedError`, and there is
187
+ no error inheritance to match on.
188
+
189
+ | Tag | Fields | Means |
190
+ |---|---|---|
191
+ | `TransportError` | `method`, `retryable`, `status?`, `cause` | The request did not reach a usable answer. Inside `Tx.submit` a timed-out `executeTransaction` lands here with `retryable: true` and `status: "DEADLINE_EXCEEDED"`; anywhere else `Effect.timeout` produces Effect's own `TimeoutError`, which is not part of this taxonomy |
192
+ | `ObjectNotFound` / `ObjectDeleted` / `ObjectUnavailable` | `objectId`, `version?` | The three `ObjectError.reason` values |
193
+ | `TransactionNotFound` | `digest` | No transaction with that digest is known |
194
+ | `NetworkMismatch` | `expected`, `actual` | The node is on another chain than the layer was built for |
195
+ | `DecodeError` | `objectId?`, `expectedType?`, `issue` | BCS content or a schema boundary did not decode |
196
+ | `SimulationFailed` | `reason`, `message` | Simulation reported an execution failure. No gas charged |
197
+ | `ExecutionFailed` | `digest`, `reason`, `command?`, `effects` | Applied on chain and failed. Gas charged |
198
+ | `SubmissionUnknown` | `digest`, `signed?`, `cause` | Bytes may have been sent; the outcome is unknown. Carries them, unless it came from reconciling a bare digest |
199
+ | `NotApplied` | `digest`, `evidence: "expired" \| "inputConsumed"` | Provably never applied, and never will be |
200
+ | `SigningError` | `cause` | A signer refused or failed |
201
+ | `BuildError` | `message`, `cause` | The transaction could not be built |
202
+ | `PolicyDenied` | `rule`, `message` | A preflight policy refused it before it was signed |
203
+ | `JournalError` | `cause` | The journal could not be read or written. It escapes `Tx.submit` only from the write that happens **before** the first send; after the network has answered, a failed write is logged and the answer stands |
204
+ | `UnexpectedEffects` | `digest`, `expected`, `found` | The effects did not contain what the caller expected. Outcome `applied` and exit 5: it comes from an `Executed`, so the transaction ran and gas was charged; only the receipt is missing |
205
+ | `GraphQLUnavailable` | `method`, `reason` | The GraphQL endpoint an extension needs is not usable. What `SuiGraphQL.layerUnavailable` rejects every call with |
206
+ | `ExtensionNotReady` | `extension`, `member` | A synchronous member of a Promise face was called before its runtime existed: `await client.<name>.$ready()`, or register with `warm` |
207
+
208
+ `ExecutionReason` mirrors the SDK's `ExecutionError` variant for variant, with
209
+ `MoveAbort.abortCode` as a `bigint` and clever-error constant names decoded.
210
+ `SuiError.isRetryable`, `SuiError.outcome`, `SuiError.describe` and
211
+ `SuiError.toJson` are the four helpers every repo otherwise hand-rolls;
212
+ `outcome` puts every failure on the `"applied" | "not_applied" | "unknown"`
213
+ axis, and an extension error may declare its own. An error that is neither a tag
214
+ above nor declares an `outcome` is *unclassified*: `outcome` answers `"unknown"`,
215
+ because an unrecognised tag is no evidence that nothing happened, and
216
+ `Script.exitCode` exits 1 rather than 3, because it is no evidence that anything
217
+ was sent either. Declare `outcome` on every error your extension defines.
218
+
219
+ ## Scripts
220
+
221
+ `Script.layer` reads the environment:
222
+
223
+ | Variable | Required | Meaning |
224
+ |---|---|---|
225
+ | `SUI_NETWORK` | yes, no default | `mainnet`, `testnet`, `devnet`, `localnet` or your own |
226
+ | `SUI_ALLOW_MAINNET` | only for mainnet | `1` or `true`; a script that means mainnet has to say so twice |
227
+ | `SUI_RPC_URL` | no | The gRPC endpoint; defaulted per known network |
228
+ | `SUI_PRIVATE_KEY` | `Script` only | A Bech32 `suiprivkey1…` key, read through `Config.redacted`. A key that does not decode fails with one fixed sentence and no cause: the Bech32 decoder quotes the whole input it rejected, so nothing derived from it is ever printed |
229
+
230
+ `Script.layerReadOnly` provides `ScriptReadOnly`, which has no signer at all —
231
+ a separate service key, so a script written to sign cannot silently build over a
232
+ layer that cannot. `Script.run` installs SIGINT and SIGTERM handlers, interrupts
233
+ the root fiber so finalizers run, writes one diagnostic line per failure to
234
+ stderr, and exits. stdout carries only what the script itself printed: the
235
+ logger is bound to stderr for the whole run, so an `Effect.log` anywhere in the
236
+ call tree cannot corrupt the output. On every non-zero exit the unresolved
237
+ entries of the journal the script ran with are printed too, so a script killed
238
+ or failed mid-submit still leaves the digest and the bytes. A second SIGINT is
239
+ ignored on purpose — the first one is what lets the finalizers that record those
240
+ bytes finish.
241
+
242
+ | Code | Means |
243
+ |---|---|
244
+ | 0 | success |
245
+ | 1 | a defect, or an unclassified failure |
246
+ | 2 | configuration: `ConfigError`, `NetworkMismatch`, `SchemaError`, the mainnet gate |
247
+ | 3 | unknown outcome: reconcile before sending anything else |
248
+ | 4 | nothing applied: safe to retry |
249
+ | 5 | applied on chain: `ExecutionFailed` (gas charged) or `UnexpectedEffects` (it ran; the receipt is missing) |
250
+ | 130 | interrupted, with nothing outstanding in the journal |
251
+
252
+ A timeout or an interrupt asks the journal: with an unresolved submission in it
253
+ the exit is 3, not 4 or 130, because an `Effect.timeout` wrapped around a
254
+ submission interrupts it from the outside and the bytes may be on the wire.
255
+
256
+ ## Testing
257
+
258
+ `sui-effect/testing` ships the in-memory `SuiCoreFake`, `layerTest(script)` (the
259
+ real `Sui` over the fake, so tests exercise the production high tier),
260
+ `layerExtensionTest(layer, script)` for an extension's own tests, and `SuiTest`
261
+ for driving the fake's state and reading back what it was sent. No test in this
262
+ repository touches the network, and neither should yours.
263
+
264
+ ## Versions
265
+
266
+ | Package | Range | Tested against |
267
+ |---|---|---|
268
+ | `effect` | `4.0.0-rc.112` (exact) | `4.0.0-rc.112`, in CI |
269
+ | `@mysten/sui` | `^2.28` (the first version whose BCS and gRPC round-trip `ValidDuring` and `Validity` expirations) | `2.29.0` and `2.30.0`, both in CI; `2.30.0` is the pinned devDependency |
270
+ | `@mysten/bcs` | `^2.1.1` | `2.1.1` |
271
+ | TypeScript | `5.9.x` to build | `5.9.3` |
272
+ | Bun | `1.4.x` | `1.4.2` |
273
+
274
+ The SDK row is a matrix, not a hope: CI builds, typechecks and runs the suite
275
+ against `@mysten/sui` 2.29.0 and 2.30.0, which are the versions consumers pin
276
+ today. The `effect` row is a matrix of one, and it is exact on purpose: rc.113
277
+ renamed three `Config` constructors this package calls at four sites
278
+ (`Script.ts`, `Signer.ts`, `SuiCore.ts`, `SuiGraphQL.ts`), so widening the range
279
+ means supporting both spellings and proving the wider one in CI.
280
+
281
+ **Consumers on TypeScript 7 (`tsgo`) are supported.** The shipped `.d.ts` needs
282
+ nothing from the old compiler. The `prepare` script here
283
+ (`effect-language-service patch`) is a library concern — it patches the checker
284
+ this repository develops against — and should not be copied into a consumer or
285
+ an extension package.
286
+
287
+ `bun run check` is typecheck, build, tests and the extension template's own
288
+ check.
289
+
290
+ ## For agents
291
+
292
+ `LLMS.md` ships in the package: every public export with its signature and the
293
+ error union its JSDoc states, plus every example verbatim. It is generated by
294
+ `bun run llms` and a test fails when it is stale. `AGENTS.md` is the short list
295
+ of invariants to read before the code, `DESIGN.md` the specification, and
296
+ `docs/extensions.md` the extension contract.
297
+
298
+ ## License
299
+
300
+ MIT
@@ -0,0 +1,101 @@
1
+ /**
2
+ * The BCS bridge: a `@mysten/bcs` layout becomes an Effect `Schema.Codec` whose
3
+ * encoded side is the `content` bytes of an object.
4
+ *
5
+ * Decoding never reads the transport-varying `json` field of an object, only
6
+ * `content`, so the same program gives the same answer on gRPC, GraphQL and
7
+ * JSON-RPC.
8
+ *
9
+ * @since 0.1.0
10
+ */
11
+ import type { BcsType } from "@mysten/bcs";
12
+ import { Effect, Schema } from "effect";
13
+ import { DecodeError } from "./errors.ts";
14
+ import type { ObjectId } from "./schemas.ts";
15
+ /**
16
+ * Turns a BCS layout into a `Schema.Codec<T, Uint8Array>` tagged with the Move
17
+ * type it belongs to.
18
+ *
19
+ * Decoding fails with a `SchemaError` (which `Sui` maps to `DecodeError`) when
20
+ * the bytes do not parse; encoding fails the same way when the value does not
21
+ * serialize. The expected type is normalized with `normalizeStructTag`, so
22
+ * `Coin<0x2::sui::SUI>` and its padded spelling are the same type, and a tag
23
+ * with no type arguments matches every instantiation of it (see
24
+ * {@link typeMatches}).
25
+ *
26
+ * **`expectedType` is optional.** A Move *return value* has no struct tag —
27
+ * `sui.view(recipe, bcs.Address())` reads a `vector<u8>` off a command result —
28
+ * and inventing one so the bridge has something to compare is worse than saying
29
+ * there is nothing to compare. A codec built without an expected type carries
30
+ * none, so nothing checks a tag before it parses: the re-serialize length check
31
+ * is still what rejects mis-shaped bytes. Give the type whenever the bytes come
32
+ * from an object, which is every `getObject(id, { schema })` read.
33
+ */
34
+ export declare const bcs: <T extends Input, Input>(bcsType: BcsType<T, Input>, expectedType?: string) => Schema.Codec<T, Uint8Array>;
35
+ /**
36
+ * The normalized Move type a codec built by {@link bcs} expects, or `undefined`
37
+ * for any other codec. `Sui.getObject` compares it with the object's own type
38
+ * tag before decoding. Composition preserves it: a codec piped into
39
+ * `Schema.decodeTo(DomainClass, ...)` still reports the type the bytes came
40
+ * from. Never fails.
41
+ */
42
+ export declare const expectedTypeOf: <T, E>(schema: Schema.Codec<T, E>) => string | undefined;
43
+ /**
44
+ * Whether an object's type tag satisfies an expected type.
45
+ *
46
+ * One rule, used everywhere a Move type is checked — the BCS bridge, the
47
+ * `expectedType` option of `Sui.getObject`, `getObjectOption` and `getObjects`,
48
+ * `SuiSchema.decode`, and the fake's owned-object filter:
49
+ *
50
+ * - **An expected tag with no type arguments names the generic itself**, so
51
+ * `pkg::m::Composition` matches every instantiation:
52
+ * `pkg::m::Composition<0x…::share::Share>` included. Only
53
+ * `address::module::name` is compared, with the address normalized. This is
54
+ * what a node does when a bare type is used as a filter, and it is what makes
55
+ * a generic Move type usable with the bridge at all: a codec is written once
56
+ * for `Composition<T>`, and the instantiation lives on the object.
57
+ * - **An expected tag that carries type arguments is compared in full**, after
58
+ * `normalizeStructTag`, so `Coin<0x2::sui::SUI>` and its padded spelling are
59
+ * the same type and `Coin<0x2::sui::SUI>` does not match `Coin<0x…::usdc::USDC>`.
60
+ *
61
+ * Anything that is not a struct tag — the literal `package`, a primitive — is
62
+ * compared as a normalized string. The object keeps its own instantiated type
63
+ * on `SuiObject.type`; this only decides whether the bytes may be decoded.
64
+ *
65
+ * **It never throws, which is the reason it is public** as
66
+ * `SuiSchema.matchesType`. A dynamic field's `name.type` is legally a primitive
67
+ * — `u64`, `bool`, `address`, `vector<u8>` — and the SDK's own
68
+ * `normalizeStructTag` throws on every one of them, so the obvious way to
69
+ * filter a `streamDynamicFields` by key type dies on the first `u64` key.
70
+ * Comparing tags with this is safe on any string at all.
71
+ *
72
+ * Never fails.
73
+ */
74
+ export declare const typeMatches: (expected: string, actual: string) => boolean;
75
+ /**
76
+ * Decodes BCS `content` bytes with a codec, turning any schema failure into a
77
+ * `DecodeError` that names the object and the type that was expected.
78
+ *
79
+ * This is what `Sui.getObject(id, { schema })` does after it has checked the
80
+ * object's type tag, exposed as `SuiSchema.decode` for the places that already
81
+ * have bytes: a dynamic field's value, an event payload, a `Stream` of
82
+ * envelopes an extension decodes itself. Without it every caller reinvents the
83
+ * same `Schema.decodeUnknownEffect(...).pipe(Effect.mapError(...))`, and the
84
+ * `DecodeError` it produces is worse than this one.
85
+ *
86
+ * `expectedType` defaults to the Move type the codec was built with, so passing
87
+ * it is only needed for a codec that carries none, or to override the recorded
88
+ * one. `actualType` is the Move type the bytes actually came from, when the
89
+ * caller knows it — a dynamic field's `name.type`, a stream envelope's `type`:
90
+ * give it and the same tag check `getObject` does runs here, under the
91
+ * {@link typeMatches} rule, before a byte is parsed. `objectId` is recorded on
92
+ * the error so an operator knows which object did not decode.
93
+ *
94
+ * Fails with: `DecodeError`.
95
+ */
96
+ export declare const decodeContent: <T>(schema: Schema.Codec<T, Uint8Array>, content: Uint8Array, context?: {
97
+ readonly objectId?: ObjectId;
98
+ readonly expectedType?: string;
99
+ readonly actualType?: string;
100
+ }) => Effect.Effect<T, DecodeError>;
101
+ //# sourceMappingURL=bcs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bcs.d.ts","sourceRoot":"","sources":["../../src/domain/bcs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAE1C,OAAO,EAAE,MAAM,EAAE,MAAM,EAAgD,MAAM,QAAQ,CAAA;AACrF,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAY5C;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,GAAG,GAAI,CAAC,SAAS,KAAK,EAAE,KAAK,EACxC,SAAS,OAAO,CAAC,CAAC,EAAE,KAAK,CAAC,EAC1B,eAAe,MAAM,KACpB,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAgD5B,CAAA;AAuBD;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,GAAI,CAAC,EAAE,CAAC,EAAE,QAAQ,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,KAAG,MAAM,GAAG,SAC/C,CAAA;AAU5B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,WAAW,GAAI,UAAU,MAAM,EAAE,QAAQ,MAAM,KAAG,OAU9D,CAAA;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,aAAa,GAAI,CAAC,EAC7B,QAAQ,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,EACnC,SAAS,UAAU,EACnB,UAAU;IACR,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAA;IAC5B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAC7B,KACA,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,CA0B9B,CAAA"}
@@ -0,0 +1,183 @@
1
+ import { normalizeStructTag, parseStructTag } from "@mysten/sui/utils";
2
+ import { Effect, Schema, SchemaAST, SchemaIssue, SchemaTransformation } from "effect";
3
+ import { DecodeError } from "./errors.js";
4
+ const SUI_TYPE_ANNOTATION = "sui-effect/suiType";
5
+ const normalizeSafe = (value) => {
6
+ try {
7
+ return normalizeStructTag(value);
8
+ }
9
+ catch {
10
+ return value;
11
+ }
12
+ };
13
+ /**
14
+ * Turns a BCS layout into a `Schema.Codec<T, Uint8Array>` tagged with the Move
15
+ * type it belongs to.
16
+ *
17
+ * Decoding fails with a `SchemaError` (which `Sui` maps to `DecodeError`) when
18
+ * the bytes do not parse; encoding fails the same way when the value does not
19
+ * serialize. The expected type is normalized with `normalizeStructTag`, so
20
+ * `Coin<0x2::sui::SUI>` and its padded spelling are the same type, and a tag
21
+ * with no type arguments matches every instantiation of it (see
22
+ * {@link typeMatches}).
23
+ *
24
+ * **`expectedType` is optional.** A Move *return value* has no struct tag —
25
+ * `sui.view(recipe, bcs.Address())` reads a `vector<u8>` off a command result —
26
+ * and inventing one so the bridge has something to compare is worse than saying
27
+ * there is nothing to compare. A codec built without an expected type carries
28
+ * none, so nothing checks a tag before it parses: the re-serialize length check
29
+ * is still what rejects mis-shaped bytes. Give the type whenever the bytes come
30
+ * from an object, which is every `getObject(id, { schema })` read.
31
+ */
32
+ export const bcs = (bcsType, expectedType) => {
33
+ const normalized = expectedType === undefined ? undefined : normalizeSafe(expectedType);
34
+ // What the failure messages call the layout when it has no Move type of its
35
+ // own: `bcsType.name` is what `@mysten/bcs` named it (`"Escrow"`, `"vector"`).
36
+ const label = normalized ?? bcsType.name;
37
+ // A BCS layout carries no runtime type to test a decoded value against: the
38
+ // parse below is the validation, so the target schema accepts whatever the
39
+ // layout produced.
40
+ const target = Schema.declare((_u) => true);
41
+ return Schema.Uint8Array.pipe(Schema.decodeTo(target, SchemaTransformation.transformOrFail({
42
+ decode: (bytes, options) => Effect.try({
43
+ try: () => {
44
+ const parsed = bcsType.parse(bytes);
45
+ // `parse` stops at the end of the layout and ignores whatever
46
+ // follows, which would silently accept an `objectBcs` envelope
47
+ // where `content` was expected. Re-serializing costs one pass and
48
+ // catches both trailing and mis-shaped bytes.
49
+ if (bcsType.serialize(parsed).toBytes().length !== bytes.length) {
50
+ throw new Error(`expected ${bytes.length} bytes of ${label}`);
51
+ }
52
+ return parsed;
53
+ },
54
+ catch: (cause) => new SchemaIssue.InvalidValue({ message: `Could not parse ${label} from BCS content: ${String(cause)}` }, bytes, options)
55
+ }),
56
+ encode: (value, options) => Effect.try({
57
+ try: () => bcsType.serialize(value).toBytes(),
58
+ catch: (cause) => new SchemaIssue.InvalidValue({ message: `Could not serialize ${label} to BCS: ${String(cause)}` }, value, options)
59
+ })
60
+ }))).annotate(normalized === undefined ? {} : { [SUI_TYPE_ANNOTATION]: normalized });
61
+ };
62
+ const MAX_ENCODING_DEPTH = 32;
63
+ /**
64
+ * Walks the encoding chain looking for the annotation {@link bcs} leaves
65
+ * behind. `SuiSchema.bcs(...).pipe(Schema.decodeTo(DomainClass, ...))` puts a
66
+ * new node on top and keeps the annotated node as the source side of the
67
+ * transformation, so the tag survives composition.
68
+ */
69
+ const findSuiType = (ast, depth) => {
70
+ const annotation = ast.annotations?.[SUI_TYPE_ANNOTATION];
71
+ if (typeof annotation === "string")
72
+ return annotation;
73
+ if (depth >= MAX_ENCODING_DEPTH)
74
+ return undefined;
75
+ const encoding = ast.encoding;
76
+ if (encoding === undefined)
77
+ return undefined;
78
+ for (const link of encoding) {
79
+ const found = findSuiType(link.to, depth + 1);
80
+ if (found !== undefined)
81
+ return found;
82
+ }
83
+ return undefined;
84
+ };
85
+ /**
86
+ * The normalized Move type a codec built by {@link bcs} expects, or `undefined`
87
+ * for any other codec. `Sui.getObject` compares it with the object's own type
88
+ * tag before decoding. Composition preserves it: a codec piped into
89
+ * `Schema.decodeTo(DomainClass, ...)` still reports the type the bytes came
90
+ * from. Never fails.
91
+ */
92
+ export const expectedTypeOf = (schema) => findSuiType(schema.ast, 0);
93
+ const parseSafe = (value) => {
94
+ try {
95
+ return parseStructTag(value);
96
+ }
97
+ catch {
98
+ return undefined;
99
+ }
100
+ };
101
+ /**
102
+ * Whether an object's type tag satisfies an expected type.
103
+ *
104
+ * One rule, used everywhere a Move type is checked — the BCS bridge, the
105
+ * `expectedType` option of `Sui.getObject`, `getObjectOption` and `getObjects`,
106
+ * `SuiSchema.decode`, and the fake's owned-object filter:
107
+ *
108
+ * - **An expected tag with no type arguments names the generic itself**, so
109
+ * `pkg::m::Composition` matches every instantiation:
110
+ * `pkg::m::Composition<0x…::share::Share>` included. Only
111
+ * `address::module::name` is compared, with the address normalized. This is
112
+ * what a node does when a bare type is used as a filter, and it is what makes
113
+ * a generic Move type usable with the bridge at all: a codec is written once
114
+ * for `Composition<T>`, and the instantiation lives on the object.
115
+ * - **An expected tag that carries type arguments is compared in full**, after
116
+ * `normalizeStructTag`, so `Coin<0x2::sui::SUI>` and its padded spelling are
117
+ * the same type and `Coin<0x2::sui::SUI>` does not match `Coin<0x…::usdc::USDC>`.
118
+ *
119
+ * Anything that is not a struct tag — the literal `package`, a primitive — is
120
+ * compared as a normalized string. The object keeps its own instantiated type
121
+ * on `SuiObject.type`; this only decides whether the bytes may be decoded.
122
+ *
123
+ * **It never throws, which is the reason it is public** as
124
+ * `SuiSchema.matchesType`. A dynamic field's `name.type` is legally a primitive
125
+ * — `u64`, `bool`, `address`, `vector<u8>` — and the SDK's own
126
+ * `normalizeStructTag` throws on every one of them, so the obvious way to
127
+ * filter a `streamDynamicFields` by key type dies on the first `u64` key.
128
+ * Comparing tags with this is safe on any string at all.
129
+ *
130
+ * Never fails.
131
+ */
132
+ export const typeMatches = (expected, actual) => {
133
+ const expectedTag = parseSafe(expected);
134
+ if (expectedTag === undefined || expectedTag.typeParams.length > 0) {
135
+ return normalizeSafe(expected) === normalizeSafe(actual);
136
+ }
137
+ const actualTag = parseSafe(actual);
138
+ if (actualTag === undefined)
139
+ return normalizeSafe(expected) === normalizeSafe(actual);
140
+ return expectedTag.address === actualTag.address &&
141
+ expectedTag.module === actualTag.module &&
142
+ expectedTag.name === actualTag.name;
143
+ };
144
+ /**
145
+ * Decodes BCS `content` bytes with a codec, turning any schema failure into a
146
+ * `DecodeError` that names the object and the type that was expected.
147
+ *
148
+ * This is what `Sui.getObject(id, { schema })` does after it has checked the
149
+ * object's type tag, exposed as `SuiSchema.decode` for the places that already
150
+ * have bytes: a dynamic field's value, an event payload, a `Stream` of
151
+ * envelopes an extension decodes itself. Without it every caller reinvents the
152
+ * same `Schema.decodeUnknownEffect(...).pipe(Effect.mapError(...))`, and the
153
+ * `DecodeError` it produces is worse than this one.
154
+ *
155
+ * `expectedType` defaults to the Move type the codec was built with, so passing
156
+ * it is only needed for a codec that carries none, or to override the recorded
157
+ * one. `actualType` is the Move type the bytes actually came from, when the
158
+ * caller knows it — a dynamic field's `name.type`, a stream envelope's `type`:
159
+ * give it and the same tag check `getObject` does runs here, under the
160
+ * {@link typeMatches} rule, before a byte is parsed. `objectId` is recorded on
161
+ * the error so an operator knows which object did not decode.
162
+ *
163
+ * Fails with: `DecodeError`.
164
+ */
165
+ export const decodeContent = (schema, content, context) => {
166
+ const expected = context?.expectedType ?? expectedTypeOf(schema);
167
+ const withExpected = expected === undefined ? {} : { expectedType: expected };
168
+ const withObject = context?.objectId === undefined ? {} : { objectId: context.objectId };
169
+ const actual = context?.actualType;
170
+ if (expected !== undefined && actual !== undefined && !typeMatches(expected, actual)) {
171
+ return Effect.fail(new DecodeError({
172
+ ...withObject,
173
+ ...withExpected,
174
+ issue: `${context?.objectId === undefined ? "the bytes have" : `object ${context.objectId} has`} type ${actual}`
175
+ }));
176
+ }
177
+ return Schema.decodeUnknownEffect(schema)(content).pipe(Effect.mapError((error) => new DecodeError({
178
+ ...withObject,
179
+ ...withExpected,
180
+ issue: error.message
181
+ })));
182
+ };
183
+ //# sourceMappingURL=bcs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bcs.js","sourceRoot":"","sources":["../../src/domain/bcs.ts"],"names":[],"mappings":"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"}