@klum-db/lobby 0.2.0-pre.27 → 0.2.0-pre.28

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.
@@ -0,0 +1,168 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/bin/klum.ts
21
+ var klum_exports = {};
22
+ __export(klum_exports, {
23
+ main: () => main,
24
+ parseArgs: () => parseArgs,
25
+ runInspectGroup: () => runInspectGroup,
26
+ runMeterGroup: () => runMeterGroup
27
+ });
28
+ module.exports = __toCommonJS(klum_exports);
29
+ var import_in_devtools = require("@noy-db/in-devtools");
30
+
31
+ // src/federation/group-inspector.ts
32
+ function groupInspector(group) {
33
+ let shardIds = /* @__PURE__ */ new Set();
34
+ const refresh = async () => {
35
+ const rows = await group.allRows();
36
+ shardIds = new Set(rows.map((r) => r.vaultId));
37
+ return rows;
38
+ };
39
+ return {
40
+ async listAccessibleVaults() {
41
+ const rows = await refresh();
42
+ return rows.map((r) => ({ id: r.vaultId, role: "owner" }));
43
+ },
44
+ async openVault(name) {
45
+ const vault = await group.db.openVault(name);
46
+ group.template.configure(vault);
47
+ return vault;
48
+ },
49
+ onAfterWrite(handler) {
50
+ return group.db.onAfterWrite((event) => {
51
+ if (shardIds.has(event.vault)) return handler(event);
52
+ });
53
+ },
54
+ onWriteConflict(handler) {
55
+ return group.db.onWriteConflict((c) => {
56
+ if (shardIds.has(c.vault)) handler(c);
57
+ });
58
+ },
59
+ get writeQueue() {
60
+ return group.db.writeQueue;
61
+ }
62
+ };
63
+ }
64
+
65
+ // src/federation/meter-group.ts
66
+ async function meterGroup(group, opts = {}) {
67
+ const { eligible, skipped } = await group.resolveEligible(
68
+ opts.minVersion !== void 0 ? { minVersion: opts.minVersion } : {}
69
+ );
70
+ const perShard = [];
71
+ const names = /* @__PURE__ */ new Set();
72
+ let records = 0;
73
+ for (const row of eligible) {
74
+ const vault = await group.shard(row.partitionKey);
75
+ const collNames = await vault.collections();
76
+ let shardRecords = 0;
77
+ for (const n of collNames) {
78
+ names.add(n);
79
+ shardRecords += await vault.collection(n).count();
80
+ }
81
+ records += shardRecords;
82
+ perShard.push({
83
+ vaultId: row.vaultId,
84
+ partitionKey: row.partitionKey,
85
+ schemaVersion: row.schemaVersion,
86
+ collections: collNames.length,
87
+ records: shardRecords
88
+ });
89
+ }
90
+ return { vaults: eligible.length, collections: names.size, records, perShard, skipped };
91
+ }
92
+
93
+ // src/bin/klum.ts
94
+ var import_meta = {};
95
+ function parseArgs(argv) {
96
+ const out = { command: argv[0] ?? "", meter: false };
97
+ for (const a of argv.slice(1)) {
98
+ if (a.startsWith("--group=")) out.group = a.slice("--group=".length);
99
+ else if (a.startsWith("--vault=")) out.vault = a.slice("--vault=".length);
100
+ else if (a === "--meter") out.meter = true;
101
+ else if (!a.startsWith("--")) out.configPath = a;
102
+ }
103
+ return out;
104
+ }
105
+ async function loadGroup(args) {
106
+ const mod = await import(args.configPath);
107
+ return mod.default(args.group);
108
+ }
109
+ async function runInspectGroup(args, log) {
110
+ if (!args.configPath) {
111
+ log("usage: klum inspect-group <config> --group=<name> [--vault=<id>]");
112
+ return 2;
113
+ }
114
+ const group = await loadGroup(args);
115
+ const inspector = (0, import_in_devtools.createInspector)(groupInspector(group));
116
+ const vaults = await inspector.listVaults();
117
+ log(`group "${args.group ?? ""}" \u2014 ${vaults.length} shard(s):`);
118
+ for (const v of vaults) log(` ${v.id} [${v.role}]`);
119
+ if (args.vault) {
120
+ const vault = await group.db.openVault(args.vault);
121
+ group.template.configure(vault);
122
+ const snap = await inspector.snapshot(vault);
123
+ log(` collections in ${args.vault}: ${snap.collections.map((c) => c.name).join(", ")}`);
124
+ }
125
+ return 0;
126
+ }
127
+ async function runMeterGroup(args, log) {
128
+ if (!args.configPath) {
129
+ log("usage: klum meter-group <config> --group=<name>");
130
+ return 2;
131
+ }
132
+ const group = await loadGroup(args);
133
+ const r = await meterGroup(group);
134
+ log(`group "${args.group ?? ""}" \u2014 ${r.vaults} vault(s), ${r.collections} collection(s), ${r.records} record(s)`);
135
+ for (const s of r.perShard) {
136
+ log(` ${s.vaultId} (${s.partitionKey}) v${s.schemaVersion}: ${s.collections} coll, ${s.records} rec`);
137
+ }
138
+ if (r.skipped.length) log(` skipped: ${r.skipped.length} shard(s)`);
139
+ return 0;
140
+ }
141
+ async function main(argv, log = console.log) {
142
+ const args = parseArgs(argv);
143
+ switch (args.command) {
144
+ case "inspect-group":
145
+ return runInspectGroup(args, log);
146
+ case "meter-group":
147
+ return runMeterGroup(args, log);
148
+ default:
149
+ log("klum <inspect-group|meter-group> <config> --group=<name> [--vault=<id>]");
150
+ return args.command ? 1 : 0;
151
+ }
152
+ }
153
+ if (process.argv[1] && import_meta.url === `file://${process.argv[1]}`) {
154
+ main(process.argv.slice(2)).then((code) => {
155
+ process.exitCode = code;
156
+ }).catch((e) => {
157
+ console.error(e);
158
+ process.exitCode = 1;
159
+ });
160
+ }
161
+ // Annotate the CommonJS export names for ESM import in node:
162
+ 0 && (module.exports = {
163
+ main,
164
+ parseArgs,
165
+ runInspectGroup,
166
+ runMeterGroup
167
+ });
168
+ //# sourceMappingURL=klum.cjs.map
@@ -0,0 +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":[]}
@@ -0,0 +1,25 @@
1
+ import { V as VaultGroup } from '../vault-group-DeBoCFT9.cjs';
2
+ import '@noy-db/hub/kernel';
3
+ import '@noy-db/hub';
4
+ import '@noy-db/hub/bundle';
5
+
6
+ /**
7
+ * A config module for the `klum` CLI default-exports this factory: given an
8
+ * optional group name, it returns an opened VaultGroup (the user's module owns
9
+ * the store, templates, and sharding config — the CLI stays agnostic of them).
10
+ */
11
+ type GroupFactory = (groupName?: string) => Promise<VaultGroup<unknown>>;
12
+ type Log = (s: string) => void;
13
+ interface ParsedArgs {
14
+ command: string;
15
+ configPath?: string;
16
+ group?: string;
17
+ vault?: string;
18
+ meter: boolean;
19
+ }
20
+ declare function parseArgs(argv: readonly string[]): ParsedArgs;
21
+ declare function runInspectGroup(args: ParsedArgs, log: Log): Promise<number>;
22
+ declare function runMeterGroup(args: ParsedArgs, log: Log): Promise<number>;
23
+ declare function main(argv: readonly string[], log?: Log): Promise<number>;
24
+
25
+ export { type GroupFactory, type ParsedArgs, main, parseArgs, runInspectGroup, runMeterGroup };
@@ -0,0 +1,25 @@
1
+ import { V as VaultGroup } from '../vault-group-DeBoCFT9.js';
2
+ import '@noy-db/hub/kernel';
3
+ import '@noy-db/hub';
4
+ import '@noy-db/hub/bundle';
5
+
6
+ /**
7
+ * A config module for the `klum` CLI default-exports this factory: given an
8
+ * optional group name, it returns an opened VaultGroup (the user's module owns
9
+ * the store, templates, and sharding config — the CLI stays agnostic of them).
10
+ */
11
+ type GroupFactory = (groupName?: string) => Promise<VaultGroup<unknown>>;
12
+ type Log = (s: string) => void;
13
+ interface ParsedArgs {
14
+ command: string;
15
+ configPath?: string;
16
+ group?: string;
17
+ vault?: string;
18
+ meter: boolean;
19
+ }
20
+ declare function parseArgs(argv: readonly string[]): ParsedArgs;
21
+ declare function runInspectGroup(args: ParsedArgs, log: Log): Promise<number>;
22
+ declare function runMeterGroup(args: ParsedArgs, log: Log): Promise<number>;
23
+ declare function main(argv: readonly string[], log?: Log): Promise<number>;
24
+
25
+ export { type GroupFactory, type ParsedArgs, main, parseArgs, runInspectGroup, runMeterGroup };
@@ -0,0 +1,139 @@
1
+ // src/bin/klum.ts
2
+ import { createInspector } from "@noy-db/in-devtools";
3
+
4
+ // src/federation/group-inspector.ts
5
+ function groupInspector(group) {
6
+ let shardIds = /* @__PURE__ */ new Set();
7
+ const refresh = async () => {
8
+ const rows = await group.allRows();
9
+ shardIds = new Set(rows.map((r) => r.vaultId));
10
+ return rows;
11
+ };
12
+ return {
13
+ async listAccessibleVaults() {
14
+ const rows = await refresh();
15
+ return rows.map((r) => ({ id: r.vaultId, role: "owner" }));
16
+ },
17
+ async openVault(name) {
18
+ const vault = await group.db.openVault(name);
19
+ group.template.configure(vault);
20
+ return vault;
21
+ },
22
+ onAfterWrite(handler) {
23
+ return group.db.onAfterWrite((event) => {
24
+ if (shardIds.has(event.vault)) return handler(event);
25
+ });
26
+ },
27
+ onWriteConflict(handler) {
28
+ return group.db.onWriteConflict((c) => {
29
+ if (shardIds.has(c.vault)) handler(c);
30
+ });
31
+ },
32
+ get writeQueue() {
33
+ return group.db.writeQueue;
34
+ }
35
+ };
36
+ }
37
+
38
+ // src/federation/meter-group.ts
39
+ async function meterGroup(group, opts = {}) {
40
+ const { eligible, skipped } = await group.resolveEligible(
41
+ opts.minVersion !== void 0 ? { minVersion: opts.minVersion } : {}
42
+ );
43
+ const perShard = [];
44
+ const names = /* @__PURE__ */ new Set();
45
+ let records = 0;
46
+ for (const row of eligible) {
47
+ const vault = await group.shard(row.partitionKey);
48
+ const collNames = await vault.collections();
49
+ let shardRecords = 0;
50
+ for (const n of collNames) {
51
+ names.add(n);
52
+ shardRecords += await vault.collection(n).count();
53
+ }
54
+ records += shardRecords;
55
+ perShard.push({
56
+ vaultId: row.vaultId,
57
+ partitionKey: row.partitionKey,
58
+ schemaVersion: row.schemaVersion,
59
+ collections: collNames.length,
60
+ records: shardRecords
61
+ });
62
+ }
63
+ return { vaults: eligible.length, collections: names.size, records, perShard, skipped };
64
+ }
65
+
66
+ // src/bin/klum.ts
67
+ function parseArgs(argv) {
68
+ const out = { command: argv[0] ?? "", meter: false };
69
+ for (const a of argv.slice(1)) {
70
+ if (a.startsWith("--group=")) out.group = a.slice("--group=".length);
71
+ else if (a.startsWith("--vault=")) out.vault = a.slice("--vault=".length);
72
+ else if (a === "--meter") out.meter = true;
73
+ else if (!a.startsWith("--")) out.configPath = a;
74
+ }
75
+ return out;
76
+ }
77
+ async function loadGroup(args) {
78
+ const mod = await import(args.configPath);
79
+ return mod.default(args.group);
80
+ }
81
+ async function runInspectGroup(args, log) {
82
+ if (!args.configPath) {
83
+ log("usage: klum inspect-group <config> --group=<name> [--vault=<id>]");
84
+ return 2;
85
+ }
86
+ const group = await loadGroup(args);
87
+ const inspector = createInspector(groupInspector(group));
88
+ const vaults = await inspector.listVaults();
89
+ log(`group "${args.group ?? ""}" \u2014 ${vaults.length} shard(s):`);
90
+ for (const v of vaults) log(` ${v.id} [${v.role}]`);
91
+ if (args.vault) {
92
+ const vault = await group.db.openVault(args.vault);
93
+ group.template.configure(vault);
94
+ const snap = await inspector.snapshot(vault);
95
+ log(` collections in ${args.vault}: ${snap.collections.map((c) => c.name).join(", ")}`);
96
+ }
97
+ return 0;
98
+ }
99
+ async function runMeterGroup(args, log) {
100
+ if (!args.configPath) {
101
+ log("usage: klum meter-group <config> --group=<name>");
102
+ return 2;
103
+ }
104
+ const group = await loadGroup(args);
105
+ const r = await meterGroup(group);
106
+ log(`group "${args.group ?? ""}" \u2014 ${r.vaults} vault(s), ${r.collections} collection(s), ${r.records} record(s)`);
107
+ for (const s of r.perShard) {
108
+ log(` ${s.vaultId} (${s.partitionKey}) v${s.schemaVersion}: ${s.collections} coll, ${s.records} rec`);
109
+ }
110
+ if (r.skipped.length) log(` skipped: ${r.skipped.length} shard(s)`);
111
+ return 0;
112
+ }
113
+ async function main(argv, log = console.log) {
114
+ const args = parseArgs(argv);
115
+ switch (args.command) {
116
+ case "inspect-group":
117
+ return runInspectGroup(args, log);
118
+ case "meter-group":
119
+ return runMeterGroup(args, log);
120
+ default:
121
+ log("klum <inspect-group|meter-group> <config> --group=<name> [--vault=<id>]");
122
+ return args.command ? 1 : 0;
123
+ }
124
+ }
125
+ if (process.argv[1] && import.meta.url === `file://${process.argv[1]}`) {
126
+ main(process.argv.slice(2)).then((code) => {
127
+ process.exitCode = code;
128
+ }).catch((e) => {
129
+ console.error(e);
130
+ process.exitCode = 1;
131
+ });
132
+ }
133
+ export {
134
+ main,
135
+ parseArgs,
136
+ runInspectGroup,
137
+ runMeterGroup
138
+ };
139
+ //# sourceMappingURL=klum.js.map
@@ -0,0 +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":[]}
package/dist/index.cjs CHANGED
@@ -2015,6 +2015,7 @@ __export(index_exports, {
2015
2015
  encodeMultiBundle: () => encodeMultiBundle,
2016
2016
  exportSurface: () => exportSurface,
2017
2017
  extractCrossVaultPartition: () => extractCrossVaultPartition,
2018
+ groupInspector: () => groupInspector,
2018
2019
  isDeedVault: () => import_hub6.isDeedVault,
2019
2020
  isSurfaceDue: () => isSurfaceDue,
2020
2021
  liberateVault: () => import_hub6.liberateVault,
@@ -2023,6 +2024,7 @@ __export(index_exports, {
2023
2024
  markSynced: () => markSynced,
2024
2025
  mergeCompartment: () => mergeCompartment,
2025
2026
  mergeDecryptedRecords: () => mergeDecryptedRecords,
2027
+ meterGroup: () => meterGroup,
2026
2028
  migrateThenMerge: () => migrateThenMerge,
2027
2029
  proposeSurface: () => proposeSurface,
2028
2030
  readMultiVaultBundleCompartment: () => readMultiVaultBundleCompartment,
@@ -2053,6 +2055,68 @@ var DockedUnit = class {
2053
2055
  }
2054
2056
  };
2055
2057
 
2058
+ // src/federation/group-inspector.ts
2059
+ function groupInspector(group) {
2060
+ let shardIds = /* @__PURE__ */ new Set();
2061
+ const refresh = async () => {
2062
+ const rows = await group.allRows();
2063
+ shardIds = new Set(rows.map((r) => r.vaultId));
2064
+ return rows;
2065
+ };
2066
+ return {
2067
+ async listAccessibleVaults() {
2068
+ const rows = await refresh();
2069
+ return rows.map((r) => ({ id: r.vaultId, role: "owner" }));
2070
+ },
2071
+ async openVault(name) {
2072
+ const vault = await group.db.openVault(name);
2073
+ group.template.configure(vault);
2074
+ return vault;
2075
+ },
2076
+ onAfterWrite(handler) {
2077
+ return group.db.onAfterWrite((event) => {
2078
+ if (shardIds.has(event.vault)) return handler(event);
2079
+ });
2080
+ },
2081
+ onWriteConflict(handler) {
2082
+ return group.db.onWriteConflict((c) => {
2083
+ if (shardIds.has(c.vault)) handler(c);
2084
+ });
2085
+ },
2086
+ get writeQueue() {
2087
+ return group.db.writeQueue;
2088
+ }
2089
+ };
2090
+ }
2091
+
2092
+ // src/federation/meter-group.ts
2093
+ async function meterGroup(group, opts = {}) {
2094
+ const { eligible, skipped } = await group.resolveEligible(
2095
+ opts.minVersion !== void 0 ? { minVersion: opts.minVersion } : {}
2096
+ );
2097
+ const perShard = [];
2098
+ const names = /* @__PURE__ */ new Set();
2099
+ let records = 0;
2100
+ for (const row of eligible) {
2101
+ const vault = await group.shard(row.partitionKey);
2102
+ const collNames = await vault.collections();
2103
+ let shardRecords = 0;
2104
+ for (const n of collNames) {
2105
+ names.add(n);
2106
+ shardRecords += await vault.collection(n).count();
2107
+ }
2108
+ records += shardRecords;
2109
+ perShard.push({
2110
+ vaultId: row.vaultId,
2111
+ partitionKey: row.partitionKey,
2112
+ schemaVersion: row.schemaVersion,
2113
+ collections: collNames.length,
2114
+ records: shardRecords
2115
+ });
2116
+ }
2117
+ return { vaults: eligible.length, collections: names.size, records, perShard, skipped };
2118
+ }
2119
+
2056
2120
  // src/index.ts
2057
2121
  var import_kernel12 = require("@noy-db/hub/kernel");
2058
2122
  init_multi_bundle();
@@ -2283,6 +2347,7 @@ function createLobby(noydb) {
2283
2347
  encodeMultiBundle,
2284
2348
  exportSurface,
2285
2349
  extractCrossVaultPartition,
2350
+ groupInspector,
2286
2351
  isDeedVault,
2287
2352
  isSurfaceDue,
2288
2353
  liberateVault,
@@ -2291,6 +2356,7 @@ function createLobby(noydb) {
2291
2356
  markSynced,
2292
2357
  mergeCompartment,
2293
2358
  mergeDecryptedRecords,
2359
+ meterGroup,
2294
2360
  migrateThenMerge,
2295
2361
  proposeSurface,
2296
2362
  readMultiVaultBundleCompartment,