solid-objects 1.0.1 → 1.0.3
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/CHANGELOG.md +24 -0
- package/README.md +3 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/compare/dbos.md +391 -0
- package/docs/compare/graphile-worker.md +296 -0
- package/docs/compare/restate.md +290 -0
- package/docs/compare/rivetkit.md +282 -0
- package/docs/comparisons.md +17 -1
- package/docs/maintenance.md +70 -0
- package/docs/versioning.md +2 -2
- package/examples/compare/agent-runtime.ts +46 -0
- package/examples/compare/agent-session.ts +50 -0
- package/examples/compare/cart-runtime.ts +80 -0
- package/examples/compare/chat-room.ts +84 -0
- package/examples/compare/chat-runtime.ts +38 -0
- package/examples/compare/shopping-cart.ts +100 -0
- package/package.json +1 -1
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createRuntime, type SolidObjectsRuntime } from "solid-objects"
|
|
2
|
+
import { sqlite } from "solid-objects/database/sqlite"
|
|
3
|
+
import { ChatRoom } from "./chat-room.js"
|
|
4
|
+
|
|
5
|
+
const MEMBER_FIELDS: Record<string, string> = {
|
|
6
|
+
join: "memberId",
|
|
7
|
+
post: "authorId",
|
|
8
|
+
markRead: "memberId",
|
|
9
|
+
pin: "memberId",
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function chatRuntime({ path }: { path: string }): SolidObjectsRuntime {
|
|
13
|
+
const runtime = createRuntime({
|
|
14
|
+
database: sqlite({ path }),
|
|
15
|
+
authorizeMessage: ({
|
|
16
|
+
actorType,
|
|
17
|
+
operation,
|
|
18
|
+
arguments: argumentsValue,
|
|
19
|
+
authorizationContext,
|
|
20
|
+
}) => {
|
|
21
|
+
const field = MEMBER_FIELDS[operation]
|
|
22
|
+
if (actorType !== ChatRoom.actorType || !field) return false
|
|
23
|
+
const memberId = memberIdOf(authorizationContext)
|
|
24
|
+
if (memberId === null) return false
|
|
25
|
+
return argumentsValue[field] === memberId
|
|
26
|
+
},
|
|
27
|
+
authorizeQuery: ({ actorType, authorizationContext }) =>
|
|
28
|
+
actorType === ChatRoom.actorType && memberIdOf(authorizationContext) !== null,
|
|
29
|
+
})
|
|
30
|
+
runtime.register(ChatRoom)
|
|
31
|
+
return runtime
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function memberIdOf(authorizationContext: unknown): string | null {
|
|
35
|
+
if (typeof authorizationContext !== "object" || authorizationContext === null) return null
|
|
36
|
+
if (!("memberId" in authorizationContext)) return null
|
|
37
|
+
return typeof authorizationContext.memberId === "string" ? authorizationContext.memberId : null
|
|
38
|
+
}
|
|
@@ -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.
|
|
3
|
+
"version": "1.0.3",
|
|
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",
|