@livestore/cli 0.0.0-snapshot-0f27d343553b9bb260543bf20de36d216f53c5d8 → 0.0.0-snapshot-b2da08eec7583e23c0679970016a84b93438039e

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 (50) hide show
  1. package/dist/cli.d.ts +1 -15
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +1 -2
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/mcp-coach.d.ts +2 -2
  6. package/dist/commands/mcp-coach.d.ts.map +1 -1
  7. package/dist/commands/mcp-tool-handlers.d.ts +1 -5
  8. package/dist/commands/mcp-tool-handlers.d.ts.map +1 -1
  9. package/dist/commands/mcp-tool-handlers.js +4 -42
  10. package/dist/commands/mcp-tool-handlers.js.map +1 -1
  11. package/dist/commands/mcp-tools-defs.d.ts +1 -31
  12. package/dist/commands/mcp-tools-defs.d.ts.map +1 -1
  13. package/dist/commands/mcp-tools-defs.js +5 -87
  14. package/dist/commands/mcp-tools-defs.js.map +1 -1
  15. package/dist/commands/new-project.d.ts +1 -1
  16. package/dist/mcp-runtime/runtime.d.ts +3 -4
  17. package/dist/mcp-runtime/runtime.d.ts.map +1 -1
  18. package/dist/mcp-runtime/runtime.js +53 -20
  19. package/dist/mcp-runtime/runtime.js.map +1 -1
  20. package/dist/tsconfig.tsbuildinfo +1 -1
  21. package/package.json +8 -15
  22. package/src/cli.ts +1 -2
  23. package/src/commands/mcp-tool-handlers.ts +5 -50
  24. package/src/commands/mcp-tools-defs.ts +4 -92
  25. package/src/mcp-runtime/runtime.ts +65 -32
  26. package/dist/__tests__/fixtures/mock-config.d.ts +0 -56
  27. package/dist/__tests__/fixtures/mock-config.d.ts.map +0 -1
  28. package/dist/__tests__/fixtures/mock-config.js +0 -88
  29. package/dist/__tests__/fixtures/mock-config.js.map +0 -1
  30. package/dist/__tests__/sync-operations.test.d.ts +0 -2
  31. package/dist/__tests__/sync-operations.test.d.ts.map +0 -1
  32. package/dist/__tests__/sync-operations.test.js +0 -167
  33. package/dist/__tests__/sync-operations.test.js.map +0 -1
  34. package/dist/commands/import-export.d.ts +0 -34
  35. package/dist/commands/import-export.d.ts.map +0 -1
  36. package/dist/commands/import-export.js +0 -133
  37. package/dist/commands/import-export.js.map +0 -1
  38. package/dist/module-loader.d.ts +0 -22
  39. package/dist/module-loader.d.ts.map +0 -1
  40. package/dist/module-loader.js +0 -75
  41. package/dist/module-loader.js.map +0 -1
  42. package/dist/sync-operations.d.ts +0 -121
  43. package/dist/sync-operations.d.ts.map +0 -1
  44. package/dist/sync-operations.js +0 -180
  45. package/dist/sync-operations.js.map +0 -1
  46. package/src/__tests__/fixtures/mock-config.ts +0 -104
  47. package/src/__tests__/sync-operations.test.ts +0 -230
  48. package/src/commands/import-export.ts +0 -278
  49. package/src/module-loader.ts +0 -93
  50. package/src/sync-operations.ts +0 -360
@@ -1,278 +0,0 @@
1
- import path from 'node:path'
2
- import type { UnknownError } from '@livestore/common'
3
- import { Console, Effect, FileSystem, type HttpClient, type Scope } from '@livestore/utils/effect'
4
- import { Cli } from '@livestore/utils/node'
5
- import * as SyncOps from '../sync-operations.ts'
6
-
7
- const LARGE_EVENT_WARNING_THRESHOLD = 100_000
8
-
9
- /**
10
- * Export events from the sync backend to a JSON file.
11
- */
12
- const exportEvents = ({
13
- configPath,
14
- storeId,
15
- clientId,
16
- outputPath,
17
- }: {
18
- configPath: string
19
- storeId: string
20
- clientId: string
21
- outputPath: string
22
- }): Effect.Effect<
23
- void,
24
- SyncOps.ExportError | SyncOps.ConnectionError | UnknownError,
25
- FileSystem.FileSystem | HttpClient.HttpClient | Scope.Scope
26
- > =>
27
- Effect.gen(function* () {
28
- yield* Console.log(`Connecting to sync backend...`)
29
-
30
- const result = yield* SyncOps.pullEventsFromSyncBackend({ configPath, storeId, clientId })
31
-
32
- yield* Console.log(`✓ Connected to sync backend: ${result.backendName}`)
33
- yield* Console.log(`Pulled ${result.eventCount} events`)
34
- if (result.eventCount > LARGE_EVENT_WARNING_THRESHOLD) {
35
- yield* Console.log(
36
- `Warning: exporting ${result.eventCount} events may consume significant memory. Consider exporting on a machine with enough RAM.`,
37
- )
38
- }
39
-
40
- const fs = yield* FileSystem.FileSystem
41
- const absOutputPath = path.isAbsolute(outputPath) ? outputPath : path.resolve(process.cwd(), outputPath)
42
-
43
- yield* fs.writeFileString(absOutputPath, JSON.stringify(result.data, null, 2)).pipe(
44
- Effect.mapError(
45
- (cause) =>
46
- new SyncOps.ExportError({
47
- cause,
48
- note: `Failed to write export file: ${cause}`,
49
- }),
50
- ),
51
- )
52
-
53
- yield* Console.log(`Exported ${result.eventCount} events to ${absOutputPath}`)
54
- }).pipe(Effect.withSpan('cli:export'))
55
-
56
- /**
57
- * Import events from a JSON file to the sync backend.
58
- */
59
- const importEvents = ({
60
- configPath,
61
- storeId,
62
- clientId,
63
- inputPath,
64
- force,
65
- dryRun,
66
- }: {
67
- configPath: string
68
- storeId: string
69
- clientId: string
70
- inputPath: string
71
- force: boolean
72
- dryRun: boolean
73
- }): Effect.Effect<
74
- void,
75
- SyncOps.ImportError | SyncOps.ConnectionError | UnknownError,
76
- FileSystem.FileSystem | HttpClient.HttpClient | Scope.Scope
77
- > =>
78
- Effect.gen(function* () {
79
- const fs = yield* FileSystem.FileSystem
80
- const absInputPath = path.isAbsolute(inputPath) ? inputPath : path.resolve(process.cwd(), inputPath)
81
-
82
- const exists = yield* fs.exists(absInputPath).pipe(
83
- Effect.mapError(
84
- (cause) =>
85
- new SyncOps.ImportError({
86
- cause,
87
- note: `Failed to check file existence: ${cause}`,
88
- }),
89
- ),
90
- )
91
- if (!exists) {
92
- return yield* new SyncOps.ImportError({
93
- cause: new Error(`File not found: ${absInputPath}`),
94
- note: `Import file does not exist at ${absInputPath}`,
95
- })
96
- }
97
-
98
- yield* Console.log(`Reading import file...`)
99
-
100
- const fileContent = yield* fs.readFileString(absInputPath).pipe(
101
- Effect.mapError(
102
- (cause) =>
103
- new SyncOps.ImportError({
104
- cause,
105
- note: `Failed to read import file: ${cause}`,
106
- }),
107
- ),
108
- )
109
-
110
- const parsedContent = yield* Effect.try({
111
- try: () => JSON.parse(fileContent),
112
- catch: (error) =>
113
- new SyncOps.ImportError({
114
- cause: new Error(`Failed to parse JSON: ${error instanceof Error ? error.message : String(error)}`),
115
- note: `Invalid JSON in import file: ${error instanceof Error ? error.message : String(error)}`,
116
- }),
117
- })
118
-
119
- /** Validate export file format before proceeding */
120
- const validation = yield* SyncOps.validateExportData({ data: parsedContent, targetStoreId: storeId })
121
-
122
- if (validation.storeIdMismatch) {
123
- if (!force) {
124
- return yield* new SyncOps.ImportError({
125
- cause: new Error(`Store ID mismatch: file has '${validation.sourceStoreId}', expected '${storeId}'`),
126
- note: `The export file was created for a different store. Use --force to import anyway.`,
127
- })
128
- }
129
- yield* Console.log(
130
- `Store ID mismatch: file has '${validation.sourceStoreId}', importing to '${storeId}' (--force)`,
131
- )
132
- }
133
-
134
- yield* Console.log(`Found ${validation.eventCount} events in export file`)
135
- if (validation.eventCount > LARGE_EVENT_WARNING_THRESHOLD) {
136
- yield* Console.log(
137
- `Warning: importing ${validation.eventCount} events may consume significant memory. Ensure the machine has enough RAM.`,
138
- )
139
- }
140
-
141
- if (dryRun) {
142
- yield* Console.log(`Dry run - validating import file...`)
143
- yield* Console.log(`Dry run complete. ${validation.eventCount} events would be imported.`)
144
- return
145
- }
146
-
147
- yield* Console.log(`Checking for existing events...`)
148
-
149
- yield* Console.log(`Connecting to sync backend...`)
150
- yield* Console.log(`Pushing events to sync backend...`)
151
-
152
- const result = yield* SyncOps.pushEventsToSyncBackend({
153
- configPath,
154
- storeId,
155
- clientId,
156
- data: parsedContent,
157
- force,
158
- dryRun: false,
159
- onProgress: (pushed, total) => Console.log(` Pushed ${pushed}/${total} events`),
160
- })
161
-
162
- yield* Console.log(`✓ Connected to sync backend: ${result.backendName ?? 'unknown'}`)
163
- yield* Console.log(`Successfully imported ${result.eventCount} events`)
164
- }).pipe(Effect.withSpan('cli:import'))
165
-
166
- export const exportCommand = Cli.Command.make(
167
- 'export',
168
- {
169
- config: Cli.Options.text('config').pipe(
170
- Cli.Options.withAlias('c'),
171
- Cli.Options.withDescription('Path to the config module that exports schema and syncBackend'),
172
- ),
173
- storeId: Cli.Options.text('store-id').pipe(
174
- Cli.Options.withAlias('i'),
175
- Cli.Options.withDescription('Store identifier'),
176
- ),
177
- clientId: Cli.Options.text('client-id').pipe(
178
- Cli.Options.withDefault('cli-export'),
179
- Cli.Options.withDescription('Client identifier for the sync connection'),
180
- ),
181
- output: Cli.Args.text({ name: 'file' }).pipe(Cli.Args.withDescription('Output JSON file path')),
182
- },
183
- Effect.fn(function* ({
184
- config,
185
- storeId,
186
- clientId,
187
- output,
188
- }: {
189
- config: string
190
- storeId: string
191
- clientId: string
192
- output: string
193
- }) {
194
- yield* Console.log(`Exporting events from LiveStore...`)
195
- yield* Console.log(` Config: ${config}`)
196
- yield* Console.log(` Store ID: ${storeId}`)
197
- yield* Console.log(` Output: ${output}`)
198
- yield* Console.log('')
199
-
200
- yield* exportEvents({
201
- configPath: config,
202
- storeId,
203
- clientId,
204
- outputPath: output,
205
- }).pipe(Effect.scoped)
206
- }),
207
- ).pipe(
208
- Cli.Command.withDescription(
209
- 'Export all events from the sync backend to a JSON file. Useful for backup and migration.',
210
- ),
211
- )
212
-
213
- export const importCommand = Cli.Command.make(
214
- 'import',
215
- {
216
- config: Cli.Options.text('config').pipe(
217
- Cli.Options.withAlias('c'),
218
- Cli.Options.withDescription('Path to the config module that exports schema and syncBackend'),
219
- ),
220
- storeId: Cli.Options.text('store-id').pipe(
221
- Cli.Options.withAlias('i'),
222
- Cli.Options.withDescription('Store identifier'),
223
- ),
224
- clientId: Cli.Options.text('client-id').pipe(
225
- Cli.Options.withDefault('cli-import'),
226
- Cli.Options.withDescription('Client identifier for the sync connection'),
227
- ),
228
- force: Cli.Options.boolean('force').pipe(
229
- Cli.Options.withAlias('f'),
230
- Cli.Options.withDefault(false),
231
- Cli.Options.withDescription('Force import even if store ID does not match'),
232
- ),
233
- dryRun: Cli.Options.boolean('dry-run').pipe(
234
- Cli.Options.withDefault(false),
235
- Cli.Options.withDescription('Validate the import file without actually importing'),
236
- ),
237
- input: Cli.Args.text({ name: 'file' }).pipe(Cli.Args.withDescription('Input JSON file to import')),
238
- },
239
- Effect.fn(function* ({
240
- config,
241
- storeId,
242
- clientId,
243
- force,
244
- dryRun,
245
- input,
246
- }: {
247
- config: string
248
- storeId: string
249
- clientId: string
250
- force: boolean
251
- dryRun: boolean
252
- input: string
253
- }) {
254
- yield* Console.log(`Importing events to LiveStore...`)
255
- yield* Console.log(` Config: ${config}`)
256
- yield* Console.log(` Store ID: ${storeId}`)
257
- yield* Console.log(` Input: ${input}`)
258
- if (force) yield* Console.log(` Force: enabled`)
259
- if (dryRun) yield* Console.log(` Dry run: enabled`)
260
- yield* Console.log('')
261
-
262
- yield* importEvents({
263
- configPath: config,
264
- storeId,
265
- clientId,
266
- inputPath: input,
267
- force,
268
- dryRun,
269
- }).pipe(Effect.scoped)
270
- }),
271
- ).pipe(
272
- Cli.Command.withDescription('Import events from a JSON file to the sync backend. The sync backend must be empty.'),
273
- )
274
-
275
- export const syncCommand = Cli.Command.make('sync').pipe(
276
- Cli.Command.withSubcommands([exportCommand, importCommand]),
277
- Cli.Command.withDescription('Import and export events from the sync backend'),
278
- )
@@ -1,93 +0,0 @@
1
- /**
2
- * Shared module loading utility for CLI and MCP.
3
- * Loads and validates user config modules that export schema, syncBackend, and optional syncPayload.
4
- */
5
- import path from 'node:path'
6
- import { pathToFileURL } from 'node:url'
7
- import type { SyncBackend } from '@livestore/common'
8
- import { UnknownError } from '@livestore/common'
9
- import { isLiveStoreSchema, type LiveStoreSchema } from '@livestore/common/schema'
10
- import { shouldNeverHappen } from '@livestore/utils'
11
- import { Effect, FileSystem, Schema } from '@livestore/utils/effect'
12
-
13
- export interface ModuleConfig {
14
- schema: LiveStoreSchema
15
- syncBackendConstructor: SyncBackend.SyncBackendConstructor
16
- syncPayloadSchema: Schema.Schema<any>
17
- syncPayload: unknown
18
- }
19
-
20
- /**
21
- * Loads and validates a user config module.
22
- * The module must export:
23
- * - `schema`: A valid LiveStore schema
24
- * - `syncBackend`: A sync backend constructor function
25
- * - `syncPayloadSchema` (optional): Schema for validating syncPayload
26
- * - `syncPayload` (optional): Payload data for the sync backend
27
- */
28
- export const loadModuleConfig = ({
29
- configPath,
30
- }: {
31
- configPath: string
32
- }): Effect.Effect<ModuleConfig, UnknownError, FileSystem.FileSystem> =>
33
- Effect.gen(function* () {
34
- const abs = path.isAbsolute(configPath) ? configPath : path.resolve(process.cwd(), configPath)
35
-
36
- const fs = yield* FileSystem.FileSystem
37
- const exists = yield* fs.exists(abs).pipe(UnknownError.mapToUnknownError)
38
- if (!exists) {
39
- return yield* UnknownError.make({
40
- cause: `Store module not found at ${abs}`,
41
- note: 'Make sure the path points to a valid LiveStore module',
42
- })
43
- }
44
-
45
- const mod = yield* Effect.tryPromise({
46
- try: () => import(pathToFileURL(abs).href),
47
- catch: (cause) =>
48
- UnknownError.make({
49
- cause,
50
- note: `Failed to import module at ${abs}`,
51
- }),
52
- })
53
-
54
- const schema = (mod as any)?.schema
55
- if (!isLiveStoreSchema(schema)) {
56
- return yield* UnknownError.make({
57
- cause: `Module at ${abs} must export a valid LiveStore 'schema'`,
58
- note: `Ex: export { schema } from './src/livestore/schema.ts'`,
59
- })
60
- }
61
-
62
- const syncBackendConstructor = (mod as any)?.syncBackend
63
- if (typeof syncBackendConstructor !== 'function') {
64
- return yield* UnknownError.make({
65
- cause: `Module at ${abs} must export a 'syncBackend' constructor`,
66
- note: `Ex: export const syncBackend = makeWsSync({ url })`,
67
- })
68
- }
69
-
70
- const syncPayloadSchemaExport = (mod as any)?.syncPayloadSchema
71
- const syncPayloadSchema =
72
- syncPayloadSchemaExport === undefined
73
- ? Schema.JsonValue
74
- : Schema.isSchema(syncPayloadSchemaExport)
75
- ? (syncPayloadSchemaExport as Schema.Schema<any>)
76
- : shouldNeverHappen(
77
- `Exported 'syncPayloadSchema' from ${abs} must be an Effect Schema (received ${typeof syncPayloadSchemaExport}).`,
78
- )
79
-
80
- const syncPayloadExport = (mod as any)?.syncPayload
81
- const syncPayload = yield* (
82
- syncPayloadExport === undefined
83
- ? Effect.succeed<unknown>(undefined)
84
- : Schema.decodeUnknown(syncPayloadSchema)(syncPayloadExport)
85
- ).pipe(UnknownError.mapToUnknownError)
86
-
87
- return {
88
- schema,
89
- syncBackendConstructor,
90
- syncPayloadSchema,
91
- syncPayload,
92
- }
93
- }).pipe(Effect.withSpan('module-loader:loadModuleConfig'))