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
@@ -0,0 +1,63 @@
1
+ /**
2
+ * The Promise face, derived rather than maintained.
3
+ *
4
+ * `SuiExtension.fromService` walks the service interface once: `Effect` members
5
+ * become Promise methods, `Stream` members become `AsyncIterable`s, nested
6
+ * namespaces are mapped recursively, plain values pass through, and a rejection
7
+ * is the same tagged error instance an Effect caller would have caught, so a
8
+ * Promise consumer can still switch on `_tag`.
9
+ *
10
+ * There is no second implementation to keep in step, which is the whole point.
11
+ */
12
+ import { SuiExtension } from "sui-effect/extension"
13
+ import type { EscrowOptions } from "./Escrow.ts"
14
+ import { Escrow } from "./Escrow.ts"
15
+
16
+ /** What a `$extend` registration needs beyond what the layer does. */
17
+ export interface EscrowRegistrationOptions extends EscrowOptions {
18
+ /**
19
+ * The chain identifier the node must be on, as `getChainIdentifier` reports
20
+ * it.
21
+ *
22
+ * **Required on `devnet`, `localnet` and any custom network**, because a
23
+ * `warm` registration takes the chain id rather than asking for it and there
24
+ * is no built-in entry for those; `register` throws without it. On `mainnet`
25
+ * and `testnet` the built-in table answers and this is optional.
26
+ *
27
+ * Give the same id to **every** registration on one client. The base `Sui`,
28
+ * its transport and its sender-lock map are shared per client **per chain
29
+ * id**, so two registrations that disagree get two of everything and two
30
+ * `Tx.run`s for one address stop serializing.
31
+ */
32
+ readonly chainId?: string
33
+ }
34
+
35
+ /**
36
+ * The registration a Promise consumer passes to `client.$extend(...)`.
37
+ *
38
+ * ```ts
39
+ * const client = new SuiGrpcClient({ network: "testnet", baseUrl }).$extend(escrow(options))
40
+ * const info = await client.escrow.get(id)
41
+ * for await (const item of client.escrow.owned.stream(owner)) console.log(item.id)
42
+ * await client.escrow.dispose()
43
+ * ```
44
+ *
45
+ * It is registered `warm`, like `platform` in `Platform.ts`: the service has
46
+ * synchronous members — `packageId`, the `claim` recipe fragment, the codecs —
47
+ * and a consumer that reads one the moment it registers should get the value
48
+ * rather than a placeholder that throws `ExtensionNotReady`. Neither layer
49
+ * touches the network at build, which is what `warm` requires.
50
+ *
51
+ * The runtime is built inside `register`, over `SuiCore.layerFromClient(client)`,
52
+ * a `Sui` pinned to `options.chainId` and `Escrow.layer`, so the extension and
53
+ * the consumer share one transport, one chain identity and one sender-lock map.
54
+ *
55
+ * Throws out of `register` when the network has no built-in chain identifier
56
+ * and `options.chainId` was not given.
57
+ */
58
+ export const escrow = (options: EscrowRegistrationOptions) =>
59
+ SuiExtension.fromService(Escrow, {
60
+ name: "escrow",
61
+ layer: Escrow.layer(options),
62
+ warm: options.chainId === undefined ? {} : { chainId: options.chainId }
63
+ })
@@ -0,0 +1,34 @@
1
+ /**
2
+ * `@your-org/example-extension`: the public surface.
3
+ *
4
+ * One service, its layers, its errors, its schemas, and the derived Promise
5
+ * registration. Nothing from `upstream.ts` is re-exported: upstream types are
6
+ * narrowed to sui-effect schemas inside the service and never reach a consumer.
7
+ */
8
+ export {
9
+ DEPLOYMENTS,
10
+ Escrow,
11
+ type ClaimForError,
12
+ type EscrowDeployment,
13
+ type EscrowFields,
14
+ type EscrowObject,
15
+ type EscrowOptions,
16
+ type EscrowService
17
+ } from "./Escrow.ts"
18
+ export { EscrowNotFound, EscrowSettlementUnknown, EscrowUnsupportedNetwork } from "./errors.ts"
19
+ export { escrow, type EscrowRegistrationOptions } from "./extension.ts"
20
+ export {
21
+ Platform,
22
+ platform,
23
+ type PlatformOptions,
24
+ type PlatformRegistrationOptions,
25
+ type PlatformService
26
+ } from "./Platform.ts"
27
+ export {
28
+ ESCROW_PACKAGE,
29
+ EscrowContent,
30
+ escrowType,
31
+ receiptType,
32
+ Settlement,
33
+ SettlementContent
34
+ } from "./schema.ts"
@@ -0,0 +1,158 @@
1
+ /**
2
+ * The Move layouts this package reads, bridged into `Schema`.
3
+ *
4
+ * `SuiSchema.bcs(layout, type)` is a `Schema.Codec<T, Uint8Array>` that also
5
+ * records the Move type, so `sui.getObject(id, { schema })` checks the object's
6
+ * type tag before it parses a single byte and a mismatch is a `DecodeError`
7
+ * naming both types rather than a confusing parse failure.
8
+ */
9
+ import { bcs } from "@mysten/sui/bcs"
10
+ import { DateTime, Effect, Schema, SchemaIssue, SchemaTransformation } from "effect"
11
+ import { ObjectId, SuiAddress, SuiSchema } from "sui-effect"
12
+
13
+ /**
14
+ * The package the template's example type lives in, and the default this
15
+ * release ships. Replace it with yours.
16
+ */
17
+ export const ESCROW_PACKAGE = "0x0000000000000000000000000000000000000000000000000000000000000002"
18
+
19
+ /** The BCS layout of `escrow::Escrow`, which is the same whatever it was published to. */
20
+ const EscrowBcs = bcs.struct("Escrow", {
21
+ id: bcs.Address,
22
+ owner: bcs.Address,
23
+ amount: bcs.u64()
24
+ })
25
+
26
+ /** The Move type of an escrow, under a given type origin. */
27
+ export const escrowType = (typeOrigin: string): string =>
28
+ `${typeOrigin}::escrow::Escrow`
29
+
30
+ /** The Move type of a claim receipt, which `claimFor` expects to be created. */
31
+ export const receiptType = (typeOrigin: string): string =>
32
+ `${typeOrigin}::escrow::Receipt`
33
+
34
+ /**
35
+ * `escrow::Escrow`, the object this extension reads, **as a function of the
36
+ * package it lives in**.
37
+ *
38
+ * A Move type name contains its package id, so a codec built from a hard-coded
39
+ * constant checks the wrong type the moment a consumer configures a different
40
+ * package: `getObject(id, { schema })` compares the object's tag before it
41
+ * parses a byte, and a correctly encoded object under the configured package
42
+ * fails with `DecodeError`. Every type-shaped constant in an extension takes
43
+ * the package id the service was built with, and the service passes its own.
44
+ *
45
+ * **Which package id.** The one that appears in a type name is the **type
46
+ * origin**: the package the type was *first* published in. Upgrading a package
47
+ * gives it a new id for *calls*, and the type origin does not move. So an
48
+ * extension over an upgraded package carries two ids — `packageId` for
49
+ * `moveCall` targets, `typeOrigin` for codecs, filters and receipt types — and
50
+ * they are the same value until the first upgrade. `Escrow.layer` takes both.
51
+ */
52
+ export const EscrowContent = (typeOrigin: string) => SuiSchema.bcs(
53
+ EscrowBcs,
54
+ escrowType(typeOrigin)
55
+ )
56
+
57
+ /**
58
+ * The Move layout of `escrow::Settlement`, whose fields are `snake_case`
59
+ * because Move's are.
60
+ */
61
+ const SettlementBcs = bcs.struct("Settlement", {
62
+ escrow_id: bcs.Address,
63
+ settled_at_ms: bcs.u64(),
64
+ claimed_by: bcs.Address
65
+ })
66
+
67
+ /**
68
+ * The domain type a consumer of this package sees: `camelCase`, branded ids,
69
+ * a `DateTime` instead of a string of milliseconds.
70
+ *
71
+ * The mapping lives in `Schema.decodeTo`, never in a custom `parse`: the bridge
72
+ * needs a real `BcsType` so it can re-serialize what it parsed and reject
73
+ * trailing bytes, and a domain type is not a BCS layout.
74
+ */
75
+ export class Settlement extends Schema.Class<Settlement>("Settlement")({
76
+ escrowId: ObjectId,
77
+ settledAt: Schema.DateTimeUtc,
78
+ claimedBy: SuiAddress
79
+ }) {}
80
+
81
+ /**
82
+ * The halfway shape the transformation produces: the domain field names, before
83
+ * `Settlement`'s own schema brands the ids.
84
+ */
85
+ interface SettlementParts {
86
+ readonly escrowId: string
87
+ readonly settledAt: DateTime.Utc
88
+ readonly claimedBy: string
89
+ }
90
+
91
+ /**
92
+ * The composed codec: BCS bytes to `Settlement`, and back.
93
+ *
94
+ * `SuiSchema.bcs(...)` decodes the bytes into the Move shape;
95
+ * `Schema.decodeTo(Settlement, SchemaTransformation.transform({ decode, encode }))`
96
+ * renames the fields, and `Settlement` itself does the rest — branding the ids
97
+ * and checking them. The Move type the bridge recorded survives the
98
+ * composition, so
99
+ * `sui.getObject(id, { schema: SettlementContent })` still checks the object's
100
+ * type tag before parsing a byte.
101
+ *
102
+ * Two details worth copying:
103
+ *
104
+ * - **`decode` produces the target's field shape, not an instance.** `decodeTo`
105
+ * sits between the source type and the target schema, which is what lets the
106
+ * target's own checks — the `ObjectId` and `SuiAddress` brands here — run
107
+ * afterwards.
108
+ * - **`encode` is the inverse mapper and is not optional.** A codec that cannot
109
+ * encode is one `Schema.encodeUnknownEffect` fails on, and the compiler asks
110
+ * for it here rather than at the call site.
111
+ * - **A mapping that can fail uses `transformOrFail`.** `transform` is for total
112
+ * mappings; a body that throws produces a defect, and a byte that was wrong on
113
+ * the wire deserves a failure.
114
+ *
115
+ * A failure *inside* this transform — an id that is not an address, a timestamp
116
+ * that is not a time — is still a `DecodeError` from `SuiSchema.decode` and
117
+ * `sui.getObject`, with the same fields: the domain mapping is part of the
118
+ * boundary, not a step after it.
119
+ */
120
+ export const SettlementContent = (typeOrigin: string) =>
121
+ SuiSchema.bcs(
122
+ SettlementBcs,
123
+ `${typeOrigin}::escrow::Settlement`
124
+ ).pipe(
125
+ Schema.decodeTo(
126
+ Settlement,
127
+ SchemaTransformation.transformOrFail<SettlementParts, typeof SettlementBcs.$inferType>({
128
+ decode: (fields, options) =>
129
+ // `transformOrFail`, not `transform`, because one of these mappings can
130
+ // fail: a `u64` of milliseconds is not necessarily a time. A `transform`
131
+ // whose body throws is a **defect**, which is not what a bad byte on the
132
+ // wire should be; failing with a `SchemaIssue` here is what makes it a
133
+ // `DecodeError` like any other.
134
+ Effect.map(
135
+ Effect.fromOption(
136
+ DateTime.make(Number(fields.settled_at_ms)),
137
+ () =>
138
+ new SchemaIssue.InvalidValue(
139
+ { message: `settled_at_ms ${fields.settled_at_ms} is not a time` },
140
+ fields,
141
+ options
142
+ )
143
+ ),
144
+ (settledAt): SettlementParts => ({
145
+ escrowId: fields.escrow_id,
146
+ settledAt,
147
+ claimedBy: fields.claimed_by
148
+ })
149
+ ),
150
+ encode: (settlement) =>
151
+ Effect.succeed({
152
+ escrow_id: settlement.escrowId,
153
+ settled_at_ms: String(DateTime.toEpochMillis(settlement.settledAt)),
154
+ claimed_by: settlement.claimedBy
155
+ })
156
+ })
157
+ )
158
+ )
@@ -0,0 +1,66 @@
1
+ /**
2
+ * A stand-in for the third-party Promise package an extension wraps.
3
+ *
4
+ * In a real extension this file does not exist: it is an `import` of the
5
+ * upstream SDK (`@some-org/escrow-sdk`), and everything below is its surface.
6
+ * It is here so the guide's "wrapping an upstream Promise package" section has
7
+ * real code to point at: one helper that needs the SDK client object, and one
8
+ * that does not.
9
+ *
10
+ * Note what it is not: it is not exported from `src/index.ts`. Upstream types
11
+ * never reach a consumer of this package; they are narrowed to sui-effect
12
+ * schemas first.
13
+ */
14
+ import type { ClientWithCoreApi } from "@mysten/sui/client"
15
+
16
+ /** What the operator's settlement service answers with. Untyped on purpose: upstream JSON. */
17
+ export interface SettlementResponse {
18
+ readonly status: string
19
+ readonly [key: string]: unknown
20
+ }
21
+
22
+ /** The upstream client surface. */
23
+ export interface SettlementApi {
24
+ /**
25
+ * Tells the operator that a claim landed. Needs no Sui client: a pure
26
+ * Promise helper, which is the `Effect.tryPromise` case.
27
+ */
28
+ readonly notifyClaim: (
29
+ input: { readonly escrowId: string; readonly digest: string },
30
+ signal?: AbortSignal
31
+ ) => Promise<SettlementResponse>
32
+ /**
33
+ * Reads the fee collector the package was published with. Needs the SDK
34
+ * client object, which is the `SuiCore.use` case.
35
+ */
36
+ readonly resolveFeeCollector: (
37
+ client: ClientWithCoreApi,
38
+ packageId: string,
39
+ signal?: AbortSignal
40
+ ) => Promise<unknown>
41
+ }
42
+
43
+ /** The upstream constructor: a base URL and an API key. */
44
+ export const settlementApi = (options: {
45
+ readonly url: string
46
+ readonly apiKey: string
47
+ }): SettlementApi => ({
48
+ notifyClaim: async (input, signal) => {
49
+ const response = await fetch(`${options.url}/claims`, {
50
+ method: "POST",
51
+ headers: { "content-type": "application/json", authorization: `Bearer ${options.apiKey}` },
52
+ body: JSON.stringify(input),
53
+ ...(signal === undefined ? {} : { signal })
54
+ })
55
+ if (!response.ok) throw new Error(`settlement service answered ${response.status}`)
56
+ return await response.json() as SettlementResponse
57
+ },
58
+ resolveFeeCollector: async (client, packageId, signal) => {
59
+ const { object } = await client.core.getObject({
60
+ objectId: packageId,
61
+ ...(signal === undefined ? {} : { signal })
62
+ })
63
+ const owner = object.owner
64
+ return owner.$kind === "AddressOwner" ? owner.AddressOwner : null
65
+ }
66
+ })