@livestore/cli 0.4.0 → 0.5.0-dev.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 (120) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/__tests__/fixtures/mock-config.d.ts +7 -9
  3. package/dist/__tests__/fixtures/mock-config.d.ts.map +1 -1
  4. package/dist/__tests__/fixtures/mock-config.js +6 -6
  5. package/dist/__tests__/fixtures/mock-config.js.map +1 -1
  6. package/dist/__tests__/sync-operations.test.js +20 -20
  7. package/dist/__tests__/sync-operations.test.js.map +1 -1
  8. package/dist/bin.js +6 -5
  9. package/dist/bin.js.map +1 -1
  10. package/dist/cli.d.ts +2 -23
  11. package/dist/cli.d.ts.map +1 -1
  12. package/dist/cli.js +1 -1
  13. package/dist/cli.js.map +1 -1
  14. package/dist/commands/__fixtures__/mcp-protocol-server.d.ts +2 -0
  15. package/dist/commands/__fixtures__/mcp-protocol-server.d.ts.map +1 -0
  16. package/dist/commands/__fixtures__/mcp-protocol-server.js +10 -0
  17. package/dist/commands/__fixtures__/mcp-protocol-server.js.map +1 -0
  18. package/dist/commands/import-export.d.ts +5 -19
  19. package/dist/commands/import-export.d.ts.map +1 -1
  20. package/dist/commands/import-export.js +12 -12
  21. package/dist/commands/import-export.js.map +1 -1
  22. package/dist/commands/mcp-coach.d.ts +8 -7
  23. package/dist/commands/mcp-coach.d.ts.map +1 -1
  24. package/dist/commands/mcp-coach.js +10 -10
  25. package/dist/commands/mcp-coach.js.map +1 -1
  26. package/dist/commands/mcp-protocols.d.ts +7 -0
  27. package/dist/commands/mcp-protocols.d.ts.map +1 -0
  28. package/dist/commands/mcp-protocols.js +12 -0
  29. package/dist/commands/mcp-protocols.js.map +1 -0
  30. package/dist/commands/mcp-protocols.test.d.ts +2 -0
  31. package/dist/commands/mcp-protocols.test.d.ts.map +1 -0
  32. package/dist/commands/mcp-protocols.test.js +60 -0
  33. package/dist/commands/mcp-protocols.test.js.map +1 -0
  34. package/dist/commands/mcp-stdio.test.d.ts +2 -0
  35. package/dist/commands/mcp-stdio.test.d.ts.map +1 -0
  36. package/dist/commands/mcp-stdio.test.js +79 -0
  37. package/dist/commands/mcp-stdio.test.js.map +1 -0
  38. package/dist/commands/mcp-tool-handlers.d.ts +1 -1
  39. package/dist/commands/mcp-tool-handlers.d.ts.map +1 -1
  40. package/dist/commands/mcp-tool-handlers.js +1 -0
  41. package/dist/commands/mcp-tool-handlers.js.map +1 -1
  42. package/dist/commands/mcp-tools-defs.d.ts +65 -64
  43. package/dist/commands/mcp-tools-defs.d.ts.map +1 -1
  44. package/dist/commands/mcp-tools-defs.js +59 -52
  45. package/dist/commands/mcp-tools-defs.js.map +1 -1
  46. package/dist/commands/mcp-tools-defs.test.d.ts +2 -0
  47. package/dist/commands/mcp-tools-defs.test.d.ts.map +1 -0
  48. package/dist/commands/mcp-tools-defs.test.js +11 -0
  49. package/dist/commands/mcp-tools-defs.test.js.map +1 -0
  50. package/dist/commands/mcp.d.ts +1 -3
  51. package/dist/commands/mcp.d.ts.map +1 -1
  52. package/dist/commands/mcp.js +5 -4
  53. package/dist/commands/mcp.js.map +1 -1
  54. package/dist/commands/new-project.d.ts +19 -27
  55. package/dist/commands/new-project.d.ts.map +1 -1
  56. package/dist/commands/new-project.js +17 -16
  57. package/dist/commands/new-project.js.map +1 -1
  58. package/dist/mcp-content/architecture.d.ts +1 -1
  59. package/dist/mcp-content/architecture.d.ts.map +1 -1
  60. package/dist/mcp-content/architecture.js +3 -3
  61. package/dist/mcp-content/features.d.ts +1 -1
  62. package/dist/mcp-content/features.d.ts.map +1 -1
  63. package/dist/mcp-content/features.js +1 -1
  64. package/dist/mcp-content/getting-started.d.ts +1 -1
  65. package/dist/mcp-content/getting-started.d.ts.map +1 -1
  66. package/dist/mcp-content/getting-started.js +37 -37
  67. package/dist/mcp-content/overview.d.ts +1 -1
  68. package/dist/mcp-content/overview.d.ts.map +1 -1
  69. package/dist/mcp-content/overview.js +6 -6
  70. package/dist/mcp-content/schemas/blog.d.ts +1 -1
  71. package/dist/mcp-content/schemas/blog.d.ts.map +1 -1
  72. package/dist/mcp-content/schemas/blog.js +25 -25
  73. package/dist/mcp-content/schemas/ecommerce.d.ts +1 -1
  74. package/dist/mcp-content/schemas/ecommerce.d.ts.map +1 -1
  75. package/dist/mcp-content/schemas/ecommerce.js +67 -67
  76. package/dist/mcp-content/schemas/social.d.ts +1 -1
  77. package/dist/mcp-content/schemas/social.d.ts.map +1 -1
  78. package/dist/mcp-content/schemas/social.js +48 -48
  79. package/dist/mcp-content/schemas/todo.d.ts +1 -1
  80. package/dist/mcp-content/schemas/todo.d.ts.map +1 -1
  81. package/dist/mcp-content/schemas/todo.js +35 -35
  82. package/dist/mcp-runtime/runtime.d.ts +2 -2
  83. package/dist/mcp-runtime/runtime.d.ts.map +1 -1
  84. package/dist/mcp-runtime/runtime.js +4 -4
  85. package/dist/mcp-runtime/runtime.js.map +1 -1
  86. package/dist/module-loader.d.ts +2 -2
  87. package/dist/module-loader.d.ts.map +1 -1
  88. package/dist/module-loader.js +6 -6
  89. package/dist/module-loader.js.map +1 -1
  90. package/dist/sync-operations.d.ts +24 -30
  91. package/dist/sync-operations.d.ts.map +1 -1
  92. package/dist/sync-operations.js +10 -12
  93. package/dist/sync-operations.js.map +1 -1
  94. package/package.json +30 -44
  95. package/src/__tests__/fixtures/mock-config.ts +8 -8
  96. package/src/__tests__/sync-operations.test.ts +21 -35
  97. package/src/bin.ts +7 -6
  98. package/src/cli.ts +1 -1
  99. package/src/commands/__fixtures__/mcp-protocol-server.ts +12 -0
  100. package/src/commands/import-export.ts +29 -29
  101. package/src/commands/mcp-coach.ts +12 -11
  102. package/src/commands/mcp-protocols.test.ts +76 -0
  103. package/src/commands/mcp-protocols.ts +12 -0
  104. package/src/commands/mcp-stdio.test.ts +93 -0
  105. package/src/commands/mcp-tool-handlers.ts +11 -8
  106. package/src/commands/mcp-tools-defs.test.ts +13 -0
  107. package/src/commands/mcp-tools-defs.ts +53 -51
  108. package/src/commands/mcp.ts +7 -4
  109. package/src/commands/new-project.ts +49 -36
  110. package/src/mcp-content/architecture.ts +3 -3
  111. package/src/mcp-content/features.ts +1 -1
  112. package/src/mcp-content/getting-started.ts +37 -37
  113. package/src/mcp-content/overview.ts +6 -6
  114. package/src/mcp-content/schemas/blog.ts +25 -25
  115. package/src/mcp-content/schemas/ecommerce.ts +67 -67
  116. package/src/mcp-content/schemas/social.ts +48 -48
  117. package/src/mcp-content/schemas/todo.ts +35 -35
  118. package/src/mcp-runtime/runtime.ts +7 -7
  119. package/src/module-loader.ts +10 -10
  120. package/src/sync-operations.ts +16 -16
@@ -22,7 +22,7 @@ export const tables = {
22
22
  deletedAt: State.SQLite.integer({ nullable: true, schema: Schema.DateFromNumber }),
23
23
  },
24
24
  }),
25
-
25
+
26
26
  categories: State.SQLite.table({
27
27
  name: 'categories',
28
28
  columns: {
@@ -37,7 +37,7 @@ export const tables = {
37
37
  createdAt: State.SQLite.integer({ schema: Schema.DateFromNumber }),
38
38
  },
39
39
  }),
40
-
40
+
41
41
  productCategories: State.SQLite.table({
42
42
  name: 'product_categories',
43
43
  columns: {
@@ -46,7 +46,7 @@ export const tables = {
46
46
  createdAt: State.SQLite.integer({ schema: Schema.DateFromNumber }),
47
47
  },
48
48
  }),
49
-
49
+
50
50
  customers: State.SQLite.table({
51
51
  name: 'customers',
52
52
  columns: {
@@ -59,7 +59,7 @@ export const tables = {
59
59
  lastOrderAt: State.SQLite.integer({ nullable: true, schema: Schema.DateFromNumber }),
60
60
  },
61
61
  }),
62
-
62
+
63
63
  addresses: State.SQLite.table({
64
64
  name: 'addresses',
65
65
  columns: {
@@ -78,7 +78,7 @@ export const tables = {
78
78
  isDefault: State.SQLite.boolean({ default: false }),
79
79
  },
80
80
  }),
81
-
81
+
82
82
  orders: State.SQLite.table({
83
83
  name: 'orders',
84
84
  columns: {
@@ -88,19 +88,19 @@ export const tables = {
88
88
  status: State.SQLite.text({ default: 'draft' }), // draft, pending, paid, processing, shipped, delivered, cancelled
89
89
  paymentStatus: State.SQLite.text({ default: 'pending' }), // pending, paid, failed, refunded
90
90
  fulfillmentStatus: State.SQLite.text({ default: 'unfulfilled' }), // unfulfilled, partial, fulfilled
91
-
91
+
92
92
  subtotal: State.SQLite.integer(), // In cents
93
93
  taxAmount: State.SQLite.integer({ default: 0 }),
94
94
  shippingAmount: State.SQLite.integer({ default: 0 }),
95
95
  discountAmount: State.SQLite.integer({ default: 0 }),
96
96
  total: State.SQLite.integer(),
97
97
  currency: State.SQLite.text({ default: 'USD' }),
98
-
98
+
99
99
  shippingAddress: State.SQLite.text(), // JSON
100
100
  billingAddress: State.SQLite.text(), // JSON
101
-
101
+
102
102
  notes: State.SQLite.text({ nullable: true }),
103
-
103
+
104
104
  createdAt: State.SQLite.integer({ schema: Schema.DateFromNumber }),
105
105
  updatedAt: State.SQLite.integer({ schema: Schema.DateFromNumber }),
106
106
  cancelledAt: State.SQLite.integer({ nullable: true, schema: Schema.DateFromNumber }),
@@ -108,7 +108,7 @@ export const tables = {
108
108
  deliveredAt: State.SQLite.integer({ nullable: true, schema: Schema.DateFromNumber }),
109
109
  },
110
110
  }),
111
-
111
+
112
112
  orderItems: State.SQLite.table({
113
113
  name: 'order_items',
114
114
  columns: {
@@ -124,7 +124,7 @@ export const tables = {
124
124
  fulfillmentStatus: State.SQLite.text({ default: 'unfulfilled' }),
125
125
  },
126
126
  }),
127
-
127
+
128
128
  // Inventory tracking with event sourcing
129
129
  inventoryEvents: State.SQLite.table({
130
130
  name: 'inventory_events',
@@ -138,7 +138,7 @@ export const tables = {
138
138
  createdAt: State.SQLite.integer({ schema: Schema.DateFromNumber }),
139
139
  },
140
140
  }),
141
-
141
+
142
142
  // Shopping cart (client-side state)
143
143
  cart: State.SQLite.clientDocument({
144
144
  name: 'cart',
@@ -151,8 +151,8 @@ export const tables = {
151
151
  discountCode: Schema.NullOr(Schema.String),
152
152
  notes: Schema.String,
153
153
  }),
154
- default: {
155
- id: SessionIdSymbol,
154
+ default: {
155
+ id: SessionIdSymbol,
156
156
  value: { items: [], discountCode: null, notes: '' }
157
157
  },
158
158
  }),
@@ -172,7 +172,7 @@ export const events = {
172
172
  createdAt: Schema.Date,
173
173
  }),
174
174
  }),
175
-
175
+
176
176
  productUpdated: Events.synced({
177
177
  name: 'v1.ProductUpdated',
178
178
  schema: Schema.Struct({
@@ -183,7 +183,7 @@ export const events = {
183
183
  updatedAt: Schema.Date,
184
184
  }),
185
185
  }),
186
-
186
+
187
187
  productStockAdjusted: Events.synced({
188
188
  name: 'v1.ProductStockAdjusted',
189
189
  schema: Schema.Struct({
@@ -194,12 +194,12 @@ export const events = {
194
194
  createdAt: Schema.Date,
195
195
  }),
196
196
  }),
197
-
197
+
198
198
  productDeactivated: Events.synced({
199
199
  name: 'v1.ProductDeactivated',
200
200
  schema: Schema.Struct({ id: Schema.String }),
201
201
  }),
202
-
202
+
203
203
  // Customer management
204
204
  customerCreated: Events.synced({
205
205
  name: 'v1.CustomerCreated',
@@ -211,7 +211,7 @@ export const events = {
211
211
  createdAt: Schema.Date,
212
212
  }),
213
213
  }),
214
-
214
+
215
215
  // Order lifecycle with state machine
216
216
  orderCreated: Events.synced({
217
217
  name: 'v1.OrderCreated',
@@ -233,7 +233,7 @@ export const events = {
233
233
  createdAt: Schema.Date,
234
234
  }),
235
235
  }),
236
-
236
+
237
237
  orderPaymentReceived: Events.synced({
238
238
  name: 'v1.OrderPaymentReceived',
239
239
  schema: Schema.Struct({
@@ -244,7 +244,7 @@ export const events = {
244
244
  paidAt: Schema.Date,
245
245
  }),
246
246
  }),
247
-
247
+
248
248
  orderShipped: Events.synced({
249
249
  name: 'v1.OrderShipped',
250
250
  schema: Schema.Struct({
@@ -254,7 +254,7 @@ export const events = {
254
254
  shippedAt: Schema.Date,
255
255
  }),
256
256
  }),
257
-
257
+
258
258
  orderDelivered: Events.synced({
259
259
  name: 'v1.OrderDelivered',
260
260
  schema: Schema.Struct({
@@ -262,7 +262,7 @@ export const events = {
262
262
  deliveredAt: Schema.Date,
263
263
  }),
264
264
  }),
265
-
265
+
266
266
  orderCancelled: Events.synced({
267
267
  name: 'v1.OrderCancelled',
268
268
  schema: Schema.Struct({
@@ -271,7 +271,7 @@ export const events = {
271
271
  cancelledAt: Schema.Date,
272
272
  }),
273
273
  }),
274
-
274
+
275
275
  // Cart management (local)
276
276
  cartUpdated: tables.cart.set,
277
277
  }
@@ -281,58 +281,58 @@ const materializers = State.SQLite.materializers(events, {
281
281
  // Product materializers
282
282
  'v1.ProductCreated': ({ id, sku, name, description, price, currency, createdAt }) =>
283
283
  tables.products.insert({ id, sku, name, description, price, currency, createdAt, updatedAt: createdAt }),
284
-
284
+
285
285
  'v1.ProductUpdated': ({ id, name, description, price, updatedAt }) =>
286
- tables.products.update({
286
+ tables.products.update({
287
287
  name: name ?? undefined,
288
- description: description ?? undefined,
288
+ description: description ?? undefined,
289
289
  price: price ?? undefined,
290
- updatedAt
290
+ updatedAt
291
291
  }).where({ id }),
292
-
292
+
293
293
  'v1.ProductStockAdjusted': ({ productId, adjustment, reason, referenceId, createdAt }) => [
294
294
  // Record the inventory event
295
- tables.inventoryEvents.insert({
295
+ tables.inventoryEvents.insert({
296
296
  id: crypto.randomUUID(),
297
- productId,
297
+ productId,
298
298
  type: 'adjustment',
299
- quantity: adjustment,
300
- reason,
301
- referenceId,
302
- createdAt
299
+ quantity: adjustment,
300
+ reason,
301
+ referenceId,
302
+ createdAt
303
303
  }),
304
304
  // Update product stock (eventually consistent)
305
- tables.products.update({
305
+ tables.products.update({
306
306
  stock: Math.max(0, tables.products.select('stock').where({ id: productId }).scalar() + adjustment),
307
307
  updatedAt: createdAt
308
308
  }).where({ id: productId }),
309
309
  ],
310
-
310
+
311
311
  'v1.ProductDeactivated': ({ id }) =>
312
312
  tables.products.update({ isActive: false }).where({ id }),
313
-
313
+
314
314
  // Customer materializers
315
315
  'v1.CustomerCreated': ({ id, email, firstName, lastName, createdAt }) =>
316
316
  tables.customers.insert({ id, email, firstName, lastName, createdAt }),
317
-
317
+
318
318
  // Order materializers with inventory reservation
319
319
  'v1.OrderCreated': ({ id, orderNumber, customerId, items, subtotal, total, shippingAddress, billingAddress, createdAt }) => [
320
320
  // Create the order
321
- tables.orders.insert({
322
- id,
323
- orderNumber,
324
- customerId,
321
+ tables.orders.insert({
322
+ id,
323
+ orderNumber,
324
+ customerId,
325
325
  status: 'pending',
326
- subtotal,
327
- total,
326
+ subtotal,
327
+ total,
328
328
  shippingAddress: JSON.stringify(shippingAddress),
329
329
  billingAddress: JSON.stringify(billingAddress),
330
- createdAt,
331
- updatedAt: createdAt
330
+ createdAt,
331
+ updatedAt: createdAt
332
332
  }),
333
333
  // Create order items and reserve inventory
334
334
  ...items.flatMap(item => [
335
- tables.orderItems.insert({
335
+ tables.orderItems.insert({
336
336
  id: crypto.randomUUID(),
337
337
  orderId: id,
338
338
  productId: item.productId,
@@ -343,52 +343,52 @@ const materializers = State.SQLite.materializers(events, {
343
343
  totalPrice: item.quantity * item.unitPrice,
344
344
  }),
345
345
  // Reserve stock
346
- tables.products.update({
346
+ tables.products.update({
347
347
  reservedStock: tables.products.select('reservedStock').where({ id: item.productId }).scalar() + item.quantity
348
348
  }).where({ id: item.productId }),
349
349
  ]),
350
350
  ],
351
-
351
+
352
352
  'v1.OrderPaymentReceived': ({ orderId, amount, paymentMethod, transactionId, paidAt }) =>
353
- tables.orders.update({
354
- status: 'paid',
353
+ tables.orders.update({
354
+ status: 'paid',
355
355
  paymentStatus: 'paid',
356
- updatedAt: paidAt
356
+ updatedAt: paidAt
357
357
  }).where({ id: orderId }),
358
-
358
+
359
359
  'v1.OrderShipped': ({ orderId, trackingNumber, carrier, shippedAt }) => [
360
- tables.orders.update({
360
+ tables.orders.update({
361
361
  status: 'shipped',
362
362
  fulfillmentStatus: 'fulfilled',
363
363
  shippedAt,
364
- updatedAt: shippedAt
364
+ updatedAt: shippedAt
365
365
  }).where({ id: orderId }),
366
366
  // Convert reserved stock to actual stock reduction
367
- ...tables.orderItems.select().where({ orderId }).map(item =>
368
- tables.products.update({
367
+ ...tables.orderItems.select().where({ orderId }).map(item =>
368
+ tables.products.update({
369
369
  stock: tables.products.select('stock').where({ id: item.productId }).scalar() - item.quantity,
370
370
  reservedStock: tables.products.select('reservedStock').where({ id: item.productId }).scalar() - item.quantity
371
371
  }).where({ id: item.productId })
372
372
  ),
373
373
  ],
374
-
374
+
375
375
  'v1.OrderDelivered': ({ orderId, deliveredAt }) =>
376
- tables.orders.update({
376
+ tables.orders.update({
377
377
  status: 'delivered',
378
378
  deliveredAt,
379
- updatedAt: deliveredAt
379
+ updatedAt: deliveredAt
380
380
  }).where({ id: orderId }),
381
-
381
+
382
382
  'v1.OrderCancelled': ({ orderId, reason, cancelledAt }) => [
383
- tables.orders.update({
383
+ tables.orders.update({
384
384
  status: 'cancelled',
385
385
  cancelledAt,
386
386
  notes: reason,
387
- updatedAt: cancelledAt
387
+ updatedAt: cancelledAt
388
388
  }).where({ id: orderId }),
389
389
  // Release reserved inventory
390
- ...tables.orderItems.select().where({ orderId }).map(item =>
391
- tables.products.update({
390
+ ...tables.orderItems.select().where({ orderId }).map(item =>
391
+ tables.products.update({
392
392
  reservedStock: Math.max(0, tables.products.select('reservedStock').where({ id: item.productId }).scalar() - item.quantity)
393
393
  }).where({ id: item.productId })
394
394
  ),
@@ -416,7 +416,7 @@ export const schema = makeSchema({ events, state })
416
416
  // tables.orders
417
417
  // .select()
418
418
  // .join(tables.customers, 'customerId', 'id')
419
- // .where({
419
+ // .where({
420
420
  // 'orders.status': 'paid',
421
421
  // 'orders.fulfillmentStatus': 'unfulfilled'
422
422
  // })
@@ -19,7 +19,7 @@ export const tables = {
19
19
  allowsFollowers: State.SQLite.boolean({ default: true }),
20
20
  },
21
21
  }),
22
-
22
+
23
23
  posts: State.SQLite.table({
24
24
  name: 'posts',
25
25
  columns: {
@@ -34,7 +34,7 @@ export const tables = {
34
34
  deletedAt: State.SQLite.integer({ nullable: true, schema: Schema.DateFromNumber }),
35
35
  },
36
36
  }),
37
-
37
+
38
38
  follows: State.SQLite.table({
39
39
  name: 'follows',
40
40
  columns: {
@@ -45,7 +45,7 @@ export const tables = {
45
45
  approvedAt: State.SQLite.integer({ nullable: true, schema: Schema.DateFromNumber }),
46
46
  },
47
47
  }),
48
-
48
+
49
49
  likes: State.SQLite.table({
50
50
  name: 'likes',
51
51
  columns: {
@@ -54,7 +54,7 @@ export const tables = {
54
54
  createdAt: State.SQLite.integer({ schema: Schema.DateFromNumber }),
55
55
  },
56
56
  }),
57
-
57
+
58
58
  // Aggregate tables for performance (eventually consistent)
59
59
  postStats: State.SQLite.table({
60
60
  name: 'post_stats',
@@ -66,9 +66,9 @@ export const tables = {
66
66
  lastUpdated: State.SQLite.integer({ schema: Schema.DateFromNumber }),
67
67
  },
68
68
  }),
69
-
69
+
70
70
  userStats: State.SQLite.table({
71
- name: 'user_stats',
71
+ name: 'user_stats',
72
72
  columns: {
73
73
  userId: State.SQLite.text({ primaryKey: true }),
74
74
  followerCount: State.SQLite.integer({ default: 0 }),
@@ -77,7 +77,7 @@ export const tables = {
77
77
  lastUpdated: State.SQLite.integer({ schema: Schema.DateFromNumber }),
78
78
  },
79
79
  }),
80
-
80
+
81
81
  // Client-side state for UI
82
82
  feedState: State.SQLite.clientDocument({
83
83
  name: 'feedState',
@@ -86,8 +86,8 @@ export const tables = {
86
86
  lastRefresh: Schema.Date,
87
87
  scrollPosition: Schema.Number,
88
88
  }),
89
- default: {
90
- id: SessionIdSymbol,
89
+ default: {
90
+ id: SessionIdSymbol,
91
91
  value: { currentFeed: 'home', lastRefresh: new Date(), scrollPosition: 0 }
92
92
  },
93
93
  }),
@@ -105,7 +105,7 @@ export const events = {
105
105
  createdAt: Schema.Date,
106
106
  }),
107
107
  }),
108
-
108
+
109
109
  userProfileUpdated: Events.synced({
110
110
  name: 'v1.UserProfileUpdated',
111
111
  schema: Schema.Struct({
@@ -115,7 +115,7 @@ export const events = {
115
115
  avatarUrl: Schema.NullOr(Schema.String),
116
116
  }),
117
117
  }),
118
-
118
+
119
119
  userPrivacySettingsChanged: Events.synced({
120
120
  name: 'v1.UserPrivacySettingsChanged',
121
121
  schema: Schema.Struct({
@@ -124,7 +124,7 @@ export const events = {
124
124
  allowsFollowers: Schema.Boolean,
125
125
  }),
126
126
  }),
127
-
127
+
128
128
  userLastActiveUpdated: Events.synced({
129
129
  name: 'v1.UserLastActiveUpdated',
130
130
  schema: Schema.Struct({
@@ -132,7 +132,7 @@ export const events = {
132
132
  lastActiveAt: Schema.Date,
133
133
  }),
134
134
  }),
135
-
135
+
136
136
  // Post events
137
137
  postCreated: Events.synced({
138
138
  name: 'v1.PostCreated',
@@ -146,7 +146,7 @@ export const events = {
146
146
  createdAt: Schema.Date,
147
147
  }),
148
148
  }),
149
-
149
+
150
150
  postEdited: Events.synced({
151
151
  name: 'v1.PostEdited',
152
152
  schema: Schema.Struct({
@@ -155,7 +155,7 @@ export const events = {
155
155
  editedAt: Schema.Date,
156
156
  }),
157
157
  }),
158
-
158
+
159
159
  postDeleted: Events.synced({
160
160
  name: 'v1.PostDeleted',
161
161
  schema: Schema.Struct({
@@ -163,7 +163,7 @@ export const events = {
163
163
  deletedAt: Schema.Date,
164
164
  }),
165
165
  }),
166
-
166
+
167
167
  // Social interaction events
168
168
  followRequested: Events.synced({
169
169
  name: 'v1.FollowRequested',
@@ -173,7 +173,7 @@ export const events = {
173
173
  createdAt: Schema.Date,
174
174
  }),
175
175
  }),
176
-
176
+
177
177
  followApproved: Events.synced({
178
178
  name: 'v1.FollowApproved',
179
179
  schema: Schema.Struct({
@@ -182,7 +182,7 @@ export const events = {
182
182
  approvedAt: Schema.Date,
183
183
  }),
184
184
  }),
185
-
185
+
186
186
  unfollowed: Events.synced({
187
187
  name: 'v1.Unfollowed',
188
188
  schema: Schema.Struct({
@@ -190,7 +190,7 @@ export const events = {
190
190
  followingId: Schema.String,
191
191
  }),
192
192
  }),
193
-
193
+
194
194
  postLiked: Events.synced({
195
195
  name: 'v1.PostLiked',
196
196
  schema: Schema.Struct({
@@ -199,7 +199,7 @@ export const events = {
199
199
  createdAt: Schema.Date,
200
200
  }),
201
201
  }),
202
-
202
+
203
203
  postUnliked: Events.synced({
204
204
  name: 'v1.PostUnliked',
205
205
  schema: Schema.Struct({
@@ -207,7 +207,7 @@ export const events = {
207
207
  postId: Schema.String,
208
208
  }),
209
209
  }),
210
-
210
+
211
211
  // Local UI state
212
212
  feedStateUpdated: tables.feedState.set,
213
213
  }
@@ -219,85 +219,85 @@ const materializers = State.SQLite.materializers(events, {
219
219
  tables.users.insert({ id, username, email, displayName, createdAt }),
220
220
  tables.userStats.insert({ userId: id, lastUpdated: createdAt }),
221
221
  ],
222
-
222
+
223
223
  'v1.UserProfileUpdated': ({ id, displayName, bio, avatarUrl }) =>
224
224
  tables.users.update({ displayName, bio, avatarUrl }).where({ id }),
225
-
225
+
226
226
  'v1.UserPrivacySettingsChanged': ({ id, isPrivate, allowsFollowers }) =>
227
227
  tables.users.update({ isPrivate, allowsFollowers }).where({ id }),
228
-
228
+
229
229
  'v1.UserLastActiveUpdated': ({ id, lastActiveAt }) =>
230
230
  tables.users.update({ lastActiveAt }).where({ id }),
231
-
231
+
232
232
  // Post materializers
233
233
  'v1.PostCreated': ({ id, authorId, content, mediaUrls, replyToId, visibility, createdAt }) => [
234
- tables.posts.insert({
235
- id,
236
- authorId,
237
- content,
234
+ tables.posts.insert({
235
+ id,
236
+ authorId,
237
+ content,
238
238
  mediaUrls: mediaUrls ? JSON.stringify(mediaUrls) : null,
239
- replyToId,
240
- visibility,
241
- createdAt
239
+ replyToId,
240
+ visibility,
241
+ createdAt
242
242
  }),
243
243
  tables.postStats.insert({ postId: id, lastUpdated: createdAt }),
244
244
  // Update user post count
245
- tables.userStats.update({
245
+ tables.userStats.update({
246
246
  postCount: tables.userStats.select('postCount').where({ userId: authorId }).scalar() + 1,
247
247
  lastUpdated: createdAt
248
248
  }).where({ userId: authorId }),
249
249
  ],
250
-
250
+
251
251
  'v1.PostEdited': ({ id, content, editedAt }) =>
252
252
  tables.posts.update({ content, editedAt }).where({ id }),
253
-
253
+
254
254
  'v1.PostDeleted': ({ id, deletedAt }) =>
255
255
  tables.posts.update({ deletedAt }).where({ id }),
256
-
256
+
257
257
  // Follow materializers
258
258
  'v1.FollowRequested': ({ followerId, followingId, createdAt }) =>
259
259
  tables.follows.insert({ followerId, followingId, status: 'pending', createdAt }),
260
-
260
+
261
261
  'v1.FollowApproved': ({ followerId, followingId, approvedAt }) => [
262
262
  tables.follows.update({ status: 'active', approvedAt }).where({ followerId, followingId }),
263
263
  // Update follower counts
264
- tables.userStats.update({
264
+ tables.userStats.update({
265
265
  followerCount: tables.userStats.select('followerCount').where({ userId: followingId }).scalar() + 1,
266
266
  lastUpdated: approvedAt
267
267
  }).where({ userId: followingId }),
268
- tables.userStats.update({
268
+ tables.userStats.update({
269
269
  followingCount: tables.userStats.select('followingCount').where({ userId: followerId }).scalar() + 1,
270
270
  lastUpdated: approvedAt
271
271
  }).where({ userId: followerId }),
272
272
  ],
273
-
273
+
274
274
  'v1.Unfollowed': ({ followerId, followingId }) => [
275
275
  tables.follows.delete().where({ followerId, followingId }),
276
276
  // Update follower counts (eventual consistency)
277
- tables.userStats.update({
277
+ tables.userStats.update({
278
278
  followerCount: Math.max(0, tables.userStats.select('followerCount').where({ userId: followingId }).scalar() - 1),
279
279
  lastUpdated: new Date()
280
280
  }).where({ userId: followingId }),
281
- tables.userStats.update({
281
+ tables.userStats.update({
282
282
  followingCount: Math.max(0, tables.userStats.select('followingCount').where({ userId: followerId }).scalar() - 1),
283
283
  lastUpdated: new Date()
284
284
  }).where({ userId: followerId }),
285
285
  ],
286
-
286
+
287
287
  // Like materializers (idempotent)
288
288
  'v1.PostLiked': ({ userId, postId, createdAt }) => [
289
289
  tables.likes.insert({ userId, postId, createdAt }),
290
290
  // Update like count
291
- tables.postStats.update({
291
+ tables.postStats.update({
292
292
  likeCount: tables.postStats.select('likeCount').where({ postId }).scalar() + 1,
293
293
  lastUpdated: createdAt
294
294
  }).where({ postId }),
295
295
  ],
296
-
296
+
297
297
  'v1.PostUnliked': ({ userId, postId }) => [
298
298
  tables.likes.delete().where({ userId, postId }),
299
299
  // Update like count
300
- tables.postStats.update({
300
+ tables.postStats.update({
301
301
  likeCount: Math.max(0, tables.postStats.select('likeCount').where({ postId }).scalar() - 1),
302
302
  lastUpdated: new Date()
303
303
  }).where({ postId }),
@@ -317,8 +317,8 @@ export const schema = makeSchema({ events, state })
317
317
  // .join(tables.follows, 'authorId', 'followingId')
318
318
  // .join(tables.users, 'authorId', 'id')
319
319
  // .leftJoin(tables.postStats, 'id', 'postId')
320
- // .where({
321
- // followerId: userId,
320
+ // .where({
321
+ // followerId: userId,
322
322
  // 'follows.status': 'active',
323
323
  // 'posts.deletedAt': null,
324
324
  // 'posts.visibility': ['public', 'followers']