@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.
Files changed (57) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +52 -2
  3. package/dist/client-contract.suite.d.mts +23 -0
  4. package/dist/client-contract.suite.mjs +279 -0
  5. package/dist/client-target.errors.d.mts +8 -0
  6. package/dist/client-target.errors.mjs +4 -0
  7. package/dist/engine-conformance.suite.d.mts +19 -0
  8. package/dist/engine-conformance.suite.mjs +357 -0
  9. package/dist/fork/fork-conformance.suite.d.mts +20 -0
  10. package/dist/fork/fork-conformance.suite.mjs +167 -0
  11. package/dist/guarantee-titles.pure.d.mts +31 -0
  12. package/dist/guarantee-titles.pure.mjs +32 -0
  13. package/dist/ledger/ledger-calls.pure.d.mts +79 -0
  14. package/dist/ledger/ledger-calls.pure.mjs +111 -0
  15. package/dist/ledger/ledger-checks.pure.d.mts +21 -0
  16. package/dist/ledger/ledger-checks.pure.mjs +37 -0
  17. package/dist/ledger/ledger.actor.d.mts +13 -0
  18. package/dist/ledger/ledger.actor.mjs +115 -0
  19. package/dist/ledger/ledger.contract.d.mts +310 -0
  20. package/dist/ledger/ledger.contract.mjs +112 -0
  21. package/dist/ledger/plain-ledger.actor.d.mts +4 -0
  22. package/dist/ledger/plain-ledger.actor.mjs +214 -0
  23. package/dist/rejected.errors.d.mts +8 -0
  24. package/dist/rejected.errors.mjs +4 -0
  25. package/dist/tally-clients.layer.d.mts +39 -0
  26. package/dist/tally-clients.layer.mjs +97 -0
  27. package/dist/tally.actor.d.mts +49 -0
  28. package/dist/tally.actor.mjs +26 -0
  29. package/dist/tally.contract.d.mts +51 -0
  30. package/dist/tally.contract.mjs +28 -0
  31. package/dist/workbench/computer-conformance.suite.d.mts +15 -0
  32. package/dist/workbench/computer-conformance.suite.mjs +330 -0
  33. package/dist/workbench/workbench-calls.pure.d.mts +45 -0
  34. package/dist/workbench/workbench-calls.pure.mjs +75 -0
  35. package/dist/workbench/workbench.actor.d.mts +4 -0
  36. package/dist/workbench/workbench.actor.mjs +107 -0
  37. package/dist/workbench/workbench.contract.d.mts +220 -0
  38. package/dist/workbench/workbench.contract.mjs +54 -0
  39. package/package.json +86 -3
  40. package/src/client-contract.suite.ts +413 -0
  41. package/src/client-target.errors.ts +7 -0
  42. package/src/engine-conformance.suite.ts +484 -0
  43. package/src/fork/fork-conformance.suite.ts +204 -0
  44. package/src/guarantee-titles.pure.ts +50 -0
  45. package/src/ledger/ledger-calls.pure.ts +179 -0
  46. package/src/ledger/ledger-checks.pure.ts +84 -0
  47. package/src/ledger/ledger.actor.ts +196 -0
  48. package/src/ledger/ledger.contract.ts +76 -0
  49. package/src/ledger/plain-ledger.actor.ts +246 -0
  50. package/src/rejected.errors.ts +6 -0
  51. package/src/tally-clients.layer.ts +221 -0
  52. package/src/tally.actor.ts +28 -0
  53. package/src/tally.contract.ts +22 -0
  54. package/src/workbench/computer-conformance.suite.ts +380 -0
  55. package/src/workbench/workbench-calls.pure.ts +92 -0
  56. package/src/workbench/workbench.actor.ts +175 -0
  57. package/src/workbench/workbench.contract.ts +47 -0
@@ -0,0 +1,79 @@
1
+ import { Ledger, OverdrawnError } from "./ledger.contract.mjs";
2
+ import { Duration, Effect, Exit, Schema } from "effect";
3
+ import { ActorHost } from "@akterjs/effect";
4
+ /** The callers, keys, amounts and payloads the engine conformance suite sends the Ledger, as the JSON the wire carries. */
5
+ interface Sent {
6
+ readonly account?: string;
7
+ readonly commandId: string;
8
+ readonly payload: string;
9
+ readonly principal?: string;
10
+ }
11
+ declare const ada = "{\"kind\":\"user\",\"principal\":{\"id\":\"ada\"}}";
12
+ declare const billing = "{\"kind\":\"service\",\"principal\":{\"id\":\"billing\"}}";
13
+ declare const bob = "{\"kind\":\"user\",\"principal\":{\"id\":\"bob\"}}";
14
+ /** The one user the Ledger's file rule lets write. */
15
+ declare const fileWriter = "ada";
16
+ /** `akter.files` payloads: a read, an upload, and an upload whose payload claims to be ada's. */
17
+ declare const reading = "{\"access\":\"read\",\"path\":\"statements/may.pdf\"}";
18
+ declare const writing = "{\"access\":\"write\",\"path\":\"\"}";
19
+ /** What the rule decides for bob's read, write and forged write, ada's write, billing's read, then ada's write once closed. */
20
+ declare const fileDecisions: string[];
21
+ declare const forgedWriting = "{\"access\":\"write\",\"path\":\"\",\"principal\":{\"kind\":\"user\",\"principal\":{\"id\":\"ada\"}}}";
22
+ declare const key = "acct-1";
23
+ declare const payee = "acct-2";
24
+ /**
25
+ * The key a fork of `acct-1` goes into. Akter Cloud forks only into a key on the parent's own shard, and `acct-5` hashes
26
+ * to `acct-1`'s shard, so an engine that serves one serves the other.
27
+ */
28
+ declare const forked = "acct-5";
29
+ declare const nothing = "null";
30
+ declare const opening = 5;
31
+ declare const topUp = 7;
32
+ /** `topUp` as the patched build releases it for a hold it started: twice over. */
33
+ declare const topUpDoubled = 14;
34
+ declare const overdraft = 9;
35
+ declare const lostInCrash = 3;
36
+ declare const refill = 1;
37
+ declare const transferred = 2;
38
+ declare const beforeDue: Duration.Input;
39
+ declare const untilDue: Duration.Input;
40
+ declare const longAfterDue: Duration.Input;
41
+ declare const amountLater: (value: number) => string;
42
+ declare const amountTo: (value: number) => string;
43
+ declare const amount: (value: number) => string;
44
+ declare const overdrawnBy: OverdrawnError;
45
+ declare const depositOpening: Sent;
46
+ declare const withdrawOverdraft: Sent;
47
+ declare const robotDeposit: Sent;
48
+ interface Approving {
49
+ readonly amount: number;
50
+ readonly job: string;
51
+ readonly refuse: boolean;
52
+ }
53
+ declare const approving: ({ amount: value, job, refuse }: Approving) => string;
54
+ /** How the engine conformance suite calls the Ledger on an engine: commands and queries as JSON, replies decoded. */
55
+ type Host = ActorHost["Service"];
56
+ type Reply = ReturnType<Host["command"]>;
57
+ type Unexpected = Effect.Error<Reply> | Effect.Error<ReturnType<Host["fork"]>> | Schema.SchemaError;
58
+ declare const send: (host: Host, member: string, { account, commandId, payload, principal }: Sent) => Reply;
59
+ declare const ask: (host: Host, member: string, holder?: string) => Reply;
60
+ declare const account: (host: Host, holder?: string) => Effect.Effect<Exit.Exit<typeof Ledger.queries.account.success.Type>, Unexpected>;
61
+ /** Forks `acct-1` into `into`, which is `forked` by default, and answers the parent's version at the cut. */
62
+ declare const fork: (host: Host, commandId: string, into?: string) => ReturnType<Host["fork"]>;
63
+ declare const amounts: (host: Host, holder?: string) => Effect.Effect<Exit.Exit<readonly number[]>, Unexpected>;
64
+ declare const approve: (host: Host, commandId: string, payload: string) => Reply;
65
+ declare const drained: (host: Host) => ReturnType<typeof amounts>;
66
+ /** Holds funds: starts a `release` job and returns its id. */
67
+ declare const held: (host: Host, commandId: string) => Effect.Effect<string, Unexpected>;
68
+ /** The calls, as one namespace: `send`, `ask`, `approve` and `held` take the host first. */
69
+ declare const ledgerHost: {
70
+ account: typeof account;
71
+ amounts: typeof amounts;
72
+ approve: typeof approve;
73
+ ask: typeof ask;
74
+ drained: typeof drained;
75
+ fork: typeof fork;
76
+ held: typeof held;
77
+ send: typeof send;
78
+ };
79
+ export { type Host, type Reply, type Sent, type Unexpected, ada, amount, amountLater, amountTo, approving, beforeDue, billing, bob, depositOpening, fileDecisions, fileWriter, forgedWriting, forked, key, ledgerHost, longAfterDue, lostInCrash, nothing, opening, overdraft, overdrawnBy, payee, reading, refill, robotDeposit, topUp, topUpDoubled, transferred, untilDue, withdrawOverdraft, writing };
@@ -0,0 +1,111 @@
1
+ import { decodeReply } from "./ledger-checks.pure.mjs";
2
+ import { Cents, Ledger, OverdrawnError } from "./ledger.contract.mjs";
3
+ import { Array, Effect, Exit } from "effect";
4
+ // src/ledger/ledger-calls.pure.ts
5
+ const ada = "{\"kind\":\"user\",\"principal\":{\"id\":\"ada\"}}";
6
+ const billing = "{\"kind\":\"service\",\"principal\":{\"id\":\"billing\"}}";
7
+ const bob = "{\"kind\":\"user\",\"principal\":{\"id\":\"bob\"}}";
8
+ /** The one user the Ledger's file rule lets write. */
9
+ const fileWriter = "ada";
10
+ /** `akter.files` payloads: a read, an upload, and an upload whose payload claims to be ada's. */
11
+ const reading = "{\"access\":\"read\",\"path\":\"statements/may.pdf\"}";
12
+ const writing = "{\"access\":\"write\",\"path\":\"\"}";
13
+ /** What the rule decides for bob's read, write and forged write, ada's write, billing's read, then ada's write once closed. */
14
+ const fileDecisions = [
15
+ "allowed",
16
+ "denied",
17
+ "denied",
18
+ "allowed",
19
+ "CallerNotAllowedError",
20
+ "denied"
21
+ ];
22
+ const forgedWriting = "{\"access\":\"write\",\"path\":\"\",\"principal\":{\"kind\":\"user\",\"principal\":{\"id\":\"ada\"}}}";
23
+ const robot = "{\"kind\":\"robot\",\"principal\":{\"id\":\"r2\"}}";
24
+ const key = "acct-1";
25
+ const payee = "acct-2";
26
+ /**
27
+ * The key a fork of `acct-1` goes into. Akter Cloud forks only into a key on the parent's own shard, and `acct-5` hashes
28
+ * to `acct-1`'s shard, so an engine that serves one serves the other.
29
+ */
30
+ const forked = "acct-5";
31
+ const nothing = "null";
32
+ const opening = 5;
33
+ const topUp = 7;
34
+ /** `topUp` as the patched build releases it for a hold it started: twice over. */
35
+ const topUpDoubled = 14;
36
+ const overdraft = 9;
37
+ const lostInCrash = 3;
38
+ const refill = 1;
39
+ const transferred = 2;
40
+ const dueMinutes = 5;
41
+ const beforeDue = "4 minutes";
42
+ const untilDue = "1 minute";
43
+ const longAfterDue = "10 minutes";
44
+ const amountLater = (value) => `{"amount":${value},"minutes":${dueMinutes}}`;
45
+ const amountTo = (value) => `{"amount":${value},"to":"${payee}"}`;
46
+ const amount = (value) => `{"amount":${value}}`;
47
+ const overdrawnBy = OverdrawnError.make({ balance: Cents.make(-4) });
48
+ const depositOpening = {
49
+ commandId: "a",
50
+ payload: amount(5)
51
+ };
52
+ const withdrawOverdraft = {
53
+ commandId: "w",
54
+ payload: amount(9)
55
+ };
56
+ const robotDeposit = {
57
+ commandId: "d-robot",
58
+ payload: amount(5),
59
+ principal: robot
60
+ };
61
+ const approving = ({ amount: value, job, refuse }) => `{"amount":${value},"job":"${job}","refuse":${refuse}}`;
62
+ const send = (host, member, { account = key, commandId, payload, principal = ada }) => host.command({
63
+ actor: "Ledger",
64
+ commandId,
65
+ key: account,
66
+ member,
67
+ payload,
68
+ principal
69
+ });
70
+ const ask = (host, member, holder = key) => host.query({
71
+ actor: "Ledger",
72
+ key: holder,
73
+ member,
74
+ payload: nothing,
75
+ principal: ada
76
+ });
77
+ const account = (host, holder = key) => Effect.flatMap(ask(host, "account", holder), (reply) => decodeReply(Ledger.queries.account, reply));
78
+ /** Forks `acct-1` into `into`, which is `forked` by default, and answers the parent's version at the cut. */
79
+ const fork = (host, commandId, into = forked) => host.fork({
80
+ actor: "Ledger",
81
+ commandId,
82
+ into,
83
+ key
84
+ });
85
+ const amounts = (host, holder = key) => Effect.map(Effect.flatMap(ask(host, "entries", holder), (reply) => decodeReply(Ledger.queries.entries, reply)), Exit.map(Array.map((entry) => entry.amount)));
86
+ const approve = (host, commandId, payload) => send(host, "approve", {
87
+ commandId,
88
+ payload
89
+ });
90
+ const drained = (host) => Effect.gen(function* drainThenRead() {
91
+ yield* account(host);
92
+ yield* host.drain;
93
+ return yield* amounts(host);
94
+ });
95
+ /** Holds funds: starts a `release` job and returns its id. */
96
+ const held = (host, commandId) => Effect.flatMap(send(host, "hold", {
97
+ commandId,
98
+ payload: nothing
99
+ }), (reply) => Effect.flatten(decodeReply(Ledger.commands.hold, reply)));
100
+ /** The calls, as one namespace: `send`, `ask`, `approve` and `held` take the host first. */
101
+ const ledgerHost = {
102
+ account,
103
+ amounts,
104
+ approve,
105
+ ask,
106
+ drained,
107
+ fork,
108
+ held,
109
+ send
110
+ };
111
+ export { ada, amount, amountLater, amountTo, approving, beforeDue, billing, bob, depositOpening, fileDecisions, fileWriter, forgedWriting, forked, key, ledgerHost, longAfterDue, lostInCrash, nothing, opening, overdraft, overdrawnBy, payee, reading, refill, robotDeposit, topUp, topUpDoubled, transferred, untilDue, withdrawOverdraft, writing };
@@ -0,0 +1,21 @@
1
+ import { Effect, Exit, Option, Schema } from "effect";
2
+ /** Checks the engine conformance suite applies to replies, table names and an engine's declared host table prefix. */
3
+ declare const akterTables = "akter_";
4
+ declare const isHostTable: ({ hostTables }: {
5
+ readonly hostTables: Option.Option<string>;
6
+ }) => (name: string) => boolean;
7
+ declare const isRefused: <Failure>(reply: Effect.Effect<string, Failure>) => Effect.Effect<boolean>;
8
+ interface Declaration<Success, SuccessEncoded, Failure, FailureEncoded> {
9
+ readonly error: Schema.Codec<Failure, FailureEncoded>;
10
+ readonly success: Schema.Codec<Success, SuccessEncoded>;
11
+ }
12
+ type Decoded<Success, Failure> = Effect.Effect<Exit.Exit<Success, Failure>, Schema.SchemaError>;
13
+ /** Decodes a member's reply, as the engine encodes it, to its exit. */
14
+ declare const decodeReply: {
15
+ (reply: string): <Success, SuccessEncoded, Failure, FailureEncoded>(declaration: Declaration<Success, SuccessEncoded, Failure, FailureEncoded>) => Decoded<Success, Failure>;
16
+ <Success, SuccessEncoded, Failure, FailureEncoded>(declaration: Declaration<Success, SuccessEncoded, Failure, FailureEncoded>, reply: string): Decoded<Success, Failure>;
17
+ };
18
+ /** What an actor's file rule decided for one `akter.files` query: `allowed`, `denied`, or the caller's refusal. */
19
+ declare const fileDecision: <Failure>(reply: Effect.Effect<string, Failure>) => Effect.Effect<string, Schema.SchemaError>;
20
+ declare const refusedPrefix: (prefix: string) => Option.Option<string>;
21
+ export { akterTables, decodeReply, fileDecision, isHostTable, isRefused, refusedPrefix };
@@ -0,0 +1,37 @@
1
+ import { FileAccessDeniedError } from "@akterjs/effect/contract/errors";
2
+ import { Effect, Exit, Option, Predicate, Schema, String } from "effect";
3
+ import { dual } from "effect/Function";
4
+ // src/ledger/ledger-checks.pure.ts
5
+ /** Checks the engine conformance suite applies to replies, table names and an engine's declared host table prefix. */
6
+ const akterTables = "akter_";
7
+ const isHostTable = ({ hostTables }) => (name) => {
8
+ const lowered = String.toLowerCase(name);
9
+ const prefix = Option.map(hostTables, String.toLowerCase);
10
+ return Option.exists(prefix, (lower) => String.startsWith(lower)(lowered));
11
+ };
12
+ const isRefused = (reply) => Effect.match(reply, {
13
+ onFailure: (failure) => Predicate.isTagged(failure, "CallerNotAllowedError"),
14
+ onSuccess: () => false
15
+ });
16
+ /** Decodes a member's reply, as the engine encodes it, to its exit. */
17
+ const decodeReply = dual(2, (declaration, reply) => {
18
+ const exit = Schema.Exit(declaration.success, declaration.error, Schema.Defect());
19
+ return Schema.decodeEffect(Schema.fromJsonString(Schema.toCodecJson(exit)))(reply);
20
+ });
21
+ /** What an actor's file rule decided for one `akter.files` query: `allowed`, `denied`, or the caller's refusal. */
22
+ const fileDecision = (reply) => Effect.matchEffect(reply, {
23
+ onFailure: (failure) => Option.liftPredicate(failure, Predicate.isTagged("CallerNotAllowedError")).pipe(Option.as("CallerNotAllowedError"), Option.getOrElse(() => "failed"), Effect.succeed),
24
+ onSuccess: (text) => Effect.map(decodeReply({
25
+ error: FileAccessDeniedError,
26
+ success: Schema.Void
27
+ }, text), Exit.match({
28
+ onFailure: () => "denied",
29
+ onSuccess: () => "allowed"
30
+ }))
31
+ });
32
+ const refusedPrefix = (prefix) => {
33
+ if (String.isEmpty(prefix)) return Option.some("an empty prefix would match every table");
34
+ if (String.toLowerCase(prefix) === "akter_") return Option.some(`${akterTables} is Akter's own prefix`);
35
+ return Option.none();
36
+ };
37
+ export { akterTables, decodeReply, fileDecision, isHostTable, isRefused, refusedPrefix };
@@ -0,0 +1,13 @@
1
+ import { Layer } from "effect";
2
+ /** A Ledger implementation the suite serves on the engine, from either SDK. */
3
+ type LedgerLayer = Layer.Layer<never>;
4
+ /** The Ledger at each migration step the suite opens: entries only, entries and notes, and one that creates a table under the engine's prefix. */
5
+ interface Ledgers {
6
+ readonly intruding: (prefix: string) => LedgerLayer;
7
+ readonly live: LedgerLayer;
8
+ /** `live` deployed again with its `release` job patched (`doubled`), over holds the earlier build started. */
9
+ readonly patched: LedgerLayer;
10
+ readonly withNotes: LedgerLayer;
11
+ }
12
+ declare const effectLedgers: Ledgers;
13
+ export { type LedgerLayer, type Ledgers, effectLedgers };
@@ -0,0 +1,115 @@
1
+ import "./ledger-calls.pure.mjs";
2
+ import { Cents, EntryId, Ledger, OverdrawnError, PatchedLedger } from "./ledger.contract.mjs";
3
+ import { FileAccessDeniedError } from "@akterjs/effect/contract/errors";
4
+ import { Array, Duration, Effect, Exit, Layer } from "effect";
5
+ import { Akter } from "@akterjs/effect/contract";
6
+ import { Akter as Akter$1 } from "@akterjs/effect";
7
+ import { DurableDeferred } from "effect/workflow";
8
+ // src/ledger/ledger.actor.ts
9
+ const lowestBalance = 0;
10
+ const entriesVersion = 1;
11
+ const notesVersion = 2;
12
+ const reservedVersion = 3;
13
+ const record = Effect.fnUntraced(function* record(balance, amount) {
14
+ const minted = yield* Akter.id;
15
+ const id = EntryId.make(minted);
16
+ const sql = yield* Akter.sql;
17
+ yield* sql`INSERT INTO entries ${sql.insert({
18
+ amount,
19
+ id
20
+ })}`;
21
+ yield* Ledger.update({ balance });
22
+ return id;
23
+ });
24
+ const withdrawal = Effect.fnUntraced(function* withdrawal(ledger, amount) {
25
+ const balance = Cents.make(ledger.balance - amount);
26
+ const id = yield* record(balance, amount);
27
+ if (balance < lowestBalance) return yield* OverdrawnError.make({ balance });
28
+ return id;
29
+ });
30
+ /** Writes an entry, reads the engine's journal, then writes the engine's state table, which no handler may do. */
31
+ const tamper = Effect.fnUntraced(function* tamper(balance, amount) {
32
+ yield* record(balance, amount);
33
+ const sql = yield* Akter.sql;
34
+ yield* sql`SELECT count(*) AS commands FROM akter_commands`;
35
+ yield* sql`UPDATE akter_state SET value = value`;
36
+ });
37
+ /** The amount a person approves for release: `hold` starts a job that waits for it, and `approve` signals it. */
38
+ const approval = DurableDeferred.make("approval", { success: Cents });
39
+ /** Signals the held job, then fails after the signal when `refuse` is set, so the command rolls back with it. */
40
+ const refusal = Effect.die(/* @__PURE__ */ new Error("the approval was refused after signalling"));
41
+ const approve = (amount, job, refuse) => {
42
+ const signalled = Akter.signal(approval, {
43
+ job,
44
+ value: amount
45
+ });
46
+ if (refuse) return Effect.andThen(signalled, refusal);
47
+ return signalled;
48
+ };
49
+ /** Any user reads the ledger's files; only ada writes them, and no one once the account is closed. */
50
+ const fileRule = (ledger, { access, path }) => Ledger.principal.pipe(Effect.filterOrFail((caller) => access === "read" || caller.id === "ada" && !ledger.closed, () => FileAccessDeniedError.make({
51
+ access,
52
+ message: "only ada writes, while the account is open",
53
+ path
54
+ })), Effect.asVoid);
55
+ const ledgerLayer = (migrations, holding = Ledger.jobs.release) => Ledger.toLayer({
56
+ commands: {
57
+ approve: (_ledger, { amount, job, refuse }) => approve(amount, job, refuse),
58
+ close: () => Ledger.update({ closed: true }),
59
+ crash: (ledger, { amount }) => Effect.andThen(record(Cents.make(ledger.balance + amount), amount), Effect.die(/* @__PURE__ */ new Error("the handler crashed after writing"))),
60
+ credit: (ledger, { amount }) => record(Cents.make(ledger.balance + amount), amount),
61
+ deposit: (ledger, { amount }) => record(Cents.make(ledger.balance + amount), amount),
62
+ depositLater: (_ledger, { amount, minutes }) => Effect.asVoid(Akter.schedule(Duration.minutes(minutes), Ledger.commands.deposit, { amount })),
63
+ hold: () => Akter.start(holding, {}),
64
+ tamper: (ledger, { amount }) => tamper(Cents.make(ledger.balance + amount), amount),
65
+ transfer: (ledger, { amount, to }) => Effect.andThen(Akter.send(Ledger.commands.credit, to, { amount }), withdrawal(ledger, amount)),
66
+ withdraw: (ledger, { amount }) => withdrawal(ledger, amount)
67
+ },
68
+ files: { authorize: fileRule },
69
+ migrations,
70
+ queries: {
71
+ account: (ledger) => Effect.succeed({
72
+ balance: ledger.balance,
73
+ closed: ledger.closed
74
+ }),
75
+ entries: () => Effect.flatMap(Akter.sql, (sql) => sql`SELECT id, amount FROM entries ORDER BY rowid`),
76
+ scribble: () => Effect.asVoid(Effect.flatMap(Akter.sql, (sql) => sql`INSERT INTO entries ${sql.insert({
77
+ amount: 1,
78
+ id: "query"
79
+ })}`)),
80
+ tables: () => Effect.flatMap(Akter.sql, (sql) => Effect.map(sql`SELECT name FROM sqlite_schema WHERE type = 'table' AND name NOT LIKE 'akter_%' AND name NOT LIKE 'sqlite_%' ORDER BY name`, Array.map((row) => row.name)))
81
+ },
82
+ results: { release: (ledger, result) => Exit.match(result, {
83
+ onFailure: () => Effect.void,
84
+ onSuccess: (amount) => Effect.asVoid(record(Cents.make(ledger.balance + amount), amount))
85
+ }) }
86
+ });
87
+ const releaseLive = Ledger.jobs.release.toLayer(() => DurableDeferred.await(approval));
88
+ const twice = 2;
89
+ /** The later build's release: twice the approved amount, but only for a hold this build started. */
90
+ const doublingReleaseLive = PatchedLedger.jobs.release.toLayer(() => Effect.gen(function* doublingRelease() {
91
+ const amount = yield* DurableDeferred.await(approval);
92
+ if (yield* PatchedLedger.jobs.release.patched("double")) return Cents.make(amount * twice);
93
+ return amount;
94
+ }));
95
+ /** Serves the Ledger with its job on the engine's job runtime, as an app does. */
96
+ const withJobs = (ledger, release = releaseLive) => Layer.merge(ledger, release).pipe(Layer.provide(Akter$1.jobs));
97
+ const createEntries = (sql) => sql`CREATE TABLE entries (id TEXT PRIMARY KEY, amount INTEGER NOT NULL)`;
98
+ const createNotes = (sql) => sql`CREATE TABLE notes (id TEXT PRIMARY KEY, text TEXT NOT NULL)`;
99
+ const LedgerLive = withJobs(ledgerLayer({ [entriesVersion]: createEntries }));
100
+ const LedgerWithNotesLive = withJobs(ledgerLayer({
101
+ [entriesVersion]: createEntries,
102
+ [notesVersion]: createNotes
103
+ }));
104
+ const ledgerIntruding = (prefix) => withJobs(ledgerLayer({
105
+ [entriesVersion]: createEntries,
106
+ [notesVersion]: createNotes,
107
+ [reservedVersion]: (sql) => sql`CREATE TABLE ${sql(`${prefix}intruder`)} (id TEXT PRIMARY KEY)`
108
+ }));
109
+ const effectLedgers = {
110
+ intruding: ledgerIntruding,
111
+ live: LedgerLive,
112
+ patched: withJobs(ledgerLayer({ [entriesVersion]: createEntries }, PatchedLedger.jobs.release), doublingReleaseLive),
113
+ withNotes: LedgerWithNotesLive
114
+ };
115
+ export { effectLedgers };