@reventlessdev/online-shop-hybrid-seed 1.0.0-alpha.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.
Files changed (55) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/LICENSE +202 -0
  3. package/lib/bs/.compiler.log +2 -0
  4. package/lib/bs/OnlineShopHybridSeed.cmi +0 -0
  5. package/lib/bs/OnlineShopHybridSeed.cmj +0 -0
  6. package/lib/bs/OnlineShopHybridSeed.cmt +0 -0
  7. package/lib/bs/OnlineShopHybridSeed.mlmap +4 -0
  8. package/lib/bs/build.ninja +0 -0
  9. package/lib/bs/compiler-info.json +8 -0
  10. package/lib/bs/src/DemoCommands-OnlineShopHybridSeed.cmi +0 -0
  11. package/lib/bs/src/DemoCommands-OnlineShopHybridSeed.cmj +0 -0
  12. package/lib/bs/src/DemoCommands-OnlineShopHybridSeed.cmt +0 -0
  13. package/lib/bs/src/DemoCommands.ast +0 -0
  14. package/lib/bs/src/DemoCommands.res +154 -0
  15. package/lib/bs/src/DemoCommands.res.mjs +388 -0
  16. package/lib/bs/src/DemoData-OnlineShopHybridSeed.cmi +0 -0
  17. package/lib/bs/src/DemoData-OnlineShopHybridSeed.cmj +0 -0
  18. package/lib/bs/src/DemoData-OnlineShopHybridSeed.cmt +0 -0
  19. package/lib/bs/src/DemoData.ast +0 -0
  20. package/lib/bs/src/DemoData.res +307 -0
  21. package/lib/bs/src/DemoData.res.mjs +481 -0
  22. package/lib/bs/src/HybridSeedData-OnlineShopHybridSeed.cmi +0 -0
  23. package/lib/bs/src/HybridSeedData-OnlineShopHybridSeed.cmj +0 -0
  24. package/lib/bs/src/HybridSeedData-OnlineShopHybridSeed.cmt +0 -0
  25. package/lib/bs/src/HybridSeedData.ast +0 -0
  26. package/lib/bs/src/HybridSeedData.res +418 -0
  27. package/lib/bs/src/HybridSeedData.res.mjs +317 -0
  28. package/lib/ocaml/.compiler.log +2 -0
  29. package/lib/ocaml/DemoCommands-OnlineShopHybridSeed.cmi +0 -0
  30. package/lib/ocaml/DemoCommands-OnlineShopHybridSeed.cmj +0 -0
  31. package/lib/ocaml/DemoCommands-OnlineShopHybridSeed.cmt +0 -0
  32. package/lib/ocaml/DemoCommands.ast +0 -0
  33. package/lib/ocaml/DemoCommands.res +154 -0
  34. package/lib/ocaml/DemoData-OnlineShopHybridSeed.cmi +0 -0
  35. package/lib/ocaml/DemoData-OnlineShopHybridSeed.cmj +0 -0
  36. package/lib/ocaml/DemoData-OnlineShopHybridSeed.cmt +0 -0
  37. package/lib/ocaml/DemoData.ast +0 -0
  38. package/lib/ocaml/DemoData.res +307 -0
  39. package/lib/ocaml/HybridSeedData-OnlineShopHybridSeed.cmi +0 -0
  40. package/lib/ocaml/HybridSeedData-OnlineShopHybridSeed.cmj +0 -0
  41. package/lib/ocaml/HybridSeedData-OnlineShopHybridSeed.cmt +0 -0
  42. package/lib/ocaml/HybridSeedData.ast +0 -0
  43. package/lib/ocaml/HybridSeedData.res +418 -0
  44. package/lib/ocaml/OnlineShopHybridSeed.cmi +0 -0
  45. package/lib/ocaml/OnlineShopHybridSeed.cmj +0 -0
  46. package/lib/ocaml/OnlineShopHybridSeed.cmt +0 -0
  47. package/lib/ocaml/OnlineShopHybridSeed.mlmap +4 -0
  48. package/package.json +34 -0
  49. package/rescript.json +27 -0
  50. package/src/DemoCommands.res +154 -0
  51. package/src/DemoCommands.res.mjs +388 -0
  52. package/src/DemoData.res +307 -0
  53. package/src/DemoData.res.mjs +481 -0
  54. package/src/HybridSeedData.res +418 -0
  55. package/src/HybridSeedData.res.mjs +317 -0
@@ -0,0 +1,418 @@
1
+ // The online-shop-hybrid demo data set(s), exported as `Seed.dataSet` values.
2
+ //
3
+ // This is the shareable artifact: `dataSets` carries everything domain-specific
4
+ // — the phases, the view verification, and the summary board — behind the
5
+ // generic `Seed.dataSet` shape, so any platform script (this example's
6
+ // `platform-local`/`platform-aws`, or a downstream consumer) seeds it with just
7
+ // `Seed.Runner.seed(~sets=HybridSeedData.dataSets, ~connect=…)`.
8
+ //
9
+ // Because every phase drives the public GraphQL command API, a run doubles as a
10
+ // smoke test: it exercises the DCB append path, both extension points, and the
11
+ // AutoShipOrder automation. `DemoData.res` holds *what* to seed; `DemoCommands.res`
12
+ // maps command values onto mutations; the transport and reporting come from
13
+ // `ReventlessSeed`.
14
+
15
+ open ReventlessSeed
16
+
17
+ // Every queryable view this example exposes. `Unfillable` is not a way to
18
+ // silence a gap — it records a view no volume of seed data can reach, with the
19
+ // reason, so a zero is never read as a seeding miss.
20
+ let views = [
21
+ Seed.Runner.Seeded("Catalog_Categories"),
22
+ Seed.Runner.Seeded("Catalog_Products"),
23
+ Seed.Runner.Seeded("Catalog_ProductDemands"),
24
+ Seed.Runner.Seeded("Catalog_ImportProductAudits"),
25
+ Seed.Runner.Seeded("Ordering_AvailableProducts"),
26
+ Seed.Runner.Seeded("Ordering_Orders"),
27
+ Seed.Runner.Seeded("Ordering_Customers"),
28
+ Seed.Runner.Seeded("Ordering_AutoShipOrderTodos"),
29
+ Seed.Runner.Seeded("Ordering_SendOrderConfirmationTodos"),
30
+ ]
31
+
32
+ // ── Phases ──────────────────────────────────────────────────────────────────
33
+
34
+ let seedCategories = async (~client: Seed.Client.t) => {
35
+ await client->Seed.Client.sendAll(
36
+ DemoData.categories->Array.map(c =>
37
+ DemoCommands.addCategory(AddCategory({categoryId: c.id, name: c.name}))
38
+ ),
39
+ )
40
+ Seed.Runner.report(`categories: ${(DemoData.categories->Array.length)->Int.toString} added`)
41
+ }
42
+
43
+ // Upload each product's deterministic placeholder SVG through the deployment's
44
+ // upload endpoint and swap `imageUrl` for the returned served `/{prefix}/{key}`
45
+ // ref, so the demo image travels the real upload → store → serve loop (local
46
+ // dev store or AWS bucket) exactly like a user upload — no external image URL.
47
+ let uploadProductImages = async (
48
+ products: array<DemoData.product>,
49
+ ~client: Seed.Client.t,
50
+ ~uploadEndpoint: string,
51
+ ): array<DemoData.product> => {
52
+ let out = []
53
+ for i in 0 to products->Array.length - 1 {
54
+ switch products->Array.get(i) {
55
+ | Some(p) =>
56
+ let svg = DemoData.productSvg(~name=p.name, ~index=i)
57
+ switch await Seed.Upload.uploadAsset(
58
+ ~uploadEndpoint,
59
+ ~bytes=svg,
60
+ ~fileName=`${p.id}.svg`,
61
+ ~contentType="image/svg+xml",
62
+ ~authToken=?Seed.Client.currentToken(client),
63
+ ) {
64
+ | Ok(servedRef) => out->Array.push({...p, imageUrl: servedRef})
65
+ | Error(msg) => throw(Seed.Failed(`product image upload for ${p.id} failed: ${msg}`))
66
+ }
67
+ | None => ()
68
+ }
69
+ }
70
+ Seed.Runner.report(
71
+ `product images: ${(out->Array.length)->Int.toString} uploaded to the served bucket`,
72
+ )
73
+ out
74
+ }
75
+
76
+ let seedProducts = async (products: array<DemoData.product>, ~client: Seed.Client.t) => {
77
+ await client->Seed.Client.sendAll(
78
+ products->Array.map(p =>
79
+ DemoCommands.addProduct(
80
+ AddProduct({
81
+ productId: p.id,
82
+ name: p.name,
83
+ description: p.description,
84
+ price: p.price,
85
+ imageUrl: p.imageUrl,
86
+ categoryId: p.categoryId,
87
+ }),
88
+ )
89
+ ),
90
+ )
91
+ Seed.Runner.report(`products: ${(products->Array.length)->Int.toString} added`)
92
+ }
93
+
94
+ let seedCatalogEdits = async (products: array<DemoData.product>, ~client: Seed.Client.t) => {
95
+ let repriced = DemoData.repricedProducts(products)
96
+ await client->Seed.Client.sendAll(
97
+ repriced->Array.map(p =>
98
+ DemoCommands.changeProductPrice(
99
+ ChangeProductPrice({productId: p.id, price: DemoData.discountedPrice(p)}),
100
+ )
101
+ ),
102
+ )
103
+ let redescribed = DemoData.redescribedProducts(products)
104
+ await client->Seed.Client.sendAll(
105
+ redescribed->Array.map(p =>
106
+ DemoCommands.changeProductDescription(
107
+ ChangeProductDescription({
108
+ productId: p.id,
109
+ description: `${p.description} Updated listing copy.`,
110
+ }),
111
+ )
112
+ ),
113
+ )
114
+ await client->Seed.Client.sendAll([
115
+ DemoCommands.renameCategory(
116
+ RenameCategory({
117
+ categoryId: DemoData.renamedCategoryId,
118
+ name: DemoData.renamedCategoryName,
119
+ }),
120
+ ),
121
+ ])
122
+ let archived = DemoData.categories->Array.filter(c => c.archive)
123
+ await client->Seed.Client.sendAll(
124
+ archived->Array.map(c => DemoCommands.archiveCategory(ArchiveCategory({categoryId: c.id}))),
125
+ )
126
+ Seed.Runner.report(
127
+ `catalog edits: ${(repriced->Array.length)->Int.toString} repriced, ${(redescribed
128
+ ->Array.length)
129
+ ->Int.toString} redescribed, 1 renamed, ${(archived->Array.length)
130
+ ->Int.toString} archived`,
131
+ )
132
+ }
133
+
134
+ let seedSupplierFeed = async (~client: Seed.Client.t) => {
135
+ for i in 0 to DemoData.supplierFeed->Array.length - 1 {
136
+ switch DemoData.supplierFeed->Array.get(i) {
137
+ | Some(row) =>
138
+ // One feed row is deliberately invalid, so a translation rejection is an
139
+ // expected outcome here rather than a seeding failure.
140
+ (
141
+ await client->Seed.Client.send(
142
+ DemoCommands.importProduct(row),
143
+ ~tolerate=["TranslationFailed"],
144
+ )
145
+ )->ignore
146
+ | None => ()
147
+ }
148
+ }
149
+ // Cross-check the per-request outcomes against the slice's audit view.
150
+ let audits = await client->Seed.Client.queryAllNodes(
151
+ ~field="Catalog_ImportProductAudits",
152
+ ~selection="status",
153
+ )
154
+ let countWith = status =>
155
+ audits->Array.filter(a => a->Seed.Client.nodeString("status") == Some(status))->Array.length
156
+ let successes = countWith("Success")
157
+ let failures = countWith("Failure")
158
+ if (
159
+ successes != DemoData.expectedImportSuccesses || failures != DemoData.expectedImportFailures
160
+ ) {
161
+ throw(
162
+ Seed.Failed(
163
+ `supplier feed: expected ${DemoData.expectedImportSuccesses->Int.toString} Success / ${DemoData.expectedImportFailures->Int.toString} Failure audit rows, got ${successes->Int.toString}/${failures->Int.toString}`,
164
+ ),
165
+ )
166
+ }
167
+ Seed.Runner.report(
168
+ `supplier feed: ${successes->Int.toString} imported, ${failures->Int.toString} rejected (both shown in the audit view)`,
169
+ )
170
+ }
171
+
172
+ let seedCustomers = async (customers: array<DemoData.customer>, ~client: Seed.Client.t) => {
173
+ await client->Seed.Client.sendAll(
174
+ customers->Array.map(c =>
175
+ DemoCommands.customer(~id=c.id, Register({email: c.email, address: c.address}))
176
+ ),
177
+ )
178
+ // Give every customer a map coordinate so the Customers map view renders a
179
+ // pin per row. The geo-point payload is a `{lat, lng}` input object.
180
+ await client->Seed.Client.sendAll(
181
+ customers->Array.map(c =>
182
+ DemoCommands.customer(~id=c.id, SetLocation({location: {lat: c.lat, lng: c.lng}}))
183
+ ),
184
+ )
185
+ let moved = DemoData.movedCustomers(customers)
186
+ await client->Seed.Client.sendAll(
187
+ moved->Array.map(c =>
188
+ DemoCommands.customer(~id=c.id, UpdateAddress({address: DemoData.newAddress()}))
189
+ ),
190
+ )
191
+ Seed.Runner.report(
192
+ `customers: ${(customers->Array.length)->Int.toString} registered, ${(moved->Array.length)
193
+ ->Int.toString} moved`,
194
+ )
195
+ }
196
+
197
+ let seedOrders = async (orders: array<DemoData.order>, ~client: Seed.Client.t) => {
198
+ for i in 0 to orders->Array.length - 1 {
199
+ switch orders->Array.get(i) {
200
+ | Some(order) =>
201
+ (await client->Seed.Client.send(
202
+ DemoCommands.placeOrder(
203
+ PlaceOrder({
204
+ orderId: order.id,
205
+ customerId: order.customerId,
206
+ productIds: order.productIds,
207
+ shippingMethod: order.shippingMethod,
208
+ }),
209
+ ),
210
+ ))->ignore
211
+ if mod(i + 1, 50) == 0 {
212
+ Seed.Runner.report(
213
+ `orders: ${(i + 1)->Int.toString}/${(orders->Array.length)->Int.toString} placed`,
214
+ )
215
+ }
216
+ | None => ()
217
+ }
218
+ }
219
+ let countMethod = (method: OrderingPlugin.PlaceOrder.shippingMethod) =>
220
+ orders->Array.filter(o => o.shippingMethod == method)->Array.length
221
+ Seed.Runner.report(
222
+ `orders: ${(orders->Array.length)->Int.toString} placed (Standard ${countMethod(
223
+ Standard,
224
+ )->Int.toString}, Express ${countMethod(Express)->Int.toString}, Pickup ${countMethod(
225
+ Pickup,
226
+ )->Int.toString}) — Express placements were auto-shipped on arrival`,
227
+ )
228
+ }
229
+
230
+ let dispatchStandardBatch = async (orders: array<DemoData.order>, ~client: Seed.Client.t) => {
231
+ let dispatched = DemoData.batchDispatched(orders)
232
+ await client->Seed.Client.sendAll(
233
+ dispatched->Array.map(o => DemoCommands.shipOrder(ShipOrder({orderId: o.id}))),
234
+ )
235
+ let standardTotal = orders->Array.filter(o => o.shippingMethod == Standard)->Array.length
236
+ Seed.Runner.report(
237
+ `batch dispatch: ${(dispatched->Array.length)
238
+ ->Int.toString}/${standardTotal->Int.toString} Standard orders shipped, ${(standardTotal -
239
+ dispatched->Array.length)->Int.toString} left pending`,
240
+ )
241
+ dispatched->Array.map(o => o.id)
242
+ }
243
+
244
+ let seedCancellations = async (
245
+ orders: array<DemoData.order>,
246
+ ~client: Seed.Client.t,
247
+ ~dispatched: array<string>,
248
+ ) => {
249
+ let cancellable = DemoData.cancellable(orders, ~dispatched)
250
+ let targets = DemoData.cancelled(cancellable)
251
+ await client->Seed.Client.sendAll(
252
+ targets->Array.map(o => DemoCommands.cancelOrder(CancelOrder({orderId: o.id}))),
253
+ )
254
+ Seed.Runner.report(
255
+ `cancellations: ${(targets->Array.length)->Int.toString} of ${(cancellable->Array.length)
256
+ ->Int.toString} cancellable orders cancelled`,
257
+ )
258
+ }
259
+
260
+ let seedDeactivations = async (customers: array<DemoData.customer>, ~client: Seed.Client.t) => {
261
+ let targets = DemoData.deactivatedCustomers(customers)
262
+ await client->Seed.Client.sendAll(
263
+ targets->Array.map(c => DemoCommands.customer(~id=c.id, Deactivate)),
264
+ )
265
+ Seed.Runner.report(`customers: ${(targets->Array.length)->Int.toString} deactivated`)
266
+ }
267
+
268
+ // ── Summary ─────────────────────────────────────────────────────────────────
269
+
270
+ let summarise = async (~client: Seed.Client.t, ~counts: dict<int>) => {
271
+ let orders = await client->Seed.Client.queryAllNodes(
272
+ ~field="Ordering_Orders",
273
+ ~selection="status shippingMethod",
274
+ )
275
+ let statuses = ["Placed", "Shipped", "Cancelled"]
276
+ let methods = ["Standard", "Express", "Pickup"]
277
+ let countCell = (status, method) =>
278
+ orders
279
+ ->Array.filter(o =>
280
+ o->Seed.Client.nodeString("status") == Some(status) &&
281
+ o->Seed.Client.nodeString("shippingMethod") == Some(method)
282
+ )
283
+ ->Array.length
284
+ let countStatus = status =>
285
+ orders->Array.filter(o => o->Seed.Client.nodeString("status") == Some(status))->Array.length
286
+
287
+ let demand = await client->Seed.Client.queryAllNodes(
288
+ ~field="Catalog_ProductDemands",
289
+ ~selection="name orderCount",
290
+ )
291
+ let orderCountOf = node =>
292
+ switch node->Seed.Client.field("orderCount") {
293
+ | Some(Number(n)) => n->Int.fromFloat
294
+ | _ => 0
295
+ }
296
+ let ranked = demand->Array.toSorted((a, b) => Int.compare(orderCountOf(b), orderCountOf(a)))
297
+ let withDemand = ranked->Array.filter(n => orderCountOf(n) > 0)
298
+ let head =
299
+ ranked
300
+ ->Array.slice(~start=0, ~end=3)
301
+ ->Array.map(n =>
302
+ `${n->Seed.Client.nodeString("name")->Option.getOr("?")} (${orderCountOf(n)->Int.toString})`
303
+ )
304
+ ->Array.join(", ")
305
+
306
+ Seed.Runner.heading("Seeded:")
307
+ Console.log(
308
+ ` order status: ${statuses
309
+ ->Array.map(s => `${s} ${countStatus(s)->Int.toString}`)
310
+ ->Array.join(", ")}`,
311
+ )
312
+ Console.log(` demand head: ${head}`)
313
+ Console.log(
314
+ ` demand spread: ${(withDemand->Array.length)->Int.toString}/${(ranked->Array.length)
315
+ ->Int.toString} products ordered, tail min ${withDemand
316
+ ->Array.last
317
+ ->Option.mapOr(0, orderCountOf)
318
+ ->Int.toString}`,
319
+ )
320
+
321
+ Console.log("")
322
+ Console.log(" status × shippingMethod:")
323
+ Console.log(
324
+ ` ${" "->String.padEnd(10, " ")}${methods
325
+ ->Array.map(m => m->String.padStart(10, " "))
326
+ ->Array.join("")}`,
327
+ )
328
+ statuses->Array.forEach(status =>
329
+ Console.log(
330
+ ` ${status->String.padEnd(10, " ")}${methods
331
+ ->Array.map(m => countCell(status, m)->Int.toString->String.padStart(10, " "))
332
+ ->Array.join("")}`,
333
+ )
334
+ )
335
+
336
+ let observedStatuses = statuses->Array.filter(s => countStatus(s) > 0)
337
+ let spreadWarning = if observedStatuses->Array.length < 3 {
338
+ [
339
+ `the board shows only ${(observedStatuses->Array.length)
340
+ ->Int.toString} of the 3 order statuses (${observedStatuses->Array.join(
341
+ ", ",
342
+ )}) — the shipping-method mix or the batch-dispatch share needs adjusting.`,
343
+ ]
344
+ } else {
345
+ []
346
+ }
347
+ Seed.Runner.warn(Array.concat(spreadWarning, Seed.Runner.unfillableWarnings(~views, ~counts)))
348
+ }
349
+
350
+ // ── Run ─────────────────────────────────────────────────────────────────────
351
+
352
+ // The shared seed run, parameterized only by volume so every data set walks the
353
+ // same phases. Uploads no-op when the connection carries no upload endpoint
354
+ // (a deployment that serves none), keeping `imageUrl` empty rather than failing.
355
+ let run = async (
356
+ connection: Seed.connection,
357
+ ~productCount: int,
358
+ ~customerCount: int,
359
+ ~orderCount: int,
360
+ ): unit => {
361
+ let client = connection.client
362
+
363
+ let built = DemoData.buildProducts(~count=productCount, ())
364
+ let products = if connection.uploadEndpoint == "" {
365
+ Seed.Runner.report(
366
+ "product images: skipped (no upload endpoint / SEED_SKIP_UPLOADS) — imageUrl left empty",
367
+ )
368
+ built
369
+ } else {
370
+ await uploadProductImages(built, ~client, ~uploadEndpoint=connection.uploadEndpoint)
371
+ }
372
+ let customers = DemoData.buildCustomers(~count=customerCount, ())
373
+ let orders = DemoData.buildOrders(products, customers, ~count=orderCount, ())
374
+
375
+ await seedCategories(~client)
376
+ await seedProducts(products, ~client)
377
+ await seedCatalogEdits(products, ~client)
378
+ await seedSupplierFeed(~client)
379
+ await seedCustomers(customers, ~client)
380
+
381
+ // Products reach Ordering asynchronously through the Products extension
382
+ // point; PlaceOrder rejects until the shadow copy lands.
383
+ let expected = Array.concat(products->Array.map(p => p.id), DemoData.importedSkus)
384
+ let available = await client->Seed.Client.waitForIds(
385
+ ~field="Ordering_AvailableProductsByIds",
386
+ ~ids=expected,
387
+ )
388
+ Seed.Runner.report(`ordering: ${available->Int.toString} products available`)
389
+
390
+ await seedOrders(orders, ~client)
391
+ let dispatched = await dispatchStandardBatch(orders, ~client)
392
+ await seedCancellations(orders, ~client, ~dispatched)
393
+ await seedDeactivations(customers, ~client)
394
+
395
+ let counts = await Seed.Runner.verifyViews(client, ~views)
396
+ await summarise(~client, ~counts)
397
+ }
398
+
399
+ // Two sets, so `Seed.Runner.seed` presents a selection: the full demo, and a
400
+ // compact sample for a quick end-to-end check. Both walk identical phases.
401
+ let dataSets: array<Seed.dataSet> = [
402
+ {
403
+ name: "full",
404
+ label: `full — ${DemoData.productCount->Int.toString} products, ${DemoData.customerCount->Int.toString} customers, ${DemoData.orderCount->Int.toString} orders`,
405
+ seed: connection =>
406
+ run(
407
+ connection,
408
+ ~productCount=DemoData.productCount,
409
+ ~customerCount=DemoData.customerCount,
410
+ ~orderCount=DemoData.orderCount,
411
+ ),
412
+ },
413
+ {
414
+ name: "sample",
415
+ label: "sample — 16 products, 8 customers, 40 orders",
416
+ seed: connection => run(connection, ~productCount=16, ~customerCount=8, ~orderCount=40),
417
+ },
418
+ ]
@@ -0,0 +1,4 @@
1
+ randjbuildsystem
2
+ DemoCommands
3
+ DemoData
4
+ HybridSeedData
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@reventlessdev/online-shop-hybrid-seed",
3
+ "version": "1.0.0-alpha.2",
4
+ "description": "Shareable demo data set(s) for the online-shop-hybrid example, exported as reventless-seed data sets",
5
+ "license": "Apache-2.0",
6
+ "dependencies": {
7
+ "sury": "^11.0.0-alpha.4",
8
+ "@reventlessdev/online-shop-hybrid-ordering": "1.0.0-alpha.155",
9
+ "@reventlessdev/online-shop-hybrid-catalog-spec": "1.0.0-alpha.74",
10
+ "@reventlessdev/online-shop-hybrid-ordering-spec": "1.0.0-alpha.74",
11
+ "@reventlessdev/reventless-seed": "1.0.0-alpha.2",
12
+ "@reventlessdev/online-shop-hybrid-catalog": "1.0.0-alpha.156"
13
+ },
14
+ "devDependencies": {
15
+ "rescript": "12.3.0"
16
+ },
17
+ "peerDependencies": {
18
+ "rescript": "12.3.0"
19
+ },
20
+ "publishConfig": {
21
+ "registry": "https://registry.npmjs.org"
22
+ },
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/ReventlessDev/reventless-core.git",
26
+ "directory": "examples/online-shop-hybrid/seed-data"
27
+ },
28
+ "scripts": {
29
+ "build": "rescript build",
30
+ "start": "rescript watch",
31
+ "clean": "rescript clean",
32
+ "rebuild": "pnpm run clean && pnpm run build"
33
+ }
34
+ }
package/rescript.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@reventlessdev/online-shop-hybrid-seed",
3
+ "package-specs": {
4
+ "module": "esmodule",
5
+ "in-source": true
6
+ },
7
+ "namespace": "OnlineShopHybridSeed",
8
+ "warnings": {
9
+ "error": "-44+101"
10
+ },
11
+ "sources": [
12
+ {
13
+ "dir": "src",
14
+ "subdirs": true
15
+ }
16
+ ],
17
+ "dependencies": [
18
+ "sury",
19
+ "@reventlessdev/reventless-seed",
20
+ "@reventlessdev/online-shop-hybrid-catalog",
21
+ "@reventlessdev/online-shop-hybrid-catalog-spec",
22
+ "@reventlessdev/online-shop-hybrid-ordering",
23
+ "@reventlessdev/online-shop-hybrid-ordering-spec"
24
+ ],
25
+ "compiler-flags": [],
26
+ "suffix": ".res.mjs"
27
+ }
@@ -0,0 +1,154 @@
1
+ // Adapter: online-shop-hybrid command values → GraphQL mutations.
2
+ //
3
+ // This is the only place that knows how this example's commands are exposed on
4
+ // the API — the mutation field names and how each variant's payload maps onto
5
+ // arguments. Everything above it works in plugin command types; everything
6
+ // below it (`ReventlessSeed`) works in generic mutations.
7
+ //
8
+ // Each encoder pattern-matches the real command variant, so a renamed field or
9
+ // a new payload member is a compile error here rather than a rejected mutation
10
+ // at seed time. That is the whole point of routing the seed through typed
11
+ // commands instead of hand-written GraphQL strings.
12
+
13
+ open ReventlessSeed
14
+
15
+ // Mutation fields are `<PluginName>_<CommandVariant>`; aggregates additionally
16
+ // carry the entity id as a separate `id` argument.
17
+ let catalog = (name: string): string => `Catalog_${name}`
18
+ let ordering = (name: string): string => `Ordering_${name}`
19
+
20
+ // ── Catalog ─────────────────────────────────────────────────────────────────
21
+
22
+ let addCategory = (command: CatalogPlugin.AddCategory.command): Seed.mutation =>
23
+ switch command {
24
+ | AddCategory({categoryId, name}) =>
25
+ Seed.mutation(catalog("AddCategory"), [("categoryId", Id(categoryId)), ("name", String(name))])
26
+ }
27
+
28
+ let renameCategory = (command: CatalogPlugin.RenameCategory.command): Seed.mutation =>
29
+ switch command {
30
+ | RenameCategory({categoryId, name}) =>
31
+ Seed.mutation(
32
+ catalog("RenameCategory"),
33
+ [("categoryId", Id(categoryId)), ("name", String(name))],
34
+ )
35
+ }
36
+
37
+ let archiveCategory = (command: CatalogPlugin.ArchiveCategory.command): Seed.mutation =>
38
+ switch command {
39
+ | ArchiveCategory({categoryId}) =>
40
+ Seed.mutation(catalog("ArchiveCategory"), [("categoryId", Id(categoryId))])
41
+ }
42
+
43
+ let addProduct = (command: CatalogPlugin.AddProduct.command): Seed.mutation =>
44
+ switch command {
45
+ | AddProduct({productId, name, description, price, imageUrl, categoryId}) =>
46
+ Seed.mutation(
47
+ catalog("AddProduct"),
48
+ [
49
+ ("productId", Id(productId)),
50
+ ("name", String(name)),
51
+ ("description", String(description)),
52
+ ("price", Float(price)),
53
+ ("imageUrl", String(imageUrl)),
54
+ ("categoryId", Id(categoryId)),
55
+ ],
56
+ )
57
+ }
58
+
59
+ let changeProductPrice = (command: CatalogPlugin.ChangeProductPrice.command): Seed.mutation =>
60
+ switch command {
61
+ | ChangeProductPrice({productId, price}) =>
62
+ Seed.mutation(
63
+ catalog("ChangeProductPrice"),
64
+ [("productId", Id(productId)), ("price", Float(price))],
65
+ )
66
+ }
67
+
68
+ let changeProductDescription = (
69
+ command: CatalogPlugin.ChangeProductDescription.command,
70
+ ): Seed.mutation =>
71
+ switch command {
72
+ | ChangeProductDescription({productId, description}) =>
73
+ Seed.mutation(
74
+ catalog("ChangeProductDescription"),
75
+ [("productId", Id(productId)), ("description", String(description))],
76
+ )
77
+ }
78
+
79
+ /** InboundTranslationSlice: the mutation arguments are the external input's
80
+ fields, not a command payload — the slice translates them itself. */
81
+ let importProduct = (input: CatalogPlugin.ImportProduct.externalInput): Seed.mutation =>
82
+ Seed.mutation(
83
+ catalog("ImportProduct"),
84
+ [
85
+ ("sku", String(input.sku)),
86
+ ("title", String(input.title)),
87
+ ("desc", String(input.desc)),
88
+ ("unitPrice", Int(input.unitPrice)),
89
+ ("currency", String(input.currency)),
90
+ ("category", Id(input.category)),
91
+ ],
92
+ )
93
+
94
+ // ── Ordering ────────────────────────────────────────────────────────────────
95
+
96
+ let shippingMethod = (method: OrderingPlugin.PlaceOrder.shippingMethod): Seed.value =>
97
+ switch method {
98
+ | Standard => Enum("Standard")
99
+ | Express => Enum("Express")
100
+ | Pickup => Enum("Pickup")
101
+ }
102
+
103
+ let placeOrder = (command: OrderingPlugin.PlaceOrder.command): Seed.mutation =>
104
+ switch command {
105
+ | PlaceOrder({orderId, customerId, productIds, shippingMethod: method}) =>
106
+ Seed.mutation(
107
+ ordering("PlaceOrder"),
108
+ [
109
+ ("orderId", Id(orderId)),
110
+ ("customerId", Id(customerId)),
111
+ ("productIds", Seed.ids(productIds)),
112
+ ("shippingMethod", shippingMethod(method)),
113
+ ],
114
+ )
115
+ }
116
+
117
+ let shipOrder = (command: OrderingPlugin.ShipOrder.command): Seed.mutation =>
118
+ switch command {
119
+ | ShipOrder({orderId}) => Seed.mutation(ordering("ShipOrder"), [("orderId", Id(orderId))])
120
+ }
121
+
122
+ /** CancelOrder's ReopenOrder variant is `@noApi`, so it has no mutation field to
123
+ encode to — reaching it here means the seed tried to drive an internal-only
124
+ command through the public API. */
125
+ let cancelOrder = (command: OrderingPlugin.CancelOrder.command): Seed.mutation =>
126
+ switch command {
127
+ | CancelOrder({orderId}) => Seed.mutation(ordering("CancelOrder"), [("orderId", Id(orderId))])
128
+ | ReopenOrder(_) =>
129
+ throw(Seed.Failed("ReopenOrder is @noApi — it cannot be seeded through the GraphQL API"))
130
+ }
131
+
132
+ /** Aggregate commands are exposed as `<Plugin>_<Aggregate>_<Variant>` and take
133
+ the aggregate id alongside the payload. */
134
+ let customer = (~id: string, command: OrderingPlugin.Customer.command): Seed.mutation =>
135
+ switch command {
136
+ | Register({email, address}) =>
137
+ Seed.mutation(
138
+ ordering("Customer_Register"),
139
+ [("id", Id(id)), ("email", String(email)), ("address", String(address))],
140
+ )
141
+ | UpdateEmail({email}) =>
142
+ Seed.mutation(ordering("Customer_UpdateEmail"), [("id", Id(id)), ("email", String(email))])
143
+ | UpdateAddress({address}) =>
144
+ Seed.mutation(
145
+ ordering("Customer_UpdateAddress"),
146
+ [("id", Id(id)), ("address", String(address))],
147
+ )
148
+ | SetLocation({location}) =>
149
+ Seed.mutation(
150
+ ordering("Customer_SetLocation"),
151
+ [("id", Id(id)), ("location", Object([("lat", Float(location.lat)), ("lng", Float(location.lng))]))],
152
+ )
153
+ | Deactivate => Seed.mutation(ordering("Customer_Deactivate"), [("id", Id(id))])
154
+ }