@livestore/cli 0.4.0-dev.22 → 0.4.0-dev.23

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 (57) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/__tests__/fixtures/mock-config.d.ts +13 -4
  3. package/dist/__tests__/fixtures/mock-config.d.ts.map +1 -1
  4. package/dist/__tests__/fixtures/mock-config.js +8 -2
  5. package/dist/__tests__/fixtures/mock-config.js.map +1 -1
  6. package/dist/__tests__/sync-operations.test.js +2 -2
  7. package/dist/__tests__/sync-operations.test.js.map +1 -1
  8. package/dist/bin.js.map +1 -1
  9. package/dist/cli.d.ts +1 -1
  10. package/dist/cli.d.ts.map +1 -1
  11. package/dist/cli.js.map +1 -1
  12. package/dist/commands/import-export.d.ts.map +1 -1
  13. package/dist/commands/import-export.js +16 -14
  14. package/dist/commands/import-export.js.map +1 -1
  15. package/dist/commands/mcp-coach.d.ts.map +1 -1
  16. package/dist/commands/mcp-coach.js +3 -3
  17. package/dist/commands/mcp-coach.js.map +1 -1
  18. package/dist/commands/mcp-tool-handlers.d.ts.map +1 -1
  19. package/dist/commands/mcp-tool-handlers.js +4 -4
  20. package/dist/commands/mcp-tool-handlers.js.map +1 -1
  21. package/dist/commands/mcp-tools-defs.d.ts.map +1 -1
  22. package/dist/commands/mcp-tools-defs.js.map +1 -1
  23. package/dist/commands/mcp.d.ts.map +1 -1
  24. package/dist/commands/mcp.js.map +1 -1
  25. package/dist/commands/new-project.d.ts +8 -1
  26. package/dist/commands/new-project.d.ts.map +1 -1
  27. package/dist/commands/new-project.js +26 -17
  28. package/dist/commands/new-project.js.map +1 -1
  29. package/dist/mcp-runtime/runtime.d.ts +2 -2
  30. package/dist/mcp-runtime/runtime.d.ts.map +1 -1
  31. package/dist/mcp-runtime/runtime.js +9 -9
  32. package/dist/mcp-runtime/runtime.js.map +1 -1
  33. package/dist/module-loader.d.ts.map +1 -1
  34. package/dist/module-loader.js +8 -8
  35. package/dist/module-loader.js.map +1 -1
  36. package/dist/package-manager.js +4 -4
  37. package/dist/package-manager.js.map +1 -1
  38. package/dist/sync-operations.d.ts +1 -1
  39. package/dist/sync-operations.d.ts.map +1 -1
  40. package/dist/sync-operations.js +15 -15
  41. package/dist/sync-operations.js.map +1 -1
  42. package/package.json +71 -27
  43. package/src/__tests__/fixtures/mock-config.ts +10 -2
  44. package/src/__tests__/sync-operations.test.ts +4 -2
  45. package/src/bin.ts +1 -0
  46. package/src/cli.ts +1 -0
  47. package/src/commands/import-export.ts +19 -14
  48. package/src/commands/mcp-coach.ts +4 -3
  49. package/src/commands/mcp-tool-handlers.ts +5 -4
  50. package/src/commands/mcp-tools-defs.ts +1 -0
  51. package/src/commands/mcp.ts +1 -0
  52. package/src/commands/new-project.ts +28 -17
  53. package/src/mcp-runtime/runtime.ts +37 -29
  54. package/src/module-loader.ts +9 -8
  55. package/src/package-manager.ts +4 -4
  56. package/src/sync-operations.ts +19 -19
  57. package/dist/tsconfig.tsbuildinfo +0 -1
package/package.json CHANGED
@@ -1,42 +1,86 @@
1
1
  {
2
2
  "name": "@livestore/cli",
3
- "version": "0.4.0-dev.22",
3
+ "version": "0.4.0-dev.23",
4
+ "license": "Apache-2.0",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/livestorejs/livestore.git"
8
+ },
9
+ "bin": {
10
+ "livestore": "./dist/bin.js"
11
+ },
12
+ "files": [
13
+ "dist",
14
+ "package.json",
15
+ "src"
16
+ ],
4
17
  "type": "module",
5
18
  "sideEffects": false,
6
19
  "exports": {
7
20
  ".": "./dist/mod.js"
8
21
  },
9
- "bin": {
10
- "livestore": "./dist/bin.js"
22
+ "publishConfig": {
23
+ "access": "public"
11
24
  },
12
25
  "dependencies": {
13
- "@effect/ai": "0.32.1",
14
- "@effect/ai-openai": "0.36.0",
15
- "@effect/experimental": "0.57.11",
16
- "@effect/opentelemetry": "0.59.3",
17
- "@effect/platform": "0.93.8",
18
- "@effect/rpc": "0.72.2",
19
- "@standard-schema/spec": "1.0.0",
20
- "effect": "3.19.12",
21
- "@livestore/common": "0.4.0-dev.22",
22
- "@livestore/livestore": "0.4.0-dev.22",
23
- "@livestore/sync-cf": "0.4.0-dev.22",
24
- "@livestore/peer-deps": "0.4.0-dev.22",
25
- "@livestore/adapter-node": "0.4.0-dev.22",
26
- "@livestore/utils": "0.4.0-dev.22"
26
+ "@effect/ai": "0.35.0",
27
+ "@effect/ai-openai": "0.39.0",
28
+ "@effect/experimental": "0.60.0",
29
+ "@effect/opentelemetry": "0.63.0",
30
+ "@effect/platform": "0.96.1",
31
+ "@effect/rpc": "0.75.1",
32
+ "effect": "3.21.2",
33
+ "@livestore/adapter-node": "^0.4.0-dev.23",
34
+ "@livestore/common": "^0.4.0-dev.23",
35
+ "@livestore/peer-deps": "^0.4.0-dev.23",
36
+ "@livestore/utils": "^0.4.0-dev.23",
37
+ "@livestore/livestore": "^0.4.0-dev.23"
27
38
  },
28
39
  "devDependencies": {
29
- "@types/node": "24.10.1",
30
- "typescript": "5.9.2",
31
- "@livestore/utils-dev": "0.4.0-dev.22"
40
+ "@types/node": "25.3.3",
41
+ "typescript": "5.9.3",
42
+ "vitest": "3.2.4",
43
+ "@livestore/utils-dev": "^0.4.0-dev.23"
32
44
  },
33
- "files": [
34
- "package.json",
35
- "src",
36
- "dist"
37
- ],
38
- "publishConfig": {
39
- "access": "public"
45
+ "peerDependencies": {
46
+ "@effect/ai": "^0.35.0",
47
+ "@effect/cli": "^0.75.1",
48
+ "@effect/cluster": "^0.58.2",
49
+ "@effect/experimental": "^0.60.0",
50
+ "@effect/opentelemetry": "^0.63.0",
51
+ "@effect/platform": "^0.96.1",
52
+ "@effect/platform-browser": "^0.76.0",
53
+ "@effect/platform-bun": "^0.89.0",
54
+ "@effect/platform-node": "^0.106.0",
55
+ "@effect/printer": "^0.49.0",
56
+ "@effect/printer-ansi": "^0.49.0",
57
+ "@effect/rpc": "^0.75.1",
58
+ "@effect/sql": "^0.51.1",
59
+ "@effect/typeclass": "^0.40.0",
60
+ "@effect/vitest": "^0.29.0",
61
+ "@opentelemetry/api": "^1.9.0",
62
+ "@opentelemetry/resources": "^2.2.0",
63
+ "@standard-schema/spec": "^1.1.0",
64
+ "effect": "^3.21.2"
65
+ },
66
+ "$genie": {
67
+ "source": "package.json.genie.ts",
68
+ "warning": "DO NOT EDIT - changes will be overwritten",
69
+ "workspaceClosureDirs": [
70
+ "packages/@livestore/adapter-node",
71
+ "packages/@livestore/adapter-web",
72
+ "packages/@livestore/cli",
73
+ "packages/@livestore/common",
74
+ "packages/@livestore/common-cf",
75
+ "packages/@livestore/devtools-web-common",
76
+ "packages/@livestore/livestore",
77
+ "packages/@livestore/peer-deps",
78
+ "packages/@livestore/sqlite-wasm",
79
+ "packages/@livestore/utils",
80
+ "packages/@livestore/utils-dev",
81
+ "packages/@livestore/wa-sqlite",
82
+ "packages/@livestore/webmesh"
83
+ ]
40
84
  },
41
85
  "scripts": {
42
86
  "build": "tsc",
@@ -1,5 +1,6 @@
1
1
  import path from 'node:path'
2
2
  import { fileURLToPath, pathToFileURL } from 'node:url'
3
+
3
4
  import { Events, makeSchema, State } from '@livestore/common/schema'
4
5
  import type { MockSyncBackend } from '@livestore/common/sync'
5
6
  import { EventFactory } from '@livestore/common/testing'
@@ -7,6 +8,14 @@ import { Effect, FileSystem, type Mailbox, Schema } from '@livestore/utils/effec
7
8
 
8
9
  const __dirname = path.dirname(fileURLToPath(import.meta.url))
9
10
 
11
+ class DynamicImportError extends Schema.TaggedError<DynamicImportError>()('DynamicImportError', {
12
+ cause: Schema.Defect,
13
+ path: Schema.String,
14
+ }) {}
15
+
16
+ // Use package-local temp directory for test config files to ensure proper module resolution
17
+ const tmpDir = path.join(__dirname, '.tmp-test-configs')
18
+
10
19
  const items = State.SQLite.table({
11
20
  name: 'items',
12
21
  columns: {
@@ -33,7 +42,6 @@ const state = State.SQLite.makeState({
33
42
 
34
43
  export const schema = makeSchema({ state, events })
35
44
 
36
- const tmpDir = path.join(process.cwd(), 'tmp', 'cli-sync-tests')
37
45
  const schemaModuleUrl = pathToFileURL(path.join(__dirname, 'mock-config.ts')).href
38
46
 
39
47
  /** Generates a per-test config module exporting schema, a mock backend, and connection event taps. */
@@ -81,7 +89,7 @@ export const useMockConfig = Effect.acquireRelease(
81
89
 
82
90
  const mod = (yield* Effect.tryPromise({
83
91
  try: () => import(pathToFileURL(tempPath).href),
84
- catch: (cause) => (cause instanceof Error ? cause : new Error(String(cause))),
92
+ catch: (cause) => new DynamicImportError({ cause, path: tempPath }),
85
93
  })) as {
86
94
  mockBackend: MockSyncBackend
87
95
  connectionEvents: Mailbox.Mailbox<'connect' | 'disconnect'>
@@ -1,8 +1,10 @@
1
+ import { expect } from 'vitest'
2
+
1
3
  import type { LiveStoreEvent } from '@livestore/common/schema'
4
+ import { Vitest } from '@livestore/utils-dev/node-vitest'
2
5
  import { Chunk, Effect, FetchHttpClient, Layer, Mailbox, Stream } from '@livestore/utils/effect'
3
6
  import { PlatformNode } from '@livestore/utils/node'
4
- import { Vitest } from '@livestore/utils-dev/node-vitest'
5
- import { expect } from 'vitest'
7
+
6
8
  import { pullEventsFromSyncBackend, pushEventsToSyncBackend } from '../sync-operations.ts'
7
9
  import { makeEventFactory, useMockConfig } from './fixtures/mock-config.ts'
8
10
 
package/src/bin.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  import { liveStoreVersion } from '@livestore/common'
4
4
  import { Console, Effect, FetchHttpClient, Layer, Logger, LogLevel } from '@livestore/utils/effect'
5
5
  import { Cli, PlatformNode } from '@livestore/utils/node'
6
+
6
7
  import { command } from './cli.ts'
7
8
 
8
9
  const cli = Cli.Command.run(command, {
package/src/cli.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Cli } from '@livestore/utils/node'
2
+
2
3
  import { syncCommand } from './commands/import-export.ts'
3
4
  import { mcpCommand } from './commands/mcp.ts'
4
5
  import { createCommand } from './commands/new-project.ts'
@@ -1,9 +1,14 @@
1
1
  import path from 'node:path'
2
+
2
3
  import type { UnknownError } from '@livestore/common'
3
- import { Console, Effect, FileSystem, type HttpClient, type Scope } from '@livestore/utils/effect'
4
+ import { Console, Effect, FileSystem, type HttpClient, Schema, type Scope } from '@livestore/utils/effect'
4
5
  import { Cli } from '@livestore/utils/node'
6
+
5
7
  import * as SyncOps from '../sync-operations.ts'
6
8
 
9
+ const jsonStringifyPretty = Schema.encodeSync(Schema.parseJson({ space: 2 }))
10
+ const jsonParse = Schema.decodeUnknownSync(Schema.parseJson())
11
+
7
12
  const LARGE_EVENT_WARNING_THRESHOLD = 100_000
8
13
 
9
14
  /**
@@ -38,14 +43,14 @@ const exportEvents = ({
38
43
  }
39
44
 
40
45
  const fs = yield* FileSystem.FileSystem
41
- const absOutputPath = path.isAbsolute(outputPath) ? outputPath : path.resolve(process.cwd(), outputPath)
46
+ const absOutputPath = path.isAbsolute(outputPath) === true ? outputPath : path.resolve(process.cwd(), outputPath)
42
47
 
43
- yield* fs.writeFileString(absOutputPath, JSON.stringify(result.data, null, 2)).pipe(
48
+ yield* fs.writeFileString(absOutputPath, jsonStringifyPretty(result.data)).pipe(
44
49
  Effect.mapError(
45
50
  (cause) =>
46
51
  new SyncOps.ExportError({
47
52
  cause,
48
- note: `Failed to write export file: ${cause}`,
53
+ note: `Failed to write export file: ${String(cause)}`,
49
54
  }),
50
55
  ),
51
56
  )
@@ -77,18 +82,18 @@ const importEvents = ({
77
82
  > =>
78
83
  Effect.gen(function* () {
79
84
  const fs = yield* FileSystem.FileSystem
80
- const absInputPath = path.isAbsolute(inputPath) ? inputPath : path.resolve(process.cwd(), inputPath)
85
+ const absInputPath = path.isAbsolute(inputPath) === true ? inputPath : path.resolve(process.cwd(), inputPath)
81
86
 
82
87
  const exists = yield* fs.exists(absInputPath).pipe(
83
88
  Effect.mapError(
84
89
  (cause) =>
85
90
  new SyncOps.ImportError({
86
91
  cause,
87
- note: `Failed to check file existence: ${cause}`,
92
+ note: `Failed to check file existence: ${String(cause)}`,
88
93
  }),
89
94
  ),
90
95
  )
91
- if (!exists) {
96
+ if (exists === false) {
92
97
  return yield* new SyncOps.ImportError({
93
98
  cause: new Error(`File not found: ${absInputPath}`),
94
99
  note: `Import file does not exist at ${absInputPath}`,
@@ -102,13 +107,13 @@ const importEvents = ({
102
107
  (cause) =>
103
108
  new SyncOps.ImportError({
104
109
  cause,
105
- note: `Failed to read import file: ${cause}`,
110
+ note: `Failed to read import file: ${String(cause)}`,
106
111
  }),
107
112
  ),
108
113
  )
109
114
 
110
115
  const parsedContent = yield* Effect.try({
111
- try: () => JSON.parse(fileContent),
116
+ try: () => jsonParse(fileContent),
112
117
  catch: (error) =>
113
118
  new SyncOps.ImportError({
114
119
  cause: new Error(`Failed to parse JSON: ${error instanceof Error ? error.message : String(error)}`),
@@ -119,8 +124,8 @@ const importEvents = ({
119
124
  /** Validate export file format before proceeding */
120
125
  const validation = yield* SyncOps.validateExportData({ data: parsedContent, targetStoreId: storeId })
121
126
 
122
- if (validation.storeIdMismatch) {
123
- if (!force) {
127
+ if (validation.storeIdMismatch === true) {
128
+ if (force !== true) {
124
129
  return yield* new SyncOps.ImportError({
125
130
  cause: new Error(`Store ID mismatch: file has '${validation.sourceStoreId}', expected '${storeId}'`),
126
131
  note: `The export file was created for a different store. Use --force to import anyway.`,
@@ -138,7 +143,7 @@ const importEvents = ({
138
143
  )
139
144
  }
140
145
 
141
- if (dryRun) {
146
+ if (dryRun === true) {
142
147
  yield* Console.log(`Dry run - validating import file...`)
143
148
  yield* Console.log(`Dry run complete. ${validation.eventCount} events would be imported.`)
144
149
  return
@@ -255,8 +260,8 @@ export const importCommand = Cli.Command.make(
255
260
  yield* Console.log(` Config: ${config}`)
256
261
  yield* Console.log(` Store ID: ${storeId}`)
257
262
  yield* Console.log(` Input: ${input}`)
258
- if (force) yield* Console.log(` Force: enabled`)
259
- if (dryRun) yield* Console.log(` Dry run: enabled`)
263
+ if (force === true) yield* Console.log(` Force: enabled`)
264
+ if (dryRun === true) yield* Console.log(` Dry run: enabled`)
260
265
  yield* Console.log('')
261
266
 
262
267
  yield* importEvents({
@@ -1,5 +1,6 @@
1
1
  import { LanguageModel } from '@effect/ai'
2
2
  import { OpenAiClient, OpenAiLanguageModel } from '@effect/ai-openai'
3
+
3
4
  import { AiError, Config, Effect, FetchHttpClient, Layer, Prompt, Schema, Tool } from '@livestore/utils/effect'
4
5
 
5
6
  // Define the coach tool that analyzes LiveStore usage
@@ -57,7 +58,7 @@ type CoachToolResult = {
57
58
  export const coachToolHandler: (input: CoachToolHandlerInput) => Effect.Effect<CoachToolResult, AiError.AiError> =
58
59
  Effect.fn('mcp-coach-handler')(({ code, codeType }) => {
59
60
  const effect = Effect.gen(function* () {
60
- const codeTypeContext = codeType ? `This is ${codeType} code using LiveStore. ` : 'This is LiveStore code. '
61
+ const codeTypeContext = codeType !== undefined ? `This is ${codeType} code using LiveStore. ` : 'This is LiveStore code. '
61
62
 
62
63
  const prompt = Prompt.makeMessage('user', {
63
64
  content: [
@@ -113,11 +114,11 @@ Format your response as constructive feedback that helps developers improve thei
113
114
  .slice(0, 5)
114
115
 
115
116
  const scoreMatch = feedback.match(/(?:score|rating|quality).*?(\d+(?:\.\d+)?)/i)
116
- const score = scoreMatch ? Number.parseFloat(scoreMatch[1] ?? '0') : undefined
117
+ const score = scoreMatch !== null ? Number.parseFloat(scoreMatch[1] ?? '0') : undefined
117
118
 
118
119
  return {
119
120
  feedback,
120
- score: score && score >= 1 && score <= 10 ? score : undefined,
121
+ score: score !== undefined && score >= 1 && score <= 10 ? score : undefined,
121
122
  suggestions,
122
123
  }
123
124
  })
@@ -1,5 +1,6 @@
1
1
  import { Effect, FetchHttpClient, Layer, type Toolkit } from '@livestore/utils/effect'
2
2
  import { PlatformNode } from '@livestore/utils/node'
3
+
3
4
  import { blogSchemaContent } from '../mcp-content/schemas/blog.ts'
4
5
  import { ecommerceSchemaContent } from '../mcp-content/schemas/ecommerce.ts'
5
6
  import { socialSchemaContent } from '../mcp-content/schemas/social.ts'
@@ -39,7 +40,7 @@ export const toolHandlers: LivestoreToolHandlers = livestoreToolkit.of({
39
40
  explanation = 'E-commerce schema with products, categories, orders, and inventory'
40
41
  break
41
42
  case 'custom': {
42
- if (!customDescription) {
43
+ if (customDescription == null) {
43
44
  schemaCode = `// Custom schema requested but no description provided
44
45
  import { Schema } from '@livestore/livestore'
45
46
 
@@ -58,11 +59,11 @@ export const schema = Schema.create({
58
59
  }
59
60
 
60
61
  // Generate a basic custom schema based on description
61
- const tableName = customDescription.toLowerCase().includes('user')
62
+ const tableName = customDescription.toLowerCase().includes('user') === true
62
63
  ? 'users'
63
- : customDescription.toLowerCase().includes('product')
64
+ : customDescription.toLowerCase().includes('product') === true
64
65
  ? 'products'
65
- : customDescription.toLowerCase().includes('post')
66
+ : customDescription.toLowerCase().includes('post') === true
66
67
  ? 'posts'
67
68
  : 'items'
68
69
 
@@ -1,4 +1,5 @@
1
1
  import { Schema, Tool, Toolkit } from '@livestore/utils/effect'
2
+
2
3
  import { coachTool } from './mcp-coach.ts'
3
4
 
4
5
  export const livestoreToolkit = Toolkit.make(
@@ -1,5 +1,6 @@
1
1
  import { Effect, Layer, Logger, McpServer } from '@livestore/utils/effect'
2
2
  import { Cli, PlatformNode } from '@livestore/utils/node'
3
+
3
4
  import { architectureContent } from '../mcp-content/architecture.ts'
4
5
  import { featuresContent } from '../mcp-content/features.ts'
5
6
  import { gettingStartedContent } from '../mcp-content/getting-started.ts'
@@ -1,5 +1,6 @@
1
1
  import * as os from 'node:os'
2
2
  import * as nodePath from 'node:path'
3
+
3
4
  import { sluggify } from '@livestore/utils'
4
5
  import {
5
6
  Command,
@@ -25,28 +26,38 @@ const GitHubContentSchema = Schema.Struct({
25
26
 
26
27
  const GitHubContentsResponseSchema = Schema.Array(GitHubContentSchema)
27
28
 
29
+ const githubRequest = (url: string) => {
30
+ const request = HttpClientRequest.get(url).pipe(HttpClientRequest.setHeader('accept', 'application/vnd.github+json'))
31
+ const token = process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN
32
+ return token === undefined ? request : request.pipe(HttpClientRequest.setHeader('authorization', `Bearer ${token}`))
33
+ }
34
+
28
35
  /** Schema for parsing package.json scripts (dev or start) */
29
36
  const PackageJsonScriptsSchema = Schema.Struct({
30
37
  scripts: Schema.Union(Schema.Struct({ dev: Schema.String }), Schema.Struct({ start: Schema.String })),
31
38
  })
32
39
 
33
40
  // Error types
34
- export class ExampleNotFoundError extends Schema.TaggedError<ExampleNotFoundError>()('ExampleNotFoundError', {
41
+ export class ExampleNotFoundError extends Schema.TaggedError<ExampleNotFoundError>('~@livestore/cli/ExampleNotFoundError')('ExampleNotFoundError', {
35
42
  exampleName: Schema.String,
36
43
  availableExamples: Schema.Array(Schema.String),
37
44
  message: Schema.String,
38
45
  }) {}
39
46
 
40
- export class NetworkError extends Schema.TaggedError<NetworkError>()('NetworkError', {
47
+ export class NetworkError extends Schema.TaggedError<NetworkError>('~@livestore/cli/NetworkError')('NetworkError', {
41
48
  cause: Schema.Unknown,
42
49
  message: Schema.String,
43
50
  }) {}
44
51
 
45
- export class DirectoryExistsError extends Schema.TaggedError<DirectoryExistsError>()('DirectoryExistsError', {
52
+ export class DirectoryExistsError extends Schema.TaggedError<DirectoryExistsError>('~@livestore/cli/DirectoryExistsError')('DirectoryExistsError', {
46
53
  path: Schema.String,
47
54
  message: Schema.String,
48
55
  }) {}
49
56
 
57
+ export class NoExamplesError extends Schema.TaggedError<NoExamplesError>('~@livestore/cli/NoExamplesError')('NoExamplesError', {
58
+ message: Schema.String,
59
+ }) {}
60
+
50
61
  // Fetch available examples from GitHub
51
62
  const fetchExamples = (ref: string) =>
52
63
  Effect.gen(function* () {
@@ -54,26 +65,26 @@ const fetchExamples = (ref: string) =>
54
65
 
55
66
  yield* Effect.log(`Fetching examples from ref: ${ref}`)
56
67
 
57
- const request = HttpClientRequest.get(url)
68
+ const request = githubRequest(url)
58
69
  const response = yield* HttpClient.execute(request).pipe(
59
70
  Effect.scoped,
60
71
  Effect.catchAll(
61
72
  (error) =>
62
73
  new NetworkError({
63
74
  cause: error,
64
- message: `Failed to fetch examples from GitHub: ${error}`,
75
+ message: `Failed to fetch examples from GitHub: ${String(error)}`,
65
76
  }),
66
77
  ),
67
78
  )
68
79
 
69
80
  const responseText = yield* response.text
70
81
 
71
- const examples = yield* Schema.decodeUnknown(GitHubContentsResponseSchema)(JSON.parse(responseText)).pipe(
82
+ const examples = yield* Schema.decodeUnknown(Schema.parseJson(GitHubContentsResponseSchema))(responseText).pipe(
72
83
  Effect.catchAll(
73
84
  (error) =>
74
85
  new NetworkError({
75
86
  cause: error,
76
- message: `Failed to parse GitHub API response: ${error}`,
87
+ message: `Failed to parse GitHub API response: ${String(error)}`,
77
88
  }),
78
89
  ),
79
90
  )
@@ -81,7 +92,7 @@ const fetchExamples = (ref: string) =>
81
92
  const exampleNames = examples
82
93
  .filter((item) => item.type === 'dir')
83
94
  .map((item) => item.name)
84
- .sort()
95
+ .toSorted()
85
96
 
86
97
  yield* Effect.log(`Found ${exampleNames.length} examples: ${exampleNames.join(', ')}`)
87
98
 
@@ -92,7 +103,7 @@ const fetchExamples = (ref: string) =>
92
103
  const selectExample = (examples: string[]) =>
93
104
  Effect.gen(function* () {
94
105
  if (examples.length === 0) {
95
- return yield* Effect.fail(new Error('No examples available'))
106
+ return yield* new NoExamplesError({ message: 'No examples available' })
96
107
  }
97
108
 
98
109
  const prompt = Cli.Prompt.select({
@@ -117,7 +128,7 @@ const downloadExample = (exampleName: string, ref: string, destinationPath: stri
117
128
  const tarballUrl = `https://api.github.com/repos/livestorejs/livestore/tarball/${ref}`
118
129
 
119
130
  // Download tarball directly
120
- const request = HttpClientRequest.get(tarballUrl)
131
+ const request = githubRequest(tarballUrl)
121
132
 
122
133
  const response = yield* HttpClient.execute(request).pipe(
123
134
  Effect.scoped,
@@ -125,7 +136,7 @@ const downloadExample = (exampleName: string, ref: string, destinationPath: stri
125
136
  (error) =>
126
137
  new NetworkError({
127
138
  cause: error,
128
- message: `Failed to download tarball: ${error}`,
139
+ message: `Failed to download tarball: ${String(error)}`,
129
140
  }),
130
141
  ),
131
142
  )
@@ -150,7 +161,7 @@ const downloadExample = (exampleName: string, ref: string, destinationPath: stri
150
161
  (error) =>
151
162
  new NetworkError({
152
163
  cause: error,
153
- message: `Failed to extract tarball: ${error}`,
164
+ message: `Failed to extract tarball: ${String(error)}`,
154
165
  }),
155
166
  ),
156
167
  )
@@ -171,7 +182,7 @@ const downloadExample = (exampleName: string, ref: string, destinationPath: stri
171
182
  // Check if the example exists
172
183
  const exampleExists = yield* fs.exists(exampleSourcePath)
173
184
 
174
- if (!exampleExists) {
185
+ if (exampleExists === false) {
175
186
  return yield* new ExampleNotFoundError({
176
187
  exampleName,
177
188
  availableExamples: [],
@@ -186,7 +197,7 @@ const downloadExample = (exampleName: string, ref: string, destinationPath: stri
186
197
  (error) =>
187
198
  new NetworkError({
188
199
  cause: error,
189
- message: `Failed to copy example files: ${error}`,
200
+ message: `Failed to copy example files: ${String(error)}`,
190
201
  }),
191
202
  ),
192
203
  )
@@ -245,10 +256,10 @@ export const createCommand = Cli.Command.make(
245
256
  }
246
257
 
247
258
  // Select example (from CLI option or interactive prompt)
248
- const selectedExample = Option.isSome(example) ? example.value : yield* selectExample(examples)
259
+ const selectedExample = Option.isSome(example) === true ? example.value : yield* selectExample(examples)
249
260
 
250
261
  // Validate selected example exists
251
- if (!examples.includes(selectedExample)) {
262
+ if (examples.includes(selectedExample) === false) {
252
263
  yield* Console.log(`❌ Example "${selectedExample}" not found`)
253
264
  yield* Console.log(`Available examples: ${examples.join(', ')}`)
254
265
  return yield* new ExampleNotFoundError({
@@ -259,7 +270,7 @@ export const createCommand = Cli.Command.make(
259
270
  }
260
271
 
261
272
  // Determine destination path
262
- const destinationPath = Option.isSome(path) ? nodePath.resolve(path.value) : nodePath.resolve(selectedExample)
273
+ const destinationPath = Option.isSome(path) === true ? nodePath.resolve(path.value) : nodePath.resolve(selectedExample)
263
274
 
264
275
  // Download and extract the example
265
276
  yield* downloadExample(selectedExample, ref, destinationPath)
@@ -30,7 +30,7 @@ export const init = ({
30
30
  sessionId?: string
31
31
  }): Effect.Effect<Store<any>, UnknownError> =>
32
32
  Effect.gen(function* () {
33
- if (!storeId || typeof storeId !== 'string') {
33
+ if (storeId === '' || typeof storeId !== 'string') {
34
34
  return yield* UnknownError.make({ cause: new Error('Invalid storeId: expected a non-empty string') })
35
35
  }
36
36
 
@@ -39,8 +39,8 @@ export const init = ({
39
39
  // Build Node adapter internally
40
40
  const adapter = makeNodeAdapter({
41
41
  storage: { type: 'in-memory' },
42
- ...(clientId ? { clientId } : {}),
43
- ...(sessionId ? { sessionId } : {}),
42
+ ...(clientId !== undefined && clientId !== '' ? { clientId } : {}),
43
+ ...(sessionId !== undefined && sessionId !== '' ? { sessionId } : {}),
44
44
  sync: {
45
45
  backend: syncBackendConstructor,
46
46
  initialSyncOptions: { _tag: 'Blocking', timeout: 5000 },
@@ -61,7 +61,7 @@ export const init = ({
61
61
  )
62
62
 
63
63
  // Replace existing store if any
64
- if (store) {
64
+ if (store !== undefined) {
65
65
  yield* Effect.promise(async () => {
66
66
  try {
67
67
  await store!.shutdownPromise()
@@ -83,7 +83,7 @@ export const status = Effect.gen(function* () {
83
83
  }
84
84
  }
85
85
  const s = opt.value
86
- const tableCounts = (Array.from(s.schema.state.sqlite.tables.keys()) as string[])
86
+ const tableCounts = Array.from(s.schema.state.sqlite.tables.keys())
87
87
  .filter((name) => !SystemTables.isStateSystemTable(name))
88
88
  .reduce(
89
89
  (acc, name) => {
@@ -102,34 +102,42 @@ export const status = Effect.gen(function* () {
102
102
  }
103
103
  }).pipe(Effect.withSpan('mcp-runtime:status'))
104
104
 
105
- export const query = ({ sql, bindValues }: { sql: string; bindValues?: readonly any[] | Record<string, unknown> }) =>
106
- Effect.gen(function* () {
107
- const opt = yield* getStore
108
- if (opt._tag === 'None') {
109
- return yield* Effect.dieMessage('LiveStore not connected. Call livestore_instance_connect first.')
110
- }
111
- const s = opt.value
105
+ export const query = Effect.fn('mcp-runtime:query')(function* ({
106
+ sql,
107
+ bindValues,
108
+ }: {
109
+ sql: string
110
+ bindValues?: readonly any[] | Record<string, unknown>
111
+ }) {
112
+ const opt = yield* getStore
113
+ if (opt._tag === 'None') {
114
+ return yield* Effect.dieMessage('LiveStore not connected. Call livestore_instance_connect first.')
115
+ }
116
+ const s = opt.value
112
117
 
113
- const rows = s.query({ query: sql, bindValues: (bindValues as any) ?? [] }) as Array<Record<string, unknown>>
114
- const jsonRows = rows.map((r) => Object.fromEntries(Object.entries(r).map(([k, v]) => [k, v as Schema.JsonValue])))
115
- return { rows: jsonRows, rowCount: jsonRows.length }
116
- }).pipe(Effect.withSpan('mcp-runtime:query'))
118
+ const rows = s.query<Array<Record<string, unknown>>>({ query: sql, bindValues: (bindValues as any) ?? [] })
119
+ const jsonRows = rows.map((r) => Object.fromEntries(Object.entries(r).map(([k, v]) => [k, v as Schema.JsonValue])))
120
+ return { rows: jsonRows, rowCount: jsonRows.length }
121
+ })
117
122
 
118
- export const commit = ({ events }: { events: ReadonlyArray<{ name: string; args: Schema.JsonValue }> }) =>
119
- Effect.gen(function* () {
120
- const opt = yield* getStore
121
- if (opt._tag === 'None') {
122
- return yield* Effect.dieMessage('LiveStore not connected. Call livestore_instance_connect first.')
123
- }
124
- const s = opt.value
125
- const InputEventSchema = LiveStoreEvent.Input.makeSchema(s.schema) as Schema.Schema<any>
126
- const decoded = events.map((e) => Schema.decodeSync(InputEventSchema)(e))
127
- s.commit(...decoded)
128
- return { committed: decoded.length }
129
- }).pipe(Effect.withSpan('mcp-runtime:commit'))
123
+ export const commit = Effect.fn('mcp-runtime:commit')(function* ({
124
+ events,
125
+ }: {
126
+ events: ReadonlyArray<{ name: string; args: Schema.JsonValue }>
127
+ }) {
128
+ const opt = yield* getStore
129
+ if (opt._tag === 'None') {
130
+ return yield* Effect.dieMessage('LiveStore not connected. Call livestore_instance_connect first.')
131
+ }
132
+ const s = opt.value
133
+ const InputEventSchema = LiveStoreEvent.Input.makeSchema(s.schema) as Schema.Schema<any>
134
+ const decoded = events.map((e) => Schema.decodeSync(InputEventSchema)(e))
135
+ s.commit(...decoded)
136
+ return { committed: decoded.length }
137
+ })
130
138
 
131
139
  export const disconnect = Effect.promise(async () => {
132
- if (store) {
140
+ if (store !== undefined) {
133
141
  try {
134
142
  await store.shutdownPromise()
135
143
  } catch {}