@akterjs/conformance 0.0.0-stage → 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.
- package/LICENSE +201 -0
- package/README.md +52 -2
- package/dist/client-contract.suite.d.mts +23 -0
- package/dist/client-contract.suite.mjs +279 -0
- package/dist/client-target.errors.d.mts +8 -0
- package/dist/client-target.errors.mjs +4 -0
- package/dist/engine-conformance.suite.d.mts +19 -0
- package/dist/engine-conformance.suite.mjs +357 -0
- package/dist/fork/fork-conformance.suite.d.mts +20 -0
- package/dist/fork/fork-conformance.suite.mjs +167 -0
- package/dist/guarantee-titles.pure.d.mts +31 -0
- package/dist/guarantee-titles.pure.mjs +32 -0
- package/dist/ledger/ledger-calls.pure.d.mts +79 -0
- package/dist/ledger/ledger-calls.pure.mjs +111 -0
- package/dist/ledger/ledger-checks.pure.d.mts +21 -0
- package/dist/ledger/ledger-checks.pure.mjs +37 -0
- package/dist/ledger/ledger.actor.d.mts +13 -0
- package/dist/ledger/ledger.actor.mjs +115 -0
- package/dist/ledger/ledger.contract.d.mts +310 -0
- package/dist/ledger/ledger.contract.mjs +112 -0
- package/dist/ledger/plain-ledger.actor.d.mts +4 -0
- package/dist/ledger/plain-ledger.actor.mjs +214 -0
- package/dist/rejected.errors.d.mts +8 -0
- package/dist/rejected.errors.mjs +4 -0
- package/dist/tally-clients.layer.d.mts +39 -0
- package/dist/tally-clients.layer.mjs +97 -0
- package/dist/tally.actor.d.mts +49 -0
- package/dist/tally.actor.mjs +26 -0
- package/dist/tally.contract.d.mts +51 -0
- package/dist/tally.contract.mjs +28 -0
- package/dist/workbench/computer-conformance.suite.d.mts +15 -0
- package/dist/workbench/computer-conformance.suite.mjs +330 -0
- package/dist/workbench/workbench-calls.pure.d.mts +45 -0
- package/dist/workbench/workbench-calls.pure.mjs +75 -0
- package/dist/workbench/workbench.actor.d.mts +4 -0
- package/dist/workbench/workbench.actor.mjs +107 -0
- package/dist/workbench/workbench.contract.d.mts +220 -0
- package/dist/workbench/workbench.contract.mjs +54 -0
- package/package.json +86 -3
- package/src/client-contract.suite.ts +413 -0
- package/src/client-target.errors.ts +7 -0
- package/src/engine-conformance.suite.ts +484 -0
- package/src/fork/fork-conformance.suite.ts +204 -0
- package/src/guarantee-titles.pure.ts +50 -0
- package/src/ledger/ledger-calls.pure.ts +179 -0
- package/src/ledger/ledger-checks.pure.ts +84 -0
- package/src/ledger/ledger.actor.ts +196 -0
- package/src/ledger/ledger.contract.ts +76 -0
- package/src/ledger/plain-ledger.actor.ts +246 -0
- package/src/rejected.errors.ts +6 -0
- package/src/tally-clients.layer.ts +221 -0
- package/src/tally.actor.ts +28 -0
- package/src/tally.contract.ts +22 -0
- package/src/workbench/computer-conformance.suite.ts +380 -0
- package/src/workbench/workbench-calls.pure.ts +92 -0
- package/src/workbench/workbench.actor.ts +175 -0
- package/src/workbench/workbench.contract.ts +47 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
declare const EntryId: Schema.brand<Schema.String, "EntryId">;
|
|
3
|
+
declare const Cents: Schema.brand<Schema.Int, "Cents">;
|
|
4
|
+
type Cents = typeof Cents.Type;
|
|
5
|
+
declare const Entry: Schema.Struct<{
|
|
6
|
+
readonly amount: Schema.brand<Schema.Int, "Cents">;
|
|
7
|
+
readonly id: Schema.brand<Schema.String, "EntryId">;
|
|
8
|
+
}>;
|
|
9
|
+
type Entry = typeof Entry.Type;
|
|
10
|
+
type Account = typeof Account.Type;
|
|
11
|
+
declare const Account: Schema.Struct<{
|
|
12
|
+
readonly balance: Schema.brand<Schema.Int, "Cents">;
|
|
13
|
+
readonly closed: Schema.Boolean;
|
|
14
|
+
}>;
|
|
15
|
+
declare const OverdrawnError_base: Schema.Class<OverdrawnError, Schema.TaggedStruct<"OverdrawnError", {
|
|
16
|
+
readonly balance: Schema.brand<Schema.Int, "Cents">;
|
|
17
|
+
}>, import("effect/Cause").YieldableError & {
|
|
18
|
+
readonly brand: unique symbol;
|
|
19
|
+
}>;
|
|
20
|
+
declare class OverdrawnError extends OverdrawnError_base {}
|
|
21
|
+
/** The patch the later build's `release` declares: it releases twice the approved amount, for holds it started. */
|
|
22
|
+
declare const doubled = "double";
|
|
23
|
+
declare const Ledger: import("@akterjs/effect/contract").Actor<"Ledger", import("@akterjs/effect/contract").DefinitionOf<{
|
|
24
|
+
readonly callers: {
|
|
25
|
+
readonly actor: Schema.Struct<{
|
|
26
|
+
readonly actor: Schema.String;
|
|
27
|
+
readonly id: Schema.brand<Schema.String, "PrincipalId">;
|
|
28
|
+
readonly key: Schema.String;
|
|
29
|
+
}>;
|
|
30
|
+
readonly service: Schema.Struct<{
|
|
31
|
+
readonly id: Schema.brand<Schema.String, "PrincipalId">;
|
|
32
|
+
}>;
|
|
33
|
+
readonly user: Schema.Struct<{
|
|
34
|
+
readonly id: Schema.brand<Schema.String, "PrincipalId">;
|
|
35
|
+
}>;
|
|
36
|
+
};
|
|
37
|
+
readonly commands: {
|
|
38
|
+
readonly approve: import("@akterjs/effect/contract").Command<Schema.Struct<{
|
|
39
|
+
readonly amount: Schema.brand<Schema.Int, "Cents">;
|
|
40
|
+
readonly job: Schema.String;
|
|
41
|
+
readonly refuse: Schema.Boolean;
|
|
42
|
+
}>, Schema.Void, Schema.Never, never>;
|
|
43
|
+
readonly close: import("@akterjs/effect/contract").Command<Schema.Void, Schema.Void, Schema.Never, "service">;
|
|
44
|
+
readonly crash: import("@akterjs/effect/contract").Command<Schema.Struct<{
|
|
45
|
+
readonly amount: Schema.brand<Schema.Int, "Cents">;
|
|
46
|
+
}>, Schema.Void, Schema.Never, never>;
|
|
47
|
+
readonly credit: import("@akterjs/effect/contract").Command<Schema.Struct<{
|
|
48
|
+
readonly amount: Schema.brand<Schema.Int, "Cents">;
|
|
49
|
+
}>, Schema.brand<Schema.String, "EntryId">, Schema.Never, "actor">;
|
|
50
|
+
readonly deposit: import("@akterjs/effect/contract").Command<Schema.Struct<{
|
|
51
|
+
readonly amount: Schema.brand<Schema.Int, "Cents">;
|
|
52
|
+
}>, Schema.brand<Schema.String, "EntryId">, Schema.Never, never>;
|
|
53
|
+
readonly depositLater: import("@akterjs/effect/contract").Command<Schema.Struct<{
|
|
54
|
+
readonly amount: Schema.brand<Schema.Int, "Cents">;
|
|
55
|
+
readonly minutes: Schema.Int;
|
|
56
|
+
}>, Schema.Void, Schema.Never, never>;
|
|
57
|
+
readonly hold: import("@akterjs/effect/contract").Command<Schema.Void, Schema.String, Schema.Never, never>;
|
|
58
|
+
readonly tamper: import("@akterjs/effect/contract").Command<Schema.Struct<{
|
|
59
|
+
readonly amount: Schema.brand<Schema.Int, "Cents">;
|
|
60
|
+
}>, Schema.Void, Schema.Never, never>;
|
|
61
|
+
readonly transfer: import("@akterjs/effect/contract").Command<Schema.Struct<{
|
|
62
|
+
readonly amount: Schema.brand<Schema.Int, "Cents">;
|
|
63
|
+
readonly to: Schema.brand<Schema.String, "AccountId">;
|
|
64
|
+
}>, Schema.brand<Schema.String, "EntryId">, typeof OverdrawnError, never>;
|
|
65
|
+
readonly withdraw: import("@akterjs/effect/contract").Command<Schema.Struct<{
|
|
66
|
+
readonly amount: Schema.brand<Schema.Int, "Cents">;
|
|
67
|
+
}>, Schema.brand<Schema.String, "EntryId">, typeof OverdrawnError, never>;
|
|
68
|
+
};
|
|
69
|
+
readonly files: import("@akterjs/effect/contract").FileAccess<"user">;
|
|
70
|
+
readonly initial: () => {
|
|
71
|
+
balance: number & import("effect/Brand").Brand<"Cents">;
|
|
72
|
+
closed: boolean;
|
|
73
|
+
};
|
|
74
|
+
readonly key: Schema.brand<Schema.String, "AccountId">;
|
|
75
|
+
readonly queries: {
|
|
76
|
+
readonly account: import("@akterjs/effect/contract").Query<Schema.Void, Schema.Struct<{
|
|
77
|
+
readonly balance: Schema.brand<Schema.Int, "Cents">;
|
|
78
|
+
readonly closed: Schema.Boolean;
|
|
79
|
+
}>, Schema.Never, never>;
|
|
80
|
+
readonly entries: import("@akterjs/effect/contract").Query<Schema.Void, Schema.$Array<Schema.Struct<{
|
|
81
|
+
readonly amount: Schema.brand<Schema.Int, "Cents">;
|
|
82
|
+
readonly id: Schema.brand<Schema.String, "EntryId">;
|
|
83
|
+
}>>, Schema.Never, never>;
|
|
84
|
+
readonly scribble: import("@akterjs/effect/contract").Query<Schema.Void, Schema.Void, Schema.Never, never>;
|
|
85
|
+
readonly tables: import("@akterjs/effect/contract").Query<Schema.Void, Schema.$Array<Schema.String>, Schema.Never, never>;
|
|
86
|
+
};
|
|
87
|
+
readonly state: Schema.Struct<{
|
|
88
|
+
readonly balance: Schema.brand<Schema.Int, "Cents">;
|
|
89
|
+
readonly closed: Schema.Boolean;
|
|
90
|
+
}>;
|
|
91
|
+
readonly jobs: {
|
|
92
|
+
release: import("@akterjs/effect/contract").Job<{}, Schema.brand<Schema.Int, "Cents">, Schema.Never, string>;
|
|
93
|
+
};
|
|
94
|
+
}>> & import("@akterjs/effect/contract").ActorHistory<import("@akterjs/effect/contract").DefinitionOf<{
|
|
95
|
+
readonly callers: {
|
|
96
|
+
readonly actor: Schema.Struct<{
|
|
97
|
+
readonly actor: Schema.String;
|
|
98
|
+
readonly id: Schema.brand<Schema.String, "PrincipalId">;
|
|
99
|
+
readonly key: Schema.String;
|
|
100
|
+
}>;
|
|
101
|
+
readonly service: Schema.Struct<{
|
|
102
|
+
readonly id: Schema.brand<Schema.String, "PrincipalId">;
|
|
103
|
+
}>;
|
|
104
|
+
readonly user: Schema.Struct<{
|
|
105
|
+
readonly id: Schema.brand<Schema.String, "PrincipalId">;
|
|
106
|
+
}>;
|
|
107
|
+
};
|
|
108
|
+
readonly commands: {
|
|
109
|
+
readonly approve: import("@akterjs/effect/contract").Command<Schema.Struct<{
|
|
110
|
+
readonly amount: Schema.brand<Schema.Int, "Cents">;
|
|
111
|
+
readonly job: Schema.String;
|
|
112
|
+
readonly refuse: Schema.Boolean;
|
|
113
|
+
}>, Schema.Void, Schema.Never, never>;
|
|
114
|
+
readonly close: import("@akterjs/effect/contract").Command<Schema.Void, Schema.Void, Schema.Never, "service">;
|
|
115
|
+
readonly crash: import("@akterjs/effect/contract").Command<Schema.Struct<{
|
|
116
|
+
readonly amount: Schema.brand<Schema.Int, "Cents">;
|
|
117
|
+
}>, Schema.Void, Schema.Never, never>;
|
|
118
|
+
readonly credit: import("@akterjs/effect/contract").Command<Schema.Struct<{
|
|
119
|
+
readonly amount: Schema.brand<Schema.Int, "Cents">;
|
|
120
|
+
}>, Schema.brand<Schema.String, "EntryId">, Schema.Never, "actor">;
|
|
121
|
+
readonly deposit: import("@akterjs/effect/contract").Command<Schema.Struct<{
|
|
122
|
+
readonly amount: Schema.brand<Schema.Int, "Cents">;
|
|
123
|
+
}>, Schema.brand<Schema.String, "EntryId">, Schema.Never, never>;
|
|
124
|
+
readonly depositLater: import("@akterjs/effect/contract").Command<Schema.Struct<{
|
|
125
|
+
readonly amount: Schema.brand<Schema.Int, "Cents">;
|
|
126
|
+
readonly minutes: Schema.Int;
|
|
127
|
+
}>, Schema.Void, Schema.Never, never>;
|
|
128
|
+
readonly hold: import("@akterjs/effect/contract").Command<Schema.Void, Schema.String, Schema.Never, never>;
|
|
129
|
+
readonly tamper: import("@akterjs/effect/contract").Command<Schema.Struct<{
|
|
130
|
+
readonly amount: Schema.brand<Schema.Int, "Cents">;
|
|
131
|
+
}>, Schema.Void, Schema.Never, never>;
|
|
132
|
+
readonly transfer: import("@akterjs/effect/contract").Command<Schema.Struct<{
|
|
133
|
+
readonly amount: Schema.brand<Schema.Int, "Cents">;
|
|
134
|
+
readonly to: Schema.brand<Schema.String, "AccountId">;
|
|
135
|
+
}>, Schema.brand<Schema.String, "EntryId">, typeof OverdrawnError, never>;
|
|
136
|
+
readonly withdraw: import("@akterjs/effect/contract").Command<Schema.Struct<{
|
|
137
|
+
readonly amount: Schema.brand<Schema.Int, "Cents">;
|
|
138
|
+
}>, Schema.brand<Schema.String, "EntryId">, typeof OverdrawnError, never>;
|
|
139
|
+
};
|
|
140
|
+
readonly files: import("@akterjs/effect/contract").FileAccess<"user">;
|
|
141
|
+
readonly initial: () => {
|
|
142
|
+
balance: number & import("effect/Brand").Brand<"Cents">;
|
|
143
|
+
closed: boolean;
|
|
144
|
+
};
|
|
145
|
+
readonly key: Schema.brand<Schema.String, "AccountId">;
|
|
146
|
+
readonly queries: {
|
|
147
|
+
readonly account: import("@akterjs/effect/contract").Query<Schema.Void, Schema.Struct<{
|
|
148
|
+
readonly balance: Schema.brand<Schema.Int, "Cents">;
|
|
149
|
+
readonly closed: Schema.Boolean;
|
|
150
|
+
}>, Schema.Never, never>;
|
|
151
|
+
readonly entries: import("@akterjs/effect/contract").Query<Schema.Void, Schema.$Array<Schema.Struct<{
|
|
152
|
+
readonly amount: Schema.brand<Schema.Int, "Cents">;
|
|
153
|
+
readonly id: Schema.brand<Schema.String, "EntryId">;
|
|
154
|
+
}>>, Schema.Never, never>;
|
|
155
|
+
readonly scribble: import("@akterjs/effect/contract").Query<Schema.Void, Schema.Void, Schema.Never, never>;
|
|
156
|
+
readonly tables: import("@akterjs/effect/contract").Query<Schema.Void, Schema.$Array<Schema.String>, Schema.Never, never>;
|
|
157
|
+
};
|
|
158
|
+
readonly state: Schema.Struct<{
|
|
159
|
+
readonly balance: Schema.brand<Schema.Int, "Cents">;
|
|
160
|
+
readonly closed: Schema.Boolean;
|
|
161
|
+
}>;
|
|
162
|
+
readonly jobs: {
|
|
163
|
+
release: import("@akterjs/effect/contract").Job<{}, Schema.brand<Schema.Int, "Cents">, Schema.Never, string>;
|
|
164
|
+
};
|
|
165
|
+
}>>;
|
|
166
|
+
/** The Ledger deployed again with its `release` job patched, over holds the earlier build started. */
|
|
167
|
+
declare const PatchedLedger: import("@akterjs/effect/contract").Actor<"Ledger", import("@akterjs/effect/contract").DefinitionOf<{
|
|
168
|
+
readonly callers: {
|
|
169
|
+
readonly actor: Schema.Struct<{
|
|
170
|
+
readonly actor: Schema.String;
|
|
171
|
+
readonly id: Schema.brand<Schema.String, "PrincipalId">;
|
|
172
|
+
readonly key: Schema.String;
|
|
173
|
+
}>;
|
|
174
|
+
readonly service: Schema.Struct<{
|
|
175
|
+
readonly id: Schema.brand<Schema.String, "PrincipalId">;
|
|
176
|
+
}>;
|
|
177
|
+
readonly user: Schema.Struct<{
|
|
178
|
+
readonly id: Schema.brand<Schema.String, "PrincipalId">;
|
|
179
|
+
}>;
|
|
180
|
+
};
|
|
181
|
+
readonly commands: {
|
|
182
|
+
readonly approve: import("@akterjs/effect/contract").Command<Schema.Struct<{
|
|
183
|
+
readonly amount: Schema.brand<Schema.Int, "Cents">;
|
|
184
|
+
readonly job: Schema.String;
|
|
185
|
+
readonly refuse: Schema.Boolean;
|
|
186
|
+
}>, Schema.Void, Schema.Never, never>;
|
|
187
|
+
readonly close: import("@akterjs/effect/contract").Command<Schema.Void, Schema.Void, Schema.Never, "service">;
|
|
188
|
+
readonly crash: import("@akterjs/effect/contract").Command<Schema.Struct<{
|
|
189
|
+
readonly amount: Schema.brand<Schema.Int, "Cents">;
|
|
190
|
+
}>, Schema.Void, Schema.Never, never>;
|
|
191
|
+
readonly credit: import("@akterjs/effect/contract").Command<Schema.Struct<{
|
|
192
|
+
readonly amount: Schema.brand<Schema.Int, "Cents">;
|
|
193
|
+
}>, Schema.brand<Schema.String, "EntryId">, Schema.Never, "actor">;
|
|
194
|
+
readonly deposit: import("@akterjs/effect/contract").Command<Schema.Struct<{
|
|
195
|
+
readonly amount: Schema.brand<Schema.Int, "Cents">;
|
|
196
|
+
}>, Schema.brand<Schema.String, "EntryId">, Schema.Never, never>;
|
|
197
|
+
readonly depositLater: import("@akterjs/effect/contract").Command<Schema.Struct<{
|
|
198
|
+
readonly amount: Schema.brand<Schema.Int, "Cents">;
|
|
199
|
+
readonly minutes: Schema.Int;
|
|
200
|
+
}>, Schema.Void, Schema.Never, never>;
|
|
201
|
+
readonly hold: import("@akterjs/effect/contract").Command<Schema.Void, Schema.String, Schema.Never, never>;
|
|
202
|
+
readonly tamper: import("@akterjs/effect/contract").Command<Schema.Struct<{
|
|
203
|
+
readonly amount: Schema.brand<Schema.Int, "Cents">;
|
|
204
|
+
}>, Schema.Void, Schema.Never, never>;
|
|
205
|
+
readonly transfer: import("@akterjs/effect/contract").Command<Schema.Struct<{
|
|
206
|
+
readonly amount: Schema.brand<Schema.Int, "Cents">;
|
|
207
|
+
readonly to: Schema.brand<Schema.String, "AccountId">;
|
|
208
|
+
}>, Schema.brand<Schema.String, "EntryId">, typeof OverdrawnError, never>;
|
|
209
|
+
readonly withdraw: import("@akterjs/effect/contract").Command<Schema.Struct<{
|
|
210
|
+
readonly amount: Schema.brand<Schema.Int, "Cents">;
|
|
211
|
+
}>, Schema.brand<Schema.String, "EntryId">, typeof OverdrawnError, never>;
|
|
212
|
+
};
|
|
213
|
+
readonly files: import("@akterjs/effect/contract").FileAccess<"user">;
|
|
214
|
+
readonly initial: () => {
|
|
215
|
+
balance: number & import("effect/Brand").Brand<"Cents">;
|
|
216
|
+
closed: boolean;
|
|
217
|
+
};
|
|
218
|
+
readonly key: Schema.brand<Schema.String, "AccountId">;
|
|
219
|
+
readonly queries: {
|
|
220
|
+
readonly account: import("@akterjs/effect/contract").Query<Schema.Void, Schema.Struct<{
|
|
221
|
+
readonly balance: Schema.brand<Schema.Int, "Cents">;
|
|
222
|
+
readonly closed: Schema.Boolean;
|
|
223
|
+
}>, Schema.Never, never>;
|
|
224
|
+
readonly entries: import("@akterjs/effect/contract").Query<Schema.Void, Schema.$Array<Schema.Struct<{
|
|
225
|
+
readonly amount: Schema.brand<Schema.Int, "Cents">;
|
|
226
|
+
readonly id: Schema.brand<Schema.String, "EntryId">;
|
|
227
|
+
}>>, Schema.Never, never>;
|
|
228
|
+
readonly scribble: import("@akterjs/effect/contract").Query<Schema.Void, Schema.Void, Schema.Never, never>;
|
|
229
|
+
readonly tables: import("@akterjs/effect/contract").Query<Schema.Void, Schema.$Array<Schema.String>, Schema.Never, never>;
|
|
230
|
+
};
|
|
231
|
+
readonly state: Schema.Struct<{
|
|
232
|
+
readonly balance: Schema.brand<Schema.Int, "Cents">;
|
|
233
|
+
readonly closed: Schema.Boolean;
|
|
234
|
+
}>;
|
|
235
|
+
readonly jobs: {
|
|
236
|
+
release: import("@akterjs/effect/contract").Job<{}, Schema.brand<Schema.Int, "Cents">, Schema.Never, string>;
|
|
237
|
+
};
|
|
238
|
+
}>> & import("@akterjs/effect/contract").ActorHistory<import("@akterjs/effect/contract").DefinitionOf<{
|
|
239
|
+
readonly callers: {
|
|
240
|
+
readonly actor: Schema.Struct<{
|
|
241
|
+
readonly actor: Schema.String;
|
|
242
|
+
readonly id: Schema.brand<Schema.String, "PrincipalId">;
|
|
243
|
+
readonly key: Schema.String;
|
|
244
|
+
}>;
|
|
245
|
+
readonly service: Schema.Struct<{
|
|
246
|
+
readonly id: Schema.brand<Schema.String, "PrincipalId">;
|
|
247
|
+
}>;
|
|
248
|
+
readonly user: Schema.Struct<{
|
|
249
|
+
readonly id: Schema.brand<Schema.String, "PrincipalId">;
|
|
250
|
+
}>;
|
|
251
|
+
};
|
|
252
|
+
readonly commands: {
|
|
253
|
+
readonly approve: import("@akterjs/effect/contract").Command<Schema.Struct<{
|
|
254
|
+
readonly amount: Schema.brand<Schema.Int, "Cents">;
|
|
255
|
+
readonly job: Schema.String;
|
|
256
|
+
readonly refuse: Schema.Boolean;
|
|
257
|
+
}>, Schema.Void, Schema.Never, never>;
|
|
258
|
+
readonly close: import("@akterjs/effect/contract").Command<Schema.Void, Schema.Void, Schema.Never, "service">;
|
|
259
|
+
readonly crash: import("@akterjs/effect/contract").Command<Schema.Struct<{
|
|
260
|
+
readonly amount: Schema.brand<Schema.Int, "Cents">;
|
|
261
|
+
}>, Schema.Void, Schema.Never, never>;
|
|
262
|
+
readonly credit: import("@akterjs/effect/contract").Command<Schema.Struct<{
|
|
263
|
+
readonly amount: Schema.brand<Schema.Int, "Cents">;
|
|
264
|
+
}>, Schema.brand<Schema.String, "EntryId">, Schema.Never, "actor">;
|
|
265
|
+
readonly deposit: import("@akterjs/effect/contract").Command<Schema.Struct<{
|
|
266
|
+
readonly amount: Schema.brand<Schema.Int, "Cents">;
|
|
267
|
+
}>, Schema.brand<Schema.String, "EntryId">, Schema.Never, never>;
|
|
268
|
+
readonly depositLater: import("@akterjs/effect/contract").Command<Schema.Struct<{
|
|
269
|
+
readonly amount: Schema.brand<Schema.Int, "Cents">;
|
|
270
|
+
readonly minutes: Schema.Int;
|
|
271
|
+
}>, Schema.Void, Schema.Never, never>;
|
|
272
|
+
readonly hold: import("@akterjs/effect/contract").Command<Schema.Void, Schema.String, Schema.Never, never>;
|
|
273
|
+
readonly tamper: import("@akterjs/effect/contract").Command<Schema.Struct<{
|
|
274
|
+
readonly amount: Schema.brand<Schema.Int, "Cents">;
|
|
275
|
+
}>, Schema.Void, Schema.Never, never>;
|
|
276
|
+
readonly transfer: import("@akterjs/effect/contract").Command<Schema.Struct<{
|
|
277
|
+
readonly amount: Schema.brand<Schema.Int, "Cents">;
|
|
278
|
+
readonly to: Schema.brand<Schema.String, "AccountId">;
|
|
279
|
+
}>, Schema.brand<Schema.String, "EntryId">, typeof OverdrawnError, never>;
|
|
280
|
+
readonly withdraw: import("@akterjs/effect/contract").Command<Schema.Struct<{
|
|
281
|
+
readonly amount: Schema.brand<Schema.Int, "Cents">;
|
|
282
|
+
}>, Schema.brand<Schema.String, "EntryId">, typeof OverdrawnError, never>;
|
|
283
|
+
};
|
|
284
|
+
readonly files: import("@akterjs/effect/contract").FileAccess<"user">;
|
|
285
|
+
readonly initial: () => {
|
|
286
|
+
balance: number & import("effect/Brand").Brand<"Cents">;
|
|
287
|
+
closed: boolean;
|
|
288
|
+
};
|
|
289
|
+
readonly key: Schema.brand<Schema.String, "AccountId">;
|
|
290
|
+
readonly queries: {
|
|
291
|
+
readonly account: import("@akterjs/effect/contract").Query<Schema.Void, Schema.Struct<{
|
|
292
|
+
readonly balance: Schema.brand<Schema.Int, "Cents">;
|
|
293
|
+
readonly closed: Schema.Boolean;
|
|
294
|
+
}>, Schema.Never, never>;
|
|
295
|
+
readonly entries: import("@akterjs/effect/contract").Query<Schema.Void, Schema.$Array<Schema.Struct<{
|
|
296
|
+
readonly amount: Schema.brand<Schema.Int, "Cents">;
|
|
297
|
+
readonly id: Schema.brand<Schema.String, "EntryId">;
|
|
298
|
+
}>>, Schema.Never, never>;
|
|
299
|
+
readonly scribble: import("@akterjs/effect/contract").Query<Schema.Void, Schema.Void, Schema.Never, never>;
|
|
300
|
+
readonly tables: import("@akterjs/effect/contract").Query<Schema.Void, Schema.$Array<Schema.String>, Schema.Never, never>;
|
|
301
|
+
};
|
|
302
|
+
readonly state: Schema.Struct<{
|
|
303
|
+
readonly balance: Schema.brand<Schema.Int, "Cents">;
|
|
304
|
+
readonly closed: Schema.Boolean;
|
|
305
|
+
}>;
|
|
306
|
+
readonly jobs: {
|
|
307
|
+
release: import("@akterjs/effect/contract").Job<{}, Schema.brand<Schema.Int, "Cents">, Schema.Never, string>;
|
|
308
|
+
};
|
|
309
|
+
}>>;
|
|
310
|
+
export { Account, Cents, Entry, EntryId, Ledger, OverdrawnError, PatchedLedger, doubled };
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { Akter } from "@akterjs/effect/contract";
|
|
3
|
+
// src/ledger/ledger.contract.ts
|
|
4
|
+
const AccountId = Schema.String.pipe(Schema.brand("AccountId"));
|
|
5
|
+
const EntryId = Schema.String.pipe(Schema.brand("EntryId"));
|
|
6
|
+
const Cents = Schema.Int.pipe(Schema.brand("Cents"));
|
|
7
|
+
const openingCents = 0;
|
|
8
|
+
const Entry = Schema.Struct({
|
|
9
|
+
amount: Cents,
|
|
10
|
+
id: EntryId
|
|
11
|
+
});
|
|
12
|
+
const Account = Schema.Struct({
|
|
13
|
+
balance: Cents,
|
|
14
|
+
closed: Schema.Boolean
|
|
15
|
+
});
|
|
16
|
+
const Service = Schema.Struct({ id: Akter.PrincipalId });
|
|
17
|
+
var OverdrawnError = class extends Schema.TaggedError()("OverdrawnError", { balance: Cents }) {};
|
|
18
|
+
/** Everything the Ledger declares but its `release` job, which the two builds below declare with different patches. */
|
|
19
|
+
const ledgerOptions = {
|
|
20
|
+
callers: {
|
|
21
|
+
actor: Akter.ActorPrincipal,
|
|
22
|
+
service: Service,
|
|
23
|
+
user: Akter.Principal
|
|
24
|
+
},
|
|
25
|
+
commands: {
|
|
26
|
+
approve: Akter.command({
|
|
27
|
+
payload: {
|
|
28
|
+
amount: Cents,
|
|
29
|
+
job: Schema.String,
|
|
30
|
+
refuse: Schema.Boolean
|
|
31
|
+
},
|
|
32
|
+
success: Schema.Void
|
|
33
|
+
}),
|
|
34
|
+
close: Akter.command({
|
|
35
|
+
callers: ["service"],
|
|
36
|
+
success: Schema.Void
|
|
37
|
+
}),
|
|
38
|
+
crash: Akter.command({
|
|
39
|
+
payload: { amount: Cents },
|
|
40
|
+
success: Schema.Void
|
|
41
|
+
}),
|
|
42
|
+
credit: Akter.command({
|
|
43
|
+
callers: ["actor"],
|
|
44
|
+
payload: { amount: Cents },
|
|
45
|
+
success: EntryId
|
|
46
|
+
}),
|
|
47
|
+
deposit: Akter.command({
|
|
48
|
+
payload: { amount: Cents },
|
|
49
|
+
success: EntryId
|
|
50
|
+
}),
|
|
51
|
+
depositLater: Akter.command({
|
|
52
|
+
payload: {
|
|
53
|
+
amount: Cents,
|
|
54
|
+
minutes: Schema.Int
|
|
55
|
+
},
|
|
56
|
+
success: Schema.Void
|
|
57
|
+
}),
|
|
58
|
+
hold: Akter.command({ success: Schema.String }),
|
|
59
|
+
tamper: Akter.command({
|
|
60
|
+
payload: { amount: Cents },
|
|
61
|
+
success: Schema.Void
|
|
62
|
+
}),
|
|
63
|
+
transfer: Akter.command({
|
|
64
|
+
error: OverdrawnError,
|
|
65
|
+
payload: {
|
|
66
|
+
amount: Cents,
|
|
67
|
+
to: AccountId
|
|
68
|
+
},
|
|
69
|
+
success: EntryId
|
|
70
|
+
}),
|
|
71
|
+
withdraw: Akter.command({
|
|
72
|
+
error: OverdrawnError,
|
|
73
|
+
payload: { amount: Cents },
|
|
74
|
+
success: EntryId
|
|
75
|
+
})
|
|
76
|
+
},
|
|
77
|
+
files: Akter.fileAccess({ callers: ["user"] }),
|
|
78
|
+
initial: () => ({
|
|
79
|
+
balance: Cents.make(openingCents),
|
|
80
|
+
closed: false
|
|
81
|
+
}),
|
|
82
|
+
key: AccountId,
|
|
83
|
+
queries: {
|
|
84
|
+
account: Akter.query({ success: Account }),
|
|
85
|
+
entries: Akter.query({ success: Schema.Array(Entry) }),
|
|
86
|
+
scribble: Akter.query({ success: Schema.Void }),
|
|
87
|
+
tables: Akter.query({ success: Schema.Array(Schema.String) })
|
|
88
|
+
},
|
|
89
|
+
state: Account
|
|
90
|
+
};
|
|
91
|
+
/** The patch the later build's `release` declares: it releases twice the approved amount, for holds it started. */
|
|
92
|
+
const doubled = "double";
|
|
93
|
+
const noPatches = [];
|
|
94
|
+
const doubling = [doubled];
|
|
95
|
+
const Ledger = Akter.make("Ledger", {
|
|
96
|
+
...ledgerOptions,
|
|
97
|
+
jobs: { release: Akter.job({
|
|
98
|
+
patches: noPatches,
|
|
99
|
+
payload: {},
|
|
100
|
+
success: Cents
|
|
101
|
+
}) }
|
|
102
|
+
});
|
|
103
|
+
/** The Ledger deployed again with its `release` job patched, over holds the earlier build started. */
|
|
104
|
+
const PatchedLedger = Akter.make("Ledger", {
|
|
105
|
+
...ledgerOptions,
|
|
106
|
+
jobs: { release: Akter.job({
|
|
107
|
+
patches: doubling,
|
|
108
|
+
payload: {},
|
|
109
|
+
success: Cents
|
|
110
|
+
}) }
|
|
111
|
+
});
|
|
112
|
+
export { Account, Cents, Entry, EntryId, Ledger, OverdrawnError, PatchedLedger, doubled };
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import "./ledger-calls.pure.mjs";
|
|
2
|
+
import { doubled } from "./ledger.contract.mjs";
|
|
3
|
+
import { Array, Effect, Layer } from "effect";
|
|
4
|
+
import { Akter } from "@akterjs/effect";
|
|
5
|
+
import { Akter as Akter$1 } from "@akterjs/sdk";
|
|
6
|
+
import { engineOf } from "@akterjs/sdk/engine";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
// src/ledger/plain-ledger.actor.ts
|
|
9
|
+
/** The plain SDK's Ledger: the wire contract of the Effect Ledger in `ledger.contract.ts`, so one suite checks both SDKs. */
|
|
10
|
+
const Cents = z.number().int();
|
|
11
|
+
const Amount = z.object({ amount: Cents });
|
|
12
|
+
const Later = z.object({
|
|
13
|
+
amount: Cents,
|
|
14
|
+
minutes: z.number().int()
|
|
15
|
+
});
|
|
16
|
+
const Approving = z.object({
|
|
17
|
+
amount: Cents,
|
|
18
|
+
job: z.string(),
|
|
19
|
+
refuse: z.boolean()
|
|
20
|
+
});
|
|
21
|
+
const Release = z.object({});
|
|
22
|
+
const Transfer = z.object({
|
|
23
|
+
amount: Cents,
|
|
24
|
+
to: z.string()
|
|
25
|
+
});
|
|
26
|
+
const EntryId = z.string();
|
|
27
|
+
const OverdrawnError = Akter$1.error("OverdrawnError", z.object({ balance: Cents }));
|
|
28
|
+
const Account = z.object({
|
|
29
|
+
balance: Cents,
|
|
30
|
+
closed: z.boolean()
|
|
31
|
+
});
|
|
32
|
+
const Entries = z.array(z.object({
|
|
33
|
+
amount: Cents,
|
|
34
|
+
id: EntryId
|
|
35
|
+
}));
|
|
36
|
+
const TableNames = z.array(z.object({ name: z.string() }));
|
|
37
|
+
const TableList = z.array(z.string()).readonly();
|
|
38
|
+
/** Everything the plain Ledger declares but its `release` job, which the two builds below declare with different patches. */
|
|
39
|
+
const ledgerOptions = {
|
|
40
|
+
callers: {
|
|
41
|
+
actor: Akter$1.ActorPrincipal,
|
|
42
|
+
service: z.object({ id: z.string() }),
|
|
43
|
+
user: Akter$1.Principal
|
|
44
|
+
},
|
|
45
|
+
commands: {
|
|
46
|
+
approve: Akter$1.command({ payload: Approving }),
|
|
47
|
+
close: Akter$1.command({ callers: ["service"] }),
|
|
48
|
+
crash: Akter$1.command({ payload: Amount }),
|
|
49
|
+
credit: Akter$1.command({
|
|
50
|
+
callers: ["actor"],
|
|
51
|
+
payload: Amount,
|
|
52
|
+
success: EntryId
|
|
53
|
+
}),
|
|
54
|
+
deposit: Akter$1.command({
|
|
55
|
+
payload: Amount,
|
|
56
|
+
success: EntryId
|
|
57
|
+
}),
|
|
58
|
+
depositLater: Akter$1.command({ payload: Later }),
|
|
59
|
+
hold: Akter$1.command({ success: z.string() }),
|
|
60
|
+
tamper: Akter$1.command({ payload: Amount }),
|
|
61
|
+
transfer: Akter$1.command({
|
|
62
|
+
errors: [OverdrawnError],
|
|
63
|
+
payload: Transfer,
|
|
64
|
+
success: EntryId
|
|
65
|
+
}),
|
|
66
|
+
withdraw: Akter$1.command({
|
|
67
|
+
errors: [OverdrawnError],
|
|
68
|
+
payload: Amount,
|
|
69
|
+
success: EntryId
|
|
70
|
+
})
|
|
71
|
+
},
|
|
72
|
+
files: Akter$1.fileAccess({ callers: ["user"] }),
|
|
73
|
+
initial: () => ({
|
|
74
|
+
balance: 0,
|
|
75
|
+
closed: false
|
|
76
|
+
}),
|
|
77
|
+
key: z.string(),
|
|
78
|
+
queries: {
|
|
79
|
+
account: Akter$1.query({ success: Account }),
|
|
80
|
+
entries: Akter$1.query({ success: Entries }),
|
|
81
|
+
scribble: Akter$1.query(),
|
|
82
|
+
tables: Akter$1.query({ success: TableList })
|
|
83
|
+
},
|
|
84
|
+
state: Account
|
|
85
|
+
};
|
|
86
|
+
const noPatches = [];
|
|
87
|
+
const doubling = [doubled];
|
|
88
|
+
const Ledger = Akter$1.make("Ledger", {
|
|
89
|
+
...ledgerOptions,
|
|
90
|
+
jobs: { release: Akter$1.job({
|
|
91
|
+
patches: noPatches,
|
|
92
|
+
payload: Release,
|
|
93
|
+
success: Cents
|
|
94
|
+
}) }
|
|
95
|
+
});
|
|
96
|
+
/** The Ledger deployed again with its `release` job patched, over holds the earlier build started. */
|
|
97
|
+
const PatchedLedger = Akter$1.make("Ledger", {
|
|
98
|
+
...ledgerOptions,
|
|
99
|
+
jobs: { release: Akter$1.job({
|
|
100
|
+
patches: doubling,
|
|
101
|
+
payload: Release,
|
|
102
|
+
success: Cents
|
|
103
|
+
}) }
|
|
104
|
+
});
|
|
105
|
+
const lowestBalance = 0;
|
|
106
|
+
const millisPerMinute = 6e4;
|
|
107
|
+
const scribbled = 1;
|
|
108
|
+
const insert = (sql, id, amount) => sql`INSERT INTO entries (id, amount) VALUES (${id}, ${amount})`;
|
|
109
|
+
/** Records an entry and moves the balance, as the Effect Ledger's `record` does. */
|
|
110
|
+
const record = (ctx, balance, amount) => {
|
|
111
|
+
const id = ctx.id();
|
|
112
|
+
insert(ctx.sql, id, amount);
|
|
113
|
+
ctx.update({ balance });
|
|
114
|
+
return id;
|
|
115
|
+
};
|
|
116
|
+
const withdrawal = (ctx, amount) => {
|
|
117
|
+
const balance = ctx.state.balance - amount;
|
|
118
|
+
const id = record(ctx, balance, amount);
|
|
119
|
+
if (balance < lowestBalance) throw new OverdrawnError({ balance });
|
|
120
|
+
return id;
|
|
121
|
+
};
|
|
122
|
+
/** Reads the engine's journal, then writes the engine's state table, which no handler may do. */
|
|
123
|
+
const tamperWith = (sql) => Array.appendAll(sql`SELECT count(*) AS commands FROM akter_commands`, sql`UPDATE akter_state SET value = value`);
|
|
124
|
+
const tableNames = (sql) => Array.map(TableNames.parse(sql`SELECT name FROM sqlite_schema WHERE type = 'table' AND name NOT LIKE 'akter_%' AND name NOT LIKE 'sqlite_%' ORDER BY name`), (row) => row.name);
|
|
125
|
+
/** Signals the held job, then throws after the signal when `refuse` is set, so the command rolls back with it. */
|
|
126
|
+
const approve = (ctx, { amount, job, refuse }) => {
|
|
127
|
+
ctx.signal(job, "approval", amount);
|
|
128
|
+
if (refuse) throw new Error("the approval was refused after signalling");
|
|
129
|
+
};
|
|
130
|
+
/** Waits for the approved amount and returns it, so its result records the entry. */
|
|
131
|
+
const release = (_payload, steps) => Effect.runPromise(Effect.map(Effect.promise(() => steps.waitFor("approval")), (value) => Cents.parse(value)));
|
|
132
|
+
const twice = 2;
|
|
133
|
+
/** The later build's release: twice the approved amount, but only for a hold this build started. */
|
|
134
|
+
const doublingRelease = (payload, steps) => Effect.runPromise(Effect.map(Effect.promise(() => release(payload, steps)), (amount) => {
|
|
135
|
+
if (steps.patched("double")) return amount * twice;
|
|
136
|
+
return amount;
|
|
137
|
+
}));
|
|
138
|
+
const released = (ctx, result) => {
|
|
139
|
+
if (result.outcome === "success") record(ctx, ctx.state.balance + result.value, result.value);
|
|
140
|
+
};
|
|
141
|
+
/** The build the suite opens first: the Ledger without patches. */
|
|
142
|
+
const firstBuild = {
|
|
143
|
+
job: release,
|
|
144
|
+
ledger: Ledger
|
|
145
|
+
};
|
|
146
|
+
const plainLedgerLayer = (migrations, { job, ledger } = firstBuild) => {
|
|
147
|
+
const implemented = ledger.implement({
|
|
148
|
+
commands: {
|
|
149
|
+
approve,
|
|
150
|
+
close: (ctx) => {
|
|
151
|
+
ctx.update({ closed: true });
|
|
152
|
+
},
|
|
153
|
+
crash: (ctx, { amount }) => {
|
|
154
|
+
record(ctx, ctx.state.balance + amount, amount);
|
|
155
|
+
throw new Error("the handler crashed after writing");
|
|
156
|
+
},
|
|
157
|
+
credit: (ctx, { amount }) => record(ctx, ctx.state.balance + amount, amount),
|
|
158
|
+
deposit: (ctx, { amount }) => record(ctx, ctx.state.balance + amount, amount),
|
|
159
|
+
depositLater: (ctx, { amount, minutes }) => {
|
|
160
|
+
ctx.schedule(minutes * millisPerMinute, Ledger.commands.deposit, { amount });
|
|
161
|
+
},
|
|
162
|
+
hold: (ctx) => ctx.start(ledger.jobs.release, {}),
|
|
163
|
+
tamper: (ctx, { amount }) => {
|
|
164
|
+
record(ctx, ctx.state.balance + amount, amount);
|
|
165
|
+
tamperWith(ctx.sql);
|
|
166
|
+
},
|
|
167
|
+
transfer: (ctx, { amount, to }) => {
|
|
168
|
+
ctx.send(Ledger.commands.credit, to, { amount });
|
|
169
|
+
return withdrawal(ctx, amount);
|
|
170
|
+
},
|
|
171
|
+
withdraw: (ctx, { amount }) => withdrawal(ctx, amount)
|
|
172
|
+
},
|
|
173
|
+
files: { authorize: (ctx, { access }) => access === "read" || ctx.principal.id === "ada" && !ctx.state.closed },
|
|
174
|
+
jobs: { release: job },
|
|
175
|
+
migrations,
|
|
176
|
+
queries: {
|
|
177
|
+
account: (ctx) => ({
|
|
178
|
+
balance: ctx.state.balance,
|
|
179
|
+
closed: ctx.state.closed
|
|
180
|
+
}),
|
|
181
|
+
entries: (ctx) => Entries.parse(ctx.sql`SELECT id, amount FROM entries ORDER BY rowid`),
|
|
182
|
+
scribble: (ctx) => {
|
|
183
|
+
insert(ctx.sql, "query", scribbled);
|
|
184
|
+
},
|
|
185
|
+
tables: (ctx) => tableNames(ctx.sql)
|
|
186
|
+
},
|
|
187
|
+
results: { release: released }
|
|
188
|
+
});
|
|
189
|
+
return engineOf(Akter$1.app([implemented])).layer.pipe(Layer.provide(Akter.jobs));
|
|
190
|
+
};
|
|
191
|
+
const createEntries = "CREATE TABLE entries (id TEXT PRIMARY KEY, amount INTEGER NOT NULL)";
|
|
192
|
+
const createNotes = "CREATE TABLE notes (id TEXT PRIMARY KEY, text TEXT NOT NULL)";
|
|
193
|
+
const entriesVersion = 1;
|
|
194
|
+
const notesVersion = 2;
|
|
195
|
+
const reservedVersion = 3;
|
|
196
|
+
const plainLedgerIntruding = (prefix) => plainLedgerLayer({
|
|
197
|
+
[entriesVersion]: createEntries,
|
|
198
|
+
[notesVersion]: createNotes,
|
|
199
|
+
[reservedVersion]: `CREATE TABLE "${prefix}intruder" (id TEXT PRIMARY KEY)`
|
|
200
|
+
});
|
|
201
|
+
/** The plain SDK's Ledgers, for `conformanceOf({ engine, ledgers: plainLedgers, skip })`: every engine must pass the suite on both SDKs. */
|
|
202
|
+
const plainLedgers = {
|
|
203
|
+
intruding: plainLedgerIntruding,
|
|
204
|
+
live: plainLedgerLayer({ [entriesVersion]: createEntries }),
|
|
205
|
+
patched: plainLedgerLayer({ [entriesVersion]: createEntries }, {
|
|
206
|
+
job: doublingRelease,
|
|
207
|
+
ledger: PatchedLedger
|
|
208
|
+
}),
|
|
209
|
+
withNotes: plainLedgerLayer({
|
|
210
|
+
[entriesVersion]: createEntries,
|
|
211
|
+
[notesVersion]: createNotes
|
|
212
|
+
})
|
|
213
|
+
};
|
|
214
|
+
export { plainLedgers };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
declare const RejectedError_base: Schema.Class<RejectedError, Schema.TaggedStruct<"RejectedError", {
|
|
3
|
+
readonly reason: Schema.Unknown;
|
|
4
|
+
}>, import("effect/Cause").YieldableError & {
|
|
5
|
+
readonly brand: unique symbol;
|
|
6
|
+
}>;
|
|
7
|
+
export declare class RejectedError extends RejectedError_base {}
|
|
8
|
+
export {};
|