@livestore/cli 0.0.0-snapshot-2ac5fd340c97c9e07fe4c5dc6d31d5132aa6557c.0

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 (80) hide show
  1. package/.claude/settings.local.json +12 -0
  2. package/LICENSE +201 -0
  3. package/dist/cli +0 -0
  4. package/dist/cli.d.ts +15 -0
  5. package/dist/cli.d.ts.map +1 -0
  6. package/dist/cli.js +22 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/commands/mcp-coach.d.ts +15 -0
  9. package/dist/commands/mcp-coach.d.ts.map +1 -0
  10. package/dist/commands/mcp-coach.js +87 -0
  11. package/dist/commands/mcp-coach.js.map +1 -0
  12. package/dist/commands/mcp-tools.d.ts +41 -0
  13. package/dist/commands/mcp-tools.d.ts.map +1 -0
  14. package/dist/commands/mcp-tools.js +148 -0
  15. package/dist/commands/mcp-tools.js.map +1 -0
  16. package/dist/commands/mcp.d.ts +5 -0
  17. package/dist/commands/mcp.d.ts.map +1 -0
  18. package/dist/commands/mcp.js +67 -0
  19. package/dist/commands/mcp.js.map +1 -0
  20. package/dist/commands/new-project.d.ts +34 -0
  21. package/dist/commands/new-project.d.ts.map +1 -0
  22. package/dist/commands/new-project.js +163 -0
  23. package/dist/commands/new-project.js.map +1 -0
  24. package/dist/mcp-content/architecture.d.ts +2 -0
  25. package/dist/mcp-content/architecture.d.ts.map +1 -0
  26. package/dist/mcp-content/architecture.js +171 -0
  27. package/dist/mcp-content/architecture.js.map +1 -0
  28. package/dist/mcp-content/features.d.ts +2 -0
  29. package/dist/mcp-content/features.d.ts.map +1 -0
  30. package/dist/mcp-content/features.js +177 -0
  31. package/dist/mcp-content/features.js.map +1 -0
  32. package/dist/mcp-content/getting-started.d.ts +2 -0
  33. package/dist/mcp-content/getting-started.d.ts.map +1 -0
  34. package/dist/mcp-content/getting-started.js +405 -0
  35. package/dist/mcp-content/getting-started.js.map +1 -0
  36. package/dist/mcp-content/overview.d.ts +2 -0
  37. package/dist/mcp-content/overview.d.ts.map +1 -0
  38. package/dist/mcp-content/overview.js +120 -0
  39. package/dist/mcp-content/overview.js.map +1 -0
  40. package/dist/mcp-content/schemas/blog.d.ts +2 -0
  41. package/dist/mcp-content/schemas/blog.d.ts.map +1 -0
  42. package/dist/mcp-content/schemas/blog.js +223 -0
  43. package/dist/mcp-content/schemas/blog.js.map +1 -0
  44. package/dist/mcp-content/schemas/ecommerce.d.ts +2 -0
  45. package/dist/mcp-content/schemas/ecommerce.d.ts.map +1 -0
  46. package/dist/mcp-content/schemas/ecommerce.js +436 -0
  47. package/dist/mcp-content/schemas/ecommerce.js.map +1 -0
  48. package/dist/mcp-content/schemas/social.d.ts +2 -0
  49. package/dist/mcp-content/schemas/social.d.ts.map +1 -0
  50. package/dist/mcp-content/schemas/social.js +339 -0
  51. package/dist/mcp-content/schemas/social.js.map +1 -0
  52. package/dist/mcp-content/schemas/todo.d.ts +2 -0
  53. package/dist/mcp-content/schemas/todo.d.ts.map +1 -0
  54. package/dist/mcp-content/schemas/todo.js +172 -0
  55. package/dist/mcp-content/schemas/todo.js.map +1 -0
  56. package/dist/mod.d.ts +2 -0
  57. package/dist/mod.d.ts.map +1 -0
  58. package/dist/mod.js +2 -0
  59. package/dist/mod.js.map +1 -0
  60. package/dist/test-tool.d.ts +2 -0
  61. package/dist/test-tool.d.ts.map +1 -0
  62. package/dist/test-tool.js +57 -0
  63. package/dist/test-tool.js.map +1 -0
  64. package/dist/tsconfig.tsbuildinfo +1 -0
  65. package/package.json +27 -0
  66. package/src/cli.ts +35 -0
  67. package/src/commands/mcp-coach.ts +121 -0
  68. package/src/commands/mcp-tools.ts +169 -0
  69. package/src/commands/mcp.ts +97 -0
  70. package/src/commands/new-project.ts +263 -0
  71. package/src/mcp-content/architecture.ts +170 -0
  72. package/src/mcp-content/features.ts +176 -0
  73. package/src/mcp-content/getting-started.ts +404 -0
  74. package/src/mcp-content/overview.ts +119 -0
  75. package/src/mcp-content/schemas/blog.ts +222 -0
  76. package/src/mcp-content/schemas/ecommerce.ts +435 -0
  77. package/src/mcp-content/schemas/social.ts +338 -0
  78. package/src/mcp-content/schemas/todo.ts +171 -0
  79. package/src/mod.ts +1 -0
  80. package/tsconfig.json +9 -0
@@ -0,0 +1,222 @@
1
+ export const blogSchemaContent = `import { Events, makeSchema, Schema, SessionIdSymbol, State } from '@livestore/livestore'
2
+
3
+ // Content management with collaborative editing capabilities
4
+ export const tables = {
5
+ posts: State.SQLite.table({
6
+ name: 'posts',
7
+ columns: {
8
+ id: State.SQLite.text({ primaryKey: true }),
9
+ title: State.SQLite.text(),
10
+ content: State.SQLite.text(), // Consider using JSON for rich text operations
11
+ slug: State.SQLite.text(),
12
+ published: State.SQLite.boolean({ default: false }),
13
+ authorId: State.SQLite.text(),
14
+ createdAt: State.SQLite.integer({ schema: Schema.DateFromNumber }),
15
+ publishedAt: State.SQLite.integer({ nullable: true, schema: Schema.DateFromNumber }),
16
+ deletedAt: State.SQLite.integer({ nullable: true, schema: Schema.DateFromNumber }),
17
+ version: State.SQLite.integer({ default: 1 }), // For optimistic concurrency
18
+ },
19
+ }),
20
+
21
+ comments: State.SQLite.table({
22
+ name: 'comments',
23
+ columns: {
24
+ id: State.SQLite.text({ primaryKey: true }),
25
+ postId: State.SQLite.text(),
26
+ authorId: State.SQLite.text(),
27
+ content: State.SQLite.text(),
28
+ parentId: State.SQLite.text({ nullable: true }), // For threaded comments
29
+ createdAt: State.SQLite.integer({ schema: Schema.DateFromNumber }),
30
+ editedAt: State.SQLite.integer({ nullable: true, schema: Schema.DateFromNumber }),
31
+ deletedAt: State.SQLite.integer({ nullable: true, schema: Schema.DateFromNumber }),
32
+ },
33
+ }),
34
+
35
+ authors: State.SQLite.table({
36
+ name: 'authors',
37
+ columns: {
38
+ id: State.SQLite.text({ primaryKey: true }),
39
+ name: State.SQLite.text(),
40
+ email: State.SQLite.text(),
41
+ bio: State.SQLite.text({ nullable: true }),
42
+ avatarUrl: State.SQLite.text({ nullable: true }),
43
+ createdAt: State.SQLite.integer({ schema: Schema.DateFromNumber }),
44
+ },
45
+ }),
46
+
47
+ // Track collaborative editing sessions
48
+ editingSessions: State.SQLite.clientDocument({
49
+ name: 'editingSessions',
50
+ schema: Schema.Struct({
51
+ postId: Schema.String,
52
+ authorId: Schema.String,
53
+ lastActivity: Schema.Date,
54
+ cursorPosition: Schema.Number,
55
+ }),
56
+ default: {
57
+ id: SessionIdSymbol,
58
+ value: { postId: '', authorId: '', lastActivity: new Date(), cursorPosition: 0 }
59
+ },
60
+ }),
61
+ }
62
+
63
+ export const events = {
64
+ // Post lifecycle events
65
+ postCreated: Events.synced({
66
+ name: 'v1.PostCreated',
67
+ schema: Schema.Struct({
68
+ id: Schema.String,
69
+ title: Schema.String,
70
+ slug: Schema.String,
71
+ authorId: Schema.String,
72
+ createdAt: Schema.Date,
73
+ }),
74
+ }),
75
+
76
+ postTitleChanged: Events.synced({
77
+ name: 'v1.PostTitleChanged',
78
+ schema: Schema.Struct({
79
+ id: Schema.String,
80
+ title: Schema.String,
81
+ version: Schema.Number, // Optimistic concurrency control
82
+ }),
83
+ }),
84
+
85
+ postContentChanged: Events.synced({
86
+ name: 'v1.PostContentChanged',
87
+ schema: Schema.Struct({
88
+ id: Schema.String,
89
+ content: Schema.String,
90
+ version: Schema.Number,
91
+ authorId: Schema.String,
92
+ }),
93
+ }),
94
+
95
+ postPublished: Events.synced({
96
+ name: 'v1.PostPublished',
97
+ schema: Schema.Struct({
98
+ id: Schema.String,
99
+ publishedAt: Schema.Date,
100
+ }),
101
+ }),
102
+
103
+ postUnpublished: Events.synced({
104
+ name: 'v1.PostUnpublished',
105
+ schema: Schema.Struct({ id: Schema.String }),
106
+ }),
107
+
108
+ postDeleted: Events.synced({
109
+ name: 'v1.PostDeleted',
110
+ schema: Schema.Struct({
111
+ id: Schema.String,
112
+ deletedAt: Schema.Date,
113
+ }),
114
+ }),
115
+
116
+ // Comment events
117
+ commentCreated: Events.synced({
118
+ name: 'v1.CommentCreated',
119
+ schema: Schema.Struct({
120
+ id: Schema.String,
121
+ postId: Schema.String,
122
+ authorId: Schema.String,
123
+ content: Schema.String,
124
+ parentId: Schema.NullOr(Schema.String),
125
+ createdAt: Schema.Date,
126
+ }),
127
+ }),
128
+
129
+ commentEdited: Events.synced({
130
+ name: 'v1.CommentEdited',
131
+ schema: Schema.Struct({
132
+ id: Schema.String,
133
+ content: Schema.String,
134
+ editedAt: Schema.Date,
135
+ }),
136
+ }),
137
+
138
+ commentDeleted: Events.synced({
139
+ name: 'v1.CommentDeleted',
140
+ schema: Schema.Struct({
141
+ id: Schema.String,
142
+ deletedAt: Schema.Date,
143
+ }),
144
+ }),
145
+
146
+ // Author events
147
+ authorCreated: Events.synced({
148
+ name: 'v1.AuthorCreated',
149
+ schema: Schema.Struct({
150
+ id: Schema.String,
151
+ name: Schema.String,
152
+ email: Schema.String,
153
+ createdAt: Schema.Date,
154
+ }),
155
+ }),
156
+
157
+ // Local editing session tracking
158
+ editingSessionUpdated: tables.editingSessions.set,
159
+ }
160
+
161
+ // Materializers with conflict resolution strategies
162
+ const materializers = State.SQLite.materializers(events, {
163
+ // Post materializers
164
+ 'v1.PostCreated': ({ id, title, slug, authorId, createdAt }) =>
165
+ tables.posts.insert({ id, title, content: '', slug, authorId, createdAt, version: 1 }),
166
+
167
+ 'v1.PostTitleChanged': ({ id, title, version }) =>
168
+ // Last-write-wins with version check for optimistic concurrency
169
+ tables.posts.update({ title, version }).where({ id }),
170
+
171
+ 'v1.PostContentChanged': ({ id, content, version, authorId }) =>
172
+ tables.posts.update({ content, version }).where({ id }),
173
+
174
+ 'v1.PostPublished': ({ id, publishedAt }) =>
175
+ tables.posts.update({ published: true, publishedAt }).where({ id }),
176
+
177
+ 'v1.PostUnpublished': ({ id }) =>
178
+ tables.posts.update({ published: false, publishedAt: null }).where({ id }),
179
+
180
+ 'v1.PostDeleted': ({ id, deletedAt }) =>
181
+ tables.posts.update({ deletedAt }).where({ id }),
182
+
183
+ // Comment materializers
184
+ 'v1.CommentCreated': ({ id, postId, authorId, content, parentId, createdAt }) =>
185
+ tables.comments.insert({ id, postId, authorId, content, parentId, createdAt }),
186
+
187
+ 'v1.CommentEdited': ({ id, content, editedAt }) =>
188
+ tables.comments.update({ content, editedAt }).where({ id }),
189
+
190
+ 'v1.CommentDeleted': ({ id, deletedAt }) =>
191
+ tables.comments.update({ deletedAt }).where({ id }),
192
+
193
+ // Author materializers
194
+ 'v1.AuthorCreated': ({ id, name, email, createdAt }) =>
195
+ tables.authors.insert({ id, name, email, createdAt }),
196
+ })
197
+
198
+ const state = State.SQLite.makeState({ tables, materializers })
199
+
200
+ export const schema = makeSchema({ events, state })
201
+
202
+ // Example queries:
203
+ //
204
+ // // Published posts with author info
205
+ // const publishedPosts$ = queryDb(
206
+ // tables.posts
207
+ // .select()
208
+ // .join(tables.authors, 'authorId', 'id')
209
+ // .where({ published: true, deletedAt: null })
210
+ // .orderBy('publishedAt', 'desc'),
211
+ // { label: 'publishedPosts' }
212
+ // )
213
+ //
214
+ // // Comments for a post (threaded)
215
+ // const postComments$ = (postId: string) => queryDb(
216
+ // tables.comments
217
+ // .select()
218
+ // .join(tables.authors, 'authorId', 'id')
219
+ // .where({ postId, deletedAt: null })
220
+ // .orderBy('createdAt'),
221
+ // { label: \`postComments-\${postId}\` }
222
+ // )`
@@ -0,0 +1,435 @@
1
+ export const ecommerceSchemaContent = `import { Events, makeSchema, Schema, SessionIdSymbol, State } from '@livestore/livestore'
2
+
3
+ // E-commerce with inventory management and order processing
4
+ export const tables = {
5
+ products: State.SQLite.table({
6
+ name: 'products',
7
+ columns: {
8
+ id: State.SQLite.text({ primaryKey: true }),
9
+ sku: State.SQLite.text(),
10
+ name: State.SQLite.text(),
11
+ description: State.SQLite.text(),
12
+ price: State.SQLite.integer(), // Store as cents to avoid floating point issues
13
+ currency: State.SQLite.text({ default: 'USD' }),
14
+ stock: State.SQLite.integer({ default: 0 }),
15
+ reservedStock: State.SQLite.integer({ default: 0 }), // For pending orders
16
+ isActive: State.SQLite.boolean({ default: true }),
17
+ weight: State.SQLite.real({ nullable: true }), // For shipping calculations
18
+ dimensions: State.SQLite.text({ nullable: true }), // JSON: {width, height, depth}
19
+ imageUrls: State.SQLite.text({ nullable: true }), // JSON array
20
+ createdAt: State.SQLite.integer({ schema: Schema.DateFromNumber }),
21
+ updatedAt: State.SQLite.integer({ schema: Schema.DateFromNumber }),
22
+ deletedAt: State.SQLite.integer({ nullable: true, schema: Schema.DateFromNumber }),
23
+ },
24
+ }),
25
+
26
+ categories: State.SQLite.table({
27
+ name: 'categories',
28
+ columns: {
29
+ id: State.SQLite.text({ primaryKey: true }),
30
+ name: State.SQLite.text(),
31
+ slug: State.SQLite.text(),
32
+ parentId: State.SQLite.text({ nullable: true }), // For hierarchical categories
33
+ description: State.SQLite.text({ nullable: true }),
34
+ imageUrl: State.SQLite.text({ nullable: true }),
35
+ sortOrder: State.SQLite.integer({ default: 0 }),
36
+ isActive: State.SQLite.boolean({ default: true }),
37
+ createdAt: State.SQLite.integer({ schema: Schema.DateFromNumber }),
38
+ },
39
+ }),
40
+
41
+ productCategories: State.SQLite.table({
42
+ name: 'product_categories',
43
+ columns: {
44
+ productId: State.SQLite.text(),
45
+ categoryId: State.SQLite.text(),
46
+ createdAt: State.SQLite.integer({ schema: Schema.DateFromNumber }),
47
+ },
48
+ }),
49
+
50
+ customers: State.SQLite.table({
51
+ name: 'customers',
52
+ columns: {
53
+ id: State.SQLite.text({ primaryKey: true }),
54
+ email: State.SQLite.text(),
55
+ firstName: State.SQLite.text({ nullable: true }),
56
+ lastName: State.SQLite.text({ nullable: true }),
57
+ phone: State.SQLite.text({ nullable: true }),
58
+ createdAt: State.SQLite.integer({ schema: Schema.DateFromNumber }),
59
+ lastOrderAt: State.SQLite.integer({ nullable: true, schema: Schema.DateFromNumber }),
60
+ },
61
+ }),
62
+
63
+ addresses: State.SQLite.table({
64
+ name: 'addresses',
65
+ columns: {
66
+ id: State.SQLite.text({ primaryKey: true }),
67
+ customerId: State.SQLite.text(),
68
+ type: State.SQLite.text({ default: 'shipping' }), // shipping, billing
69
+ firstName: State.SQLite.text(),
70
+ lastName: State.SQLite.text(),
71
+ company: State.SQLite.text({ nullable: true }),
72
+ address1: State.SQLite.text(),
73
+ address2: State.SQLite.text({ nullable: true }),
74
+ city: State.SQLite.text(),
75
+ state: State.SQLite.text(),
76
+ country: State.SQLite.text(),
77
+ postalCode: State.SQLite.text(),
78
+ isDefault: State.SQLite.boolean({ default: false }),
79
+ },
80
+ }),
81
+
82
+ orders: State.SQLite.table({
83
+ name: 'orders',
84
+ columns: {
85
+ id: State.SQLite.text({ primaryKey: true }),
86
+ orderNumber: State.SQLite.text(), // Human-readable order number
87
+ customerId: State.SQLite.text(),
88
+ status: State.SQLite.text({ default: 'draft' }), // draft, pending, paid, processing, shipped, delivered, cancelled
89
+ paymentStatus: State.SQLite.text({ default: 'pending' }), // pending, paid, failed, refunded
90
+ fulfillmentStatus: State.SQLite.text({ default: 'unfulfilled' }), // unfulfilled, partial, fulfilled
91
+
92
+ subtotal: State.SQLite.integer(), // In cents
93
+ taxAmount: State.SQLite.integer({ default: 0 }),
94
+ shippingAmount: State.SQLite.integer({ default: 0 }),
95
+ discountAmount: State.SQLite.integer({ default: 0 }),
96
+ total: State.SQLite.integer(),
97
+ currency: State.SQLite.text({ default: 'USD' }),
98
+
99
+ shippingAddress: State.SQLite.text(), // JSON
100
+ billingAddress: State.SQLite.text(), // JSON
101
+
102
+ notes: State.SQLite.text({ nullable: true }),
103
+
104
+ createdAt: State.SQLite.integer({ schema: Schema.DateFromNumber }),
105
+ updatedAt: State.SQLite.integer({ schema: Schema.DateFromNumber }),
106
+ cancelledAt: State.SQLite.integer({ nullable: true, schema: Schema.DateFromNumber }),
107
+ shippedAt: State.SQLite.integer({ nullable: true, schema: Schema.DateFromNumber }),
108
+ deliveredAt: State.SQLite.integer({ nullable: true, schema: Schema.DateFromNumber }),
109
+ },
110
+ }),
111
+
112
+ orderItems: State.SQLite.table({
113
+ name: 'order_items',
114
+ columns: {
115
+ id: State.SQLite.text({ primaryKey: true }),
116
+ orderId: State.SQLite.text(),
117
+ productId: State.SQLite.text(),
118
+ variantId: State.SQLite.text({ nullable: true }),
119
+ sku: State.SQLite.text(), // Snapshot at time of order
120
+ name: State.SQLite.text(), // Product name at time of order
121
+ quantity: State.SQLite.integer(),
122
+ unitPrice: State.SQLite.integer(), // Price per unit in cents
123
+ totalPrice: State.SQLite.integer(), // quantity * unitPrice
124
+ fulfillmentStatus: State.SQLite.text({ default: 'unfulfilled' }),
125
+ },
126
+ }),
127
+
128
+ // Inventory tracking with event sourcing
129
+ inventoryEvents: State.SQLite.table({
130
+ name: 'inventory_events',
131
+ columns: {
132
+ id: State.SQLite.text({ primaryKey: true }),
133
+ productId: State.SQLite.text(),
134
+ type: State.SQLite.text(), // 'adjustment', 'sale', 'return', 'damage', 'restock'
135
+ quantity: State.SQLite.integer(), // Can be positive or negative
136
+ reason: State.SQLite.text({ nullable: true }),
137
+ referenceId: State.SQLite.text({ nullable: true }), // Order ID, adjustment ID, etc.
138
+ createdAt: State.SQLite.integer({ schema: Schema.DateFromNumber }),
139
+ },
140
+ }),
141
+
142
+ // Shopping cart (client-side state)
143
+ cart: State.SQLite.clientDocument({
144
+ name: 'cart',
145
+ schema: Schema.Struct({
146
+ items: Schema.Array(Schema.Struct({
147
+ productId: Schema.String,
148
+ quantity: Schema.Number,
149
+ addedAt: Schema.Date,
150
+ })),
151
+ discountCode: Schema.NullOr(Schema.String),
152
+ notes: Schema.String,
153
+ }),
154
+ default: {
155
+ id: SessionIdSymbol,
156
+ value: { items: [], discountCode: null, notes: '' }
157
+ },
158
+ }),
159
+ }
160
+
161
+ export const events = {
162
+ // Product management
163
+ productCreated: Events.synced({
164
+ name: 'v1.ProductCreated',
165
+ schema: Schema.Struct({
166
+ id: Schema.String,
167
+ sku: Schema.String,
168
+ name: Schema.String,
169
+ description: Schema.String,
170
+ price: Schema.Number, // In cents
171
+ currency: Schema.String,
172
+ createdAt: Schema.Date,
173
+ }),
174
+ }),
175
+
176
+ productUpdated: Events.synced({
177
+ name: 'v1.ProductUpdated',
178
+ schema: Schema.Struct({
179
+ id: Schema.String,
180
+ name: Schema.NullOr(Schema.String),
181
+ description: Schema.NullOr(Schema.String),
182
+ price: Schema.NullOr(Schema.Number),
183
+ updatedAt: Schema.Date,
184
+ }),
185
+ }),
186
+
187
+ productStockAdjusted: Events.synced({
188
+ name: 'v1.ProductStockAdjusted',
189
+ schema: Schema.Struct({
190
+ productId: Schema.String,
191
+ adjustment: Schema.Number, // Can be positive or negative
192
+ reason: Schema.String,
193
+ referenceId: Schema.NullOr(Schema.String),
194
+ createdAt: Schema.Date,
195
+ }),
196
+ }),
197
+
198
+ productDeactivated: Events.synced({
199
+ name: 'v1.ProductDeactivated',
200
+ schema: Schema.Struct({ id: Schema.String }),
201
+ }),
202
+
203
+ // Customer management
204
+ customerCreated: Events.synced({
205
+ name: 'v1.CustomerCreated',
206
+ schema: Schema.Struct({
207
+ id: Schema.String,
208
+ email: Schema.String,
209
+ firstName: Schema.NullOr(Schema.String),
210
+ lastName: Schema.NullOr(Schema.String),
211
+ createdAt: Schema.Date,
212
+ }),
213
+ }),
214
+
215
+ // Order lifecycle with state machine
216
+ orderCreated: Events.synced({
217
+ name: 'v1.OrderCreated',
218
+ schema: Schema.Struct({
219
+ id: Schema.String,
220
+ orderNumber: Schema.String,
221
+ customerId: Schema.String,
222
+ items: Schema.Array(Schema.Struct({
223
+ productId: Schema.String,
224
+ sku: Schema.String,
225
+ name: Schema.String,
226
+ quantity: Schema.Number,
227
+ unitPrice: Schema.Number,
228
+ })),
229
+ subtotal: Schema.Number,
230
+ total: Schema.Number,
231
+ shippingAddress: Schema.Object,
232
+ billingAddress: Schema.Object,
233
+ createdAt: Schema.Date,
234
+ }),
235
+ }),
236
+
237
+ orderPaymentReceived: Events.synced({
238
+ name: 'v1.OrderPaymentReceived',
239
+ schema: Schema.Struct({
240
+ orderId: Schema.String,
241
+ amount: Schema.Number,
242
+ paymentMethod: Schema.String,
243
+ transactionId: Schema.String,
244
+ paidAt: Schema.Date,
245
+ }),
246
+ }),
247
+
248
+ orderShipped: Events.synced({
249
+ name: 'v1.OrderShipped',
250
+ schema: Schema.Struct({
251
+ orderId: Schema.String,
252
+ trackingNumber: Schema.NullOr(Schema.String),
253
+ carrier: Schema.NullOr(Schema.String),
254
+ shippedAt: Schema.Date,
255
+ }),
256
+ }),
257
+
258
+ orderDelivered: Events.synced({
259
+ name: 'v1.OrderDelivered',
260
+ schema: Schema.Struct({
261
+ orderId: Schema.String,
262
+ deliveredAt: Schema.Date,
263
+ }),
264
+ }),
265
+
266
+ orderCancelled: Events.synced({
267
+ name: 'v1.OrderCancelled',
268
+ schema: Schema.Struct({
269
+ orderId: Schema.String,
270
+ reason: Schema.String,
271
+ cancelledAt: Schema.Date,
272
+ }),
273
+ }),
274
+
275
+ // Cart management (local)
276
+ cartUpdated: tables.cart.set,
277
+ }
278
+
279
+ // Materializers with business logic and constraints
280
+ const materializers = State.SQLite.materializers(events, {
281
+ // Product materializers
282
+ 'v1.ProductCreated': ({ id, sku, name, description, price, currency, createdAt }) =>
283
+ tables.products.insert({ id, sku, name, description, price, currency, createdAt, updatedAt: createdAt }),
284
+
285
+ 'v1.ProductUpdated': ({ id, name, description, price, updatedAt }) =>
286
+ tables.products.update({
287
+ name: name ?? undefined,
288
+ description: description ?? undefined,
289
+ price: price ?? undefined,
290
+ updatedAt
291
+ }).where({ id }),
292
+
293
+ 'v1.ProductStockAdjusted': ({ productId, adjustment, reason, referenceId, createdAt }) => [
294
+ // Record the inventory event
295
+ tables.inventoryEvents.insert({
296
+ id: crypto.randomUUID(),
297
+ productId,
298
+ type: 'adjustment',
299
+ quantity: adjustment,
300
+ reason,
301
+ referenceId,
302
+ createdAt
303
+ }),
304
+ // Update product stock (eventually consistent)
305
+ tables.products.update({
306
+ stock: Math.max(0, tables.products.select('stock').where({ id: productId }).scalar() + adjustment),
307
+ updatedAt: createdAt
308
+ }).where({ id: productId }),
309
+ ],
310
+
311
+ 'v1.ProductDeactivated': ({ id }) =>
312
+ tables.products.update({ isActive: false }).where({ id }),
313
+
314
+ // Customer materializers
315
+ 'v1.CustomerCreated': ({ id, email, firstName, lastName, createdAt }) =>
316
+ tables.customers.insert({ id, email, firstName, lastName, createdAt }),
317
+
318
+ // Order materializers with inventory reservation
319
+ 'v1.OrderCreated': ({ id, orderNumber, customerId, items, subtotal, total, shippingAddress, billingAddress, createdAt }) => [
320
+ // Create the order
321
+ tables.orders.insert({
322
+ id,
323
+ orderNumber,
324
+ customerId,
325
+ status: 'pending',
326
+ subtotal,
327
+ total,
328
+ shippingAddress: JSON.stringify(shippingAddress),
329
+ billingAddress: JSON.stringify(billingAddress),
330
+ createdAt,
331
+ updatedAt: createdAt
332
+ }),
333
+ // Create order items and reserve inventory
334
+ ...items.flatMap(item => [
335
+ tables.orderItems.insert({
336
+ id: crypto.randomUUID(),
337
+ orderId: id,
338
+ productId: item.productId,
339
+ sku: item.sku,
340
+ name: item.name,
341
+ quantity: item.quantity,
342
+ unitPrice: item.unitPrice,
343
+ totalPrice: item.quantity * item.unitPrice,
344
+ }),
345
+ // Reserve stock
346
+ tables.products.update({
347
+ reservedStock: tables.products.select('reservedStock').where({ id: item.productId }).scalar() + item.quantity
348
+ }).where({ id: item.productId }),
349
+ ]),
350
+ ],
351
+
352
+ 'v1.OrderPaymentReceived': ({ orderId, amount, paymentMethod, transactionId, paidAt }) =>
353
+ tables.orders.update({
354
+ status: 'paid',
355
+ paymentStatus: 'paid',
356
+ updatedAt: paidAt
357
+ }).where({ id: orderId }),
358
+
359
+ 'v1.OrderShipped': ({ orderId, trackingNumber, carrier, shippedAt }) => [
360
+ tables.orders.update({
361
+ status: 'shipped',
362
+ fulfillmentStatus: 'fulfilled',
363
+ shippedAt,
364
+ updatedAt: shippedAt
365
+ }).where({ id: orderId }),
366
+ // Convert reserved stock to actual stock reduction
367
+ ...tables.orderItems.select().where({ orderId }).map(item =>
368
+ tables.products.update({
369
+ stock: tables.products.select('stock').where({ id: item.productId }).scalar() - item.quantity,
370
+ reservedStock: tables.products.select('reservedStock').where({ id: item.productId }).scalar() - item.quantity
371
+ }).where({ id: item.productId })
372
+ ),
373
+ ],
374
+
375
+ 'v1.OrderDelivered': ({ orderId, deliveredAt }) =>
376
+ tables.orders.update({
377
+ status: 'delivered',
378
+ deliveredAt,
379
+ updatedAt: deliveredAt
380
+ }).where({ id: orderId }),
381
+
382
+ 'v1.OrderCancelled': ({ orderId, reason, cancelledAt }) => [
383
+ tables.orders.update({
384
+ status: 'cancelled',
385
+ cancelledAt,
386
+ notes: reason,
387
+ updatedAt: cancelledAt
388
+ }).where({ id: orderId }),
389
+ // Release reserved inventory
390
+ ...tables.orderItems.select().where({ orderId }).map(item =>
391
+ tables.products.update({
392
+ reservedStock: Math.max(0, tables.products.select('reservedStock').where({ id: item.productId }).scalar() - item.quantity)
393
+ }).where({ id: item.productId })
394
+ ),
395
+ ],
396
+ })
397
+
398
+ const state = State.SQLite.makeState({ tables, materializers })
399
+
400
+ export const schema = makeSchema({ events, state })
401
+
402
+ // Example queries for business intelligence:
403
+ //
404
+ // // Available products with real-time stock
405
+ // const availableProducts$ = queryDb(
406
+ // tables.products
407
+ // .select()
408
+ // .where({ isActive: true, deletedAt: null })
409
+ // .having(tables.products.column('stock').minus(tables.products.column('reservedStock')).gt(0))
410
+ // .orderBy('name'),
411
+ // { label: 'availableProducts' }
412
+ // )
413
+ //
414
+ // // Orders requiring fulfillment
415
+ // const pendingOrders$ = queryDb(
416
+ // tables.orders
417
+ // .select()
418
+ // .join(tables.customers, 'customerId', 'id')
419
+ // .where({
420
+ // 'orders.status': 'paid',
421
+ // 'orders.fulfillmentStatus': 'unfulfilled'
422
+ // })
423
+ // .orderBy('orders.createdAt'),
424
+ // { label: 'pendingOrders' }
425
+ // )
426
+ //
427
+ // // Low stock alerts
428
+ // const lowStockProducts$ = queryDb(
429
+ // tables.products
430
+ // .select()
431
+ // .where({ isActive: true })
432
+ // .having(tables.products.column('stock').minus(tables.products.column('reservedStock')).lt(10))
433
+ // .orderBy('stock'),
434
+ // { label: 'lowStockProducts' }
435
+ // )`