yichus 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/cli.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/studio-page/system-map.js +23 -4
- package/dist/studio-reference/refund-desk-one-round.bosatsu +21 -18
- package/dist/studio-reference/refund-desk.bosatsu +21 -18
- package/dist/studio-reference/withdrawal.bosatsu +11 -15
- package/package.json +2 -2
|
@@ -440,7 +440,7 @@ function renderService(map, W, opts) {
|
|
|
440
440
|
// path that takes two lines lifts the row.
|
|
441
441
|
const roleText = e => [...new Set(e.members.flatMap(m => m.roles))].map(r => dense && rolePrefix ? r.slice(rolePrefix.length) : r).join(', ');
|
|
442
442
|
// the lines a fork's types take from x to end (none when not even a letter fits: then they are its tooltip)
|
|
443
|
-
const forkLines = (types, room) => (
|
|
443
|
+
const forkLines = (types, room) => (dense ? [] : forkTypeLines(types, room, fsS));
|
|
444
444
|
const gapL = lineGap(fs);
|
|
445
445
|
const titleSz = phone ? 9.5 : 10.5;
|
|
446
446
|
// a hull's name over its first row, on as many lines as the gap between risers takes
|
|
@@ -1227,9 +1227,26 @@ function wallPathGaps(x0, x1, yTop, yBot, openings, r = 14) {
|
|
|
1227
1227
|
// line; each package is a stretch starting with a tick and its name; the
|
|
1228
1228
|
// parts' labels alternate above and below the line so it never wraps at
|
|
1229
1229
|
// phone width. A package with many forks draws one fork with a count.
|
|
1230
|
+
// The lines a fork's types take in `room` at `size`: none for no types, one
|
|
1231
|
+
// empty line when not even a letter fits (the types are then its tooltip).
|
|
1232
|
+
// Lines break between types, so a long list keeps its lines, a type too
|
|
1233
|
+
// long for a line clipped where the line ends. Only a type broken mid-word
|
|
1234
|
+
// across lines (a line that ends inside a type) with the rest clipped puts
|
|
1235
|
+
// the types on one line, clipped.
|
|
1236
|
+
export function forkTypeLines(types, room, size) {
|
|
1237
|
+
const s = types.join(',');
|
|
1238
|
+
if (!types.length) return [];
|
|
1239
|
+
if (room < tw(`${s[0]}…`, size)) return [''];
|
|
1240
|
+
const ls = wrapped(s, room, { size });
|
|
1241
|
+
const brokenMidWord = ls.slice(0, -1).some(l => !l.endsWith(',')) && ls.slice(1).some(l => l.endsWith('…'));
|
|
1242
|
+
return brokenMidWord ? [clip(s, size, room)] : ls;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1230
1245
|
// A gathered mark's count, said in words: what it counts, never a bare ×n.
|
|
1231
1246
|
const SET_WORDS = { forkset: ['choice', 'choices'], dbset: ['db use', 'db uses'], genset: ['generic part', 'generic parts'], stepset: ['step', 'steps'] };
|
|
1232
1247
|
export const setWord = (kind, n) => `${n} ${SET_WORDS[kind][n === 1 ? 0 : 1]}`;
|
|
1248
|
+
// a db use off every path whose handle is handed in on some paths only: a ? beside its station
|
|
1249
|
+
const strayUnknown = (x, y, phone) => mark.unknown(x + (phone ? 6 : 7.5), y - (phone ? 6 : 7.5), phone ? 3.6 : 4.4);
|
|
1233
1250
|
|
|
1234
1251
|
export function quietItems(map, placed, { services = false, dense = false, phone = false, drawn = new Set(), socketFns = new Map(), genericById = new Map(), exportsLeft = [], strays = [] } = {}) {
|
|
1235
1252
|
const pkgOf = id => String(id).split('::')[0];
|
|
@@ -1250,7 +1267,8 @@ export function quietItems(map, placed, { services = false, dense = false, phone
|
|
|
1250
1267
|
for (const u of strays) { const p = pkgOf(u.definition); pk(p); if (!strayBy.has(p)) strayBy.set(p, []); strayBy.get(p).push(u); }
|
|
1251
1268
|
for (const [p, us] of strayBy) {
|
|
1252
1269
|
const defs = [...new Set(us.map(u => u.definition))];
|
|
1253
|
-
|
|
1270
|
+
const mixed = us.filter(u => u.how === 'mixed');
|
|
1271
|
+
P.get(p).parts.push({ kind: 'dbset', label: defs.length === 1 && !dense ? bare(defs[0]) : setWord('dbset', defs.length), n: defs.length, writes: us.some(u => u.writes), mixed: mixed.length > 0, keys: [...us.map(u => `use:${u.i}`), ...mixed.map(u => `unknown:${u.i}`)] });
|
|
1254
1272
|
}
|
|
1255
1273
|
// forks (and bare generic parts) of one package gathered under one mark with a count when the line would be long
|
|
1256
1274
|
const total = [...list.values()].reduce((n, l) => n + l.length, 0);
|
|
@@ -1277,7 +1295,7 @@ export function quietItems(map, placed, { services = false, dense = false, phone
|
|
|
1277
1295
|
const forks = of('forkset'), dbs = of('dbset'), others = all.filter(it => !forks.includes(it) && !dbs.includes(it));
|
|
1278
1296
|
const n = list => list.reduce((c, it) => c + (it.n || 1), 0);
|
|
1279
1297
|
if (forks.length) parts.push({ kind: 'forkset', label: setWord('forkset', n(forks)), n: n(forks), fork: [...new Set(forks.flatMap(it => it.fork))], keys: forks.flatMap(it => it.keys) });
|
|
1280
|
-
if (dbs.length) parts.push({ kind: 'dbset', label: setWord('dbset', n(dbs)), n: n(dbs), writes: dbs.some(it => it.writes), keys: dbs.flatMap(it => it.keys) });
|
|
1298
|
+
if (dbs.length) parts.push({ kind: 'dbset', label: setWord('dbset', n(dbs)), n: n(dbs), writes: dbs.some(it => it.writes), mixed: dbs.some(it => it.mixed), keys: dbs.flatMap(it => it.keys) });
|
|
1281
1299
|
parts.push(...others);
|
|
1282
1300
|
if (steps.length) parts.push({ kind: 'stepset', label: setWord('stepset', steps.length), n: steps.length, keys: steps.map(st => `step:${st.pkg}:${st.type}`) });
|
|
1283
1301
|
return [{ pkg: '', exports: [], steps: [], parts, gathered: groups.length }];
|
|
@@ -1390,6 +1408,7 @@ function angledQuiet(pkgs, lsx, lex, y0, phone, fsS) {
|
|
|
1390
1408
|
g.parts.forEach((it, i) => {
|
|
1391
1409
|
const x = xs[i];
|
|
1392
1410
|
let gg = it.kind === 'forkset' ? mark.fork(x - (phone ? 3 : 4), ry, phone ? 0.7 : 0.85) : it.kind === 'dbset' ? mark.station(x, ry, phone ? 3.6 : 4.6, it.writes) : it.socket ? mark.socket(x, ry, phone ? 4.2 : 5.4) : mark.dot(x, ry, phone ? 2.8 : 3.4);
|
|
1411
|
+
if (it.mixed) gg += strayUnknown(x, ry, phone);
|
|
1393
1412
|
if (it.fork) gg += mark.fork(x + 1, ry - (phone ? 7 : 9), phone ? 0.5 : 0.6);
|
|
1394
1413
|
gg += rot(x + 2, ry - (phone ? 12 : 15), -ang, ups.get(it));
|
|
1395
1414
|
over.push(tg(it.keys, gg));
|
|
@@ -1468,7 +1487,7 @@ export function layoutQuiet(pkgs, lsx, lex, y0, phone, fsS0, bands = []) {
|
|
|
1468
1487
|
// a step set's count starts past its loop's arrowhead
|
|
1469
1488
|
let gg = '', tx = it.kind === 'stepset' ? x + (phone ? 6 : 8) : x - 3;
|
|
1470
1489
|
if (it.kind === 'forkset') gg += mark.fork(x - (phone ? 3 : 4), ry, phone ? 0.75 : 0.9);
|
|
1471
|
-
else if (it.kind === 'dbset') gg += mark.station(x, ry, phone ? 3.6 : 4.6, it.writes);
|
|
1490
|
+
else if (it.kind === 'dbset') gg += mark.station(x, ry, phone ? 3.6 : 4.6, it.writes) + (it.mixed ? strayUnknown(x, ry, phone) : '');
|
|
1472
1491
|
else if (it.kind === 'stepset') gg += mark.loop(x, ry - 0.5, phone ? 4.2 : 5.4);
|
|
1473
1492
|
else if (it.socket) gg += mark.socket(x, ry, phone ? 4.6 : 6);
|
|
1474
1493
|
else gg += mark.dot(x, ry, phone ? 3 : 3.8);
|
|
@@ -20,7 +20,7 @@ from Yichus/IO import IO, flat_map, pure
|
|
|
20
20
|
from Yichus/Access import Principal, AccessRule, AccessSpec, OwnerScoped, owner_key
|
|
21
21
|
from Yichus/Data import Db
|
|
22
22
|
from Yichus/Service import Reply, Done, Invalid, NotFound, Refused as Turned, TryAgain, handler, route, service_def, ReadPerm, WritePerm, CreatePerm
|
|
23
|
-
from Yichus/Transaction import
|
|
23
|
+
from Yichus/Transaction import TxTable, Txn, Committed, transaction, replied, stored_table, tx_bind, tx_pure, tx_read, tx_write, tx_insert, tx_new_token
|
|
24
24
|
from Yichus/OpenRouter import ProviderPolicy, ModelRequest, ModelResult, Completed, Refused, RetryableFailure, PermanentFailure, TimedOut, openrouter_call
|
|
25
25
|
from Yichus/Json import JsonObject, JsonString, JsonArray, JsonBool, Parsed, InvalidJson, Rendered, RenderError, Decoded, DecodeError, parse_json, render_json, decode_bind, required_field, as_bool
|
|
26
26
|
from Zafu/Control/IterState import IterState, Continue, Done as Finished
|
|
@@ -53,34 +53,33 @@ access_rules = AccessSpec([
|
|
|
53
53
|
AccessRule("refunds", OwnerScoped),
|
|
54
54
|
])
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
cases: TxTable[s, Case] = stored_table("refunds")
|
|
57
57
|
|
|
58
58
|
# Open the order's refund, unless it already has one.
|
|
59
|
-
def open_plan[s](
|
|
60
|
-
reference
|
|
61
|
-
tx_insert(cases(scope), key, Case(reference, 0, refund, [], None))
|
|
59
|
+
def open_plan[s](key: String, refund: Refund, reference: String) -> Txn[s, RefundError, Unit]:
|
|
60
|
+
tx_insert(cases, key, Case(reference, 0, refund, [], None))
|
|
62
61
|
|
|
63
|
-
def read_plan[s](
|
|
64
|
-
tx_read(cases
|
|
62
|
+
def read_plan[s](key: String) -> Txn[s, RefundError, Option[Case]]:
|
|
63
|
+
tx_read(cases, key)
|
|
65
64
|
|
|
66
65
|
# Append an exchange to an undecided case, or decide it, only if its
|
|
67
66
|
# version is still the one the round read; either raises the version.
|
|
68
|
-
def append_plan[s](
|
|
69
|
-
found <- tx_bind(tx_read(cases
|
|
67
|
+
def append_plan[s](key: String, seen: Int, exchange: Exchange) -> Txn[s, RefundError, Bool]:
|
|
68
|
+
found <- tx_bind(tx_read(cases, key))
|
|
70
69
|
match found:
|
|
71
70
|
case Some(Case(reference, version, refund, record, None)):
|
|
72
71
|
if eq_Int(version, seen):
|
|
73
|
-
_ <- tx_bind(tx_write(cases
|
|
72
|
+
_ <- tx_bind(tx_write(cases, key, Case(reference, version.add(1), refund, record.concat([exchange]), None)))
|
|
74
73
|
tx_pure(True)
|
|
75
74
|
else: tx_pure(False)
|
|
76
75
|
case _: tx_pure(False)
|
|
77
76
|
|
|
78
|
-
def decide_plan[s](
|
|
79
|
-
found <- tx_bind(tx_read(cases
|
|
77
|
+
def decide_plan[s](key: String, seen: Int, decision: Decision) -> Txn[s, RefundError, Bool]:
|
|
78
|
+
found <- tx_bind(tx_read(cases, key))
|
|
80
79
|
match found:
|
|
81
80
|
case Some(Case(reference, version, refund, record, None)):
|
|
82
81
|
if eq_Int(version, seen):
|
|
83
|
-
_ <- tx_bind(tx_write(cases
|
|
82
|
+
_ <- tx_bind(tx_write(cases, key, Case(reference, version.add(1), refund, record, Some(decision))))
|
|
84
83
|
tx_pure(True)
|
|
85
84
|
else: tx_pure(False)
|
|
86
85
|
case _: tx_pure(False)
|
|
@@ -139,20 +138,20 @@ def request_refund(db: Db, p: Principal, refund: Refund) -> IO[Reply[RefundError
|
|
|
139
138
|
# carries on with the stored refund; a different one for the same order
|
|
140
139
|
# is turned away.
|
|
141
140
|
def round(_: Unit, _: Int) -> IO[IterState[Unit, Reply[RefundError, Decision]]]:
|
|
142
|
-
found <- flat_map(transaction(db,
|
|
141
|
+
found <- flat_map(transaction(db, _ -> read_plan(key)))
|
|
143
142
|
match found:
|
|
144
143
|
case Committed(Some(Case(reference, seen, stored, record, decided))):
|
|
145
144
|
if same_refund(stored, refund):
|
|
146
145
|
match (decided, replay(stored, record)):
|
|
147
146
|
case (Some(decision), _): pure(Finished(Done(decision)))
|
|
148
147
|
case (None, Some(Ruled(decision))):
|
|
149
|
-
saved <- flat_map(transaction(db,
|
|
148
|
+
saved <- flat_map(transaction(db, _ -> decide_plan(key, seen, decision)))
|
|
150
149
|
match saved:
|
|
151
150
|
case Committed(True): pure(Finished(Done(decision)))
|
|
152
151
|
case _: pure(Continue(()))
|
|
153
152
|
case (None, Some(Perform(request))):
|
|
154
153
|
answer <- flat_map(perform(reference, length_List(record), request))
|
|
155
|
-
_ <- flat_map(transaction(db,
|
|
154
|
+
_ <- flat_map(transaction(db, _ -> append_plan(key, seen, Exchange(request, answer))))
|
|
156
155
|
pure(Continue(()))
|
|
157
156
|
case (None, None): pure(Finished(Turned("this refund's record cannot be read back", Unreadable)))
|
|
158
157
|
else: pure(Finished(Turned("this order already has a refund request for another amount or reason", AlreadyRequested)))
|
|
@@ -160,7 +159,11 @@ def request_refund(db: Db, p: Principal, refund: Refund) -> IO[Reply[RefundError
|
|
|
160
159
|
# The store was busy: read again next round.
|
|
161
160
|
case _: pure(Continue(()))
|
|
162
161
|
def decide():
|
|
163
|
-
|
|
162
|
+
def open_case():
|
|
163
|
+
scope <- transaction(db)
|
|
164
|
+
reference <- tx_bind(tx_new_token(scope))
|
|
165
|
+
open_plan(key, refund, reference)
|
|
166
|
+
opened <- flat_map(open_case())
|
|
164
167
|
match opened:
|
|
165
168
|
case Committed(_):
|
|
166
169
|
ended <- flat_map(round((), 0))
|
|
@@ -177,7 +180,7 @@ def request_refund(db: Db, p: Principal, refund: Refund) -> IO[Reply[RefundError
|
|
|
177
180
|
|
|
178
181
|
def refund_status(db: Db, p: Principal, input: StatusRequest) -> IO[Reply[RefundError, RefundView]]:
|
|
179
182
|
StatusRequest(order) = input
|
|
180
|
-
found <- flat_map(transaction(db,
|
|
183
|
+
found <- flat_map(transaction(db, _ -> read_plan(owner_key(p, order))))
|
|
181
184
|
pure(replied(found,
|
|
182
185
|
c -> (
|
|
183
186
|
match c:
|
|
@@ -14,7 +14,7 @@ from Yichus/IO import IO, flat_map, pure, fold_until
|
|
|
14
14
|
from Yichus/Access import Principal, AccessRule, AccessSpec, OwnerScoped, owner_key
|
|
15
15
|
from Yichus/Data import Db
|
|
16
16
|
from Yichus/Service import Reply, Done, Invalid, NotFound, Refused as Turned, TryAgain, handler, route, service_def, ReadPerm, WritePerm, CreatePerm
|
|
17
|
-
from Yichus/Transaction import
|
|
17
|
+
from Yichus/Transaction import TxTable, Txn, Committed, transaction, replied, stored_table, tx_bind, tx_pure, tx_read, tx_write, tx_insert, tx_new_token
|
|
18
18
|
from Yichus/OpenRouter import ProviderPolicy, ModelRequest, ModelResult, Completed, Refused, RetryableFailure, PermanentFailure, TimedOut, openrouter_call
|
|
19
19
|
from Yichus/Json import JsonObject, JsonString, JsonArray, JsonBool, Parsed, InvalidJson, Rendered, RenderError, Decoded, DecodeError, parse_json, render_json, decode_bind, required_field, as_bool
|
|
20
20
|
from Zafu/Control/IterState import IterState, Continue, Done as Finished
|
|
@@ -47,34 +47,33 @@ access_rules = AccessSpec([
|
|
|
47
47
|
AccessRule("refunds", OwnerScoped),
|
|
48
48
|
])
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
cases: TxTable[s, Case] = stored_table("refunds")
|
|
51
51
|
|
|
52
52
|
# Open the order's refund, unless it already has one.
|
|
53
|
-
def open_plan[s](
|
|
54
|
-
reference
|
|
55
|
-
tx_insert(cases(scope), key, Case(reference, 0, refund, [], None))
|
|
53
|
+
def open_plan[s](key: String, refund: Refund, reference: String) -> Txn[s, RefundError, Unit]:
|
|
54
|
+
tx_insert(cases, key, Case(reference, 0, refund, [], None))
|
|
56
55
|
|
|
57
|
-
def read_plan[s](
|
|
58
|
-
tx_read(cases
|
|
56
|
+
def read_plan[s](key: String) -> Txn[s, RefundError, Option[Case]]:
|
|
57
|
+
tx_read(cases, key)
|
|
59
58
|
|
|
60
59
|
# Append an exchange to an undecided case, or decide it, only if its
|
|
61
60
|
# version is still the one the round read; either raises the version.
|
|
62
|
-
def append_plan[s](
|
|
63
|
-
found <- tx_bind(tx_read(cases
|
|
61
|
+
def append_plan[s](key: String, seen: Int, exchange: Exchange) -> Txn[s, RefundError, Bool]:
|
|
62
|
+
found <- tx_bind(tx_read(cases, key))
|
|
64
63
|
match found:
|
|
65
64
|
case Some(Case(reference, version, refund, record, None)):
|
|
66
65
|
if eq_Int(version, seen):
|
|
67
|
-
_ <- tx_bind(tx_write(cases
|
|
66
|
+
_ <- tx_bind(tx_write(cases, key, Case(reference, version.add(1), refund, record.concat([exchange]), None)))
|
|
68
67
|
tx_pure(True)
|
|
69
68
|
else: tx_pure(False)
|
|
70
69
|
case _: tx_pure(False)
|
|
71
70
|
|
|
72
|
-
def decide_plan[s](
|
|
73
|
-
found <- tx_bind(tx_read(cases
|
|
71
|
+
def decide_plan[s](key: String, seen: Int, decision: Decision) -> Txn[s, RefundError, Bool]:
|
|
72
|
+
found <- tx_bind(tx_read(cases, key))
|
|
74
73
|
match found:
|
|
75
74
|
case Some(Case(reference, version, refund, record, None)):
|
|
76
75
|
if eq_Int(version, seen):
|
|
77
|
-
_ <- tx_bind(tx_write(cases
|
|
76
|
+
_ <- tx_bind(tx_write(cases, key, Case(reference, version.add(1), refund, record, Some(decision))))
|
|
78
77
|
tx_pure(True)
|
|
79
78
|
else: tx_pure(False)
|
|
80
79
|
case _: tx_pure(False)
|
|
@@ -137,20 +136,20 @@ def request_refund(db: Db, p: Principal, refund: Refund) -> IO[Reply[RefundError
|
|
|
137
136
|
# carries on with the stored refund; a different one for the same order
|
|
138
137
|
# is turned away.
|
|
139
138
|
def round(_: Unit, _: Int) -> IO[IterState[Unit, Reply[RefundError, Decision]]]:
|
|
140
|
-
found <- flat_map(transaction(db,
|
|
139
|
+
found <- flat_map(transaction(db, _ -> read_plan(key)))
|
|
141
140
|
match found:
|
|
142
141
|
case Committed(Some(Case(reference, seen, stored, record, decided))):
|
|
143
142
|
if same_refund(stored, refund):
|
|
144
143
|
match (decided, replay(stored, record)):
|
|
145
144
|
case (Some(decision), _): pure(Finished(Done(decision)))
|
|
146
145
|
case (None, Some(Ruled(decision))):
|
|
147
|
-
saved <- flat_map(transaction(db,
|
|
146
|
+
saved <- flat_map(transaction(db, _ -> decide_plan(key, seen, decision)))
|
|
148
147
|
match saved:
|
|
149
148
|
case Committed(True): pure(Finished(Done(decision)))
|
|
150
149
|
case _: pure(Continue(()))
|
|
151
150
|
case (None, Some(Perform(request))):
|
|
152
151
|
answer <- flat_map(perform(reference, length_List(record), request))
|
|
153
|
-
_ <- flat_map(transaction(db,
|
|
152
|
+
_ <- flat_map(transaction(db, _ -> append_plan(key, seen, Exchange(request, answer))))
|
|
154
153
|
pure(Continue(()))
|
|
155
154
|
case (None, None): pure(Finished(Turned("this refund's record cannot be read back", Unreadable)))
|
|
156
155
|
else: pure(Finished(Turned("this order already has a refund request for another amount or reason", AlreadyRequested)))
|
|
@@ -158,7 +157,11 @@ def request_refund(db: Db, p: Principal, refund: Refund) -> IO[Reply[RefundError
|
|
|
158
157
|
# The store was busy: read again next round.
|
|
159
158
|
case _: pure(Continue(()))
|
|
160
159
|
def decide():
|
|
161
|
-
|
|
160
|
+
def open_case():
|
|
161
|
+
scope <- transaction(db)
|
|
162
|
+
reference <- tx_bind(tx_new_token(scope))
|
|
163
|
+
open_plan(key, refund, reference)
|
|
164
|
+
opened <- flat_map(open_case())
|
|
162
165
|
match opened:
|
|
163
166
|
case Committed(_):
|
|
164
167
|
ended <- flat_map(fold_until(range(rounds), (), round))
|
|
@@ -175,7 +178,7 @@ def request_refund(db: Db, p: Principal, refund: Refund) -> IO[Reply[RefundError
|
|
|
175
178
|
|
|
176
179
|
def refund_status(db: Db, p: Principal, input: StatusRequest) -> IO[Reply[RefundError, RefundView]]:
|
|
177
180
|
StatusRequest(order) = input
|
|
178
|
-
found <- flat_map(transaction(db,
|
|
181
|
+
found <- flat_map(transaction(db, _ -> read_plan(owner_key(p, order))))
|
|
179
182
|
pure(replied(found,
|
|
180
183
|
c -> (
|
|
181
184
|
match c:
|
|
@@ -27,7 +27,7 @@ from Yichus/Service import (
|
|
|
27
27
|
)
|
|
28
28
|
from Yichus/Transaction import (
|
|
29
29
|
Scope, TxTable, Txn, Outcome,
|
|
30
|
-
|
|
30
|
+
stored_table, tx_pure, tx_bind, tx_abort, tx_read, tx_write, tx_insert,
|
|
31
31
|
tx_database_time_millis, transaction
|
|
32
32
|
)
|
|
33
33
|
|
|
@@ -159,11 +159,9 @@ access_rules = AccessSpec([
|
|
|
159
159
|
AccessRule("withdrawal_receipts", OwnerScoped),
|
|
160
160
|
])
|
|
161
161
|
|
|
162
|
-
|
|
163
|
-
tx_table(scope, "accounts")
|
|
162
|
+
accounts_table: TxTable[s, Account] = stored_table("accounts")
|
|
164
163
|
|
|
165
|
-
|
|
166
|
-
tx_table(scope, "withdrawal_receipts")
|
|
164
|
+
receipts_table: TxTable[s, Receipt] = stored_table("withdrawal_receipts")
|
|
167
165
|
|
|
168
166
|
def account_rows(db: Db) -> Table[Account]:
|
|
169
167
|
table(db, "accounts")
|
|
@@ -178,12 +176,11 @@ def receipt_rows(db: Db) -> Table[Receipt]:
|
|
|
178
176
|
# ---------------------------------------------------------------------------
|
|
179
177
|
|
|
180
178
|
def move_balance[s](
|
|
181
|
-
scope: Scope[s],
|
|
182
179
|
principal: Principal,
|
|
183
180
|
delta: Int
|
|
184
181
|
) -> Txn[s, LedgerError, Movement]:
|
|
185
182
|
Principal(uid, _) = principal
|
|
186
|
-
row <- tx_bind(tx_read(accounts_table
|
|
183
|
+
row <- tx_bind(tx_read(accounts_table, uid))
|
|
187
184
|
match row:
|
|
188
185
|
case None: tx_abort(UnknownAccount(uid))
|
|
189
186
|
case Some(Account(id, owner, balance)):
|
|
@@ -191,7 +188,7 @@ def move_balance[s](
|
|
|
191
188
|
if int_lt(next, 0):
|
|
192
189
|
tx_abort(InsufficientFunds(balance, sub(0, delta)))
|
|
193
190
|
else:
|
|
194
|
-
_ <- tx_bind(tx_write(accounts_table
|
|
191
|
+
_ <- tx_bind(tx_write(accounts_table, uid, Account(id, owner, next)))
|
|
195
192
|
tx_pure(Movement(balance, next))
|
|
196
193
|
|
|
197
194
|
# ---------------------------------------------------------------------------
|
|
@@ -207,7 +204,7 @@ def withdraw_plan[s](
|
|
|
207
204
|
case Some(error): tx_abort(error)
|
|
208
205
|
case None:
|
|
209
206
|
key = owner_key(principal, command_local_key(input))
|
|
210
|
-
previous <- tx_bind(tx_read(receipts_table
|
|
207
|
+
previous <- tx_bind(tx_read(receipts_table, key))
|
|
211
208
|
match previous:
|
|
212
209
|
case Some(receipt): tx_pure(receipt)
|
|
213
210
|
case None:
|
|
@@ -215,17 +212,16 @@ def withdraw_plan[s](
|
|
|
215
212
|
if int_lt(now, 0): tx_abort(InvalidDatabaseTime(now))
|
|
216
213
|
else:
|
|
217
214
|
WithdrawInput(_, amount) = input
|
|
218
|
-
moved <- tx_bind(move_balance(
|
|
215
|
+
moved <- tx_bind(move_balance(principal, sub(0, amount)))
|
|
219
216
|
Movement(_, after) = moved
|
|
220
217
|
receipt = Receipt(input, after, now)
|
|
221
|
-
_ <- tx_bind(tx_insert(receipts_table
|
|
218
|
+
_ <- tx_bind(tx_insert(receipts_table, key, receipt))
|
|
222
219
|
# The receipt filed in the ledger and the receipt in the
|
|
223
220
|
# response are one value, so the customer's copy and the
|
|
224
221
|
# bank's copy cannot say different things.
|
|
225
222
|
tx_pure(receipt)
|
|
226
223
|
|
|
227
224
|
def deposit_plan[s](
|
|
228
|
-
scope: Scope[s],
|
|
229
225
|
principal: Principal,
|
|
230
226
|
input: DepositInput
|
|
231
227
|
) -> Txn[s, LedgerError, Movement]:
|
|
@@ -233,7 +229,7 @@ def deposit_plan[s](
|
|
|
233
229
|
case Some(error): tx_abort(error)
|
|
234
230
|
case None:
|
|
235
231
|
DepositInput(_, amount) = input
|
|
236
|
-
move_balance(
|
|
232
|
+
move_balance(principal, amount)
|
|
237
233
|
|
|
238
234
|
# ---------------------------------------------------------------------------
|
|
239
235
|
# Handlers. The engine injects Db and Principal; nothing here
|
|
@@ -253,8 +249,8 @@ def deposit(
|
|
|
253
249
|
principal: Principal,
|
|
254
250
|
input: DepositInput
|
|
255
251
|
) -> IO[Outcome[LedgerError, Movement]]:
|
|
256
|
-
|
|
257
|
-
deposit_plan(
|
|
252
|
+
_ <- transaction(db)
|
|
253
|
+
deposit_plan(principal, input)
|
|
258
254
|
|
|
259
255
|
def withdrawal_status(
|
|
260
256
|
db: Db,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yichus",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Local Bosatsu compilation, Yichus analysis, and generated apps using the WebMCP engine",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://yich.us",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"zod": "^4.3.6",
|
|
22
22
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
23
|
-
"@yichus/api-mcp": "0.1.
|
|
23
|
+
"@yichus/api-mcp": "0.1.4"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@types/node": "^22.0.0",
|