@korajs/cli 1.0.0-beta.10 → 1.0.0-beta.11

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/bin.js CHANGED
@@ -2642,7 +2642,7 @@ var studioCommand = defineCommand9({
2642
2642
  }
2643
2643
  const { loadSchemaDefinition: loadSchemaDefinition2 } = await import("./schema-loader-GGHECMTM.js");
2644
2644
  const schema = await loadSchemaDefinition2(resolve6(process.cwd(), args.schema), process.cwd());
2645
- const { SpectatorManager } = await import("./spectator-manager-E2LH2P54.js");
2645
+ const { SpectatorManager } = await import("./spectator-manager-FTH2NFSE.js");
2646
2646
  const spectator = new SpectatorManager({
2647
2647
  url: args.connect,
2648
2648
  schema,
@@ -39,6 +39,7 @@ var SpectatorManager = class {
39
39
  MergeEngine: (await import("@korajs/merge")).MergeEngine,
40
40
  SyncEngine: (await import("@korajs/sync")).SyncEngine,
41
41
  WebSocketTransport: (await import("@korajs/sync")).WebSocketTransport,
42
+ WebSocket: (await import("ws")).default,
42
43
  testing: await import("korajs/testing"),
43
44
  SimpleEventEmitter: (await import("@korajs/core/internal")).SimpleEventEmitter
44
45
  };
@@ -57,7 +58,9 @@ var SpectatorManager = class {
57
58
  const unsubscribeAudit = mods.testing.wireAuditPersistence(store, emitter);
58
59
  const syncStore = new mods.testing.MergeAwareSyncStore(store, mergeEngine, emitter);
59
60
  const engine = new mods.SyncEngine({
60
- transport: new mods.WebSocketTransport(),
61
+ transport: new mods.WebSocketTransport({
62
+ WebSocketImpl: mods.WebSocket
63
+ }),
61
64
  store: syncStore,
62
65
  queueStorage: new mods.testing.StoreQueueStorage(adapter),
63
66
  syncState: new mods.testing.StoreSyncStatePersistence(store),
@@ -139,4 +142,4 @@ var SpectatorManager = class {
139
142
  export {
140
143
  SpectatorManager
141
144
  };
142
- //# sourceMappingURL=spectator-manager-E2LH2P54.js.map
145
+ //# sourceMappingURL=spectator-manager-FTH2NFSE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/studio/spectator-manager.ts"],"sourcesContent":["import { mkdtempSync, rmSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\nimport type { SchemaDefinition } from '@korajs/core'\nimport type { WebSocketConstructor } from '@korajs/sync'\n\n/**\n * Kora Studio SPECTATOR: live, read-only inspection of a production sync\n * server.\n *\n * The spectator is a REAL Kora client — its own store, merge pipeline, and\n * sync engine — connected to the target server over the real WebSocket\n * protocol. It materializes its own replica into a throwaway database, which\n * every Studio view (records, per-field writers, op log, DAG, time travel,\n * merge audit) then reads. Nothing is simulated: what Studio shows is exactly\n * what any device syncing against that server computes.\n *\n * Read-only by construction: the spectator exposes NO mutation API, so it can\n * never push an operation to the server. It only receives, applies, and\n * observes. The server sees an ordinary client whose version vector starts\n * empty (so it receives the full history — which is what makes production\n * time travel possible) and which never sends operations.\n */\n\nexport interface SpectatorStatus {\n\turl: string\n\tconnected: boolean\n\toperationsReceived: number\n\tlastEventAt: number | null\n\tdbPath: string\n}\n\nexport interface SpectatorEvent {\n\tseq: number\n\tat: number\n\ttype: string\n\tsummary: string\n}\n\nexport interface SpectatorOptions {\n\turl: string\n\tschema: SchemaDefinition\n\t/** Bearer token forwarded through the sync auth handshake. */\n\ttoken?: string\n\t/** Client schema version override. Defaults to schema.version. */\n\tschemaVersion?: number\n}\n\n// NOTE: 'operation:applied' is in the event spec but not yet emitted by the\n// store — received-op counting uses 'sync:received' batches instead.\nconst FORWARDED_EVENTS = [\n\t'merge:started',\n\t'merge:conflict',\n\t'merge:completed',\n\t'sync:connected',\n\t'sync:disconnected',\n\t'sync:received',\n\t'sync:apply-failed',\n] as const\n\nconst MAX_EVENT_BUFFER = 500\n\n/** Structural types for the lazily-imported runtime packages. */\ninterface SpectatorRuntime {\n\tstore: {\n\t\topen(): Promise<void>\n\t\tclose(): Promise<void>\n\t\tsetLocalMutationHandler(handler: unknown): void\n\t}\n\tengine: { start(): Promise<void>; stop(): Promise<void> }\n\tunsubscribers: Array<() => void>\n}\n\nexport class SpectatorManager {\n\tprivate runtime: SpectatorRuntime | null = null\n\tprivate tmpDir: string | null = null\n\tprivate connected = false\n\tprivate operationsReceived = 0\n\tprivate lastEventAt: number | null = null\n\tprivate eventSeq = 0\n\tprivate readonly eventBuffer: SpectatorEvent[] = []\n\tprivate readonly eventListeners = new Set<(event: SpectatorEvent) => void>()\n\treadonly dbPath: string\n\n\tconstructor(private readonly options: SpectatorOptions) {\n\t\tthis.tmpDir = mkdtempSync(join(tmpdir(), 'kora-studio-spectator-'))\n\t\tthis.dbPath = join(this.tmpDir, 'spectator-replica.db')\n\t}\n\n\tasync start(): Promise<void> {\n\t\tlet mods: {\n\t\t\tStore: typeof import('@korajs/store').Store\n\t\t\tBetterSqlite3Adapter: typeof import('@korajs/store/better-sqlite3').BetterSqlite3Adapter\n\t\t\tMergeEngine: typeof import('@korajs/merge').MergeEngine\n\t\t\tSyncEngine: typeof import('@korajs/sync').SyncEngine\n\t\t\tWebSocketTransport: typeof import('@korajs/sync').WebSocketTransport\n\t\t\tWebSocket: typeof import('ws').default\n\t\t\ttesting: typeof import('korajs/testing')\n\t\t\tSimpleEventEmitter: typeof import('@korajs/core/internal').SimpleEventEmitter\n\t\t}\n\t\ttry {\n\t\t\tmods = {\n\t\t\t\tStore: (await import('@korajs/store')).Store,\n\t\t\t\tBetterSqlite3Adapter: (await import('@korajs/store/better-sqlite3')).BetterSqlite3Adapter,\n\t\t\t\tMergeEngine: (await import('@korajs/merge')).MergeEngine,\n\t\t\t\tSyncEngine: (await import('@korajs/sync')).SyncEngine,\n\t\t\t\tWebSocketTransport: (await import('@korajs/sync')).WebSocketTransport,\n\t\t\t\tWebSocket: (await import('ws')).default,\n\t\t\t\ttesting: await import('korajs/testing'),\n\t\t\t\tSimpleEventEmitter: (await import('@korajs/core/internal')).SimpleEventEmitter,\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t`Spectator mode needs the Kora runtime packages installed (pnpm add -D korajs @korajs/store @korajs/merge @korajs/sync better-sqlite3). Underlying error: ${\n\t\t\t\t\terror instanceof Error ? error.message : String(error)\n\t\t\t\t}`,\n\t\t\t)\n\t\t}\n\n\t\tconst emitter = new mods.SimpleEventEmitter()\n\t\tconst adapter = new mods.BetterSqlite3Adapter(this.dbPath)\n\t\tconst store = new mods.Store({ schema: this.options.schema, adapter, emitter })\n\t\tawait store.open()\n\n\t\tconst mergeEngine = new mods.MergeEngine()\n\t\tconst pipeline = new mods.testing.ApplyPipeline({ store, mergeEngine, emitter })\n\t\tstore.setLocalMutationHandler(pipeline)\n\n\t\t// Production parity: merge decisions persist to the audit trail.\n\t\tconst unsubscribeAudit = mods.testing.wireAuditPersistence(store, emitter)\n\n\t\tconst syncStore = new mods.testing.MergeAwareSyncStore(store, mergeEngine, emitter)\n\t\tconst engine = new mods.SyncEngine({\n\t\t\ttransport: new mods.WebSocketTransport({\n\t\t\t\tWebSocketImpl: mods.WebSocket as unknown as WebSocketConstructor,\n\t\t\t}),\n\t\t\tstore: syncStore,\n\t\t\tqueueStorage: new mods.testing.StoreQueueStorage(adapter),\n\t\t\tsyncState: new mods.testing.StoreSyncStatePersistence(store),\n\t\t\tconfig: {\n\t\t\t\turl: this.options.url,\n\t\t\t\tschemaVersion: this.options.schemaVersion ?? this.options.schema.version,\n\t\t\t\t...(this.options.token\n\t\t\t\t\t? { auth: async () => ({ token: this.options.token as string }) }\n\t\t\t\t\t: {}),\n\t\t\t},\n\t\t\temitter,\n\t\t})\n\n\t\tconst unsubscribers = FORWARDED_EVENTS.map((type) =>\n\t\t\temitter.on(type, (event: Record<string, unknown>) => {\n\t\t\t\tthis.lastEventAt = Date.now()\n\t\t\t\tif (type === 'sync:connected') {\n\t\t\t\t\tthis.connected = true\n\t\t\t\t}\n\t\t\t\tif (type === 'sync:disconnected') {\n\t\t\t\t\tthis.connected = false\n\t\t\t\t}\n\t\t\t\tif (type === 'sync:received' && Array.isArray(event.operations)) {\n\t\t\t\t\tthis.operationsReceived += event.operations.length\n\t\t\t\t}\n\t\t\t\tthis.pushEvent(type, event)\n\t\t\t}),\n\t\t)\n\t\tunsubscribers.push(unsubscribeAudit)\n\n\t\tthis.runtime = { store, engine, unsubscribers }\n\t\tawait engine.start()\n\t}\n\n\tstatus(): SpectatorStatus {\n\t\treturn {\n\t\t\turl: this.options.url,\n\t\t\tconnected: this.connected,\n\t\t\toperationsReceived: this.operationsReceived,\n\t\t\tlastEventAt: this.lastEventAt,\n\t\t\tdbPath: this.dbPath,\n\t\t}\n\t}\n\n\trecentEvents(): SpectatorEvent[] {\n\t\treturn [...this.eventBuffer]\n\t}\n\n\tonEvent(listener: (event: SpectatorEvent) => void): () => void {\n\t\tthis.eventListeners.add(listener)\n\t\treturn () => this.eventListeners.delete(listener)\n\t}\n\n\tasync close(): Promise<void> {\n\t\tif (this.runtime) {\n\t\t\tfor (const unsub of this.runtime.unsubscribers) {\n\t\t\t\tunsub()\n\t\t\t}\n\t\t\tawait this.runtime.engine.stop().catch(() => {})\n\t\t\tawait this.runtime.store.close().catch(() => {})\n\t\t\tthis.runtime = null\n\t\t}\n\t\tif (this.tmpDir) {\n\t\t\trmSync(this.tmpDir, { recursive: true, force: true })\n\t\t\tthis.tmpDir = null\n\t\t}\n\t}\n\n\tprivate pushEvent(type: string, payload: Record<string, unknown>): void {\n\t\tconst summary =\n\t\t\ttype === 'sync:received' && Array.isArray(payload.operations)\n\t\t\t\t? `received ${payload.operations.length} op(s)`\n\t\t\t\t: type.replace('sync:', '').replace('merge:', 'merge ')\n\t\tconst event: SpectatorEvent = {\n\t\t\tseq: ++this.eventSeq,\n\t\t\tat: Date.now(),\n\t\t\ttype,\n\t\t\tsummary,\n\t\t}\n\t\tthis.eventBuffer.push(event)\n\t\tif (this.eventBuffer.length > MAX_EVENT_BUFFER) {\n\t\t\tthis.eventBuffer.shift()\n\t\t}\n\t\tfor (const listener of this.eventListeners) {\n\t\t\tlistener(event)\n\t\t}\n\t}\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@korajs/cli",
3
- "version": "1.0.0-beta.10",
3
+ "version": "1.0.0-beta.11",
4
4
  "description": "Kora.js CLI tooling and project scaffolding",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -37,22 +37,24 @@
37
37
  "conf": "^15.1.0",
38
38
  "esbuild": "^0.28.0",
39
39
  "validate-npm-package-name": "^7.0.2",
40
- "@korajs/core": "1.0.0-beta.10"
40
+ "ws": "^8.18.0",
41
+ "@korajs/core": "1.0.0-beta.11"
41
42
  },
42
43
  "devDependencies": {
43
44
  "@types/better-sqlite3": "^7.6.13",
44
45
  "@types/node": "^25.5.2",
45
46
  "@types/validate-npm-package-name": "^4.0.2",
47
+ "@types/ws": "^8.5.13",
46
48
  "better-sqlite3": "^12.8.0",
47
49
  "tsup": "^8.3.6",
48
50
  "typescript": "^5.7.3",
49
51
  "vitest": "^3.0.4",
50
- "@korajs/server": "1.0.0-beta.10",
51
- "@korajs/store": "1.0.0-beta.10",
52
- "@korajs/sync": "1.0.0-beta.10",
53
- "@korajs/merge": "1.0.0-beta.10",
54
- "@korajs/test": "1.0.0-beta.10",
55
- "korajs": "1.0.0-beta.10"
52
+ "@korajs/server": "1.0.0-beta.11",
53
+ "@korajs/sync": "1.0.0-beta.11",
54
+ "@korajs/store": "1.0.0-beta.11",
55
+ "@korajs/test": "1.0.0-beta.11",
56
+ "@korajs/merge": "1.0.0-beta.11",
57
+ "korajs": "1.0.0-beta.11"
56
58
  },
57
59
  "license": "MIT",
58
60
  "scripts": {
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/commands/studio/spectator-manager.ts"],"sourcesContent":["import { mkdtempSync, rmSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\nimport type { SchemaDefinition } from '@korajs/core'\n\n/**\n * Kora Studio SPECTATOR: live, read-only inspection of a production sync\n * server.\n *\n * The spectator is a REAL Kora client — its own store, merge pipeline, and\n * sync engine — connected to the target server over the real WebSocket\n * protocol. It materializes its own replica into a throwaway database, which\n * every Studio view (records, per-field writers, op log, DAG, time travel,\n * merge audit) then reads. Nothing is simulated: what Studio shows is exactly\n * what any device syncing against that server computes.\n *\n * Read-only by construction: the spectator exposes NO mutation API, so it can\n * never push an operation to the server. It only receives, applies, and\n * observes. The server sees an ordinary client whose version vector starts\n * empty (so it receives the full history — which is what makes production\n * time travel possible) and which never sends operations.\n */\n\nexport interface SpectatorStatus {\n\turl: string\n\tconnected: boolean\n\toperationsReceived: number\n\tlastEventAt: number | null\n\tdbPath: string\n}\n\nexport interface SpectatorEvent {\n\tseq: number\n\tat: number\n\ttype: string\n\tsummary: string\n}\n\nexport interface SpectatorOptions {\n\turl: string\n\tschema: SchemaDefinition\n\t/** Bearer token forwarded through the sync auth handshake. */\n\ttoken?: string\n\t/** Client schema version override. Defaults to schema.version. */\n\tschemaVersion?: number\n}\n\n// NOTE: 'operation:applied' is in the event spec but not yet emitted by the\n// store — received-op counting uses 'sync:received' batches instead.\nconst FORWARDED_EVENTS = [\n\t'merge:started',\n\t'merge:conflict',\n\t'merge:completed',\n\t'sync:connected',\n\t'sync:disconnected',\n\t'sync:received',\n\t'sync:apply-failed',\n] as const\n\nconst MAX_EVENT_BUFFER = 500\n\n/** Structural types for the lazily-imported runtime packages. */\ninterface SpectatorRuntime {\n\tstore: {\n\t\topen(): Promise<void>\n\t\tclose(): Promise<void>\n\t\tsetLocalMutationHandler(handler: unknown): void\n\t}\n\tengine: { start(): Promise<void>; stop(): Promise<void> }\n\tunsubscribers: Array<() => void>\n}\n\nexport class SpectatorManager {\n\tprivate runtime: SpectatorRuntime | null = null\n\tprivate tmpDir: string | null = null\n\tprivate connected = false\n\tprivate operationsReceived = 0\n\tprivate lastEventAt: number | null = null\n\tprivate eventSeq = 0\n\tprivate readonly eventBuffer: SpectatorEvent[] = []\n\tprivate readonly eventListeners = new Set<(event: SpectatorEvent) => void>()\n\treadonly dbPath: string\n\n\tconstructor(private readonly options: SpectatorOptions) {\n\t\tthis.tmpDir = mkdtempSync(join(tmpdir(), 'kora-studio-spectator-'))\n\t\tthis.dbPath = join(this.tmpDir, 'spectator-replica.db')\n\t}\n\n\tasync start(): Promise<void> {\n\t\tlet mods: {\n\t\t\tStore: typeof import('@korajs/store').Store\n\t\t\tBetterSqlite3Adapter: typeof import('@korajs/store/better-sqlite3').BetterSqlite3Adapter\n\t\t\tMergeEngine: typeof import('@korajs/merge').MergeEngine\n\t\t\tSyncEngine: typeof import('@korajs/sync').SyncEngine\n\t\t\tWebSocketTransport: typeof import('@korajs/sync').WebSocketTransport\n\t\t\ttesting: typeof import('korajs/testing')\n\t\t\tSimpleEventEmitter: typeof import('@korajs/core/internal').SimpleEventEmitter\n\t\t}\n\t\ttry {\n\t\t\tmods = {\n\t\t\t\tStore: (await import('@korajs/store')).Store,\n\t\t\t\tBetterSqlite3Adapter: (await import('@korajs/store/better-sqlite3')).BetterSqlite3Adapter,\n\t\t\t\tMergeEngine: (await import('@korajs/merge')).MergeEngine,\n\t\t\t\tSyncEngine: (await import('@korajs/sync')).SyncEngine,\n\t\t\t\tWebSocketTransport: (await import('@korajs/sync')).WebSocketTransport,\n\t\t\t\ttesting: await import('korajs/testing'),\n\t\t\t\tSimpleEventEmitter: (await import('@korajs/core/internal')).SimpleEventEmitter,\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t`Spectator mode needs the Kora runtime packages installed (pnpm add -D korajs @korajs/store @korajs/merge @korajs/sync better-sqlite3). Underlying error: ${\n\t\t\t\t\terror instanceof Error ? error.message : String(error)\n\t\t\t\t}`,\n\t\t\t)\n\t\t}\n\n\t\tconst emitter = new mods.SimpleEventEmitter()\n\t\tconst adapter = new mods.BetterSqlite3Adapter(this.dbPath)\n\t\tconst store = new mods.Store({ schema: this.options.schema, adapter, emitter })\n\t\tawait store.open()\n\n\t\tconst mergeEngine = new mods.MergeEngine()\n\t\tconst pipeline = new mods.testing.ApplyPipeline({ store, mergeEngine, emitter })\n\t\tstore.setLocalMutationHandler(pipeline)\n\n\t\t// Production parity: merge decisions persist to the audit trail.\n\t\tconst unsubscribeAudit = mods.testing.wireAuditPersistence(store, emitter)\n\n\t\tconst syncStore = new mods.testing.MergeAwareSyncStore(store, mergeEngine, emitter)\n\t\tconst engine = new mods.SyncEngine({\n\t\t\ttransport: new mods.WebSocketTransport(),\n\t\t\tstore: syncStore,\n\t\t\tqueueStorage: new mods.testing.StoreQueueStorage(adapter),\n\t\t\tsyncState: new mods.testing.StoreSyncStatePersistence(store),\n\t\t\tconfig: {\n\t\t\t\turl: this.options.url,\n\t\t\t\tschemaVersion: this.options.schemaVersion ?? this.options.schema.version,\n\t\t\t\t...(this.options.token\n\t\t\t\t\t? { auth: async () => ({ token: this.options.token as string }) }\n\t\t\t\t\t: {}),\n\t\t\t},\n\t\t\temitter,\n\t\t})\n\n\t\tconst unsubscribers = FORWARDED_EVENTS.map((type) =>\n\t\t\temitter.on(type, (event: Record<string, unknown>) => {\n\t\t\t\tthis.lastEventAt = Date.now()\n\t\t\t\tif (type === 'sync:connected') {\n\t\t\t\t\tthis.connected = true\n\t\t\t\t}\n\t\t\t\tif (type === 'sync:disconnected') {\n\t\t\t\t\tthis.connected = false\n\t\t\t\t}\n\t\t\t\tif (type === 'sync:received' && Array.isArray(event.operations)) {\n\t\t\t\t\tthis.operationsReceived += event.operations.length\n\t\t\t\t}\n\t\t\t\tthis.pushEvent(type, event)\n\t\t\t}),\n\t\t)\n\t\tunsubscribers.push(unsubscribeAudit)\n\n\t\tthis.runtime = { store, engine, unsubscribers }\n\t\tawait engine.start()\n\t}\n\n\tstatus(): SpectatorStatus {\n\t\treturn {\n\t\t\turl: this.options.url,\n\t\t\tconnected: this.connected,\n\t\t\toperationsReceived: this.operationsReceived,\n\t\t\tlastEventAt: this.lastEventAt,\n\t\t\tdbPath: this.dbPath,\n\t\t}\n\t}\n\n\trecentEvents(): SpectatorEvent[] {\n\t\treturn [...this.eventBuffer]\n\t}\n\n\tonEvent(listener: (event: SpectatorEvent) => void): () => void {\n\t\tthis.eventListeners.add(listener)\n\t\treturn () => this.eventListeners.delete(listener)\n\t}\n\n\tasync close(): Promise<void> {\n\t\tif (this.runtime) {\n\t\t\tfor (const unsub of this.runtime.unsubscribers) {\n\t\t\t\tunsub()\n\t\t\t}\n\t\t\tawait this.runtime.engine.stop().catch(() => {})\n\t\t\tawait this.runtime.store.close().catch(() => {})\n\t\t\tthis.runtime = null\n\t\t}\n\t\tif (this.tmpDir) {\n\t\t\trmSync(this.tmpDir, { recursive: true, force: true })\n\t\t\tthis.tmpDir = null\n\t\t}\n\t}\n\n\tprivate pushEvent(type: string, payload: Record<string, unknown>): void {\n\t\tconst summary =\n\t\t\ttype === 'sync:received' && Array.isArray(payload.operations)\n\t\t\t\t? `received ${payload.operations.length} op(s)`\n\t\t\t\t: type.replace('sync:', '').replace('merge:', 'merge ')\n\t\tconst event: SpectatorEvent = {\n\t\t\tseq: ++this.eventSeq,\n\t\t\tat: Date.now(),\n\t\t\ttype,\n\t\t\tsummary,\n\t\t}\n\t\tthis.eventBuffer.push(event)\n\t\tif (this.eventBuffer.length > MAX_EVENT_BUFFER) {\n\t\t\tthis.eventBuffer.shift()\n\t\t}\n\t\tfor (const listener of this.eventListeners) {\n\t\t\tlistener(event)\n\t\t}\n\t}\n}\n"],"mappings":"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","names":[]}