@livestore/cli 0.4.0 → 0.5.0-dev.1

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 (125) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/__tests__/fixtures/mock-config.d.ts +8 -9
  3. package/dist/__tests__/fixtures/mock-config.d.ts.map +1 -1
  4. package/dist/__tests__/fixtures/mock-config.js +18 -8
  5. package/dist/__tests__/fixtures/mock-config.js.map +1 -1
  6. package/dist/__tests__/sync-operations.test.js +32 -22
  7. package/dist/__tests__/sync-operations.test.js.map +1 -1
  8. package/dist/bin.js +7 -6
  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 +4 -4
  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 +13 -13
  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 +11 -11
  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 +9 -8
  41. package/dist/commands/mcp-tool-handlers.js.map +1 -1
  42. package/dist/commands/mcp-tools-defs.d.ts +74 -73
  43. package/dist/commands/mcp-tools-defs.d.ts.map +1 -1
  44. package/dist/commands/mcp-tools-defs.js +64 -57
  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 +15 -14
  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 +19 -18
  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 +5 -5
  83. package/dist/mcp-runtime/runtime.d.ts.map +1 -1
  84. package/dist/mcp-runtime/runtime.js +5 -5
  85. package/dist/mcp-runtime/runtime.js.map +1 -1
  86. package/dist/mod.js +1 -1
  87. package/dist/module-loader.d.ts +2 -2
  88. package/dist/module-loader.d.ts.map +1 -1
  89. package/dist/module-loader.js +8 -8
  90. package/dist/module-loader.js.map +1 -1
  91. package/dist/package-manager.d.ts +3 -3
  92. package/dist/package-manager.d.ts.map +1 -1
  93. package/dist/package-manager.test.js +1 -1
  94. package/dist/sync-operations.d.ts +24 -30
  95. package/dist/sync-operations.d.ts.map +1 -1
  96. package/dist/sync-operations.js +15 -15
  97. package/dist/sync-operations.js.map +1 -1
  98. package/package.json +45 -59
  99. package/src/__tests__/fixtures/.gitignore +1 -0
  100. package/src/__tests__/fixtures/mock-config.ts +21 -10
  101. package/src/__tests__/sync-operations.test.ts +36 -35
  102. package/src/bin.ts +7 -6
  103. package/src/cli.ts +1 -1
  104. package/src/commands/__fixtures__/mcp-protocol-server.ts +12 -0
  105. package/src/commands/import-export.ts +29 -29
  106. package/src/commands/mcp-coach.ts +13 -12
  107. package/src/commands/mcp-protocols.test.ts +76 -0
  108. package/src/commands/mcp-protocols.ts +12 -0
  109. package/src/commands/mcp-stdio.test.ts +93 -0
  110. package/src/commands/mcp-tool-handlers.ts +11 -8
  111. package/src/commands/mcp-tools-defs.test.ts +13 -0
  112. package/src/commands/mcp-tools-defs.ts +57 -55
  113. package/src/commands/mcp.ts +9 -4
  114. package/src/commands/new-project.ts +50 -37
  115. package/src/mcp-content/architecture.ts +3 -3
  116. package/src/mcp-content/features.ts +1 -1
  117. package/src/mcp-content/getting-started.ts +37 -37
  118. package/src/mcp-content/overview.ts +6 -6
  119. package/src/mcp-content/schemas/blog.ts +25 -25
  120. package/src/mcp-content/schemas/ecommerce.ts +67 -67
  121. package/src/mcp-content/schemas/social.ts +48 -48
  122. package/src/mcp-content/schemas/todo.ts +35 -35
  123. package/src/mcp-runtime/runtime.ts +7 -7
  124. package/src/module-loader.ts +11 -12
  125. package/src/sync-operations.ts +26 -19
@@ -1,4 +1,6 @@
1
- import { Schema, Tool, Toolkit } from '@livestore/utils/effect'
1
+ import { Tool, Toolkit } from 'effect/ai'
2
+
3
+ import { Schema } from '@livestore/utils/effect'
2
4
 
3
5
  import { coachTool } from './mcp-coach.ts'
4
6
 
@@ -8,32 +10,32 @@ export const livestoreToolkit = Toolkit.make(
8
10
  Tool.make('livestore_generate_schema', {
9
11
  description:
10
12
  'Generate a LiveStore schema for a specific use case. Choose from predefined types (todo, blog, social, ecommerce) or request a custom schema by providing a description.',
11
- parameters: {
12
- schemaType: Schema.String.annotations({
13
+ parameters: Schema.Struct({
14
+ schemaType: Schema.String.annotate({
13
15
  description: "Schema type: 'todo', 'blog', 'social', 'ecommerce', or 'custom'",
14
16
  }),
15
17
  customDescription: Schema.optional(
16
- Schema.String.annotations({
18
+ Schema.String.annotate({
17
19
  description:
18
20
  "For custom schemas: describe your data model needs (e.g., 'user management system with roles and permissions')",
19
21
  }),
20
22
  ),
21
- },
23
+ }),
22
24
  success: Schema.Struct({
23
- schemaCode: Schema.String.annotations({ description: 'The generated LiveStore schema TypeScript code' }),
24
- explanation: Schema.String.annotations({ description: 'Brief explanation of the schema structure' }),
25
+ schemaCode: Schema.String.annotate({ description: 'The generated LiveStore schema TypeScript code' }),
26
+ explanation: Schema.String.annotate({ description: 'Brief explanation of the schema structure' }),
25
27
  }),
26
28
  }),
27
29
 
28
30
  Tool.make('livestore_get_example_schema', {
29
31
  description:
30
32
  'Get a complete example LiveStore schema with TypeScript code. Returns ready-to-use schema definitions for common application types.',
31
- parameters: {
32
- type: Schema.String.annotations({ description: "Example type: 'todo', 'blog', 'social', or 'ecommerce'" }),
33
- },
33
+ parameters: Schema.Struct({
34
+ type: Schema.String.annotate({ description: "Example type: 'todo', 'blog', 'social', or 'ecommerce'" }),
35
+ }),
34
36
  success: Schema.Struct({
35
- schemaCode: Schema.String.annotations({ description: 'The complete LiveStore schema code' }),
36
- description: Schema.String.annotations({ description: 'Description of what this schema models' }),
37
+ schemaCode: Schema.String.annotate({ description: 'The complete LiveStore schema code' }),
38
+ description: Schema.String.annotate({ description: 'Description of what this schema models' }),
37
39
  }),
38
40
  })
39
41
  .annotate(Tool.Readonly, true)
@@ -86,27 +88,27 @@ Returns on success:
86
88
  "eventNames": ["<event-name-1>", "<event-name-2>", "..."]
87
89
  }
88
90
  }`,
89
- parameters: {
90
- configPath: Schema.String.annotations({
91
+ parameters: Schema.Struct({
92
+ configPath: Schema.String.annotate({
91
93
  description: 'Path to a module that exports named variables: schema and syncBackend',
92
94
  }),
93
- storeId: Schema.String.annotations({ description: 'Required store id for the LiveStore instance.' }),
95
+ storeId: Schema.String.annotate({ description: 'Required store id for the LiveStore instance.' }),
94
96
  clientId: Schema.optional(
95
- Schema.String.annotations({ description: 'Optional client id for the LiveStore instance.' }),
97
+ Schema.String.annotate({ description: 'Optional client id for the LiveStore instance.' }),
96
98
  ),
97
99
  sessionId: Schema.optional(
98
- Schema.String.annotations({ description: 'Optional session id for the LiveStore instance.' }),
100
+ Schema.String.annotate({ description: 'Optional session id for the LiveStore instance.' }),
99
101
  ),
100
- },
102
+ }),
101
103
  success: Schema.Struct({
102
104
  storeId: Schema.String,
103
105
  clientId: Schema.String,
104
106
  sessionId: Schema.String,
105
107
  schemaInfo: Schema.Struct({
106
- tableNames: Schema.Array(Schema.String).annotations({
108
+ tableNames: Schema.Array(Schema.String).annotate({
107
109
  description: 'Non-system table names in the connected schema',
108
110
  }),
109
- eventNames: Schema.Array(Schema.String).annotations({
111
+ eventNames: Schema.Array(Schema.String).annotate({
110
112
  description: 'Canonical event names defined by the connected schema',
111
113
  }),
112
114
  }),
@@ -137,18 +139,18 @@ Returns on success:
137
139
  "rows": [{ "col": "value" }],
138
140
  "rowCount": 1
139
141
  }`,
140
- parameters: {
141
- sql: Schema.String.annotations({ description: 'The SQL query to execute' }),
142
- bindValues: Schema.Union(
143
- Schema.Array(Schema.JsonValue),
144
- Schema.Record({ key: Schema.String, value: Schema.JsonValue }),
145
- ).annotations({
142
+ parameters: Schema.Struct({
143
+ sql: Schema.String.annotate({ description: 'The SQL query to execute' }),
144
+ bindValues: Schema.Union([
145
+ Schema.Array(Schema.Json),
146
+ Schema.Record(Schema.String, Schema.Json),
147
+ ]).annotate({
146
148
  description: 'Bind values for the SQL query (array or record). Record keys must not start with $.',
147
149
  }),
148
- },
150
+ }),
149
151
  success: Schema.Struct({
150
- rows: Schema.Array(Schema.Record({ key: Schema.String, value: Schema.JsonValue })),
151
- rowCount: Schema.Number,
152
+ rows: Schema.Array(Schema.Record(Schema.String, Schema.Json)),
153
+ rowCount: Schema.Finite,
152
154
  }),
153
155
  }).annotate(Tool.Destructive, false),
154
156
 
@@ -176,17 +178,17 @@ Example parameters:
176
178
 
177
179
  Returns on success:
178
180
  { "committed": 1 }`,
179
- parameters: {
181
+ parameters: Schema.Struct({
180
182
  events: Schema.Array(
181
183
  Schema.Struct({
182
- name: Schema.String.annotations({ description: 'The name of the event' }),
183
- args: Schema.JsonValue.annotations({
184
+ name: Schema.String.annotate({ description: 'The name of the event' }),
185
+ args: Schema.Json.annotate({
184
186
  description: 'The arguments for the event as a non-stringified JSON value',
185
187
  }),
186
188
  }),
187
189
  ),
188
- },
189
- success: Schema.Struct({ committed: Schema.Number }),
190
+ }),
191
+ success: Schema.Struct({ committed: Schema.Finite }),
190
192
  }).annotate(Tool.Destructive, true),
191
193
 
192
194
  Tool.make('livestore_instance_status', {
@@ -205,18 +207,18 @@ Returns when not connected:
205
207
  {
206
208
  "_tag": "disconnected"
207
209
  }`,
208
- parameters: {},
209
- success: Schema.Union(
210
+ parameters: Tool.EmptyParams,
211
+ success: Schema.Union([
210
212
  Schema.TaggedStruct('connected', {
211
213
  storeId: Schema.String,
212
214
  clientId: Schema.String,
213
215
  sessionId: Schema.String,
214
- tableCounts: Schema.Record({ key: Schema.String, value: Schema.Number }).annotations({
216
+ tableCounts: Schema.Record(Schema.String, Schema.Finite).annotate({
215
217
  description: 'Tables in the LiveStore instance with their row count',
216
218
  }),
217
219
  }),
218
220
  Schema.TaggedStruct('disconnected', {}),
219
- ),
221
+ ]),
220
222
  }).annotate(Tool.Readonly, true),
221
223
 
222
224
  Tool.make('livestore_instance_disconnect', {
@@ -224,7 +226,7 @@ Returns when not connected:
224
226
 
225
227
  Example success:
226
228
  { "_tag": "disconnected" }`,
227
- parameters: {},
229
+ parameters: Tool.EmptyParams,
228
230
  success: Schema.TaggedStruct('disconnected', {}),
229
231
  }),
230
232
 
@@ -253,18 +255,18 @@ Returns on success:
253
255
  "exportedAt": "2024-01-15T10:30:00.000Z",
254
256
  "data": { "version": 1, "storeId": "my-store", ... }
255
257
  }`,
256
- parameters: {
257
- configPath: Schema.String.annotations({
258
+ parameters: Schema.Struct({
259
+ configPath: Schema.String.annotate({
258
260
  description: 'Path to a module that exports schema and syncBackend',
259
261
  }),
260
- storeId: Schema.String.annotations({ description: 'Store identifier' }),
261
- clientId: Schema.optional(Schema.String.annotations({ description: 'Client identifier (default: mcp-export)' })),
262
- },
262
+ storeId: Schema.String.annotate({ description: 'Store identifier' }),
263
+ clientId: Schema.optional(Schema.String.annotate({ description: 'Client identifier (default: mcp-export)' })),
264
+ }),
263
265
  success: Schema.Struct({
264
266
  storeId: Schema.String,
265
- eventCount: Schema.Number,
267
+ eventCount: Schema.Finite,
266
268
  exportedAt: Schema.String,
267
- data: Schema.JsonValue.annotations({ description: 'The export file data (can be saved or passed to import)' }),
269
+ data: Schema.Json.annotate({ description: 'The export file data (can be saved or passed to import)' }),
268
270
  }),
269
271
  }).annotate(Tool.Readonly, true),
270
272
 
@@ -295,23 +297,23 @@ Returns on success:
295
297
  "eventCount": 127,
296
298
  "dryRun": false
297
299
  }`,
298
- parameters: {
299
- configPath: Schema.String.annotations({
300
+ parameters: Schema.Struct({
301
+ configPath: Schema.String.annotate({
300
302
  description: 'Path to a module that exports schema and syncBackend',
301
303
  }),
302
- storeId: Schema.String.annotations({ description: 'Store identifier' }),
303
- clientId: Schema.optional(Schema.String.annotations({ description: 'Client identifier (default: mcp-import)' })),
304
- data: Schema.JsonValue.annotations({
304
+ storeId: Schema.String.annotate({ description: 'Store identifier' }),
305
+ clientId: Schema.optional(Schema.String.annotate({ description: 'Client identifier (default: mcp-import)' })),
306
+ data: Schema.Json.annotate({
305
307
  description: 'The export data to import (from livestore_sync_export or a file)',
306
308
  }),
307
309
  force: Schema.optional(
308
- Schema.Boolean.annotations({ description: 'Force import even if store ID does not match' }),
310
+ Schema.Boolean.annotate({ description: 'Force import even if store ID does not match' }),
309
311
  ),
310
- dryRun: Schema.optional(Schema.Boolean.annotations({ description: 'Validate without actually importing' })),
311
- },
312
+ dryRun: Schema.optional(Schema.Boolean.annotate({ description: 'Validate without actually importing' })),
313
+ }),
312
314
  success: Schema.Struct({
313
315
  storeId: Schema.String,
314
- eventCount: Schema.Number,
316
+ eventCount: Schema.Finite,
315
317
  dryRun: Schema.Boolean,
316
318
  }),
317
319
  }).annotate(Tool.Destructive, true),
@@ -1,4 +1,6 @@
1
- import { Effect, Layer, Logger, McpServer } from '@livestore/utils/effect'
1
+ import { McpProtocol, McpServer } from 'effect/ai'
2
+
3
+ import { Effect, Layer, Logger } from '@livestore/utils/effect'
2
4
  import { Cli, PlatformNode } from '@livestore/utils/node'
3
5
 
4
6
  import { architectureContent } from '../mcp-content/architecture.ts'
@@ -11,6 +13,7 @@ import { ecommerceSchemaContent } from '../mcp-content/schemas/ecommerce.ts'
11
13
  import { socialSchemaContent } from '../mcp-content/schemas/social.ts'
12
14
  import { todoSchemaContent } from '../mcp-content/schemas/todo.ts'
13
15
  import { toolHandlers } from './mcp-tool-handlers.ts'
16
+ import { supportedMcpProtocols } from './mcp-protocols.ts'
14
17
  // Tools imports
15
18
  import { livestoreToolkit } from './mcp-tools-defs.ts'
16
19
 
@@ -84,12 +87,14 @@ const mcpServerCommand = Cli.Command.make(
84
87
  return yield* McpServer.layerStdio({
85
88
  name: 'livestore-mcp',
86
89
  version: '0.1.0',
87
- stdin: PlatformNode.NodeStream.stdin,
88
- stdout: PlatformNode.NodeSink.stdout,
90
+ protocols: supportedMcpProtocols,
89
91
  }).pipe(
90
92
  Layer.provide(LivestoreResources),
91
93
  Layer.provide(LivestoreTools),
92
- Layer.provide(Logger.add(Logger.prettyLogger({ stderr: true }))),
94
+ Layer.provide(
95
+ Layer.merge(Logger.layer([Logger.consolePretty()]), Layer.succeed(Logger.LogToStderr, true)),
96
+ ),
97
+ Layer.provide(PlatformNode.NodeStdio.layer),
93
98
  Layer.launch,
94
99
  )
95
100
  }),
@@ -3,7 +3,8 @@ import * as nodePath from 'node:path'
3
3
 
4
4
  import { sluggify } from '@livestore/utils'
5
5
  import {
6
- Command,
6
+ ChildProcess,
7
+ ChildProcessSpawner,
7
8
  Console,
8
9
  Effect,
9
10
  FileSystem,
@@ -19,7 +20,7 @@ import { detectPackageManager, pmCommands } from '../package-manager.ts'
19
20
  // Schema for GitHub API response
20
21
  const GitHubContentSchema = Schema.Struct({
21
22
  name: Schema.String,
22
- type: Schema.Literal('dir', 'file'),
23
+ type: Schema.Literals(['dir', 'file']),
23
24
  path: Schema.String,
24
25
  download_url: Schema.NullOr(Schema.String),
25
26
  })
@@ -34,11 +35,13 @@ const githubRequest = (url: string) => {
34
35
 
35
36
  /** Schema for parsing package.json scripts (dev or start) */
36
37
  const PackageJsonScriptsSchema = Schema.Struct({
37
- scripts: Schema.Union(Schema.Struct({ dev: Schema.String }), Schema.Struct({ start: Schema.String })),
38
+ scripts: Schema.Union([Schema.Struct({ dev: Schema.String }), Schema.Struct({ start: Schema.String })]),
38
39
  })
39
40
 
40
41
  // Error types
41
- export class ExampleNotFoundError extends Schema.TaggedError<ExampleNotFoundError>('~@livestore/cli/ExampleNotFoundError')('ExampleNotFoundError', {
42
+ export class ExampleNotFoundError extends Schema.TaggedError<ExampleNotFoundError>(
43
+ '~@livestore/cli/ExampleNotFoundError',
44
+ )('ExampleNotFoundError', {
42
45
  exampleName: Schema.String,
43
46
  availableExamples: Schema.Array(Schema.String),
44
47
  message: Schema.String,
@@ -49,14 +52,19 @@ export class NetworkError extends Schema.TaggedError<NetworkError>('~@livestore/
49
52
  message: Schema.String,
50
53
  }) {}
51
54
 
52
- export class DirectoryExistsError extends Schema.TaggedError<DirectoryExistsError>('~@livestore/cli/DirectoryExistsError')('DirectoryExistsError', {
55
+ export class DirectoryExistsError extends Schema.TaggedError<DirectoryExistsError>(
56
+ '~@livestore/cli/DirectoryExistsError',
57
+ )('DirectoryExistsError', {
53
58
  path: Schema.String,
54
59
  message: Schema.String,
55
60
  }) {}
56
61
 
57
- export class NoExamplesError extends Schema.TaggedError<NoExamplesError>('~@livestore/cli/NoExamplesError')('NoExamplesError', {
58
- message: Schema.String,
59
- }) {}
62
+ export class NoExamplesError extends Schema.TaggedError<NoExamplesError>('~@livestore/cli/NoExamplesError')(
63
+ 'NoExamplesError',
64
+ {
65
+ message: Schema.String,
66
+ },
67
+ ) {}
60
68
 
61
69
  // Fetch available examples from GitHub
62
70
  const fetchExamples = (ref: string) =>
@@ -68,7 +76,7 @@ const fetchExamples = (ref: string) =>
68
76
  const request = githubRequest(url)
69
77
  const response = yield* HttpClient.execute(request).pipe(
70
78
  Effect.scoped,
71
- Effect.catchAll(
79
+ Effect.catch(
72
80
  (error) =>
73
81
  new NetworkError({
74
82
  cause: error,
@@ -79,8 +87,10 @@ const fetchExamples = (ref: string) =>
79
87
 
80
88
  const responseText = yield* response.text
81
89
 
82
- const examples = yield* Schema.decodeUnknown(Schema.parseJson(GitHubContentsResponseSchema))(responseText).pipe(
83
- Effect.catchAll(
90
+ const examples = yield* Schema.decodeEffect(Schema.fromJsonString(GitHubContentsResponseSchema))(
91
+ responseText,
92
+ ).pipe(
93
+ Effect.catch(
84
94
  (error) =>
85
95
  new NetworkError({
86
96
  cause: error,
@@ -106,7 +116,7 @@ const selectExample = (examples: string[]) =>
106
116
  return yield* new NoExamplesError({ message: 'No examples available' })
107
117
  }
108
118
 
109
- const prompt = Cli.Prompt.select({
119
+ const prompt = Cli.Prompt.Select({
110
120
  message: '📦 Select a LiveStore example to create:',
111
121
  choices: examples.map((example) => ({
112
122
  title: example,
@@ -123,6 +133,8 @@ const downloadExample = (exampleName: string, ref: string, destinationPath: stri
123
133
  Effect.gen(function* () {
124
134
  yield* Console.log(`📥 Downloading example "${exampleName}" from ref "${ref}"...`)
125
135
 
136
+ const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
137
+
126
138
  const tempDir = yield* Effect.sync(() => os.tmpdir())
127
139
  const tarballPath = nodePath.join(tempDir, `livestore-${sluggify(ref)}-${Date.now()}.tar.gz`)
128
140
  const tarballUrl = `https://api.github.com/repos/livestorejs/livestore/tarball/${ref}`
@@ -132,7 +144,7 @@ const downloadExample = (exampleName: string, ref: string, destinationPath: stri
132
144
 
133
145
  const response = yield* HttpClient.execute(request).pipe(
134
146
  Effect.scoped,
135
- Effect.catchAll(
147
+ Effect.catch(
136
148
  (error) =>
137
149
  new NetworkError({
138
150
  cause: error,
@@ -154,10 +166,9 @@ const downloadExample = (exampleName: string, ref: string, destinationPath: stri
154
166
  const extractDir = nodePath.join(tempDir, `extract-${Date.now()}`)
155
167
  yield* fs.makeDirectory(extractDir, { recursive: true })
156
168
 
157
- // Extract tarball using Effect Command
158
- yield* Command.make('tar', '-xzf', tarballPath, '-C', extractDir).pipe(
159
- Command.exitCode,
160
- Effect.catchAll(
169
+ // Extract tarball using a child process
170
+ yield* spawner.exitCode(ChildProcess.make('tar', ['-xzf', tarballPath, '-C', extractDir])).pipe(
171
+ Effect.catch(
161
172
  (error) =>
162
173
  new NetworkError({
163
174
  cause: error,
@@ -190,10 +201,9 @@ const downloadExample = (exampleName: string, ref: string, destinationPath: stri
190
201
  })
191
202
  }
192
203
 
193
- // Copy the example directory contents to the destination using Effect Command
194
- yield* Command.make('cp', '-r', `${exampleSourcePath}/.`, destinationPath).pipe(
195
- Command.exitCode,
196
- Effect.catchAll(
204
+ // Copy the example directory contents to the destination using a child process
205
+ yield* spawner.exitCode(ChildProcess.make('cp', ['-r', `${exampleSourcePath}/.`, destinationPath])).pipe(
206
+ Effect.catch(
197
207
  (error) =>
198
208
  new NetworkError({
199
209
  cause: error,
@@ -203,10 +213,10 @@ const downloadExample = (exampleName: string, ref: string, destinationPath: stri
203
213
  )
204
214
 
205
215
  // Clean up extract directory
206
- yield* fs.remove(extractDir, { recursive: true }).pipe(Effect.catchAll(() => Effect.void))
216
+ yield* fs.remove(extractDir, { recursive: true }).pipe(Effect.ignore)
207
217
 
208
218
  // Clean up tarball
209
- yield* fs.remove(tarballPath).pipe(Effect.catchAll(() => Effect.void))
219
+ yield* fs.remove(tarballPath).pipe(Effect.ignore)
210
220
 
211
221
  yield* Console.log(`✅ Example "${exampleName}" created successfully at: ${destinationPath}`)
212
222
  })
@@ -214,22 +224,22 @@ const downloadExample = (exampleName: string, ref: string, destinationPath: stri
214
224
  export const createCommand = Cli.Command.make(
215
225
  'create',
216
226
  {
217
- example: Cli.Options.text('example').pipe(
218
- Cli.Options.optional,
219
- Cli.Options.withDescription('Example name to create (bypasses interactive selection)'),
227
+ example: Cli.Flag.String('example').pipe(
228
+ Cli.Flag.optional,
229
+ Cli.Flag.withDescription('Example name to create (bypasses interactive selection)'),
220
230
  ),
221
- ref: Cli.Options.text('ref').pipe(
222
- Cli.Options.withAlias('commit'),
223
- Cli.Options.withAlias('branch'),
224
- Cli.Options.withAlias('tag'),
225
- Cli.Options.withDefault('main'),
226
- Cli.Options.withDescription(
231
+ ref: Cli.Flag.String('ref').pipe(
232
+ Cli.Flag.withAlias('commit'),
233
+ Cli.Flag.withAlias('branch'),
234
+ Cli.Flag.withAlias('tag'),
235
+ Cli.Flag.withDefault('main'),
236
+ Cli.Flag.withDescription(
227
237
  'The name of the commit/branch/tag to fetch examples from. Pull requests refs must be fully-formed (e.g., `refs/pull/123/merge`).',
228
238
  ),
229
239
  ),
230
- path: Cli.Args.text({ name: 'path' }).pipe(
231
- Cli.Args.optional,
232
- Cli.Args.withDescription('Destination path for the new project'),
240
+ path: Cli.Argument.String('path').pipe(
241
+ Cli.Argument.optional,
242
+ Cli.Argument.withDescription('Destination path for the new project'),
233
243
  ),
234
244
  },
235
245
  Effect.fn(function* ({
@@ -270,7 +280,8 @@ export const createCommand = Cli.Command.make(
270
280
  }
271
281
 
272
282
  // Determine destination path
273
- const destinationPath = Option.isSome(path) === true ? nodePath.resolve(path.value) : nodePath.resolve(selectedExample)
283
+ const destinationPath =
284
+ Option.isSome(path) === true ? nodePath.resolve(path.value) : nodePath.resolve(selectedExample)
274
285
 
275
286
  // Download and extract the example
276
287
  yield* downloadExample(selectedExample, ref, destinationPath)
@@ -281,7 +292,9 @@ export const createCommand = Cli.Command.make(
281
292
  const fs = yield* FileSystem.FileSystem
282
293
  const packageJsonPath = nodePath.join(destinationPath, 'package.json')
283
294
  const packageJsonContent = yield* fs.readFileString(packageJsonPath)
284
- const runScript = yield* Schema.decodeUnknown(Schema.parseJson(PackageJsonScriptsSchema))(packageJsonContent).pipe(
295
+ const runScript = yield* Schema.decodeEffect(Schema.fromJsonString(PackageJsonScriptsSchema))(
296
+ packageJsonContent,
297
+ ).pipe(
285
298
  Effect.map((pkg) => ('dev' in pkg.scripts ? ('dev' as const) : ('start' as const))),
286
299
  Effect.orElseSucceed(() => undefined),
287
300
  )
@@ -28,9 +28,9 @@ LiveStore implements distributed systems principles from Martin Kleppmann's rese
28
28
  \`\`\`typescript
29
29
  // Materialized views from event log
30
30
  const materializers = State.SQLite.materializers(events, {
31
- 'TodoCreated': ({ id, text, createdAt }) =>
31
+ 'TodoCreated': ({ id, text, createdAt }) =>
32
32
  tables.todos.insert({ id, text, completed: false, createdAt }),
33
- 'TodoCompleted': ({ id }) =>
33
+ 'TodoCompleted': ({ id }) =>
34
34
  tables.todos.update({ completed: true }).where({ id })
35
35
  })
36
36
  \`\`\`
@@ -46,7 +46,7 @@ const materializers = State.SQLite.materializers(events, {
46
46
  const events = {
47
47
  todoCompleted: Events.synced({
48
48
  name: 'v1.TodoCompleted',
49
- schema: Schema.Struct({
49
+ schema: Schema.Struct({
50
50
  id: Schema.String,
51
51
  completedAt: Schema.Date // For conflict resolution
52
52
  })
@@ -149,7 +149,7 @@ await testStore.replay([
149
149
 
150
150
  ### Developer Tools
151
151
  - **Event Inspector**: Real-time event log visualization
152
- - **Query Profiler**: Performance analysis for slow queries
152
+ - **Query Profiler**: Performance analysis for slow queries
153
153
  - **Sync Monitor**: Network synchronization health dashboard
154
154
  - **Schema Explorer**: Interactive database schema browsing
155
155