@dev.fast/whiteboard 0.2.5-preview.20261013.113 → 0.2.5-preview.20261015.115
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/THIRD_PARTY_NOTICES.md +29 -0
- package/dist/{account-alias-D0hF0sKk.js → account-alias-ChZR3RIU.js} +94 -8
- package/dist/{agent-cli-04xbDj23.js → agent-cli-CR2PnA9G.js} +9 -12
- package/dist/agent-cli-CRPPmS3l.js +2 -0
- package/dist/{agent-client-P3J4y8Zn.js → agent-client-DVd-prfp.js} +31 -14
- package/dist/{authoring-tools-fdA0RPIT.js → authoring-tools-BXKEwhfi.js} +5 -5
- package/dist/background-server-Cw7CcfaZ.js +156 -0
- package/dist/background-server-D2Rrr_e2.js +2 -0
- package/dist/build-info.json +1 -1
- package/dist/{cli-C0ziP6hg.js → cli-CSrz_wGk.js} +1 -1
- package/dist/{cli-runner-C3men22A.js → cli-runner-PlGqBeMw.js} +368 -252
- package/dist/cli.js +3 -2
- package/dist/{client-DzcSt6pV.js → client-DhZzPePs.js} +5 -3
- package/dist/{process-error-telemetry-BCBYp2t4.js → headless-host-CVXTSIlx.js} +3285 -2585
- package/dist/headless-host-Cp37C-v0.js +2 -0
- package/dist/input-error-OLgB_h31.js +11 -0
- package/dist/{local-data-Dy1pNtLF.js → local-data-yfLp5PiJ.js} +113 -62
- package/dist/{mcp-B8kuRSXM.js → mcp-DBbDwG4y.js} +4 -4
- package/dist/{package-paths-B9PDoAl6.js → package-paths-BsuI4WUf.js} +1 -1
- package/dist/profile-BSOkA6A-.js +2 -0
- package/dist/{profile-tbGWuWHu.js → profile-pAzg7fxC.js} +3 -3
- package/dist/remote-attach-BAt2eZx3.js +137 -0
- package/dist/remote-extensions-2Cxf3hMo.js +2 -0
- package/dist/remote-extensions-BiNx6NQK.js +323 -0
- package/dist/remote-language-server-DQgWUNeV.js +252 -0
- package/dist/remote-uninstall-DLtDG0o7.js +153 -0
- package/dist/{request-origin-DA4K1qWY.js → request-origin-DaRIV3cB.js} +5 -2
- package/dist/review-app-launcher-D7I4DGoH.js +222 -0
- package/dist/{review-home-paths-Y9mYWkCI.js → review-home-paths-zwevIq4I.js} +7 -7
- package/dist/{review-telemetry-CmM6tCbj.js → review-telemetry-BqB4CiTD.js} +5 -5
- package/dist/{s3-SzLpeCG6.js → s3-77-qkEhs.js} +1 -1
- package/dist/{s3-config-BgxkSoOy.js → s3-config-0xuCh-F_.js} +1 -1
- package/dist/{s3-config-CAxhO9u_.js → s3-config-DDWWMh5x.js} +5 -1
- package/dist/server/desktop-host.js +1663 -266
- package/dist/{desktop-discovery-Ba3dD5Cn.js → server-discovery-CifQ3yQS.js} +88 -3
- package/dist/sharing/index.d.ts +88 -32
- package/dist/sharing/index.js +1 -1
- package/dist/{src-DiHP11l3.js → src-BMPuxWv4.js} +6 -11
- package/dist/{src-WCbabvdS.js → src-Bgvf1sEN.js} +128 -8
- package/dist/{stored-document-migration-u4L_1VZa.js → stored-document-migration-CFOvjRwW.js} +5 -18
- package/dist/{tool-failure-CJHBCHMh.js → tool-failure-BXWzQG8T.js} +2 -2
- package/instructions/authoring.md +16 -12
- package/instructions/file-lenses.md +4 -2
- package/package.json +9 -14
- package/src/agent-connect.ts +507 -0
- package/src/agent-selection.ts +4 -0
- package/src/ask/agents.ts +59 -13
- package/src/ask/pi-mcp.ts +7 -3
- package/src/ask/thread-state.ts +9 -4
- package/src/ask/thread.ts +88 -50
- package/src/ask/threads.ts +53 -7
- package/src/cli-install.ts +3 -1
- package/src/cli-runner.ts +472 -81
- package/src/cli.ts +1 -1
- package/src/connect-agents.ts +285 -0
- package/src/connect-prompts.ts +60 -6
- package/src/desktop-discovery.ts +24 -0
- package/src/pinned-download.ts +118 -0
- package/src/remote-attach.ts +130 -0
- package/src/remote-extensions.ts +359 -0
- package/src/remote-language-server.ts +428 -0
- package/src/remote-toolchains.ts +107 -0
- package/src/remote-uninstall.ts +280 -0
- package/src/review-api/README.md +1 -3
- package/src/review-api/agent-cli.ts +4 -4
- package/src/review-api/agent-client.ts +23 -0
- package/src/review-api/ask-history.ts +31 -16
- package/src/review-api/authoring-tools.ts +1 -1
- package/src/review-api/blocks/index.ts +2 -4
- package/src/review-api/document.ts +1 -4
- package/src/review-api/http.ts +265 -237
- package/src/review-api/local-data.ts +18 -3
- package/src/review-api/onboarding.ts +478 -0
- package/src/review-api/origin.ts +7 -0
- package/src/review-api/recent-folders.ts +173 -0
- package/src/review-api/store.ts +56 -10
- package/src/review-api/workspaces.ts +56 -11
- package/src/review-app-launcher.ts +61 -3
- package/src/review-stack.ts +162 -36
- package/src/server/background-server.ts +250 -0
- package/src/server/desktop-host-shutdown.ts +24 -0
- package/src/server/desktop-host.ts +39 -24
- package/src/server/desktop-server.ts +68 -118
- package/src/server/global-verb-relay.ts +16 -0
- package/src/server/headless-host.ts +128 -3
- package/src/server/hono-http.ts +6 -1
- package/src/server/legacy-tutorial-cleanup.ts +38 -0
- package/src/server/review-gateway-hosts.ts +379 -0
- package/src/server/review-gateway-list.ts +109 -0
- package/src/server/review-gateway-memory.ts +130 -0
- package/src/server/review-gateway-pushes.ts +142 -0
- package/src/server/review-gateway-streams.ts +415 -0
- package/src/server/review-gateway-transport.ts +204 -0
- package/src/server/review-gateway.ts +433 -0
- package/src/server/review-server-core.ts +36 -6
- package/src/server-discovery.ts +11 -0
- package/src/sharing/import.ts +1 -0
- package/src/ui-telemetry-events.ts +5 -1
- package/src/vsix.ts +122 -0
- package/dist/agent-cli-BR_Jw5BL.js +0 -2
- package/dist/headless-host-BLLJ7Dyu.js +0 -95
- package/dist/profile-aMSw1oCM.js +0 -2
- package/dist/server-discovery-ZVi8Ay8J.js +0 -66
- package/src/fixtures/blocks/tutorial.json +0 -14
- package/src/review-api/blocks/tutorial.ts +0 -23
- package/src/server/tutorial-service.ts +0 -217
- package/tutorial/document.json +0 -216
- package/tutorial/lenses.json +0 -10
- package/tutorial/runtime-manifest.json +0 -11
- package/tutorial/sample-service/package.json +0 -9
- package/tutorial/sample-service/src/api/checkout-api.ts +0 -17
- package/tutorial/sample-service/src/app.ts +0 -26
- package/tutorial/sample-service/src/database/drizzle-orm.d.ts +0 -16
- package/tutorial/sample-service/src/database/schema.ts +0 -9
- package/tutorial/sample-service/src/fulfillment/fulfillment-queue.ts +0 -15
- package/tutorial/sample-service/src/fulfillment/fulfillment-worker.ts +0 -24
- package/tutorial/sample-service/src/inventory/forklift-mood.ts +0 -4
- package/tutorial/sample-service/src/inventory/inventory-service.ts +0 -28
- package/tutorial/sample-service/src/inventory/warehouse-lore.ts +0 -11
- package/tutorial/sample-service/src/orders/order-service.ts +0 -35
- package/tutorial/sample-service/src/orders/order.ts +0 -43
- package/tutorial/sample-service/src/orders/orders-repository.ts +0 -24
- package/tutorial/sample-service/src/payments/fraud-vibes.ts +0 -4
- package/tutorial/sample-service/src/payments/payment-gateway.ts +0 -29
- package/tutorial/sample-service/src/payments/raccoon-ledger.ts +0 -9
- package/tutorial/sample-service/src/shipping/shipping-gateway.ts +0 -15
- package/tutorial/sample-service/tsconfig.json +0 -12
package/tutorial/document.json
DELETED
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"title": "Whiteboard Desktop: three-minute tour",
|
|
3
|
-
"document": [
|
|
4
|
-
{
|
|
5
|
-
"type": "markdown",
|
|
6
|
-
"markdown": "# Whiteboard Desktop: three-minute tour\n"
|
|
7
|
-
},
|
|
8
|
-
{
|
|
9
|
-
"type": "section",
|
|
10
|
-
"title": "Welcome",
|
|
11
|
-
"children": [
|
|
12
|
-
{
|
|
13
|
-
"type": "markdown",
|
|
14
|
-
"markdown": "A whiteboard is a guided, interactive explanation of a code change. Code,\ndiffs, and diagrams live in one document.\n"
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
"type": "markdown",
|
|
18
|
-
"markdown": "### Your keybindings\n\nChoose your editor keys. Whiteboard highlights the active choice and uses its\nexisting extension and reload flow for Vim, Emacs, and Sublime Text.\n"
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
"type": "tutorial",
|
|
22
|
-
"kind": "keymap"
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
"type": "markdown",
|
|
26
|
-
"markdown": "### Read code like your editor\n\nThe editor below is live. Hover a symbol to see its type. **Go to\nDefinition** and **Find References** use the same keys as your editor.\n"
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
"type": "code_peek",
|
|
30
|
-
"source": "head/src/orders/order-service.ts#L13-L30",
|
|
31
|
-
"caption": "Order service creates the order"
|
|
32
|
-
}
|
|
33
|
-
]
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
"type": "section",
|
|
37
|
-
"title": "Diffs and lenses",
|
|
38
|
-
"children": [
|
|
39
|
-
{
|
|
40
|
-
"type": "markdown",
|
|
41
|
-
"markdown": "The **Diff** tab shows the whole change between the pinned base and head.\ndiffr folds unchanged structure so you read only what moved; select a fold to\nexpand it.\n"
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
"type": "tutorial",
|
|
45
|
-
"kind": "view",
|
|
46
|
-
"view": "diff",
|
|
47
|
-
"label": "Open the diff"
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
"type": "markdown",
|
|
51
|
-
"markdown": "**Lenses** split a change into the parts that matter. Select a lens to\nfilter the diff to just those lines. Mark lines viewed and the remaining\ncounts go down.\n\nAI summaries for folded code are opt-in under **Settings → Diff display and\nAI summaries**.\n"
|
|
52
|
-
}
|
|
53
|
-
]
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
"type": "section",
|
|
57
|
-
"title": "Interactive diagrams",
|
|
58
|
-
"children": [
|
|
59
|
-
{
|
|
60
|
-
"type": "markdown",
|
|
61
|
-
"markdown": "The sequence diagram connects the checkout request to fulfillment. Select\n**Tour** to walk it fullscreen; each message opens its supporting code.\n"
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
"type": "sequence",
|
|
65
|
-
"title": "Checkout to fulfillment",
|
|
66
|
-
"actors": {
|
|
67
|
-
"checkout": "Checkout API",
|
|
68
|
-
"orderService": "Order service",
|
|
69
|
-
"repository": "Orders repository",
|
|
70
|
-
"queue": "Fulfillment queue",
|
|
71
|
-
"worker": "Fulfillment worker",
|
|
72
|
-
"shipping": "Shipping gateway"
|
|
73
|
-
},
|
|
74
|
-
"steps": [
|
|
75
|
-
{
|
|
76
|
-
"type": "step",
|
|
77
|
-
"from": "checkout",
|
|
78
|
-
"to": "orderService",
|
|
79
|
-
"label": "Place order",
|
|
80
|
-
"style": "call",
|
|
81
|
-
"source": "head/src/api/checkout-api.ts#L12-L15"
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
"type": "step",
|
|
85
|
-
"from": "orderService",
|
|
86
|
-
"to": "repository",
|
|
87
|
-
"label": "Save order",
|
|
88
|
-
"style": "call",
|
|
89
|
-
"source": "head/src/orders/order-service.ts#L13-L30"
|
|
90
|
-
},
|
|
91
|
-
{
|
|
92
|
-
"type": "step",
|
|
93
|
-
"from": "repository",
|
|
94
|
-
"to": "queue",
|
|
95
|
-
"label": "Enqueue fulfillment",
|
|
96
|
-
"style": "call",
|
|
97
|
-
"source": "head/src/orders/orders-repository.ts#L9-L12"
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
"type": "step",
|
|
101
|
-
"from": "queue",
|
|
102
|
-
"to": "worker",
|
|
103
|
-
"label": "Deliver job",
|
|
104
|
-
"style": "call",
|
|
105
|
-
"source": "head/src/fulfillment/fulfillment-worker.ts#L13-L14"
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
"type": "step",
|
|
109
|
-
"from": "worker",
|
|
110
|
-
"to": "shipping",
|
|
111
|
-
"label": "Create shipment",
|
|
112
|
-
"style": "call",
|
|
113
|
-
"source": "head/src/fulfillment/fulfillment-worker.ts#L17-L19"
|
|
114
|
-
}
|
|
115
|
-
]
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
"type": "markdown",
|
|
119
|
-
"markdown": "The database view groups reads and writes by use case. Its operations open\ncode too.\n"
|
|
120
|
-
},
|
|
121
|
-
{
|
|
122
|
-
"type": "database_lens",
|
|
123
|
-
"title": "Order storage",
|
|
124
|
-
"actors": {
|
|
125
|
-
"orderService": {
|
|
126
|
-
"label": "Order service"
|
|
127
|
-
},
|
|
128
|
-
"worker": {
|
|
129
|
-
"label": "Fulfillment worker"
|
|
130
|
-
}
|
|
131
|
-
},
|
|
132
|
-
"stores": {
|
|
133
|
-
"orderDatabase": {
|
|
134
|
-
"label": "Order database",
|
|
135
|
-
"storage": "relational",
|
|
136
|
-
"collections": {
|
|
137
|
-
"orders": {
|
|
138
|
-
"label": "orders",
|
|
139
|
-
"fields": {
|
|
140
|
-
"id": {
|
|
141
|
-
"label": "id",
|
|
142
|
-
"dataType": "text",
|
|
143
|
-
"primaryKey": true,
|
|
144
|
-
"example": "order-customer-42"
|
|
145
|
-
},
|
|
146
|
-
"customerId": {
|
|
147
|
-
"label": "customerId",
|
|
148
|
-
"dataType": "text",
|
|
149
|
-
"example": "customer-42"
|
|
150
|
-
},
|
|
151
|
-
"totalCents": {
|
|
152
|
-
"label": "totalCents",
|
|
153
|
-
"dataType": "integer",
|
|
154
|
-
"example": 12500
|
|
155
|
-
},
|
|
156
|
-
"status": {
|
|
157
|
-
"label": "status",
|
|
158
|
-
"dataType": "text",
|
|
159
|
-
"example": "fulfilled"
|
|
160
|
-
},
|
|
161
|
-
"createdAt": {
|
|
162
|
-
"label": "createdAt",
|
|
163
|
-
"dataType": "timestamp",
|
|
164
|
-
"example": "2026-08-10T12:00:00Z"
|
|
165
|
-
}
|
|
166
|
-
},
|
|
167
|
-
"source": "head/src/database/schema.ts#L3-L9"
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
},
|
|
172
|
-
"useCases": [
|
|
173
|
-
{
|
|
174
|
-
"label": "Create an order",
|
|
175
|
-
"operations": [
|
|
176
|
-
{
|
|
177
|
-
"kind": "write",
|
|
178
|
-
"store": "orderDatabase",
|
|
179
|
-
"collection": "orders",
|
|
180
|
-
"field": "status",
|
|
181
|
-
"actor": "orderService",
|
|
182
|
-
"label": "write pending order",
|
|
183
|
-
"source": "head/src/orders/orders-repository.ts#L9-L12"
|
|
184
|
-
}
|
|
185
|
-
]
|
|
186
|
-
},
|
|
187
|
-
{
|
|
188
|
-
"label": "Fulfill an order",
|
|
189
|
-
"operations": [
|
|
190
|
-
{
|
|
191
|
-
"kind": "write",
|
|
192
|
-
"store": "orderDatabase",
|
|
193
|
-
"collection": "orders",
|
|
194
|
-
"field": "status",
|
|
195
|
-
"actor": "worker",
|
|
196
|
-
"label": "write fulfilled status",
|
|
197
|
-
"source": "head/src/fulfillment/fulfillment-worker.ts#L17-L19"
|
|
198
|
-
}
|
|
199
|
-
]
|
|
200
|
-
}
|
|
201
|
-
]
|
|
202
|
-
}
|
|
203
|
-
]
|
|
204
|
-
},
|
|
205
|
-
{
|
|
206
|
-
"type": "section",
|
|
207
|
-
"title": "You're all done",
|
|
208
|
-
"children": [
|
|
209
|
-
{
|
|
210
|
-
"type": "markdown",
|
|
211
|
-
"markdown": "To connect Whiteboard to your agents, open **Preferences → Settings**. To\nrevisit setup or take this tour again, open **Preferences → Getting Started**.\n"
|
|
212
|
-
}
|
|
213
|
-
]
|
|
214
|
-
}
|
|
215
|
-
]
|
|
216
|
-
}
|
package/tutorial/lenses.json
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { OrderService } from "../orders/order-service.js";
|
|
2
|
-
import type { CheckoutRequest, Order } from "../orders/order.js";
|
|
3
|
-
|
|
4
|
-
export interface CheckoutResponse {
|
|
5
|
-
status: 201;
|
|
6
|
-
order: Order;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export class CheckoutApi {
|
|
10
|
-
constructor(private readonly orderService: OrderService) {}
|
|
11
|
-
|
|
12
|
-
checkout(request: CheckoutRequest): CheckoutResponse {
|
|
13
|
-
const order = this.orderService.placeOrder(request);
|
|
14
|
-
|
|
15
|
-
return { status: 201, order };
|
|
16
|
-
}
|
|
17
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { CheckoutApi } from "./api/checkout-api.js";
|
|
2
|
-
import { FulfillmentQueue } from "./fulfillment/fulfillment-queue.js";
|
|
3
|
-
import { FulfillmentWorker } from "./fulfillment/fulfillment-worker.js";
|
|
4
|
-
import { InventoryService } from "./inventory/inventory-service.js";
|
|
5
|
-
import { OrderService } from "./orders/order-service.js";
|
|
6
|
-
import { OrdersRepository } from "./orders/orders-repository.js";
|
|
7
|
-
import { PaymentGateway } from "./payments/payment-gateway.js";
|
|
8
|
-
import { ShippingGateway } from "./shipping/shipping-gateway.js";
|
|
9
|
-
|
|
10
|
-
const queue = new FulfillmentQueue();
|
|
11
|
-
|
|
12
|
-
const orders = new OrdersRepository(queue);
|
|
13
|
-
|
|
14
|
-
const orderService = new OrderService(
|
|
15
|
-
new InventoryService(),
|
|
16
|
-
new PaymentGateway(),
|
|
17
|
-
orders,
|
|
18
|
-
);
|
|
19
|
-
|
|
20
|
-
export const checkoutApi = new CheckoutApi(orderService);
|
|
21
|
-
|
|
22
|
-
export const fulfillmentWorker = new FulfillmentWorker(
|
|
23
|
-
queue,
|
|
24
|
-
orders,
|
|
25
|
-
new ShippingGateway(),
|
|
26
|
-
);
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
// The sample has no dependencies; this declares the slice of drizzle-orm it uses.
|
|
2
|
-
declare module "drizzle-orm/pg-core" {
|
|
3
|
-
interface Column {
|
|
4
|
-
primaryKey(): Column;
|
|
5
|
-
notNull(): Column;
|
|
6
|
-
defaultNow(): Column;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function text(name: string): Column;
|
|
10
|
-
export function integer(name: string): Column;
|
|
11
|
-
export function timestamp(name: string): Column;
|
|
12
|
-
export function pgTable(
|
|
13
|
-
name: string,
|
|
14
|
-
columns: Record<string, Column>,
|
|
15
|
-
): Record<string, Column>;
|
|
16
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { integer, pgTable, text, timestamp } from "drizzle-orm/pg-core";
|
|
2
|
-
|
|
3
|
-
export const orders = pgTable("orders", {
|
|
4
|
-
id: text("id").primaryKey(),
|
|
5
|
-
customerId: text("customer_id").notNull(),
|
|
6
|
-
totalCents: integer("total_cents").notNull(),
|
|
7
|
-
status: text("status").notNull(),
|
|
8
|
-
createdAt: timestamp("created_at").notNull().defaultNow(),
|
|
9
|
-
});
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export interface FulfillmentJob {
|
|
2
|
-
orderId: string;
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
export class FulfillmentQueue {
|
|
6
|
-
private readonly jobs: FulfillmentJob[] = [];
|
|
7
|
-
|
|
8
|
-
enqueue(job: FulfillmentJob): void {
|
|
9
|
-
this.jobs.push(job);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
dequeue(): FulfillmentJob | undefined {
|
|
13
|
-
return this.jobs.shift();
|
|
14
|
-
}
|
|
15
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { OrdersRepository } from "../orders/orders-repository.js";
|
|
2
|
-
import type { ShippingGateway } from "../shipping/shipping-gateway.js";
|
|
3
|
-
import type { FulfillmentQueue } from "./fulfillment-queue.js";
|
|
4
|
-
|
|
5
|
-
export class FulfillmentWorker {
|
|
6
|
-
constructor(
|
|
7
|
-
private readonly queue: FulfillmentQueue,
|
|
8
|
-
private readonly orders: OrdersRepository,
|
|
9
|
-
private readonly shipping: ShippingGateway,
|
|
10
|
-
) {}
|
|
11
|
-
|
|
12
|
-
runNext(): string | null {
|
|
13
|
-
const job = this.queue.dequeue();
|
|
14
|
-
|
|
15
|
-
if (!job) return null;
|
|
16
|
-
const order = this.orders.find(job.orderId);
|
|
17
|
-
|
|
18
|
-
if (!order) throw new Error(`Order not found: ${job.orderId}`);
|
|
19
|
-
const shipment = this.shipping.createShipment(order);
|
|
20
|
-
this.orders.setStatus(order.id, "fulfilled");
|
|
21
|
-
|
|
22
|
-
return shipment.trackingNumber;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import type { CheckoutItem, Sku } from "../orders/order.js";
|
|
2
|
-
import { FORKLIFT_MOOD } from "./forklift-mood.js";
|
|
3
|
-
import { blameTheGremlins, countShelvesTwice } from "./warehouse-lore.js";
|
|
4
|
-
|
|
5
|
-
export class InventoryService {
|
|
6
|
-
private readonly held = new Map<string, number>();
|
|
7
|
-
|
|
8
|
-
reserve(items: readonly CheckoutItem[]): void {
|
|
9
|
-
const unavailable = items.find((item) => item.quantity < 1);
|
|
10
|
-
|
|
11
|
-
if (unavailable) {
|
|
12
|
-
throw new Error(`Invalid quantity for ${unavailable.sku}`);
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
release(items: readonly CheckoutItem[]): void {
|
|
17
|
-
for (const item of items) {
|
|
18
|
-
this.held.delete(item.sku);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/** How sure the warehouse is about a shelf, given the forklift's mood. */
|
|
23
|
-
audit(sku: Sku): string {
|
|
24
|
-
if (countShelvesTwice(sku) === 0) return blameTheGremlins(sku);
|
|
25
|
-
|
|
26
|
-
return `${sku} was counted while the forklift felt ${FORKLIFT_MOOD}`;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { Sku } from "../orders/order.js";
|
|
2
|
-
|
|
3
|
-
/** Every shelf is counted twice, because the first count is always wrong. */
|
|
4
|
-
export function countShelvesTwice(sku: Sku): number {
|
|
5
|
-
return sku.length * 2;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
/** When stock goes missing, it was them. */
|
|
9
|
-
export function blameTheGremlins(sku: Sku): string {
|
|
10
|
-
return `Gremlins took the last ${sku}`;
|
|
11
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import type { InventoryService } from "../inventory/inventory-service.js";
|
|
2
|
-
import type { PaymentGateway } from "../payments/payment-gateway.js";
|
|
3
|
-
import type { Cents, CheckoutRequest, Order } from "./order.js";
|
|
4
|
-
import type { OrdersRepository } from "./orders-repository.js";
|
|
5
|
-
|
|
6
|
-
export class OrderService {
|
|
7
|
-
constructor(
|
|
8
|
-
private readonly inventory: InventoryService,
|
|
9
|
-
private readonly payments: PaymentGateway,
|
|
10
|
-
private readonly orders: OrdersRepository,
|
|
11
|
-
) {}
|
|
12
|
-
|
|
13
|
-
/** Reserves the stock, charges the customer, and saves a pending order. */
|
|
14
|
-
placeOrder(request: CheckoutRequest): Order {
|
|
15
|
-
const totalCents: Cents = request.items.reduce(
|
|
16
|
-
(sum, item) => sum + item.quantity * item.unitPriceCents,
|
|
17
|
-
0,
|
|
18
|
-
);
|
|
19
|
-
|
|
20
|
-
this.inventory.reserve(request.items);
|
|
21
|
-
this.payments.charge(request.paymentToken, totalCents);
|
|
22
|
-
|
|
23
|
-
const order: Order = {
|
|
24
|
-
id: `order-${request.customerId}`,
|
|
25
|
-
customerId: request.customerId,
|
|
26
|
-
items: request.items,
|
|
27
|
-
totalCents,
|
|
28
|
-
status: "pending",
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
this.orders.insert(order);
|
|
32
|
-
|
|
33
|
-
return order;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
/** Where an order is in its short, eventful life. */
|
|
2
|
-
export type OrderStatus =
|
|
3
|
-
/** Charged, but still in the building. */
|
|
4
|
-
| "pending"
|
|
5
|
-
/** Waiting its turn on the fulfillment queue. */
|
|
6
|
-
| "queued"
|
|
7
|
-
/** Out the door. Regret is not a refund reason. */
|
|
8
|
-
| "fulfilled";
|
|
9
|
-
|
|
10
|
-
/** A stock-keeping unit, like `SKU-RUBBER-DUCK`. */
|
|
11
|
-
export type Sku = `SKU-${string}`;
|
|
12
|
-
|
|
13
|
-
/** Money in whole cents, because floats cannot be trusted with money. */
|
|
14
|
-
export type Cents = number;
|
|
15
|
-
|
|
16
|
-
/** One line of a cart. */
|
|
17
|
-
export interface CheckoutItem {
|
|
18
|
-
/** What the customer wants. */
|
|
19
|
-
sku: Sku;
|
|
20
|
-
/** How many they want. Zero is a cry for help. */
|
|
21
|
-
quantity: number;
|
|
22
|
-
/** The price of one unit, before the payment gateway takes its cut. */
|
|
23
|
-
unitPriceCents: Cents;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/** An order the service has accepted and charged for. */
|
|
27
|
-
export interface Order {
|
|
28
|
-
/** Derived from the customer, so each customer gets one order. Allegedly. */
|
|
29
|
-
id: `order-${string}`;
|
|
30
|
-
customerId: string;
|
|
31
|
-
items: readonly CheckoutItem[];
|
|
32
|
-
/** Every line's quantity times its unit price, in cents. */
|
|
33
|
-
totalCents: Cents;
|
|
34
|
-
status: OrderStatus;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/** What the checkout API hands the order service. */
|
|
38
|
-
export interface CheckoutRequest {
|
|
39
|
-
customerId: string;
|
|
40
|
-
items: readonly CheckoutItem[];
|
|
41
|
-
/** An opaque token from the payment provider. Never log it. */
|
|
42
|
-
paymentToken: string;
|
|
43
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { FulfillmentQueue } from "../fulfillment/fulfillment-queue.js";
|
|
2
|
-
import type { Order, OrderStatus } from "./order.js";
|
|
3
|
-
|
|
4
|
-
export class OrdersRepository {
|
|
5
|
-
private readonly rows = new Map<string, Order>();
|
|
6
|
-
|
|
7
|
-
constructor(private readonly queue: FulfillmentQueue) {}
|
|
8
|
-
|
|
9
|
-
insert(order: Order): void {
|
|
10
|
-
this.rows.set(order.id, order);
|
|
11
|
-
this.queue.enqueue({ orderId: order.id });
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
find(orderId: string): Order | undefined {
|
|
15
|
-
return this.rows.get(orderId);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
setStatus(orderId: string, status: OrderStatus): void {
|
|
19
|
-
const order = this.find(orderId);
|
|
20
|
-
|
|
21
|
-
if (!order) throw new Error(`Order not found: ${orderId}`);
|
|
22
|
-
this.rows.set(orderId, { ...order, status });
|
|
23
|
-
}
|
|
24
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import type { Cents } from "../orders/order.js";
|
|
2
|
-
import { vibeCheck } from "./fraud-vibes.js";
|
|
3
|
-
import { RACCOON_ON_DUTY, nervouslyRound } from "./raccoon-ledger.js";
|
|
4
|
-
|
|
5
|
-
export interface PaymentReceipt {
|
|
6
|
-
chargeId: string;
|
|
7
|
-
amountCents: Cents;
|
|
8
|
-
/** Who signed off on the charge. */
|
|
9
|
-
approvedBy: typeof RACCOON_ON_DUTY;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export class PaymentGateway {
|
|
13
|
-
charge(paymentToken: string, amountCents: number): PaymentReceipt {
|
|
14
|
-
if (!paymentToken) throw new Error("A payment token is required");
|
|
15
|
-
|
|
16
|
-
return {
|
|
17
|
-
chargeId: `charge-${amountCents}`,
|
|
18
|
-
amountCents,
|
|
19
|
-
approvedBy: RACCOON_ON_DUTY,
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/** Refunds a receipt, provided the vibes allow it. */
|
|
24
|
-
refund(receipt: PaymentReceipt, paymentToken: string): Cents {
|
|
25
|
-
if (vibeCheck(paymentToken) > 0.5) return 0;
|
|
26
|
-
|
|
27
|
-
return nervouslyRound(receipt.amountCents);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { Cents } from "../orders/order.js";
|
|
2
|
-
|
|
3
|
-
/** The ledger is kept by a raccoon. It has never been audited. */
|
|
4
|
-
export const RACCOON_ON_DUTY = "Gerald";
|
|
5
|
-
|
|
6
|
-
/** Rounds to the nearest cent, then rounds again out of anxiety. */
|
|
7
|
-
export function nervouslyRound(cents: Cents): Cents {
|
|
8
|
-
return Math.round(Math.round(cents));
|
|
9
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { Order } from "../orders/order.js";
|
|
2
|
-
|
|
3
|
-
export interface Shipment {
|
|
4
|
-
trackingNumber: string;
|
|
5
|
-
orderId: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export class ShippingGateway {
|
|
9
|
-
createShipment(order: Order): Shipment {
|
|
10
|
-
return {
|
|
11
|
-
trackingNumber: `track-${order.id}`,
|
|
12
|
-
orderId: order.id,
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
}
|