solid-objects 1.0.0 → 1.0.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.
@@ -0,0 +1,100 @@
1
+ import { Actor, type EffectFailurePayload, type EffectSuccessPayload } from "solid-objects"
2
+
3
+ export const IDLE_MILLISECONDS = 30 * 60 * 1000
4
+
5
+ type ChargeArguments = { cartId: string; amountCents: number }
6
+ type ChargeResult = { status: "paid"; paymentId: string } | { status: "declined" }
7
+ type CheckArguments = { paymentKey: string }
8
+ type CheckResult = { status: "paid"; paymentId: string } | { status: "not_found" }
9
+
10
+ export class ShoppingCart extends Actor {
11
+ static override readonly actorType = "ShoppingCart"
12
+
13
+ items: Record<string, number> = {}
14
+ couponCode: string | null = null
15
+ status: "open" | "paying" | "checking" | "paid" | "released" = "open"
16
+ paymentId: string | null = null
17
+ activity = 0
18
+
19
+ addItem({ productId, quantity }: { productId: string; quantity: number }): {
20
+ items: Record<string, number>
21
+ } {
22
+ this.#requireOpen()
23
+ this.items = { ...this.items, [productId]: (this.items[productId] ?? 0) + quantity }
24
+ this.#armIdleRelease()
25
+ return { items: this.items }
26
+ }
27
+
28
+ applyCoupon({ code }: { code: string }): void {
29
+ this.#requireOpen()
30
+ this.couponCode = code
31
+ this.#armIdleRelease()
32
+ }
33
+
34
+ checkOut({ amountCents }: { amountCents: number }): { status: string } {
35
+ if (this.status === "paying" || this.status === "checking" || this.status === "paid") {
36
+ return { status: this.status }
37
+ }
38
+ this.#requireOpen()
39
+ this.status = "paying"
40
+ this.emit("charge_payment", {
41
+ arguments: { cartId: this.actorId, amountCents },
42
+ onSuccess: "paymentSettled",
43
+ onFailure: "paymentUnknown",
44
+ })
45
+ return { status: this.status }
46
+ }
47
+
48
+ paymentSettled({ result }: EffectSuccessPayload<ChargeArguments, ChargeResult>): void {
49
+ if (result.status === "paid") {
50
+ this.#markPaid(result.paymentId)
51
+ return
52
+ }
53
+ this.#reopen()
54
+ }
55
+
56
+ paymentUnknown({ effectId }: EffectFailurePayload<ChargeArguments>): void {
57
+ this.status = "checking"
58
+ this.emit("check_payment", {
59
+ arguments: { paymentKey: effectId },
60
+ onSuccess: "paymentChecked",
61
+ })
62
+ }
63
+
64
+ paymentChecked({ result }: EffectSuccessPayload<CheckArguments, CheckResult>): void {
65
+ if (result.status === "paid") {
66
+ this.#markPaid(result.paymentId)
67
+ return
68
+ }
69
+ this.#reopen()
70
+ }
71
+
72
+ releaseIfIdle({ activity }: { activity: number }): void {
73
+ if (activity !== this.activity || this.status !== "open") return
74
+ this.status = "released"
75
+ this.emit("release_stock", { arguments: { cartId: this.actorId, items: this.items } })
76
+ }
77
+
78
+ #markPaid(paymentId: string): void {
79
+ this.status = "paid"
80
+ this.paymentId = paymentId
81
+ }
82
+
83
+ #reopen(): void {
84
+ this.status = "open"
85
+ this.#armIdleRelease()
86
+ }
87
+
88
+ #requireOpen(): void {
89
+ if (this.status !== "open") {
90
+ this.reject("cart_closed", { message: `The cart is ${this.status}` })
91
+ }
92
+ }
93
+
94
+ #armIdleRelease(): void {
95
+ this.activity += 1
96
+ this.schedule({ at: new Date(Date.now() + IDLE_MILLISECONDS) }).releaseIfIdle({
97
+ activity: this.activity,
98
+ })
99
+ }
100
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "solid-objects",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Fix race conditions across Node.js processes: ordered operations, expiring holds, and state that survives a restart, in your SQL database. A SQL-backed virtual actor library for TypeScript and Node.js.",
5
5
  "type": "module",
6
6
  "license": "MIT",