@klum-db/lobby 0.2.0-pre.32 → 0.3.0-pre.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/README.md +18 -0
- package/dist/bin/klum.cjs.map +1 -1
- package/dist/bin/klum.d.cts +2 -3
- package/dist/bin/klum.d.ts +2 -3
- package/dist/bin/klum.js.map +1 -1
- package/dist/index.cjs +168 -117
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -11
- package/dist/index.d.ts +11 -11
- package/dist/index.js +97 -47
- package/dist/index.js.map +1 -1
- package/dist/{vault-group-BchRCDm5.d.cts → vault-group-D0EC-D5c.d.cts} +32 -11
- package/dist/{vault-group-BchRCDm5.d.ts → vault-group-D0EC-D5c.d.ts} +32 -11
- package/package.json +15 -14
package/README.md
CHANGED
|
@@ -167,6 +167,24 @@ The one-way law and the kernel seam are covered up top — here are the specific
|
|
|
167
167
|
- **Federation** lives in the Lobby, not in hub — open fleets with `lobby.openVaultGroup` (`@noy-db/hub` no longer ships the `openVaultGroup` / `openStateManagementVault` / `withVaultTemplate` fleet methods).
|
|
168
168
|
- **Enforced, not conventional:** an `@noy-db` package importing `@klum-db` fails noy-db's build-time architecture check.
|
|
169
169
|
|
|
170
|
+
## Consumer requirements (hub >= 0.3.0-pre.1)
|
|
171
|
+
|
|
172
|
+
`@noy-db/hub` gates `extractPartition` (the primitive under `extractCrossVaultPartition`, `mergeCompartment`, `migrateThenMerge`, `exportSurface`/`applySurface`) and search/retrieval behind opt-in strategies. The Lobby never constructs your `Noydb`/`Vault` instances — you do — so **you** must opt in on any vault passed into these APIs:
|
|
173
|
+
|
|
174
|
+
- **Interchange** (`extractCrossVaultPartition`, `mergeCompartment`, `migrateThenMerge`, `exportSurface`, `applySurface`): every source vault involved must be created with `cargoStrategy: withCargo()` from `@noy-db/hub/cargo`, or these calls throw `CargoNotEnabledError`.
|
|
175
|
+
- **Federated retrieve** (`ShardedCollection.retrieve()` across a `VaultGroup`): additionally requires `searchStrategy: withSearch()` from `@noy-db/hub` on every vault in the group, or lexical retrieve silently returns no hits and semantic/hybrid retrieve throws `SearchNotEnabledError`.
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
import { createNoydb, withSearch } from '@noy-db/hub'
|
|
179
|
+
import { withCargo } from '@noy-db/hub/cargo'
|
|
180
|
+
|
|
181
|
+
const db = await createNoydb({
|
|
182
|
+
store, user, secret,
|
|
183
|
+
cargoStrategy: withCargo(), // required for interchange (extractPartition-backed APIs)
|
|
184
|
+
searchStrategy: withSearch(), // required for federated retrieve()
|
|
185
|
+
})
|
|
186
|
+
```
|
|
187
|
+
|
|
170
188
|
## Status
|
|
171
189
|
|
|
172
190
|
Preview. `@klum-db/lobby` is its own repository and the sole publisher of `@klum-db/*` to npm. It depends on the **published** `@noy-db/*` packages through the stable `@noy-db/hub/kernel` boundary and versions **independently** (`0.2.0-pre.N`, decoupled from noy-db). Pilot-1 (FR-1…FR-9), the dock tier, and `Lobby.graduate()` are complete.
|
package/dist/bin/klum.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/bin/klum.ts","../../src/federation/group-inspector.ts","../../src/federation/meter-group.ts"],"sourcesContent":["import { createInspector } from '@noy-db/in-devtools'\nimport { groupInspector } from '../federation/group-inspector.js'\nimport { meterGroup } from '../federation/meter-group.js'\nimport type { VaultGroup } from '../federation/vault-group.js'\n\n/**\n * A config module for the `klum` CLI default-exports this factory: given an\n * optional group name, it returns an opened VaultGroup (the user's module owns\n * the store, templates, and sharding config — the CLI stays agnostic of them).\n */\nexport type GroupFactory = (groupName?: string) => Promise<VaultGroup<unknown>>\n\ntype Log = (s: string) => void\n\nexport interface ParsedArgs {\n command: string\n configPath?: string\n group?: string\n vault?: string\n meter: boolean\n}\n\nexport function parseArgs(argv: readonly string[]): ParsedArgs {\n const out: ParsedArgs = { command: argv[0] ?? '', meter: false }\n for (const a of argv.slice(1)) {\n if (a.startsWith('--group=')) out.group = a.slice('--group='.length)\n else if (a.startsWith('--vault=')) out.vault = a.slice('--vault='.length)\n else if (a === '--meter') out.meter = true\n else if (!a.startsWith('--')) out.configPath = a\n }\n return out\n}\n\nasync function loadGroup(args: ParsedArgs): Promise<VaultGroup<unknown>> {\n const mod = (await import(args.configPath!)) as { default: GroupFactory }\n return mod.default(args.group)\n}\n\nexport async function runInspectGroup(args: ParsedArgs, log: Log): Promise<number> {\n if (!args.configPath) {\n log('usage: klum inspect-group <config> --group=<name> [--vault=<id>]')\n return 2\n }\n const group = await loadGroup(args)\n const inspector = createInspector(groupInspector(group))\n const vaults = await inspector.listVaults()\n log(`group \"${args.group ?? ''}\" — ${vaults.length} shard(s):`)\n for (const v of vaults) log(` ${v.id} [${v.role}]`)\n if (args.vault) {\n const vault = await group.db.openVault(args.vault)\n group.template.configure(vault)\n const snap = await inspector.snapshot(vault)\n log(` collections in ${args.vault}: ${snap.collections.map((c) => c.name).join(', ')}`)\n }\n return 0\n}\n\nexport async function runMeterGroup(args: ParsedArgs, log: Log): Promise<number> {\n if (!args.configPath) {\n log('usage: klum meter-group <config> --group=<name>')\n return 2\n }\n const group = await loadGroup(args)\n const r = await meterGroup(group)\n log(`group \"${args.group ?? ''}\" — ${r.vaults} vault(s), ${r.collections} collection(s), ${r.records} record(s)`)\n for (const s of r.perShard) {\n log(` ${s.vaultId} (${s.partitionKey}) v${s.schemaVersion}: ${s.collections} coll, ${s.records} rec`)\n }\n if (r.skipped.length) log(` skipped: ${r.skipped.length} shard(s)`)\n return 0\n}\n\nexport async function main(argv: readonly string[], log: Log = console.log): Promise<number> {\n const args = parseArgs(argv)\n switch (args.command) {\n case 'inspect-group':\n return runInspectGroup(args, log)\n case 'meter-group':\n return runMeterGroup(args, log)\n default:\n log('klum <inspect-group|meter-group> <config> --group=<name> [--vault=<id>]')\n return args.command ? 1 : 0\n }\n}\n\n// bin entrypoint — only runs when executed directly, not when imported in tests.\nif (process.argv[1] && import.meta.url === `file://${process.argv[1]}`) {\n main(process.argv.slice(2))\n .then((code) => {\n process.exitCode = code\n })\n .catch((e: unknown) => {\n console.error(e)\n process.exitCode = 1\n })\n}\n","import type { InspectableContainer } from '@noy-db/in-devtools'\nimport type { AccessibleVault, Vault, WriteHook, WriteConflict, WriteQueue, Unsubscribe } from '@noy-db/hub'\nimport type { VaultGroup } from './vault-group.js'\n\n/**\n * Adapt a federation {@link VaultGroup} to the dev-tools `InspectableContainer`\n * contract from `@noy-db/in-devtools`, so the inspector / TUI can browse a\n * whole fleet exactly like a single instance.\n *\n * Built entirely on the group's public surface (`allRows`, `db`, `template`) —\n * no `VaultGroup` changes, and (critically) no `@klum-db` import lands in any\n * `@noy-db` package: the dependency runs one way, klum → noy.\n *\n * Write-event scoping: `group.db` may host vaults outside this group, so write\n * and conflict events are filtered to the group's shard ids. The id set is\n * primed/refreshed on every `listAccessibleVaults()` call — drive `listVaults()`\n * (the inspector's normal first step) before relying on event scoping.\n */\nexport function groupInspector<T>(group: VaultGroup<T>): InspectableContainer {\n let shardIds = new Set<string>()\n const refresh = async () => {\n const rows = await group.allRows()\n shardIds = new Set(rows.map((r) => r.vaultId))\n return rows\n }\n return {\n async listAccessibleVaults(): Promise<readonly AccessibleVault[]> {\n const rows = await refresh()\n return rows.map((r): AccessibleVault => ({ id: r.vaultId, role: 'owner' }))\n },\n async openVault(name: string): Promise<Vault> {\n const vault = await group.db.openVault(name)\n group.template.configure(vault)\n return vault\n },\n onAfterWrite(handler: WriteHook): Unsubscribe {\n return group.db.onAfterWrite((event) => {\n if (shardIds.has(event.vault)) return handler(event)\n })\n },\n onWriteConflict(handler: (c: WriteConflict) => void): Unsubscribe {\n return group.db.onWriteConflict((c) => {\n if (shardIds.has(c.vault)) handler(c)\n })\n },\n get writeQueue(): WriteQueue {\n return group.db.writeQueue\n },\n }\n}\n","import type { VaultGroup } from './vault-group.js'\nimport type { SkippedVault } from './types.js'\n\nexport interface GroupShardMetrics {\n readonly vaultId: string\n readonly partitionKey: string\n readonly schemaVersion: number\n readonly collections: number\n readonly records: number\n}\n\nexport interface GroupMeterReport {\n /** Number of eligible shards measured. */\n readonly vaults: number\n /** Distinct collection names across the group. */\n readonly collections: number\n /** Total record count summed across shards. */\n readonly records: number\n readonly perShard: ReadonlyArray<GroupShardMetrics>\n /** Drifted / provisioning-failed shards — surfaced, never counted or hidden. */\n readonly skipped: ReadonlyArray<SkippedVault>\n}\n\n/**\n * Fan shape-metrics (collection count + record count) across the group's\n * ELIGIBLE shards. Skipped shards (schema-drift / provisioning failures) are\n * reported in `skipped`, never silently dropped. Reuses the per-vault pattern\n * from `multi-bundle.ts` (`vault.collections()` → `collection(n).count()`).\n *\n * Operational store metrics (calls, CAS conflicts) are a separate concern and\n * are already group-wide via `@noy-db/to-meter` on the underlying store.\n */\nexport async function meterGroup<T>(\n group: VaultGroup<T>,\n opts: { minVersion?: number } = {},\n): Promise<GroupMeterReport> {\n const { eligible, skipped } = await group.resolveEligible(\n opts.minVersion !== undefined ? { minVersion: opts.minVersion } : {},\n )\n const perShard: GroupShardMetrics[] = []\n const names = new Set<string>()\n let records = 0\n for (const row of eligible) {\n const vault = await group.shard(row.partitionKey)\n const collNames = await vault.collections()\n let shardRecords = 0\n for (const n of collNames) {\n names.add(n)\n shardRecords += await vault.collection(n).count()\n }\n records += shardRecords\n perShard.push({\n vaultId: row.vaultId,\n partitionKey: row.partitionKey,\n schemaVersion: row.schemaVersion,\n collections: collNames.length,\n records: shardRecords,\n })\n }\n return { vaults: eligible.length, collections: names.size, records, perShard, skipped }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/bin/klum.ts","../../src/federation/group-inspector.ts","../../src/federation/meter-group.ts"],"sourcesContent":["import { createInspector } from '@noy-db/in-devtools'\nimport { groupInspector } from '../federation/group-inspector.js'\nimport { meterGroup } from '../federation/meter-group.js'\nimport type { VaultGroup } from '../federation/vault-group.js'\n\n/**\n * A config module for the `klum` CLI default-exports this factory: given an\n * optional group name, it returns an opened VaultGroup (the user's module owns\n * the store, templates, and sharding config — the CLI stays agnostic of them).\n */\nexport type GroupFactory = (groupName?: string) => Promise<VaultGroup<unknown>>\n\ntype Log = (s: string) => void\n\nexport interface ParsedArgs {\n command: string\n configPath?: string\n group?: string\n vault?: string\n meter: boolean\n}\n\nexport function parseArgs(argv: readonly string[]): ParsedArgs {\n const out: ParsedArgs = { command: argv[0] ?? '', meter: false }\n for (const a of argv.slice(1)) {\n if (a.startsWith('--group=')) out.group = a.slice('--group='.length)\n else if (a.startsWith('--vault=')) out.vault = a.slice('--vault='.length)\n else if (a === '--meter') out.meter = true\n else if (!a.startsWith('--')) out.configPath = a\n }\n return out\n}\n\nasync function loadGroup(args: ParsedArgs): Promise<VaultGroup<unknown>> {\n const mod = (await import(args.configPath!)) as { default: GroupFactory }\n return mod.default(args.group)\n}\n\nexport async function runInspectGroup(args: ParsedArgs, log: Log): Promise<number> {\n if (!args.configPath) {\n log('usage: klum inspect-group <config> --group=<name> [--vault=<id>]')\n return 2\n }\n const group = await loadGroup(args)\n const inspector = createInspector(groupInspector(group))\n const vaults = await inspector.listVaults()\n log(`group \"${args.group ?? ''}\" — ${vaults.length} shard(s):`)\n for (const v of vaults) log(` ${v.id} [${v.role}]`)\n if (args.vault) {\n const vault = await group.db.openVault(args.vault)\n group.template.configure(vault)\n const snap = await inspector.snapshot(vault)\n log(` collections in ${args.vault}: ${snap.collections.map((c) => c.name).join(', ')}`)\n }\n return 0\n}\n\nexport async function runMeterGroup(args: ParsedArgs, log: Log): Promise<number> {\n if (!args.configPath) {\n log('usage: klum meter-group <config> --group=<name>')\n return 2\n }\n const group = await loadGroup(args)\n const r = await meterGroup(group)\n log(`group \"${args.group ?? ''}\" — ${r.vaults} vault(s), ${r.collections} collection(s), ${r.records} record(s)`)\n for (const s of r.perShard) {\n log(` ${s.vaultId} (${s.partitionKey}) v${s.schemaVersion}: ${s.collections} coll, ${s.records} rec`)\n }\n if (r.skipped.length) log(` skipped: ${r.skipped.length} shard(s)`)\n return 0\n}\n\nexport async function main(argv: readonly string[], log: Log = console.log): Promise<number> {\n const args = parseArgs(argv)\n switch (args.command) {\n case 'inspect-group':\n return runInspectGroup(args, log)\n case 'meter-group':\n return runMeterGroup(args, log)\n default:\n log('klum <inspect-group|meter-group> <config> --group=<name> [--vault=<id>]')\n return args.command ? 1 : 0\n }\n}\n\n// bin entrypoint — only runs when executed directly, not when imported in tests.\nif (process.argv[1] && import.meta.url === `file://${process.argv[1]}`) {\n main(process.argv.slice(2))\n .then((code) => {\n process.exitCode = code\n })\n .catch((e: unknown) => {\n console.error(e)\n process.exitCode = 1\n })\n}\n","import type { InspectableContainer } from '@noy-db/in-devtools'\nimport type { AccessibleVault, Vault, WriteHook, WriteConflict, WriteQueue, Unsubscribe } from '@noy-db/hub/cargo'\nimport type { VaultGroup } from './vault-group.js'\n\n/**\n * Adapt a federation {@link VaultGroup} to the dev-tools `InspectableContainer`\n * contract from `@noy-db/in-devtools`, so the inspector / TUI can browse a\n * whole fleet exactly like a single instance.\n *\n * Built entirely on the group's public surface (`allRows`, `db`, `template`) —\n * no `VaultGroup` changes, and (critically) no `@klum-db` import lands in any\n * `@noy-db` package: the dependency runs one way, klum → noy.\n *\n * Write-event scoping: `group.db` may host vaults outside this group, so write\n * and conflict events are filtered to the group's shard ids. The id set is\n * primed/refreshed on every `listAccessibleVaults()` call — drive `listVaults()`\n * (the inspector's normal first step) before relying on event scoping.\n */\nexport function groupInspector<T>(group: VaultGroup<T>): InspectableContainer {\n let shardIds = new Set<string>()\n const refresh = async () => {\n const rows = await group.allRows()\n shardIds = new Set(rows.map((r) => r.vaultId))\n return rows\n }\n return {\n async listAccessibleVaults(): Promise<readonly AccessibleVault[]> {\n const rows = await refresh()\n return rows.map((r): AccessibleVault => ({ id: r.vaultId, role: 'owner' }))\n },\n async openVault(name: string): Promise<Vault> {\n const vault = await group.db.openVault(name)\n group.template.configure(vault)\n return vault\n },\n onAfterWrite(handler: WriteHook): Unsubscribe {\n return group.db.onAfterWrite((event) => {\n if (shardIds.has(event.vault)) return handler(event)\n })\n },\n onWriteConflict(handler: (c: WriteConflict) => void): Unsubscribe {\n return group.db.onWriteConflict((c) => {\n if (shardIds.has(c.vault)) handler(c)\n })\n },\n get writeQueue(): WriteQueue {\n return group.db.writeQueue\n },\n }\n}\n","import type { VaultGroup } from './vault-group.js'\nimport type { SkippedVault } from './types.js'\n\nexport interface GroupShardMetrics {\n readonly vaultId: string\n readonly partitionKey: string\n readonly schemaVersion: number\n readonly collections: number\n readonly records: number\n}\n\nexport interface GroupMeterReport {\n /** Number of eligible shards measured. */\n readonly vaults: number\n /** Distinct collection names across the group. */\n readonly collections: number\n /** Total record count summed across shards. */\n readonly records: number\n readonly perShard: ReadonlyArray<GroupShardMetrics>\n /** Drifted / provisioning-failed shards — surfaced, never counted or hidden. */\n readonly skipped: ReadonlyArray<SkippedVault>\n}\n\n/**\n * Fan shape-metrics (collection count + record count) across the group's\n * ELIGIBLE shards. Skipped shards (schema-drift / provisioning failures) are\n * reported in `skipped`, never silently dropped. Reuses the per-vault pattern\n * from `multi-bundle.ts` (`vault.collections()` → `collection(n).count()`).\n *\n * Operational store metrics (calls, CAS conflicts) are a separate concern and\n * are already group-wide via `@noy-db/to-meter` on the underlying store.\n */\nexport async function meterGroup<T>(\n group: VaultGroup<T>,\n opts: { minVersion?: number } = {},\n): Promise<GroupMeterReport> {\n const { eligible, skipped } = await group.resolveEligible(\n opts.minVersion !== undefined ? { minVersion: opts.minVersion } : {},\n )\n const perShard: GroupShardMetrics[] = []\n const names = new Set<string>()\n let records = 0\n for (const row of eligible) {\n const vault = await group.shard(row.partitionKey)\n const collNames = await vault.collections()\n let shardRecords = 0\n for (const n of collNames) {\n names.add(n)\n shardRecords += await vault.collection(n).count()\n }\n records += shardRecords\n perShard.push({\n vaultId: row.vaultId,\n partitionKey: row.partitionKey,\n schemaVersion: row.schemaVersion,\n collections: collNames.length,\n records: shardRecords,\n })\n }\n return { vaults: eligible.length, collections: names.size, records, perShard, skipped }\n}\n"],"mappings":"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","names":[]}
|
package/dist/bin/klum.d.cts
CHANGED
package/dist/bin/klum.d.ts
CHANGED
package/dist/bin/klum.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/bin/klum.ts","../../src/federation/group-inspector.ts","../../src/federation/meter-group.ts"],"sourcesContent":["import { createInspector } from '@noy-db/in-devtools'\nimport { groupInspector } from '../federation/group-inspector.js'\nimport { meterGroup } from '../federation/meter-group.js'\nimport type { VaultGroup } from '../federation/vault-group.js'\n\n/**\n * A config module for the `klum` CLI default-exports this factory: given an\n * optional group name, it returns an opened VaultGroup (the user's module owns\n * the store, templates, and sharding config — the CLI stays agnostic of them).\n */\nexport type GroupFactory = (groupName?: string) => Promise<VaultGroup<unknown>>\n\ntype Log = (s: string) => void\n\nexport interface ParsedArgs {\n command: string\n configPath?: string\n group?: string\n vault?: string\n meter: boolean\n}\n\nexport function parseArgs(argv: readonly string[]): ParsedArgs {\n const out: ParsedArgs = { command: argv[0] ?? '', meter: false }\n for (const a of argv.slice(1)) {\n if (a.startsWith('--group=')) out.group = a.slice('--group='.length)\n else if (a.startsWith('--vault=')) out.vault = a.slice('--vault='.length)\n else if (a === '--meter') out.meter = true\n else if (!a.startsWith('--')) out.configPath = a\n }\n return out\n}\n\nasync function loadGroup(args: ParsedArgs): Promise<VaultGroup<unknown>> {\n const mod = (await import(args.configPath!)) as { default: GroupFactory }\n return mod.default(args.group)\n}\n\nexport async function runInspectGroup(args: ParsedArgs, log: Log): Promise<number> {\n if (!args.configPath) {\n log('usage: klum inspect-group <config> --group=<name> [--vault=<id>]')\n return 2\n }\n const group = await loadGroup(args)\n const inspector = createInspector(groupInspector(group))\n const vaults = await inspector.listVaults()\n log(`group \"${args.group ?? ''}\" — ${vaults.length} shard(s):`)\n for (const v of vaults) log(` ${v.id} [${v.role}]`)\n if (args.vault) {\n const vault = await group.db.openVault(args.vault)\n group.template.configure(vault)\n const snap = await inspector.snapshot(vault)\n log(` collections in ${args.vault}: ${snap.collections.map((c) => c.name).join(', ')}`)\n }\n return 0\n}\n\nexport async function runMeterGroup(args: ParsedArgs, log: Log): Promise<number> {\n if (!args.configPath) {\n log('usage: klum meter-group <config> --group=<name>')\n return 2\n }\n const group = await loadGroup(args)\n const r = await meterGroup(group)\n log(`group \"${args.group ?? ''}\" — ${r.vaults} vault(s), ${r.collections} collection(s), ${r.records} record(s)`)\n for (const s of r.perShard) {\n log(` ${s.vaultId} (${s.partitionKey}) v${s.schemaVersion}: ${s.collections} coll, ${s.records} rec`)\n }\n if (r.skipped.length) log(` skipped: ${r.skipped.length} shard(s)`)\n return 0\n}\n\nexport async function main(argv: readonly string[], log: Log = console.log): Promise<number> {\n const args = parseArgs(argv)\n switch (args.command) {\n case 'inspect-group':\n return runInspectGroup(args, log)\n case 'meter-group':\n return runMeterGroup(args, log)\n default:\n log('klum <inspect-group|meter-group> <config> --group=<name> [--vault=<id>]')\n return args.command ? 1 : 0\n }\n}\n\n// bin entrypoint — only runs when executed directly, not when imported in tests.\nif (process.argv[1] && import.meta.url === `file://${process.argv[1]}`) {\n main(process.argv.slice(2))\n .then((code) => {\n process.exitCode = code\n })\n .catch((e: unknown) => {\n console.error(e)\n process.exitCode = 1\n })\n}\n","import type { InspectableContainer } from '@noy-db/in-devtools'\nimport type { AccessibleVault, Vault, WriteHook, WriteConflict, WriteQueue, Unsubscribe } from '@noy-db/hub'\nimport type { VaultGroup } from './vault-group.js'\n\n/**\n * Adapt a federation {@link VaultGroup} to the dev-tools `InspectableContainer`\n * contract from `@noy-db/in-devtools`, so the inspector / TUI can browse a\n * whole fleet exactly like a single instance.\n *\n * Built entirely on the group's public surface (`allRows`, `db`, `template`) —\n * no `VaultGroup` changes, and (critically) no `@klum-db` import lands in any\n * `@noy-db` package: the dependency runs one way, klum → noy.\n *\n * Write-event scoping: `group.db` may host vaults outside this group, so write\n * and conflict events are filtered to the group's shard ids. The id set is\n * primed/refreshed on every `listAccessibleVaults()` call — drive `listVaults()`\n * (the inspector's normal first step) before relying on event scoping.\n */\nexport function groupInspector<T>(group: VaultGroup<T>): InspectableContainer {\n let shardIds = new Set<string>()\n const refresh = async () => {\n const rows = await group.allRows()\n shardIds = new Set(rows.map((r) => r.vaultId))\n return rows\n }\n return {\n async listAccessibleVaults(): Promise<readonly AccessibleVault[]> {\n const rows = await refresh()\n return rows.map((r): AccessibleVault => ({ id: r.vaultId, role: 'owner' }))\n },\n async openVault(name: string): Promise<Vault> {\n const vault = await group.db.openVault(name)\n group.template.configure(vault)\n return vault\n },\n onAfterWrite(handler: WriteHook): Unsubscribe {\n return group.db.onAfterWrite((event) => {\n if (shardIds.has(event.vault)) return handler(event)\n })\n },\n onWriteConflict(handler: (c: WriteConflict) => void): Unsubscribe {\n return group.db.onWriteConflict((c) => {\n if (shardIds.has(c.vault)) handler(c)\n })\n },\n get writeQueue(): WriteQueue {\n return group.db.writeQueue\n },\n }\n}\n","import type { VaultGroup } from './vault-group.js'\nimport type { SkippedVault } from './types.js'\n\nexport interface GroupShardMetrics {\n readonly vaultId: string\n readonly partitionKey: string\n readonly schemaVersion: number\n readonly collections: number\n readonly records: number\n}\n\nexport interface GroupMeterReport {\n /** Number of eligible shards measured. */\n readonly vaults: number\n /** Distinct collection names across the group. */\n readonly collections: number\n /** Total record count summed across shards. */\n readonly records: number\n readonly perShard: ReadonlyArray<GroupShardMetrics>\n /** Drifted / provisioning-failed shards — surfaced, never counted or hidden. */\n readonly skipped: ReadonlyArray<SkippedVault>\n}\n\n/**\n * Fan shape-metrics (collection count + record count) across the group's\n * ELIGIBLE shards. Skipped shards (schema-drift / provisioning failures) are\n * reported in `skipped`, never silently dropped. Reuses the per-vault pattern\n * from `multi-bundle.ts` (`vault.collections()` → `collection(n).count()`).\n *\n * Operational store metrics (calls, CAS conflicts) are a separate concern and\n * are already group-wide via `@noy-db/to-meter` on the underlying store.\n */\nexport async function meterGroup<T>(\n group: VaultGroup<T>,\n opts: { minVersion?: number } = {},\n): Promise<GroupMeterReport> {\n const { eligible, skipped } = await group.resolveEligible(\n opts.minVersion !== undefined ? { minVersion: opts.minVersion } : {},\n )\n const perShard: GroupShardMetrics[] = []\n const names = new Set<string>()\n let records = 0\n for (const row of eligible) {\n const vault = await group.shard(row.partitionKey)\n const collNames = await vault.collections()\n let shardRecords = 0\n for (const n of collNames) {\n names.add(n)\n shardRecords += await vault.collection(n).count()\n }\n records += shardRecords\n perShard.push({\n vaultId: row.vaultId,\n partitionKey: row.partitionKey,\n schemaVersion: row.schemaVersion,\n collections: collNames.length,\n records: shardRecords,\n })\n }\n return { vaults: eligible.length, collections: names.size, records, perShard, skipped }\n}\n"],"mappings":";AAAA,SAAS,uBAAuB;;;ACkBzB,SAAS,eAAkB,OAA4C;AAC5E,MAAI,WAAW,oBAAI,IAAY;AAC/B,QAAM,UAAU,YAAY;AAC1B,UAAM,OAAO,MAAM,MAAM,QAAQ;AACjC,eAAW,IAAI,IAAI,KAAK,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC;AAC7C,WAAO;AAAA,EACT;AACA,SAAO;AAAA,IACL,MAAM,uBAA4D;AAChE,YAAM,OAAO,MAAM,QAAQ;AAC3B,aAAO,KAAK,IAAI,CAAC,OAAwB,EAAE,IAAI,EAAE,SAAS,MAAM,QAAQ,EAAE;AAAA,IAC5E;AAAA,IACA,MAAM,UAAU,MAA8B;AAC5C,YAAM,QAAQ,MAAM,MAAM,GAAG,UAAU,IAAI;AAC3C,YAAM,SAAS,UAAU,KAAK;AAC9B,aAAO;AAAA,IACT;AAAA,IACA,aAAa,SAAiC;AAC5C,aAAO,MAAM,GAAG,aAAa,CAAC,UAAU;AACtC,YAAI,SAAS,IAAI,MAAM,KAAK,EAAG,QAAO,QAAQ,KAAK;AAAA,MACrD,CAAC;AAAA,IACH;AAAA,IACA,gBAAgB,SAAkD;AAChE,aAAO,MAAM,GAAG,gBAAgB,CAAC,MAAM;AACrC,YAAI,SAAS,IAAI,EAAE,KAAK,EAAG,SAAQ,CAAC;AAAA,MACtC,CAAC;AAAA,IACH;AAAA,IACA,IAAI,aAAyB;AAC3B,aAAO,MAAM,GAAG;AAAA,IAClB;AAAA,EACF;AACF;;;ACjBA,eAAsB,WACpB,OACA,OAAgC,CAAC,GACN;AAC3B,QAAM,EAAE,UAAU,QAAQ,IAAI,MAAM,MAAM;AAAA,IACxC,KAAK,eAAe,SAAY,EAAE,YAAY,KAAK,WAAW,IAAI,CAAC;AAAA,EACrE;AACA,QAAM,WAAgC,CAAC;AACvC,QAAM,QAAQ,oBAAI,IAAY;AAC9B,MAAI,UAAU;AACd,aAAW,OAAO,UAAU;AAC1B,UAAM,QAAQ,MAAM,MAAM,MAAM,IAAI,YAAY;AAChD,UAAM,YAAY,MAAM,MAAM,YAAY;AAC1C,QAAI,eAAe;AACnB,eAAW,KAAK,WAAW;AACzB,YAAM,IAAI,CAAC;AACX,sBAAgB,MAAM,MAAM,WAAW,CAAC,EAAE,MAAM;AAAA,IAClD;AACA,eAAW;AACX,aAAS,KAAK;AAAA,MACZ,SAAS,IAAI;AAAA,MACb,cAAc,IAAI;AAAA,MAClB,eAAe,IAAI;AAAA,MACnB,aAAa,UAAU;AAAA,MACvB,SAAS;AAAA,IACX,CAAC;AAAA,EACH;AACA,SAAO,EAAE,QAAQ,SAAS,QAAQ,aAAa,MAAM,MAAM,SAAS,UAAU,QAAQ;AACxF;;;AFtCO,SAAS,UAAU,MAAqC;AAC7D,QAAM,MAAkB,EAAE,SAAS,KAAK,CAAC,KAAK,IAAI,OAAO,MAAM;AAC/D,aAAW,KAAK,KAAK,MAAM,CAAC,GAAG;AAC7B,QAAI,EAAE,WAAW,UAAU,EAAG,KAAI,QAAQ,EAAE,MAAM,WAAW,MAAM;AAAA,aAC1D,EAAE,WAAW,UAAU,EAAG,KAAI,QAAQ,EAAE,MAAM,WAAW,MAAM;AAAA,aAC/D,MAAM,UAAW,KAAI,QAAQ;AAAA,aAC7B,CAAC,EAAE,WAAW,IAAI,EAAG,KAAI,aAAa;AAAA,EACjD;AACA,SAAO;AACT;AAEA,eAAe,UAAU,MAAgD;AACvE,QAAM,MAAO,MAAM,OAAO,KAAK;AAC/B,SAAO,IAAI,QAAQ,KAAK,KAAK;AAC/B;AAEA,eAAsB,gBAAgB,MAAkB,KAA2B;AACjF,MAAI,CAAC,KAAK,YAAY;AACpB,QAAI,kEAAkE;AACtE,WAAO;AAAA,EACT;AACA,QAAM,QAAQ,MAAM,UAAU,IAAI;AAClC,QAAM,YAAY,gBAAgB,eAAe,KAAK,CAAC;AACvD,QAAM,SAAS,MAAM,UAAU,WAAW;AAC1C,MAAI,UAAU,KAAK,SAAS,EAAE,YAAO,OAAO,MAAM,YAAY;AAC9D,aAAW,KAAK,OAAQ,KAAI,KAAK,EAAE,EAAE,KAAK,EAAE,IAAI,GAAG;AACnD,MAAI,KAAK,OAAO;AACd,UAAM,QAAQ,MAAM,MAAM,GAAG,UAAU,KAAK,KAAK;AACjD,UAAM,SAAS,UAAU,KAAK;AAC9B,UAAM,OAAO,MAAM,UAAU,SAAS,KAAK;AAC3C,QAAI,oBAAoB,KAAK,KAAK,KAAK,KAAK,YAAY,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC,EAAE;AAAA,EACzF;AACA,SAAO;AACT;AAEA,eAAsB,cAAc,MAAkB,KAA2B;AAC/E,MAAI,CAAC,KAAK,YAAY;AACpB,QAAI,iDAAiD;AACrD,WAAO;AAAA,EACT;AACA,QAAM,QAAQ,MAAM,UAAU,IAAI;AAClC,QAAM,IAAI,MAAM,WAAW,KAAK;AAChC,MAAI,UAAU,KAAK,SAAS,EAAE,YAAO,EAAE,MAAM,cAAc,EAAE,WAAW,mBAAmB,EAAE,OAAO,YAAY;AAChH,aAAW,KAAK,EAAE,UAAU;AAC1B,QAAI,KAAK,EAAE,OAAO,KAAK,EAAE,YAAY,MAAM,EAAE,aAAa,KAAK,EAAE,WAAW,UAAU,EAAE,OAAO,MAAM;AAAA,EACvG;AACA,MAAI,EAAE,QAAQ,OAAQ,KAAI,cAAc,EAAE,QAAQ,MAAM,WAAW;AACnE,SAAO;AACT;AAEA,eAAsB,KAAK,MAAyB,MAAW,QAAQ,KAAsB;AAC3F,QAAM,OAAO,UAAU,IAAI;AAC3B,UAAQ,KAAK,SAAS;AAAA,IACpB,KAAK;AACH,aAAO,gBAAgB,MAAM,GAAG;AAAA,IAClC,KAAK;AACH,aAAO,cAAc,MAAM,GAAG;AAAA,IAChC;AACE,UAAI,yEAAyE;AAC7E,aAAO,KAAK,UAAU,IAAI;AAAA,EAC9B;AACF;AAGA,IAAI,QAAQ,KAAK,CAAC,KAAK,YAAY,QAAQ,UAAU,QAAQ,KAAK,CAAC,CAAC,IAAI;AACtE,OAAK,QAAQ,KAAK,MAAM,CAAC,CAAC,EACvB,KAAK,CAAC,SAAS;AACd,YAAQ,WAAW;AAAA,EACrB,CAAC,EACA,MAAM,CAAC,MAAe;AACrB,YAAQ,MAAM,CAAC;AACf,YAAQ,WAAW;AAAA,EACrB,CAAC;AACL;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/bin/klum.ts","../../src/federation/group-inspector.ts","../../src/federation/meter-group.ts"],"sourcesContent":["import { createInspector } from '@noy-db/in-devtools'\nimport { groupInspector } from '../federation/group-inspector.js'\nimport { meterGroup } from '../federation/meter-group.js'\nimport type { VaultGroup } from '../federation/vault-group.js'\n\n/**\n * A config module for the `klum` CLI default-exports this factory: given an\n * optional group name, it returns an opened VaultGroup (the user's module owns\n * the store, templates, and sharding config — the CLI stays agnostic of them).\n */\nexport type GroupFactory = (groupName?: string) => Promise<VaultGroup<unknown>>\n\ntype Log = (s: string) => void\n\nexport interface ParsedArgs {\n command: string\n configPath?: string\n group?: string\n vault?: string\n meter: boolean\n}\n\nexport function parseArgs(argv: readonly string[]): ParsedArgs {\n const out: ParsedArgs = { command: argv[0] ?? '', meter: false }\n for (const a of argv.slice(1)) {\n if (a.startsWith('--group=')) out.group = a.slice('--group='.length)\n else if (a.startsWith('--vault=')) out.vault = a.slice('--vault='.length)\n else if (a === '--meter') out.meter = true\n else if (!a.startsWith('--')) out.configPath = a\n }\n return out\n}\n\nasync function loadGroup(args: ParsedArgs): Promise<VaultGroup<unknown>> {\n const mod = (await import(args.configPath!)) as { default: GroupFactory }\n return mod.default(args.group)\n}\n\nexport async function runInspectGroup(args: ParsedArgs, log: Log): Promise<number> {\n if (!args.configPath) {\n log('usage: klum inspect-group <config> --group=<name> [--vault=<id>]')\n return 2\n }\n const group = await loadGroup(args)\n const inspector = createInspector(groupInspector(group))\n const vaults = await inspector.listVaults()\n log(`group \"${args.group ?? ''}\" — ${vaults.length} shard(s):`)\n for (const v of vaults) log(` ${v.id} [${v.role}]`)\n if (args.vault) {\n const vault = await group.db.openVault(args.vault)\n group.template.configure(vault)\n const snap = await inspector.snapshot(vault)\n log(` collections in ${args.vault}: ${snap.collections.map((c) => c.name).join(', ')}`)\n }\n return 0\n}\n\nexport async function runMeterGroup(args: ParsedArgs, log: Log): Promise<number> {\n if (!args.configPath) {\n log('usage: klum meter-group <config> --group=<name>')\n return 2\n }\n const group = await loadGroup(args)\n const r = await meterGroup(group)\n log(`group \"${args.group ?? ''}\" — ${r.vaults} vault(s), ${r.collections} collection(s), ${r.records} record(s)`)\n for (const s of r.perShard) {\n log(` ${s.vaultId} (${s.partitionKey}) v${s.schemaVersion}: ${s.collections} coll, ${s.records} rec`)\n }\n if (r.skipped.length) log(` skipped: ${r.skipped.length} shard(s)`)\n return 0\n}\n\nexport async function main(argv: readonly string[], log: Log = console.log): Promise<number> {\n const args = parseArgs(argv)\n switch (args.command) {\n case 'inspect-group':\n return runInspectGroup(args, log)\n case 'meter-group':\n return runMeterGroup(args, log)\n default:\n log('klum <inspect-group|meter-group> <config> --group=<name> [--vault=<id>]')\n return args.command ? 1 : 0\n }\n}\n\n// bin entrypoint — only runs when executed directly, not when imported in tests.\nif (process.argv[1] && import.meta.url === `file://${process.argv[1]}`) {\n main(process.argv.slice(2))\n .then((code) => {\n process.exitCode = code\n })\n .catch((e: unknown) => {\n console.error(e)\n process.exitCode = 1\n })\n}\n","import type { InspectableContainer } from '@noy-db/in-devtools'\nimport type { AccessibleVault, Vault, WriteHook, WriteConflict, WriteQueue, Unsubscribe } from '@noy-db/hub/cargo'\nimport type { VaultGroup } from './vault-group.js'\n\n/**\n * Adapt a federation {@link VaultGroup} to the dev-tools `InspectableContainer`\n * contract from `@noy-db/in-devtools`, so the inspector / TUI can browse a\n * whole fleet exactly like a single instance.\n *\n * Built entirely on the group's public surface (`allRows`, `db`, `template`) —\n * no `VaultGroup` changes, and (critically) no `@klum-db` import lands in any\n * `@noy-db` package: the dependency runs one way, klum → noy.\n *\n * Write-event scoping: `group.db` may host vaults outside this group, so write\n * and conflict events are filtered to the group's shard ids. The id set is\n * primed/refreshed on every `listAccessibleVaults()` call — drive `listVaults()`\n * (the inspector's normal first step) before relying on event scoping.\n */\nexport function groupInspector<T>(group: VaultGroup<T>): InspectableContainer {\n let shardIds = new Set<string>()\n const refresh = async () => {\n const rows = await group.allRows()\n shardIds = new Set(rows.map((r) => r.vaultId))\n return rows\n }\n return {\n async listAccessibleVaults(): Promise<readonly AccessibleVault[]> {\n const rows = await refresh()\n return rows.map((r): AccessibleVault => ({ id: r.vaultId, role: 'owner' }))\n },\n async openVault(name: string): Promise<Vault> {\n const vault = await group.db.openVault(name)\n group.template.configure(vault)\n return vault\n },\n onAfterWrite(handler: WriteHook): Unsubscribe {\n return group.db.onAfterWrite((event) => {\n if (shardIds.has(event.vault)) return handler(event)\n })\n },\n onWriteConflict(handler: (c: WriteConflict) => void): Unsubscribe {\n return group.db.onWriteConflict((c) => {\n if (shardIds.has(c.vault)) handler(c)\n })\n },\n get writeQueue(): WriteQueue {\n return group.db.writeQueue\n },\n }\n}\n","import type { VaultGroup } from './vault-group.js'\nimport type { SkippedVault } from './types.js'\n\nexport interface GroupShardMetrics {\n readonly vaultId: string\n readonly partitionKey: string\n readonly schemaVersion: number\n readonly collections: number\n readonly records: number\n}\n\nexport interface GroupMeterReport {\n /** Number of eligible shards measured. */\n readonly vaults: number\n /** Distinct collection names across the group. */\n readonly collections: number\n /** Total record count summed across shards. */\n readonly records: number\n readonly perShard: ReadonlyArray<GroupShardMetrics>\n /** Drifted / provisioning-failed shards — surfaced, never counted or hidden. */\n readonly skipped: ReadonlyArray<SkippedVault>\n}\n\n/**\n * Fan shape-metrics (collection count + record count) across the group's\n * ELIGIBLE shards. Skipped shards (schema-drift / provisioning failures) are\n * reported in `skipped`, never silently dropped. Reuses the per-vault pattern\n * from `multi-bundle.ts` (`vault.collections()` → `collection(n).count()`).\n *\n * Operational store metrics (calls, CAS conflicts) are a separate concern and\n * are already group-wide via `@noy-db/to-meter` on the underlying store.\n */\nexport async function meterGroup<T>(\n group: VaultGroup<T>,\n opts: { minVersion?: number } = {},\n): Promise<GroupMeterReport> {\n const { eligible, skipped } = await group.resolveEligible(\n opts.minVersion !== undefined ? { minVersion: opts.minVersion } : {},\n )\n const perShard: GroupShardMetrics[] = []\n const names = new Set<string>()\n let records = 0\n for (const row of eligible) {\n const vault = await group.shard(row.partitionKey)\n const collNames = await vault.collections()\n let shardRecords = 0\n for (const n of collNames) {\n names.add(n)\n shardRecords += await vault.collection(n).count()\n }\n records += shardRecords\n perShard.push({\n vaultId: row.vaultId,\n partitionKey: row.partitionKey,\n schemaVersion: row.schemaVersion,\n collections: collNames.length,\n records: shardRecords,\n })\n }\n return { vaults: eligible.length, collections: names.size, records, perShard, skipped }\n}\n"],"mappings":"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","names":[]}
|