@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.
- package/dist/cli.d.ts +1 -15
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1 -2
- package/dist/cli.js.map +1 -1
- package/dist/commands/mcp-coach.d.ts +2 -2
- package/dist/commands/mcp-coach.d.ts.map +1 -1
- package/dist/commands/mcp-tool-handlers.d.ts +1 -5
- package/dist/commands/mcp-tool-handlers.d.ts.map +1 -1
- package/dist/commands/mcp-tool-handlers.js +4 -42
- package/dist/commands/mcp-tool-handlers.js.map +1 -1
- package/dist/commands/mcp-tools-defs.d.ts +1 -31
- package/dist/commands/mcp-tools-defs.d.ts.map +1 -1
- package/dist/commands/mcp-tools-defs.js +5 -87
- package/dist/commands/mcp-tools-defs.js.map +1 -1
- package/dist/commands/new-project.d.ts +1 -1
- package/dist/mcp-runtime/runtime.d.ts +3 -4
- package/dist/mcp-runtime/runtime.d.ts.map +1 -1
- package/dist/mcp-runtime/runtime.js +53 -20
- package/dist/mcp-runtime/runtime.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -15
- package/src/cli.ts +1 -2
- package/src/commands/mcp-tool-handlers.ts +5 -50
- package/src/commands/mcp-tools-defs.ts +4 -92
- package/src/mcp-runtime/runtime.ts +65 -32
- package/dist/__tests__/fixtures/mock-config.d.ts +0 -56
- package/dist/__tests__/fixtures/mock-config.d.ts.map +0 -1
- package/dist/__tests__/fixtures/mock-config.js +0 -88
- package/dist/__tests__/fixtures/mock-config.js.map +0 -1
- package/dist/__tests__/sync-operations.test.d.ts +0 -2
- package/dist/__tests__/sync-operations.test.d.ts.map +0 -1
- package/dist/__tests__/sync-operations.test.js +0 -167
- package/dist/__tests__/sync-operations.test.js.map +0 -1
- package/dist/commands/import-export.d.ts +0 -34
- package/dist/commands/import-export.d.ts.map +0 -1
- package/dist/commands/import-export.js +0 -133
- package/dist/commands/import-export.js.map +0 -1
- package/dist/module-loader.d.ts +0 -22
- package/dist/module-loader.d.ts.map +0 -1
- package/dist/module-loader.js +0 -75
- package/dist/module-loader.js.map +0 -1
- package/dist/sync-operations.d.ts +0 -121
- package/dist/sync-operations.d.ts.map +0 -1
- package/dist/sync-operations.js +0 -180
- package/dist/sync-operations.js.map +0 -1
- package/src/__tests__/fixtures/mock-config.ts +0 -104
- package/src/__tests__/sync-operations.test.ts +0 -230
- package/src/commands/import-export.ts +0 -278
- package/src/module-loader.ts +0 -93
- package/src/sync-operations.ts +0 -360
package/dist/sync-operations.js
DELETED
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
import { UnknownError } from '@livestore/common';
|
|
2
|
-
import { LiveStoreEvent } from '@livestore/common/schema';
|
|
3
|
-
import { Cause, Chunk, Effect, KeyValueStore, Option, Schema, Stream, SubscriptionRef, } from '@livestore/utils/effect';
|
|
4
|
-
import { loadModuleConfig } from "./module-loader.js";
|
|
5
|
-
/** Connection timeout for sync backend ping (5 seconds) */
|
|
6
|
-
const CONNECTION_TIMEOUT_MS = 5000;
|
|
7
|
-
/**
|
|
8
|
-
* Schema for the export file format.
|
|
9
|
-
* Contains metadata about the export and an array of events in global encoded format.
|
|
10
|
-
*/
|
|
11
|
-
export const ExportFileSchema = Schema.Struct({
|
|
12
|
-
/** Format version for future compatibility */
|
|
13
|
-
version: Schema.Literal(1),
|
|
14
|
-
/** Store identifier */
|
|
15
|
-
storeId: Schema.String,
|
|
16
|
-
/** ISO timestamp of when the export was created */
|
|
17
|
-
exportedAt: Schema.String,
|
|
18
|
-
/** Total number of events in the export */
|
|
19
|
-
eventCount: Schema.Number,
|
|
20
|
-
/** Array of events in global encoded format */
|
|
21
|
-
events: Schema.Array(LiveStoreEvent.Global.Encoded),
|
|
22
|
-
});
|
|
23
|
-
export class ConnectionError extends Schema.TaggedError()('ConnectionError', {
|
|
24
|
-
cause: Schema.Defect,
|
|
25
|
-
note: Schema.String,
|
|
26
|
-
}) {
|
|
27
|
-
}
|
|
28
|
-
export class ExportError extends Schema.TaggedError()('ExportError', {
|
|
29
|
-
cause: Schema.Defect,
|
|
30
|
-
note: Schema.String,
|
|
31
|
-
}) {
|
|
32
|
-
}
|
|
33
|
-
export class ImportError extends Schema.TaggedError()('ImportError', {
|
|
34
|
-
cause: Schema.Defect,
|
|
35
|
-
note: Schema.String,
|
|
36
|
-
}) {
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Creates a sync backend connection from a user module and verifies connectivity.
|
|
40
|
-
* This is a simplified version of the MCP runtime that only creates the sync backend.
|
|
41
|
-
*/
|
|
42
|
-
export const makeSyncBackend = ({ configPath, storeId, clientId, }) => Effect.gen(function* () {
|
|
43
|
-
const { syncBackendConstructor, syncPayload } = yield* loadModuleConfig({ configPath });
|
|
44
|
-
const syncBackend = yield* syncBackendConstructor({
|
|
45
|
-
storeId,
|
|
46
|
-
clientId,
|
|
47
|
-
/** syncPayload is validated against syncPayloadSchema by loadModuleConfig */
|
|
48
|
-
payload: syncPayload,
|
|
49
|
-
}).pipe(Effect.provide(KeyValueStore.layerMemory), UnknownError.mapToUnknownError);
|
|
50
|
-
/** Connect to the sync backend */
|
|
51
|
-
yield* syncBackend.connect.pipe(Effect.mapError((cause) => new ConnectionError({
|
|
52
|
-
cause,
|
|
53
|
-
note: `Failed to connect to sync backend: ${cause._tag === 'IsOfflineError' ? 'Backend is offline or unreachable' : String(cause)}`,
|
|
54
|
-
})));
|
|
55
|
-
/** Verify connectivity with a ping (with timeout) */
|
|
56
|
-
yield* syncBackend.ping.pipe(Effect.timeout(CONNECTION_TIMEOUT_MS), Effect.catchAll((cause) => {
|
|
57
|
-
if (Cause.isTimeoutException(cause)) {
|
|
58
|
-
return Effect.fail(new ConnectionError({
|
|
59
|
-
cause,
|
|
60
|
-
note: `Connection timeout: Sync backend did not respond within ${CONNECTION_TIMEOUT_MS}ms`,
|
|
61
|
-
}));
|
|
62
|
-
}
|
|
63
|
-
return Effect.fail(new ConnectionError({
|
|
64
|
-
cause,
|
|
65
|
-
note: `Failed to ping sync backend: ${cause._tag === 'IsOfflineError' ? 'Backend is offline or unreachable' : String(cause)}`,
|
|
66
|
-
}));
|
|
67
|
-
}));
|
|
68
|
-
return syncBackend;
|
|
69
|
-
});
|
|
70
|
-
const releaseSyncBackend = (syncBackend) => {
|
|
71
|
-
const maybeDisconnect = syncBackend.disconnect;
|
|
72
|
-
const releaseEffect = maybeDisconnect ?? SubscriptionRef.set(syncBackend.isConnected, false);
|
|
73
|
-
return releaseEffect.pipe(Effect.orElse(() => Effect.void));
|
|
74
|
-
};
|
|
75
|
-
/**
|
|
76
|
-
* Core export operation - pulls all events from sync backend.
|
|
77
|
-
* Returns the export data structure without writing to file.
|
|
78
|
-
*/
|
|
79
|
-
export const pullEventsFromSyncBackend = ({ configPath, storeId, clientId, }) => Effect.acquireUseRelease(makeSyncBackend({ configPath, storeId, clientId }), (syncBackend) => Effect.gen(function* () {
|
|
80
|
-
const backendName = syncBackend.metadata.name;
|
|
81
|
-
const batchesChunk = yield* syncBackend.pull(Option.none(), { live: false }).pipe(Stream.takeUntil((item) => item.pageInfo._tag === 'NoMore'), Stream.runCollect, Effect.mapError((cause) => new ExportError({
|
|
82
|
-
cause,
|
|
83
|
-
note: `Failed to pull events from sync backend: ${cause}`,
|
|
84
|
-
})));
|
|
85
|
-
const events = Chunk.toReadonlyArray(batchesChunk)
|
|
86
|
-
.flatMap((item) => item.batch)
|
|
87
|
-
.map((item) => item.eventEncoded);
|
|
88
|
-
const exportedAt = new Date().toISOString();
|
|
89
|
-
const exportData = {
|
|
90
|
-
version: 1,
|
|
91
|
-
storeId,
|
|
92
|
-
exportedAt,
|
|
93
|
-
eventCount: events.length,
|
|
94
|
-
events,
|
|
95
|
-
};
|
|
96
|
-
return {
|
|
97
|
-
storeId,
|
|
98
|
-
eventCount: events.length,
|
|
99
|
-
exportedAt,
|
|
100
|
-
backendName,
|
|
101
|
-
data: exportData,
|
|
102
|
-
};
|
|
103
|
-
}), releaseSyncBackend).pipe(Effect.withSpan('sync:pullEvents'));
|
|
104
|
-
/**
|
|
105
|
-
* Validates an export file for import.
|
|
106
|
-
* Returns validation info without actually importing.
|
|
107
|
-
*/
|
|
108
|
-
export const validateExportData = ({ data, targetStoreId, }) => Effect.gen(function* () {
|
|
109
|
-
const exportData = yield* Schema.decodeUnknown(ExportFileSchema)(data).pipe(Effect.mapError((cause) => new ImportError({
|
|
110
|
-
cause: new Error(`Invalid export file format: ${cause}`),
|
|
111
|
-
note: `Invalid export file format: ${cause}`,
|
|
112
|
-
})));
|
|
113
|
-
return {
|
|
114
|
-
storeId: targetStoreId,
|
|
115
|
-
eventCount: exportData.events.length,
|
|
116
|
-
sourceStoreId: exportData.storeId,
|
|
117
|
-
storeIdMismatch: exportData.storeId !== targetStoreId,
|
|
118
|
-
};
|
|
119
|
-
});
|
|
120
|
-
/**
|
|
121
|
-
* Core import operation - pushes events to sync backend.
|
|
122
|
-
* Validates that the backend is empty before importing.
|
|
123
|
-
*/
|
|
124
|
-
export const pushEventsToSyncBackend = ({ configPath, storeId, clientId, data, force, dryRun, onProgress, }) => Effect.acquireUseRelease(makeSyncBackend({ configPath, storeId, clientId }), (syncBackend) => Effect.gen(function* () {
|
|
125
|
-
const exportData = yield* Schema.decodeUnknown(ExportFileSchema)(data).pipe(Effect.mapError((cause) => new ImportError({
|
|
126
|
-
cause: new Error(`Invalid export file format: ${cause}`),
|
|
127
|
-
note: `Invalid export file format: ${cause}`,
|
|
128
|
-
})));
|
|
129
|
-
if (exportData.storeId !== storeId && !force) {
|
|
130
|
-
return yield* new ImportError({
|
|
131
|
-
cause: new Error(`Store ID mismatch: file has '${exportData.storeId}', expected '${storeId}'`),
|
|
132
|
-
note: `The export file was created for a different store. Use force option to import anyway.`,
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
if (dryRun) {
|
|
136
|
-
return {
|
|
137
|
-
storeId,
|
|
138
|
-
eventCount: exportData.events.length,
|
|
139
|
-
dryRun: true,
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
const backendName = syncBackend.metadata.name;
|
|
143
|
-
/** Check if events already exist by pulling from the backend first (short-circuit on first non-empty batch) */
|
|
144
|
-
const existingBatchOption = yield* syncBackend.pull(Option.none(), { live: false }).pipe(Stream.filter((item) => item.batch.length > 0), Stream.runHead, Effect.mapError((cause) => new ImportError({
|
|
145
|
-
cause,
|
|
146
|
-
note: `Failed to check existing events: ${cause}`,
|
|
147
|
-
})));
|
|
148
|
-
if (Option.isSome(existingBatchOption)) {
|
|
149
|
-
const existingBatch = existingBatchOption.value;
|
|
150
|
-
const estimatedCount = existingBatch.pageInfo._tag === 'MoreKnown'
|
|
151
|
-
? existingBatch.batch.length + existingBatch.pageInfo.remaining
|
|
152
|
-
: existingBatch.batch.length;
|
|
153
|
-
return yield* new ImportError({
|
|
154
|
-
cause: new Error(`Sync backend already contains at least ${estimatedCount} events`),
|
|
155
|
-
note: `Cannot import into a non-empty sync backend. The sync backend must be empty.`,
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
/** Push events in batches of 100 (sync backend constraint) */
|
|
159
|
-
const batchSize = 100;
|
|
160
|
-
const total = exportData.events.length;
|
|
161
|
-
let pushed = 0;
|
|
162
|
-
for (let i = 0; i < exportData.events.length; i += batchSize) {
|
|
163
|
-
const batch = exportData.events.slice(i, i + batchSize);
|
|
164
|
-
yield* syncBackend.push(batch).pipe(Effect.mapError((cause) => new ImportError({
|
|
165
|
-
cause,
|
|
166
|
-
note: `Failed to push events at position ${i}: ${cause}`,
|
|
167
|
-
})));
|
|
168
|
-
pushed += batch.length;
|
|
169
|
-
if (onProgress) {
|
|
170
|
-
yield* onProgress(pushed, total);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
return {
|
|
174
|
-
storeId,
|
|
175
|
-
eventCount: exportData.events.length,
|
|
176
|
-
dryRun: false,
|
|
177
|
-
backendName,
|
|
178
|
-
};
|
|
179
|
-
}), releaseSyncBackend).pipe(Effect.withSpan('sync:pushEvents'));
|
|
180
|
-
//# sourceMappingURL=sync-operations.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sync-operations.js","sourceRoot":"","sources":["../src/sync-operations.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import path from 'node:path'
|
|
2
|
-
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
3
|
-
import { Events, makeSchema, State } from '@livestore/common/schema'
|
|
4
|
-
import type { MockSyncBackend } from '@livestore/common/sync'
|
|
5
|
-
import { EventFactory } from '@livestore/common/testing'
|
|
6
|
-
import { Effect, FileSystem, type Mailbox, Schema } from '@livestore/utils/effect'
|
|
7
|
-
|
|
8
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
9
|
-
|
|
10
|
-
const items = State.SQLite.table({
|
|
11
|
-
name: 'items',
|
|
12
|
-
columns: {
|
|
13
|
-
id: State.SQLite.text({ primaryKey: true }),
|
|
14
|
-
title: State.SQLite.text({ default: '', nullable: false }),
|
|
15
|
-
},
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
export const events = {
|
|
19
|
-
itemAdded: Events.synced({
|
|
20
|
-
name: 'itemAdded',
|
|
21
|
-
schema: Schema.Struct({ id: Schema.String, title: Schema.String }),
|
|
22
|
-
}),
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const materializers = State.SQLite.materializers(events, {
|
|
26
|
-
itemAdded: ({ id, title }) => items.insert({ id, title }),
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
const state = State.SQLite.makeState({
|
|
30
|
-
tables: { items },
|
|
31
|
-
materializers,
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
export const schema = makeSchema({ state, events })
|
|
35
|
-
|
|
36
|
-
const tmpDir = path.join(process.cwd(), 'tmp', 'cli-sync-tests')
|
|
37
|
-
const schemaModuleUrl = pathToFileURL(path.join(__dirname, 'mock-config.ts')).href
|
|
38
|
-
|
|
39
|
-
/** Generates a per-test config module exporting schema, a mock backend, and connection event taps. */
|
|
40
|
-
const makeTempConfig = () => {
|
|
41
|
-
const moduleSource = `
|
|
42
|
-
import { schema } from ${JSON.stringify(schemaModuleUrl)}
|
|
43
|
-
import { makeMockSyncBackend } from '@livestore/common/sync'
|
|
44
|
-
import { Effect, Mailbox } from '@livestore/utils/effect'
|
|
45
|
-
|
|
46
|
-
export const mockBackend = await Effect.runPromise(Effect.scoped(makeMockSyncBackend({ startConnected: true })))
|
|
47
|
-
export const connectionEvents = await Effect.runPromise(Mailbox.make<'connect' | 'disconnect'>())
|
|
48
|
-
|
|
49
|
-
export { schema }
|
|
50
|
-
|
|
51
|
-
export const syncBackend = (_args) =>
|
|
52
|
-
mockBackend.makeSyncBackend.pipe(
|
|
53
|
-
Effect.tap(() => connectionEvents.offer('connect')),
|
|
54
|
-
Effect.map((backend) => {
|
|
55
|
-
const disconnect = backend.disconnect ?? Effect.void
|
|
56
|
-
return {
|
|
57
|
-
...backend,
|
|
58
|
-
disconnect: disconnect.pipe(Effect.tap(() => connectionEvents.offer('disconnect'))),
|
|
59
|
-
}
|
|
60
|
-
}),
|
|
61
|
-
)
|
|
62
|
-
`
|
|
63
|
-
|
|
64
|
-
return moduleSource
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Creates a temporary config module (schema + mock backend) and cleans it up afterwards.
|
|
69
|
-
* Returns the module path plus handles to the backend and connection event mailbox, keeping lifecycle assertions local to each test.
|
|
70
|
-
*/
|
|
71
|
-
export const useMockConfig = Effect.acquireRelease(
|
|
72
|
-
Effect.gen(function* () {
|
|
73
|
-
const fs = yield* FileSystem.FileSystem
|
|
74
|
-
|
|
75
|
-
yield* fs.makeDirectory(tmpDir, { recursive: true })
|
|
76
|
-
|
|
77
|
-
const tempPath = path.join(tmpDir, `mock-config-${Date.now()}-${Math.random().toString(16).slice(2)}.ts`)
|
|
78
|
-
const moduleSource = makeTempConfig()
|
|
79
|
-
|
|
80
|
-
yield* fs.writeFileString(tempPath, moduleSource)
|
|
81
|
-
|
|
82
|
-
const mod = (yield* Effect.tryPromise({
|
|
83
|
-
try: () => import(pathToFileURL(tempPath).href),
|
|
84
|
-
catch: (cause) => (cause instanceof Error ? cause : new Error(String(cause))),
|
|
85
|
-
})) as {
|
|
86
|
-
mockBackend: MockSyncBackend
|
|
87
|
-
connectionEvents: Mailbox.Mailbox<'connect' | 'disconnect'>
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return { configPath: tempPath, mockBackend: mod.mockBackend, connectionEvents: mod.connectionEvents }
|
|
91
|
-
}),
|
|
92
|
-
({ configPath }) =>
|
|
93
|
-
Effect.gen(function* () {
|
|
94
|
-
const fs = yield* FileSystem.FileSystem
|
|
95
|
-
yield* fs.remove(configPath, { recursive: false }).pipe(Effect.catchAll(() => Effect.void))
|
|
96
|
-
}),
|
|
97
|
-
)
|
|
98
|
-
|
|
99
|
-
export const makeEventFactory = () =>
|
|
100
|
-
EventFactory.makeFactory(events)({
|
|
101
|
-
client: EventFactory.clientIdentity('cli-test-client'),
|
|
102
|
-
startSeq: 1,
|
|
103
|
-
initialParent: 'root',
|
|
104
|
-
})
|
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
import type { LiveStoreEvent } from '@livestore/common/schema'
|
|
2
|
-
import { Chunk, Effect, FetchHttpClient, Layer, Mailbox, Stream } from '@livestore/utils/effect'
|
|
3
|
-
import { PlatformNode } from '@livestore/utils/node'
|
|
4
|
-
import { Vitest } from '@livestore/utils-dev/node-vitest'
|
|
5
|
-
import { expect } from 'vitest'
|
|
6
|
-
import { pullEventsFromSyncBackend, pushEventsToSyncBackend } from '../sync-operations.ts'
|
|
7
|
-
import { makeEventFactory, useMockConfig } from './fixtures/mock-config.ts'
|
|
8
|
-
|
|
9
|
-
const baseLayer = Layer.mergeAll(PlatformNode.NodeFileSystem.layer, FetchHttpClient.layer)
|
|
10
|
-
const withTestCtx = Vitest.makeWithTestCtx({ makeLayer: () => baseLayer })
|
|
11
|
-
|
|
12
|
-
/** Each test acquires its own temporary config module via useMockConfig, avoiding shared state. */
|
|
13
|
-
Vitest.describe('sync-operations', { timeout: 10_000 }, () => {
|
|
14
|
-
const storeId = 'test-store'
|
|
15
|
-
const clientId = 'test-client'
|
|
16
|
-
|
|
17
|
-
/** Collects the connect + disconnect lifecycle emitted by the mock sync backend. */
|
|
18
|
-
const expectConnectLifecycle = (
|
|
19
|
-
mailbox: Mailbox.Mailbox<'connect' | 'disconnect'>,
|
|
20
|
-
): Effect.Effect<ReadonlyArray<'connect' | 'disconnect'>> =>
|
|
21
|
-
Mailbox.toStream(mailbox).pipe(
|
|
22
|
-
Stream.take(2),
|
|
23
|
-
Stream.runCollect,
|
|
24
|
-
Effect.map((chunk) => Chunk.toReadonlyArray(chunk)),
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
Vitest.scopedLive('exports events and releases the backend connection', (test: Vitest.TestContext) =>
|
|
28
|
-
Effect.gen(function* () {
|
|
29
|
-
const { mockBackend, connectionEvents, configPath } = yield* useMockConfig
|
|
30
|
-
const factory = makeEventFactory()
|
|
31
|
-
|
|
32
|
-
const batch = [
|
|
33
|
-
factory.itemAdded.next({ id: 'e1', title: 'First' }),
|
|
34
|
-
factory.itemAdded.next({ id: 'e2', title: 'Second' }),
|
|
35
|
-
]
|
|
36
|
-
|
|
37
|
-
yield* mockBackend.advance(...batch)
|
|
38
|
-
|
|
39
|
-
const result = yield* pullEventsFromSyncBackend({
|
|
40
|
-
configPath,
|
|
41
|
-
storeId,
|
|
42
|
-
clientId,
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
expect(result.eventCount).toBe(2)
|
|
46
|
-
expect(result.data.events).toHaveLength(2)
|
|
47
|
-
|
|
48
|
-
const lifecycle = yield* expectConnectLifecycle(connectionEvents)
|
|
49
|
-
expect(lifecycle).toEqual(['connect', 'disconnect'])
|
|
50
|
-
}).pipe(withTestCtx(test)),
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
Vitest.scopedLive('fails import when backend is not empty', (test: Vitest.TestContext) =>
|
|
54
|
-
Effect.gen(function* () {
|
|
55
|
-
const { mockBackend, connectionEvents, configPath } = yield* useMockConfig
|
|
56
|
-
const factory = makeEventFactory()
|
|
57
|
-
|
|
58
|
-
yield* mockBackend.advance(factory.itemAdded.next({ id: 'existing', title: 'Present' }))
|
|
59
|
-
|
|
60
|
-
const importBatch = [
|
|
61
|
-
factory.itemAdded.next({ id: 'incoming-1', title: 'Incoming' }),
|
|
62
|
-
factory.itemAdded.next({ id: 'incoming-2', title: 'Incoming 2' }),
|
|
63
|
-
]
|
|
64
|
-
|
|
65
|
-
const result = yield* pushEventsToSyncBackend({
|
|
66
|
-
configPath,
|
|
67
|
-
storeId,
|
|
68
|
-
clientId,
|
|
69
|
-
data: {
|
|
70
|
-
version: 1,
|
|
71
|
-
storeId,
|
|
72
|
-
exportedAt: new Date().toISOString(),
|
|
73
|
-
eventCount: importBatch.length,
|
|
74
|
-
events: importBatch,
|
|
75
|
-
},
|
|
76
|
-
force: false,
|
|
77
|
-
dryRun: false,
|
|
78
|
-
}).pipe(Effect.either)
|
|
79
|
-
|
|
80
|
-
expect(result._tag).toBe('Left')
|
|
81
|
-
if (result._tag === 'Left') {
|
|
82
|
-
expect(result.left._tag).toBe('ImportError')
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const lifecycle = yield* expectConnectLifecycle(connectionEvents)
|
|
86
|
-
expect(lifecycle).toEqual(['connect', 'disconnect'])
|
|
87
|
-
}).pipe(withTestCtx(test)),
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
Vitest.scopedLive('supports dry-run import and releases backend', (test: Vitest.TestContext) =>
|
|
91
|
-
Effect.gen(function* () {
|
|
92
|
-
const { configPath, connectionEvents } = yield* useMockConfig
|
|
93
|
-
const factory = makeEventFactory()
|
|
94
|
-
const importBatch = [factory.itemAdded.next({ id: 'dry-run', title: 'Simulated' })]
|
|
95
|
-
|
|
96
|
-
const result = yield* pushEventsToSyncBackend({
|
|
97
|
-
configPath,
|
|
98
|
-
storeId,
|
|
99
|
-
clientId,
|
|
100
|
-
data: {
|
|
101
|
-
version: 1,
|
|
102
|
-
storeId,
|
|
103
|
-
exportedAt: new Date().toISOString(),
|
|
104
|
-
eventCount: importBatch.length,
|
|
105
|
-
events: importBatch,
|
|
106
|
-
},
|
|
107
|
-
force: false,
|
|
108
|
-
dryRun: true,
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
expect(result.dryRun).toBe(true)
|
|
112
|
-
expect(result.eventCount).toBe(importBatch.length)
|
|
113
|
-
|
|
114
|
-
const lifecycle = yield* expectConnectLifecycle(connectionEvents)
|
|
115
|
-
expect(lifecycle).toEqual(['connect', 'disconnect'])
|
|
116
|
-
}).pipe(withTestCtx(test)),
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
Vitest.scopedLive('imports events into empty backend with progress and batching', (test: Vitest.TestContext) =>
|
|
120
|
-
Effect.gen(function* () {
|
|
121
|
-
const { mockBackend, configPath, connectionEvents } = yield* useMockConfig
|
|
122
|
-
const factory = makeEventFactory()
|
|
123
|
-
const importBatch = Array.from({ length: 120 }, (_, idx) =>
|
|
124
|
-
factory.itemAdded.next({ id: `id-${idx + 1}`, title: `Item ${idx + 1}` }),
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
const progress: Array<{ pushed: number; total: number }> = []
|
|
128
|
-
|
|
129
|
-
const result = yield* pushEventsToSyncBackend({
|
|
130
|
-
configPath,
|
|
131
|
-
storeId,
|
|
132
|
-
clientId,
|
|
133
|
-
data: {
|
|
134
|
-
version: 1,
|
|
135
|
-
storeId,
|
|
136
|
-
exportedAt: new Date().toISOString(),
|
|
137
|
-
eventCount: importBatch.length,
|
|
138
|
-
events: importBatch,
|
|
139
|
-
},
|
|
140
|
-
force: false,
|
|
141
|
-
dryRun: false,
|
|
142
|
-
onProgress: (pushed, total) => Effect.sync(() => progress.push({ pushed, total })),
|
|
143
|
-
})
|
|
144
|
-
|
|
145
|
-
expect(result.dryRun).toBe(false)
|
|
146
|
-
expect(result.eventCount).toBe(importBatch.length)
|
|
147
|
-
expect(progress).toEqual([
|
|
148
|
-
{ pushed: 100, total: 120 },
|
|
149
|
-
{ pushed: 120, total: 120 },
|
|
150
|
-
])
|
|
151
|
-
|
|
152
|
-
const pushedEvents = yield* mockBackend.pushedEvents.pipe(
|
|
153
|
-
Stream.take(importBatch.length),
|
|
154
|
-
Stream.runCollect,
|
|
155
|
-
Effect.map((chunk: Chunk.Chunk<LiveStoreEvent.Global.Encoded>) => Chunk.toReadonlyArray(chunk)),
|
|
156
|
-
)
|
|
157
|
-
expect(pushedEvents.map((event) => event.seqNum)).toHaveLength(importBatch.length)
|
|
158
|
-
|
|
159
|
-
const lifecycle = yield* expectConnectLifecycle(connectionEvents)
|
|
160
|
-
expect(lifecycle).toEqual(['connect', 'disconnect'])
|
|
161
|
-
}).pipe(withTestCtx(test)),
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
Vitest.scopedLive('allows force import on store ID mismatch', (test: Vitest.TestContext) =>
|
|
165
|
-
Effect.gen(function* () {
|
|
166
|
-
const { mockBackend, configPath, connectionEvents } = yield* useMockConfig
|
|
167
|
-
const factory = makeEventFactory()
|
|
168
|
-
const importBatch = [factory.itemAdded.next({ id: 'force-1', title: 'Force' })]
|
|
169
|
-
|
|
170
|
-
const result = yield* pushEventsToSyncBackend({
|
|
171
|
-
configPath,
|
|
172
|
-
storeId,
|
|
173
|
-
clientId,
|
|
174
|
-
data: {
|
|
175
|
-
version: 1,
|
|
176
|
-
storeId: 'different-store',
|
|
177
|
-
exportedAt: new Date().toISOString(),
|
|
178
|
-
eventCount: importBatch.length,
|
|
179
|
-
events: importBatch,
|
|
180
|
-
},
|
|
181
|
-
force: true,
|
|
182
|
-
dryRun: false,
|
|
183
|
-
})
|
|
184
|
-
|
|
185
|
-
expect(result.dryRun).toBe(false)
|
|
186
|
-
expect(result.eventCount).toBe(importBatch.length)
|
|
187
|
-
|
|
188
|
-
const pushedEvents = yield* mockBackend.pushedEvents.pipe(
|
|
189
|
-
Stream.take(1),
|
|
190
|
-
Stream.runCollect,
|
|
191
|
-
Effect.map((chunk: Chunk.Chunk<LiveStoreEvent.Global.Encoded>) => Chunk.toReadonlyArray(chunk)),
|
|
192
|
-
)
|
|
193
|
-
expect(pushedEvents).toHaveLength(1)
|
|
194
|
-
|
|
195
|
-
const lifecycle = yield* expectConnectLifecycle(connectionEvents)
|
|
196
|
-
expect(lifecycle).toEqual(['connect', 'disconnect'])
|
|
197
|
-
}).pipe(withTestCtx(test)),
|
|
198
|
-
)
|
|
199
|
-
|
|
200
|
-
Vitest.scopedLive('rejects store ID mismatch without force', (test: Vitest.TestContext) =>
|
|
201
|
-
Effect.gen(function* () {
|
|
202
|
-
const { configPath, connectionEvents } = yield* useMockConfig
|
|
203
|
-
const factory = makeEventFactory()
|
|
204
|
-
const importBatch = [factory.itemAdded.next({ id: 'mismatch', title: 'Mismatch' })]
|
|
205
|
-
|
|
206
|
-
const result = yield* pushEventsToSyncBackend({
|
|
207
|
-
configPath,
|
|
208
|
-
storeId,
|
|
209
|
-
clientId,
|
|
210
|
-
data: {
|
|
211
|
-
version: 1,
|
|
212
|
-
storeId: 'other-store',
|
|
213
|
-
exportedAt: new Date().toISOString(),
|
|
214
|
-
eventCount: importBatch.length,
|
|
215
|
-
events: importBatch,
|
|
216
|
-
},
|
|
217
|
-
force: false,
|
|
218
|
-
dryRun: false,
|
|
219
|
-
}).pipe(Effect.either)
|
|
220
|
-
|
|
221
|
-
expect(result._tag).toBe('Left')
|
|
222
|
-
if (result._tag === 'Left') {
|
|
223
|
-
expect(result.left._tag).toBe('ImportError')
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
const lifecycle = yield* expectConnectLifecycle(connectionEvents)
|
|
227
|
-
expect(lifecycle).toEqual(['connect', 'disconnect'])
|
|
228
|
-
}).pipe(withTestCtx(test)),
|
|
229
|
-
)
|
|
230
|
-
})
|