@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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/__tests__/fixtures/mock-config.d.ts +8 -9
- package/dist/__tests__/fixtures/mock-config.d.ts.map +1 -1
- package/dist/__tests__/fixtures/mock-config.js +18 -8
- package/dist/__tests__/fixtures/mock-config.js.map +1 -1
- package/dist/__tests__/sync-operations.test.js +32 -22
- package/dist/__tests__/sync-operations.test.js.map +1 -1
- package/dist/bin.js +7 -6
- package/dist/bin.js.map +1 -1
- package/dist/cli.d.ts +2 -23
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +4 -4
- package/dist/cli.js.map +1 -1
- package/dist/commands/__fixtures__/mcp-protocol-server.d.ts +2 -0
- package/dist/commands/__fixtures__/mcp-protocol-server.d.ts.map +1 -0
- package/dist/commands/__fixtures__/mcp-protocol-server.js +10 -0
- package/dist/commands/__fixtures__/mcp-protocol-server.js.map +1 -0
- package/dist/commands/import-export.d.ts +5 -19
- package/dist/commands/import-export.d.ts.map +1 -1
- package/dist/commands/import-export.js +13 -13
- package/dist/commands/import-export.js.map +1 -1
- package/dist/commands/mcp-coach.d.ts +8 -7
- package/dist/commands/mcp-coach.d.ts.map +1 -1
- package/dist/commands/mcp-coach.js +11 -11
- package/dist/commands/mcp-coach.js.map +1 -1
- package/dist/commands/mcp-protocols.d.ts +7 -0
- package/dist/commands/mcp-protocols.d.ts.map +1 -0
- package/dist/commands/mcp-protocols.js +12 -0
- package/dist/commands/mcp-protocols.js.map +1 -0
- package/dist/commands/mcp-protocols.test.d.ts +2 -0
- package/dist/commands/mcp-protocols.test.d.ts.map +1 -0
- package/dist/commands/mcp-protocols.test.js +60 -0
- package/dist/commands/mcp-protocols.test.js.map +1 -0
- package/dist/commands/mcp-stdio.test.d.ts +2 -0
- package/dist/commands/mcp-stdio.test.d.ts.map +1 -0
- package/dist/commands/mcp-stdio.test.js +79 -0
- package/dist/commands/mcp-stdio.test.js.map +1 -0
- package/dist/commands/mcp-tool-handlers.d.ts +1 -1
- package/dist/commands/mcp-tool-handlers.d.ts.map +1 -1
- package/dist/commands/mcp-tool-handlers.js +9 -8
- package/dist/commands/mcp-tool-handlers.js.map +1 -1
- package/dist/commands/mcp-tools-defs.d.ts +74 -73
- package/dist/commands/mcp-tools-defs.d.ts.map +1 -1
- package/dist/commands/mcp-tools-defs.js +64 -57
- package/dist/commands/mcp-tools-defs.js.map +1 -1
- package/dist/commands/mcp-tools-defs.test.d.ts +2 -0
- package/dist/commands/mcp-tools-defs.test.d.ts.map +1 -0
- package/dist/commands/mcp-tools-defs.test.js +11 -0
- package/dist/commands/mcp-tools-defs.test.js.map +1 -0
- package/dist/commands/mcp.d.ts +1 -3
- package/dist/commands/mcp.d.ts.map +1 -1
- package/dist/commands/mcp.js +15 -14
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/new-project.d.ts +19 -27
- package/dist/commands/new-project.d.ts.map +1 -1
- package/dist/commands/new-project.js +19 -18
- package/dist/commands/new-project.js.map +1 -1
- package/dist/mcp-content/architecture.d.ts +1 -1
- package/dist/mcp-content/architecture.d.ts.map +1 -1
- package/dist/mcp-content/architecture.js +3 -3
- package/dist/mcp-content/features.d.ts +1 -1
- package/dist/mcp-content/features.d.ts.map +1 -1
- package/dist/mcp-content/features.js +1 -1
- package/dist/mcp-content/getting-started.d.ts +1 -1
- package/dist/mcp-content/getting-started.d.ts.map +1 -1
- package/dist/mcp-content/getting-started.js +37 -37
- package/dist/mcp-content/overview.d.ts +1 -1
- package/dist/mcp-content/overview.d.ts.map +1 -1
- package/dist/mcp-content/overview.js +6 -6
- package/dist/mcp-content/schemas/blog.d.ts +1 -1
- package/dist/mcp-content/schemas/blog.d.ts.map +1 -1
- package/dist/mcp-content/schemas/blog.js +25 -25
- package/dist/mcp-content/schemas/ecommerce.d.ts +1 -1
- package/dist/mcp-content/schemas/ecommerce.d.ts.map +1 -1
- package/dist/mcp-content/schemas/ecommerce.js +67 -67
- package/dist/mcp-content/schemas/social.d.ts +1 -1
- package/dist/mcp-content/schemas/social.d.ts.map +1 -1
- package/dist/mcp-content/schemas/social.js +48 -48
- package/dist/mcp-content/schemas/todo.d.ts +1 -1
- package/dist/mcp-content/schemas/todo.d.ts.map +1 -1
- package/dist/mcp-content/schemas/todo.js +35 -35
- package/dist/mcp-runtime/runtime.d.ts +5 -5
- package/dist/mcp-runtime/runtime.d.ts.map +1 -1
- package/dist/mcp-runtime/runtime.js +5 -5
- package/dist/mcp-runtime/runtime.js.map +1 -1
- package/dist/mod.js +1 -1
- package/dist/module-loader.d.ts +2 -2
- package/dist/module-loader.d.ts.map +1 -1
- package/dist/module-loader.js +8 -8
- package/dist/module-loader.js.map +1 -1
- package/dist/package-manager.d.ts +3 -3
- package/dist/package-manager.d.ts.map +1 -1
- package/dist/package-manager.test.js +1 -1
- package/dist/sync-operations.d.ts +24 -30
- package/dist/sync-operations.d.ts.map +1 -1
- package/dist/sync-operations.js +15 -15
- package/dist/sync-operations.js.map +1 -1
- package/package.json +45 -59
- package/src/__tests__/fixtures/.gitignore +1 -0
- package/src/__tests__/fixtures/mock-config.ts +21 -10
- package/src/__tests__/sync-operations.test.ts +36 -35
- package/src/bin.ts +7 -6
- package/src/cli.ts +1 -1
- package/src/commands/__fixtures__/mcp-protocol-server.ts +12 -0
- package/src/commands/import-export.ts +29 -29
- package/src/commands/mcp-coach.ts +13 -12
- package/src/commands/mcp-protocols.test.ts +76 -0
- package/src/commands/mcp-protocols.ts +12 -0
- package/src/commands/mcp-stdio.test.ts +93 -0
- package/src/commands/mcp-tool-handlers.ts +11 -8
- package/src/commands/mcp-tools-defs.test.ts +13 -0
- package/src/commands/mcp-tools-defs.ts +57 -55
- package/src/commands/mcp.ts +9 -4
- package/src/commands/new-project.ts +50 -37
- package/src/mcp-content/architecture.ts +3 -3
- package/src/mcp-content/features.ts +1 -1
- package/src/mcp-content/getting-started.ts +37 -37
- package/src/mcp-content/overview.ts +6 -6
- package/src/mcp-content/schemas/blog.ts +25 -25
- package/src/mcp-content/schemas/ecommerce.ts +67 -67
- package/src/mcp-content/schemas/social.ts +48 -48
- package/src/mcp-content/schemas/todo.ts +35 -35
- package/src/mcp-runtime/runtime.ts +7 -7
- package/src/module-loader.ts +11 -12
- package/src/sync-operations.ts +26 -19
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
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.
|
|
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.
|
|
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.
|
|
24
|
-
explanation: Schema.String.
|
|
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.
|
|
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.
|
|
36
|
-
description: Schema.String.
|
|
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.
|
|
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.
|
|
95
|
+
storeId: Schema.String.annotate({ description: 'Required store id for the LiveStore instance.' }),
|
|
94
96
|
clientId: Schema.optional(
|
|
95
|
-
Schema.String.
|
|
97
|
+
Schema.String.annotate({ description: 'Optional client id for the LiveStore instance.' }),
|
|
96
98
|
),
|
|
97
99
|
sessionId: Schema.optional(
|
|
98
|
-
Schema.String.
|
|
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).
|
|
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).
|
|
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.
|
|
142
|
-
bindValues: Schema.Union(
|
|
143
|
-
Schema.Array(Schema.
|
|
144
|
-
Schema.Record(
|
|
145
|
-
).
|
|
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(
|
|
151
|
-
rowCount: Schema.
|
|
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.
|
|
183
|
-
args: Schema.
|
|
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.
|
|
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(
|
|
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.
|
|
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.
|
|
261
|
-
clientId: Schema.optional(Schema.String.
|
|
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.
|
|
267
|
+
eventCount: Schema.Finite,
|
|
266
268
|
exportedAt: Schema.String,
|
|
267
|
-
data: Schema.
|
|
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.
|
|
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.
|
|
303
|
-
clientId: Schema.optional(Schema.String.
|
|
304
|
-
data: Schema.
|
|
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.
|
|
310
|
+
Schema.Boolean.annotate({ description: 'Force import even if store ID does not match' }),
|
|
309
311
|
),
|
|
310
|
-
dryRun: Schema.optional(Schema.Boolean.
|
|
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.
|
|
316
|
+
eventCount: Schema.Finite,
|
|
315
317
|
dryRun: Schema.Boolean,
|
|
316
318
|
}),
|
|
317
319
|
}).annotate(Tool.Destructive, true),
|
package/src/commands/mcp.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
88
|
-
stdout: PlatformNode.NodeSink.stdout,
|
|
90
|
+
protocols: supportedMcpProtocols,
|
|
89
91
|
}).pipe(
|
|
90
92
|
Layer.provide(LivestoreResources),
|
|
91
93
|
Layer.provide(LivestoreTools),
|
|
92
|
-
Layer.provide(
|
|
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
|
-
|
|
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.
|
|
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>(
|
|
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>(
|
|
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')(
|
|
58
|
-
|
|
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.
|
|
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.
|
|
83
|
-
|
|
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.
|
|
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.
|
|
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
|
|
158
|
-
yield*
|
|
159
|
-
|
|
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
|
|
194
|
-
yield*
|
|
195
|
-
|
|
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.
|
|
216
|
+
yield* fs.remove(extractDir, { recursive: true }).pipe(Effect.ignore)
|
|
207
217
|
|
|
208
218
|
// Clean up tarball
|
|
209
|
-
yield* fs.remove(tarballPath).pipe(Effect.
|
|
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.
|
|
218
|
-
Cli.
|
|
219
|
-
Cli.
|
|
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.
|
|
222
|
-
Cli.
|
|
223
|
-
Cli.
|
|
224
|
-
Cli.
|
|
225
|
-
Cli.
|
|
226
|
-
Cli.
|
|
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.
|
|
231
|
-
Cli.
|
|
232
|
-
Cli.
|
|
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 =
|
|
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.
|
|
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
|
|