@noy-db/as-sql 0.3.0-pre.1 → 0.3.0-pre.3
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/index.d.ts +21 -0
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -33,6 +33,27 @@ interface AsSQLOptions {
|
|
|
33
33
|
readonly tableNames?: (collection: string) => string;
|
|
34
34
|
/** Include `_noydb_version` / `_noydb_ts` metadata columns. Default `false`. */
|
|
35
35
|
readonly metadataColumns?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Apply the hub's `applyListProjection` read-projection before
|
|
38
|
+
* serialising rows. `true` redacts only `classifiedFields` (mask /
|
|
39
|
+
* omit / rider, per the field's preset). The object form additionally
|
|
40
|
+
* redacts fields carrying a plain `fieldMeta` `sensitivity: 'pii' |
|
|
41
|
+
* 'secret'` tag, per `sensitivity: 'omit' | 'mask'`.
|
|
42
|
+
*
|
|
43
|
+
* Caveat: `describe()` reflects the declarations of *this session's*
|
|
44
|
+
* collection instance — redaction only takes effect when the
|
|
45
|
+
* collection was opened (this call or earlier in the session) with
|
|
46
|
+
* its `classifiedFields` / `fieldMeta` options. This is presentation-
|
|
47
|
+
* layer redaction; it never affects what's on disk. Sealed handles
|
|
48
|
+
* are unaffected either way — they always serialize as `'[sealed]'`,
|
|
49
|
+
* so ciphertext never leaks regardless of this option.
|
|
50
|
+
*
|
|
51
|
+
* Rider companion fields (e.g. `pan_last4`) remain visible as their
|
|
52
|
+
* own columns — they are safe write-time projections.
|
|
53
|
+
*/
|
|
54
|
+
readonly redact?: boolean | {
|
|
55
|
+
readonly sensitivity: 'omit' | 'mask';
|
|
56
|
+
};
|
|
36
57
|
}
|
|
37
58
|
interface AsSQLDownloadOptions extends AsSQLOptions {
|
|
38
59
|
/** Filename offered to the browser. Default `'vault-export.sql'`. */
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
+
import { applyListProjection } from "@noy-db/hub";
|
|
2
3
|
async function toString(vault, options = {}) {
|
|
3
4
|
vault.assertCanExport("plaintext", "sql");
|
|
4
5
|
const dialect = options.dialect ?? "postgres";
|
|
@@ -9,8 +10,14 @@ async function toString(vault, options = {}) {
|
|
|
9
10
|
const buckets = /* @__PURE__ */ new Map();
|
|
10
11
|
for await (const chunk of vault.exportStream({ granularity: "collection" })) {
|
|
11
12
|
if (!includeAll && allowlist && !allowlist.has(chunk.collection)) continue;
|
|
13
|
+
let records = chunk.records.map((r) => stripMeta(r));
|
|
14
|
+
if (options.redact !== void 0 && options.redact !== false) {
|
|
15
|
+
const desc = vault.collection(chunk.collection).describe();
|
|
16
|
+
const projectionOpts = options.redact === true ? void 0 : { sensitivity: options.redact.sensitivity };
|
|
17
|
+
records = records.map((r) => applyListProjection(desc, r, projectionOpts));
|
|
18
|
+
}
|
|
12
19
|
const bucket = buckets.get(chunk.collection) ?? [];
|
|
13
|
-
|
|
20
|
+
bucket.push(...records);
|
|
14
21
|
buckets.set(chunk.collection, bucket);
|
|
15
22
|
}
|
|
16
23
|
const parts = [];
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * **@noy-db/as-sql** — SQL dump export for migration.\n *\n * One-way, at-export-time string formatter that emits dialect-aware\n * `CREATE TABLE` + `INSERT INTO` statements. This is NOT a runtime\n * SQL query frontend — it's the migration bridge for consumers moving\n * noy-db data into Postgres, MySQL, or SQLite.\n *\n * Column types are inferred from value types in the first 100 records\n * per collection. Every record's row uses parameterless inline-literal\n * INSERT (no prepared statements) so the dump loads with a single\n * `psql -f`, `mysql <`, or `sqlite3 < dump.sql` invocation.\n *\n * **Zero dependencies** — hand-rolled string formatter, ~300 LoC.\n *\n * See `docs/patterns/as-exports.md` for the three-tier egress model.\n *\n * @packageDocumentation\n */\n\nimport type { Vault } from '@noy-db/hub'\n\nexport type SqlDialect = 'postgres' | 'mysql' | 'sqlite'\nexport type SqlMode = 'schema-only' | 'data-only' | 'schema+data'\n\nexport interface AsSQLOptions {\n /** Target dialect. Default `'postgres'`. */\n readonly dialect?: SqlDialect\n /** Collection allowlist. Omit for all. */\n readonly include?: readonly string[]\n /** Schema + data, schema only, or data only. Default `'schema+data'`. */\n readonly mode?: SqlMode\n /** Map collection name → table name. Default identity. */\n readonly tableNames?: (collection: string) => string\n /** Include `_noydb_version` / `_noydb_ts` metadata columns. Default `false`. */\n readonly metadataColumns?: boolean\n}\n\nexport interface AsSQLDownloadOptions extends AsSQLOptions {\n /** Filename offered to the browser. Default `'vault-export.sql'`. */\n readonly filename?: string\n}\n\nexport interface AsSQLWriteOptions extends AsSQLOptions {\n /** Required for Node file-write — Tier 3 risk gate. */\n readonly acknowledgeRisks: true\n}\n\nexport async function toString(vault: Vault, options: AsSQLOptions = {}): Promise<string> {\n vault.assertCanExport('plaintext', 'sql')\n\n const dialect = options.dialect ?? 'postgres'\n const mode = options.mode ?? 'schema+data'\n const tableName = options.tableNames ?? ((c: string) => c)\n const includeAll = !options.include || options.include.length === 0\n const allowlist = options.include ? new Set(options.include) : null\n\n // Bucket records by collection so we can emit schema+data atomically.\n const buckets = new Map<string, Record<string, unknown>[]>()\n for await (const chunk of vault.exportStream({ granularity: 'collection' })) {\n if (!includeAll && allowlist && !allowlist.has(chunk.collection)) continue\n const bucket = buckets.get(chunk.collection) ?? []\n for (const r of chunk.records) bucket.push(stripMeta(r as Record<string, unknown>))\n buckets.set(chunk.collection, bucket)\n }\n\n const parts: string[] = []\n parts.push(`-- Generated by @noy-db/as-sql · dialect: ${dialect} · mode: ${mode}`)\n parts.push(`-- Bundle snapshot — NOT a live connection. Load with: ${loadCommand(dialect)}`)\n parts.push('')\n\n for (const [collection, records] of buckets) {\n const table = tableName(collection)\n const schema = inferSchema(records, options.metadataColumns === true)\n if (mode !== 'data-only') {\n parts.push(createTable(dialect, table, schema))\n parts.push('')\n }\n if (mode !== 'schema-only') {\n for (const rec of records) {\n parts.push(insertRow(dialect, table, schema, rec))\n }\n parts.push('')\n }\n }\n\n return parts.join('\\n')\n}\n\nexport async function download(vault: Vault, options: AsSQLDownloadOptions = {}): Promise<void> {\n const sql = await toString(vault, options)\n const filename = options.filename ?? 'vault-export.sql'\n const blob = new Blob([sql], { type: 'application/sql;charset=utf-8' })\n const url = URL.createObjectURL(blob)\n const a = document.createElement('a')\n a.href = url\n a.download = filename\n a.click()\n URL.revokeObjectURL(url)\n}\n\nexport async function write(vault: Vault, path: string, options: AsSQLWriteOptions): Promise<void> {\n if (options.acknowledgeRisks !== true) {\n throw new Error(\n 'as-sql.write: acknowledgeRisks: true is required for on-disk plaintext output. ' +\n 'See docs/patterns/as-exports.md §\"The three tiers of \\\\\"plaintext out\\\\\"\"',\n )\n }\n const sql = await toString(vault, options)\n const { writeFile } = await import('node:fs/promises')\n await writeFile(path, sql, 'utf-8')\n}\n\n// ─── SQL formatting internals ───────────────────────────────────────────\n\ntype SqlType = 'text' | 'integer' | 'real' | 'boolean' | 'timestamp' | 'jsonb'\n\ninterface ColumnSchema {\n readonly name: string\n readonly type: SqlType\n readonly nullable: boolean\n}\n\nfunction inferSchema(records: readonly Record<string, unknown>[], includeMeta: boolean): ColumnSchema[] {\n const observed = new Map<string, { types: Set<SqlType>; nullable: boolean }>()\n const sample = records.slice(0, 100) // sample up to 100 rows for type inference\n for (const rec of sample) {\n for (const key of Object.keys(rec)) {\n if (!observed.has(key)) observed.set(key, { types: new Set(), nullable: false })\n const slot = observed.get(key)!\n const value = rec[key]\n if (value === null || value === undefined) {\n slot.nullable = true\n continue\n }\n slot.types.add(inferType(value))\n }\n // Fields missing from this record → nullable\n for (const key of observed.keys()) {\n if (!(key in rec)) observed.get(key)!.nullable = true\n }\n }\n\n const columns: ColumnSchema[] = []\n for (const [name, info] of observed) {\n if (!includeMeta && (name === '_v' || name === '_ts' || name === '_by')) continue\n const type: SqlType = info.types.size === 1\n ? [...info.types][0]!\n : 'text' // mixed types → fall back to text\n columns.push({ name, type, nullable: info.nullable })\n }\n if (includeMeta) {\n columns.push({ name: '_noydb_version', type: 'integer', nullable: true })\n columns.push({ name: '_noydb_ts', type: 'timestamp', nullable: true })\n }\n return columns\n}\n\nfunction inferType(value: unknown): SqlType {\n if (typeof value === 'boolean') return 'boolean'\n if (typeof value === 'number') return Number.isInteger(value) ? 'integer' : 'real'\n if (value instanceof Date) return 'timestamp'\n if (typeof value === 'string') {\n if (/^\\d{4}-\\d{2}-\\d{2}T/.test(value)) return 'timestamp'\n return 'text'\n }\n return 'jsonb'\n}\n\nfunction createTable(dialect: SqlDialect, table: string, schema: readonly ColumnSchema[]): string {\n const cols = schema.map(c => ` ${quoteIdent(dialect, c.name)} ${mapType(dialect, c.type)}${c.nullable ? '' : ' NOT NULL'}`)\n return `CREATE TABLE ${quoteIdent(dialect, table)} (\\n${cols.join(',\\n')}\\n);`\n}\n\nfunction mapType(dialect: SqlDialect, type: SqlType): string {\n const map: Record<SqlDialect, Record<SqlType, string>> = {\n postgres: { text: 'TEXT', integer: 'INTEGER', real: 'REAL', boolean: 'BOOLEAN', timestamp: 'TIMESTAMPTZ', jsonb: 'JSONB' },\n mysql: { text: 'TEXT', integer: 'BIGINT', real: 'DOUBLE', boolean: 'TINYINT(1)', timestamp: 'DATETIME', jsonb: 'JSON' },\n sqlite: { text: 'TEXT', integer: 'INTEGER', real: 'REAL', boolean: 'INTEGER', timestamp: 'TEXT', jsonb: 'TEXT' },\n }\n return map[dialect][type]\n}\n\nfunction insertRow(\n dialect: SqlDialect,\n table: string,\n schema: readonly ColumnSchema[],\n record: Record<string, unknown>,\n): string {\n const cols = schema.map(c => quoteIdent(dialect, c.name)).join(', ')\n const values = schema.map(c => formatLiteral(dialect, c.type, record[c.name])).join(', ')\n return `INSERT INTO ${quoteIdent(dialect, table)} (${cols}) VALUES (${values});`\n}\n\nfunction formatLiteral(dialect: SqlDialect, type: SqlType, value: unknown): string {\n if (value === null || value === undefined) return 'NULL'\n if (type === 'boolean') {\n if (dialect === 'mysql' || dialect === 'sqlite') return value ? '1' : '0'\n return value ? 'TRUE' : 'FALSE'\n }\n if (type === 'integer' || type === 'real') return stringifyScalar(value)\n if (type === 'timestamp') {\n const s = value instanceof Date ? value.toISOString() : stringifyScalar(value)\n return quoteString(dialect, s)\n }\n if (type === 'jsonb') {\n return quoteString(dialect, JSON.stringify(value))\n }\n return quoteString(dialect, stringifyScalar(value))\n}\n\nfunction stringifyScalar(value: unknown): string {\n if (typeof value === 'string') return value\n if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {\n return String(value)\n }\n if (value === null || value === undefined) return ''\n return JSON.stringify(value)\n}\n\nfunction quoteString(dialect: SqlDialect, s: string): string {\n // Both standard SQL and common dialects use '' to escape single quotes.\n const escaped = s.replace(/'/g, \"''\")\n return `'${escaped}'`\n}\n\nfunction quoteIdent(dialect: SqlDialect, name: string): string {\n if (dialect === 'mysql') return `\\`${name.replace(/`/g, '``')}\\``\n return `\"${name.replace(/\"/g, '\"\"')}\"`\n}\n\nfunction loadCommand(dialect: SqlDialect): string {\n if (dialect === 'postgres') return 'psql -f dump.sql'\n if (dialect === 'mysql') return 'mysql -u <user> -p < dump.sql'\n return 'sqlite3 <database>.db < dump.sql'\n}\n\nfunction stripMeta(record: Record<string, unknown>): Record<string, unknown> {\n const out: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(record)) {\n if (key === '_iv' || key === '_data' || key === '_noydb') continue\n out[key] = value\n }\n return out\n}\n"],"mappings":";AAgDA,eAAsB,SAAS,OAAc,UAAwB,CAAC,GAAoB;AACxF,QAAM,gBAAgB,aAAa,KAAK;AAExC,QAAM,UAAU,QAAQ,WAAW;AACnC,QAAM,OAAO,QAAQ,QAAQ;AAC7B,QAAM,YAAY,QAAQ,eAAe,CAAC,MAAc;AACxD,QAAM,aAAa,CAAC,QAAQ,WAAW,QAAQ,QAAQ,WAAW;AAClE,QAAM,YAAY,QAAQ,UAAU,IAAI,IAAI,QAAQ,OAAO,IAAI;AAG/D,QAAM,UAAU,oBAAI,IAAuC;AAC3D,mBAAiB,SAAS,MAAM,aAAa,EAAE,aAAa,aAAa,CAAC,GAAG;AAC3E,QAAI,CAAC,cAAc,aAAa,CAAC,UAAU,IAAI,MAAM,UAAU,EAAG;AAClE,UAAM,SAAS,QAAQ,IAAI,MAAM,UAAU,KAAK,CAAC;AACjD,eAAW,KAAK,MAAM,QAAS,QAAO,KAAK,UAAU,CAA4B,CAAC;AAClF,YAAQ,IAAI,MAAM,YAAY,MAAM;AAAA,EACtC;AAEA,QAAM,QAAkB,CAAC;AACzB,QAAM,KAAK,gDAA6C,OAAO,eAAY,IAAI,EAAE;AACjF,QAAM,KAAK,+DAA0D,YAAY,OAAO,CAAC,EAAE;AAC3F,QAAM,KAAK,EAAE;AAEb,aAAW,CAAC,YAAY,OAAO,KAAK,SAAS;AAC3C,UAAM,QAAQ,UAAU,UAAU;AAClC,UAAM,SAAS,YAAY,SAAS,QAAQ,oBAAoB,IAAI;AACpE,QAAI,SAAS,aAAa;AACxB,YAAM,KAAK,YAAY,SAAS,OAAO,MAAM,CAAC;AAC9C,YAAM,KAAK,EAAE;AAAA,IACf;AACA,QAAI,SAAS,eAAe;AAC1B,iBAAW,OAAO,SAAS;AACzB,cAAM,KAAK,UAAU,SAAS,OAAO,QAAQ,GAAG,CAAC;AAAA,MACnD;AACA,YAAM,KAAK,EAAE;AAAA,IACf;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;AAEA,eAAsB,SAAS,OAAc,UAAgC,CAAC,GAAkB;AAC9F,QAAM,MAAM,MAAM,SAAS,OAAO,OAAO;AACzC,QAAM,WAAW,QAAQ,YAAY;AACrC,QAAM,OAAO,IAAI,KAAK,CAAC,GAAG,GAAG,EAAE,MAAM,gCAAgC,CAAC;AACtE,QAAM,MAAM,IAAI,gBAAgB,IAAI;AACpC,QAAM,IAAI,SAAS,cAAc,GAAG;AACpC,IAAE,OAAO;AACT,IAAE,WAAW;AACb,IAAE,MAAM;AACR,MAAI,gBAAgB,GAAG;AACzB;AAEA,eAAsB,MAAM,OAAc,MAAc,SAA2C;AACjG,MAAI,QAAQ,qBAAqB,MAAM;AACrC,UAAM,IAAI;AAAA,MACR;AAAA,IAEF;AAAA,EACF;AACA,QAAM,MAAM,MAAM,SAAS,OAAO,OAAO;AACzC,QAAM,EAAE,UAAU,IAAI,MAAM,OAAO,aAAkB;AACrD,QAAM,UAAU,MAAM,KAAK,OAAO;AACpC;AAYA,SAAS,YAAY,SAA6C,aAAsC;AACtG,QAAM,WAAW,oBAAI,IAAwD;AAC7E,QAAM,SAAS,QAAQ,MAAM,GAAG,GAAG;AACnC,aAAW,OAAO,QAAQ;AACxB,eAAW,OAAO,OAAO,KAAK,GAAG,GAAG;AAClC,UAAI,CAAC,SAAS,IAAI,GAAG,EAAG,UAAS,IAAI,KAAK,EAAE,OAAO,oBAAI,IAAI,GAAG,UAAU,MAAM,CAAC;AAC/E,YAAM,OAAO,SAAS,IAAI,GAAG;AAC7B,YAAM,QAAQ,IAAI,GAAG;AACrB,UAAI,UAAU,QAAQ,UAAU,QAAW;AACzC,aAAK,WAAW;AAChB;AAAA,MACF;AACA,WAAK,MAAM,IAAI,UAAU,KAAK,CAAC;AAAA,IACjC;AAEA,eAAW,OAAO,SAAS,KAAK,GAAG;AACjC,UAAI,EAAE,OAAO,KAAM,UAAS,IAAI,GAAG,EAAG,WAAW;AAAA,IACnD;AAAA,EACF;AAEA,QAAM,UAA0B,CAAC;AACjC,aAAW,CAAC,MAAM,IAAI,KAAK,UAAU;AACnC,QAAI,CAAC,gBAAgB,SAAS,QAAQ,SAAS,SAAS,SAAS,OAAQ;AACzE,UAAM,OAAgB,KAAK,MAAM,SAAS,IACtC,CAAC,GAAG,KAAK,KAAK,EAAE,CAAC,IACjB;AACJ,YAAQ,KAAK,EAAE,MAAM,MAAM,UAAU,KAAK,SAAS,CAAC;AAAA,EACtD;AACA,MAAI,aAAa;AACf,YAAQ,KAAK,EAAE,MAAM,kBAAkB,MAAM,WAAW,UAAU,KAAK,CAAC;AACxE,YAAQ,KAAK,EAAE,MAAM,aAAa,MAAM,aAAa,UAAU,KAAK,CAAC;AAAA,EACvE;AACA,SAAO;AACT;AAEA,SAAS,UAAU,OAAyB;AAC1C,MAAI,OAAO,UAAU,UAAW,QAAO;AACvC,MAAI,OAAO,UAAU,SAAU,QAAO,OAAO,UAAU,KAAK,IAAI,YAAY;AAC5E,MAAI,iBAAiB,KAAM,QAAO;AAClC,MAAI,OAAO,UAAU,UAAU;AAC7B,QAAI,sBAAsB,KAAK,KAAK,EAAG,QAAO;AAC9C,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,SAAS,YAAY,SAAqB,OAAe,QAAyC;AAChG,QAAM,OAAO,OAAO,IAAI,OAAK,KAAK,WAAW,SAAS,EAAE,IAAI,CAAC,IAAI,QAAQ,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,WAAW,KAAK,WAAW,EAAE;AAC3H,SAAO,gBAAgB,WAAW,SAAS,KAAK,CAAC;AAAA,EAAO,KAAK,KAAK,KAAK,CAAC;AAAA;AAC1E;AAEA,SAAS,QAAQ,SAAqB,MAAuB;AAC3D,QAAM,MAAmD;AAAA,IACvD,UAAU,EAAE,MAAM,QAAQ,SAAS,WAAW,MAAM,QAAQ,SAAS,WAAW,WAAW,eAAe,OAAO,QAAQ;AAAA,IACzH,OAAU,EAAE,MAAM,QAAQ,SAAS,UAAU,MAAM,UAAU,SAAS,cAAc,WAAW,YAAY,OAAO,OAAO;AAAA,IACzH,QAAU,EAAE,MAAM,QAAQ,SAAS,WAAW,MAAM,QAAQ,SAAS,WAAW,WAAW,QAAQ,OAAO,OAAO;AAAA,EACnH;AACA,SAAO,IAAI,OAAO,EAAE,IAAI;AAC1B;AAEA,SAAS,UACP,SACA,OACA,QACA,QACQ;AACR,QAAM,OAAO,OAAO,IAAI,OAAK,WAAW,SAAS,EAAE,IAAI,CAAC,EAAE,KAAK,IAAI;AACnE,QAAM,SAAS,OAAO,IAAI,OAAK,cAAc,SAAS,EAAE,MAAM,OAAO,EAAE,IAAI,CAAC,CAAC,EAAE,KAAK,IAAI;AACxF,SAAO,eAAe,WAAW,SAAS,KAAK,CAAC,KAAK,IAAI,aAAa,MAAM;AAC9E;AAEA,SAAS,cAAc,SAAqB,MAAe,OAAwB;AACjF,MAAI,UAAU,QAAQ,UAAU,OAAW,QAAO;AAClD,MAAI,SAAS,WAAW;AACtB,QAAI,YAAY,WAAW,YAAY,SAAU,QAAO,QAAQ,MAAM;AACtE,WAAO,QAAQ,SAAS;AAAA,EAC1B;AACA,MAAI,SAAS,aAAa,SAAS,OAAQ,QAAO,gBAAgB,KAAK;AACvE,MAAI,SAAS,aAAa;AACxB,UAAM,IAAI,iBAAiB,OAAO,MAAM,YAAY,IAAI,gBAAgB,KAAK;AAC7E,WAAO,YAAY,SAAS,CAAC;AAAA,EAC/B;AACA,MAAI,SAAS,SAAS;AACpB,WAAO,YAAY,SAAS,KAAK,UAAU,KAAK,CAAC;AAAA,EACnD;AACA,SAAO,YAAY,SAAS,gBAAgB,KAAK,CAAC;AACpD;AAEA,SAAS,gBAAgB,OAAwB;AAC/C,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI,OAAO,UAAU,YAAY,OAAO,UAAU,aAAa,OAAO,UAAU,UAAU;AACxF,WAAO,OAAO,KAAK;AAAA,EACrB;AACA,MAAI,UAAU,QAAQ,UAAU,OAAW,QAAO;AAClD,SAAO,KAAK,UAAU,KAAK;AAC7B;AAEA,SAAS,YAAY,SAAqB,GAAmB;AAE3D,QAAM,UAAU,EAAE,QAAQ,MAAM,IAAI;AACpC,SAAO,IAAI,OAAO;AACpB;AAEA,SAAS,WAAW,SAAqB,MAAsB;AAC7D,MAAI,YAAY,QAAS,QAAO,KAAK,KAAK,QAAQ,MAAM,IAAI,CAAC;AAC7D,SAAO,IAAI,KAAK,QAAQ,MAAM,IAAI,CAAC;AACrC;AAEA,SAAS,YAAY,SAA6B;AAChD,MAAI,YAAY,WAAY,QAAO;AACnC,MAAI,YAAY,QAAS,QAAO;AAChC,SAAO;AACT;AAEA,SAAS,UAAU,QAA0D;AAC3E,QAAM,MAA+B,CAAC;AACtC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,QAAI,QAAQ,SAAS,QAAQ,WAAW,QAAQ,SAAU;AAC1D,QAAI,GAAG,IAAI;AAAA,EACb;AACA,SAAO;AACT;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * **@noy-db/as-sql** — SQL dump export for migration.\n *\n * One-way, at-export-time string formatter that emits dialect-aware\n * `CREATE TABLE` + `INSERT INTO` statements. This is NOT a runtime\n * SQL query frontend — it's the migration bridge for consumers moving\n * noy-db data into Postgres, MySQL, or SQLite.\n *\n * Column types are inferred from value types in the first 100 records\n * per collection. Every record's row uses parameterless inline-literal\n * INSERT (no prepared statements) so the dump loads with a single\n * `psql -f`, `mysql <`, or `sqlite3 < dump.sql` invocation.\n *\n * **Zero dependencies** — hand-rolled string formatter, ~300 LoC.\n *\n * See `docs/patterns/as-exports.md` for the three-tier egress model.\n *\n * @packageDocumentation\n */\n\nimport { applyListProjection, type Vault, type CollectionDescription } from '@noy-db/hub'\n\nexport type SqlDialect = 'postgres' | 'mysql' | 'sqlite'\nexport type SqlMode = 'schema-only' | 'data-only' | 'schema+data'\n\nexport interface AsSQLOptions {\n /** Target dialect. Default `'postgres'`. */\n readonly dialect?: SqlDialect\n /** Collection allowlist. Omit for all. */\n readonly include?: readonly string[]\n /** Schema + data, schema only, or data only. Default `'schema+data'`. */\n readonly mode?: SqlMode\n /** Map collection name → table name. Default identity. */\n readonly tableNames?: (collection: string) => string\n /** Include `_noydb_version` / `_noydb_ts` metadata columns. Default `false`. */\n readonly metadataColumns?: boolean\n\n /**\n * Apply the hub's `applyListProjection` read-projection before\n * serialising rows. `true` redacts only `classifiedFields` (mask /\n * omit / rider, per the field's preset). The object form additionally\n * redacts fields carrying a plain `fieldMeta` `sensitivity: 'pii' |\n * 'secret'` tag, per `sensitivity: 'omit' | 'mask'`.\n *\n * Caveat: `describe()` reflects the declarations of *this session's*\n * collection instance — redaction only takes effect when the\n * collection was opened (this call or earlier in the session) with\n * its `classifiedFields` / `fieldMeta` options. This is presentation-\n * layer redaction; it never affects what's on disk. Sealed handles\n * are unaffected either way — they always serialize as `'[sealed]'`,\n * so ciphertext never leaks regardless of this option.\n *\n * Rider companion fields (e.g. `pan_last4`) remain visible as their\n * own columns — they are safe write-time projections.\n */\n readonly redact?: boolean | { readonly sensitivity: 'omit' | 'mask' }\n}\n\nexport interface AsSQLDownloadOptions extends AsSQLOptions {\n /** Filename offered to the browser. Default `'vault-export.sql'`. */\n readonly filename?: string\n}\n\nexport interface AsSQLWriteOptions extends AsSQLOptions {\n /** Required for Node file-write — Tier 3 risk gate. */\n readonly acknowledgeRisks: true\n}\n\nexport async function toString(vault: Vault, options: AsSQLOptions = {}): Promise<string> {\n vault.assertCanExport('plaintext', 'sql')\n\n const dialect = options.dialect ?? 'postgres'\n const mode = options.mode ?? 'schema+data'\n const tableName = options.tableNames ?? ((c: string) => c)\n const includeAll = !options.include || options.include.length === 0\n const allowlist = options.include ? new Set(options.include) : null\n\n // Bucket records by collection so we can emit schema+data atomically.\n const buckets = new Map<string, Record<string, unknown>[]>()\n for await (const chunk of vault.exportStream({ granularity: 'collection' })) {\n if (!includeAll && allowlist && !allowlist.has(chunk.collection)) continue\n let records = chunk.records.map(r => stripMeta(r as Record<string, unknown>))\n\n // Apply redaction projection if specified.\n if (options.redact !== undefined && options.redact !== false) {\n const desc: CollectionDescription = vault.collection(chunk.collection).describe()\n const projectionOpts = options.redact === true ? undefined\n : { sensitivity: options.redact.sensitivity }\n records = records.map((r) => applyListProjection(desc, r, projectionOpts))\n }\n\n const bucket = buckets.get(chunk.collection) ?? []\n bucket.push(...records)\n buckets.set(chunk.collection, bucket)\n }\n\n const parts: string[] = []\n parts.push(`-- Generated by @noy-db/as-sql · dialect: ${dialect} · mode: ${mode}`)\n parts.push(`-- Bundle snapshot — NOT a live connection. Load with: ${loadCommand(dialect)}`)\n parts.push('')\n\n for (const [collection, records] of buckets) {\n const table = tableName(collection)\n const schema = inferSchema(records, options.metadataColumns === true)\n if (mode !== 'data-only') {\n parts.push(createTable(dialect, table, schema))\n parts.push('')\n }\n if (mode !== 'schema-only') {\n for (const rec of records) {\n parts.push(insertRow(dialect, table, schema, rec))\n }\n parts.push('')\n }\n }\n\n return parts.join('\\n')\n}\n\nexport async function download(vault: Vault, options: AsSQLDownloadOptions = {}): Promise<void> {\n const sql = await toString(vault, options)\n const filename = options.filename ?? 'vault-export.sql'\n const blob = new Blob([sql], { type: 'application/sql;charset=utf-8' })\n const url = URL.createObjectURL(blob)\n const a = document.createElement('a')\n a.href = url\n a.download = filename\n a.click()\n URL.revokeObjectURL(url)\n}\n\nexport async function write(vault: Vault, path: string, options: AsSQLWriteOptions): Promise<void> {\n if (options.acknowledgeRisks !== true) {\n throw new Error(\n 'as-sql.write: acknowledgeRisks: true is required for on-disk plaintext output. ' +\n 'See docs/patterns/as-exports.md §\"The three tiers of \\\\\"plaintext out\\\\\"\"',\n )\n }\n const sql = await toString(vault, options)\n const { writeFile } = await import('node:fs/promises')\n await writeFile(path, sql, 'utf-8')\n}\n\n// ─── SQL formatting internals ───────────────────────────────────────────\n\ntype SqlType = 'text' | 'integer' | 'real' | 'boolean' | 'timestamp' | 'jsonb'\n\ninterface ColumnSchema {\n readonly name: string\n readonly type: SqlType\n readonly nullable: boolean\n}\n\nfunction inferSchema(records: readonly Record<string, unknown>[], includeMeta: boolean): ColumnSchema[] {\n const observed = new Map<string, { types: Set<SqlType>; nullable: boolean }>()\n const sample = records.slice(0, 100) // sample up to 100 rows for type inference\n for (const rec of sample) {\n for (const key of Object.keys(rec)) {\n if (!observed.has(key)) observed.set(key, { types: new Set(), nullable: false })\n const slot = observed.get(key)!\n const value = rec[key]\n if (value === null || value === undefined) {\n slot.nullable = true\n continue\n }\n slot.types.add(inferType(value))\n }\n // Fields missing from this record → nullable\n for (const key of observed.keys()) {\n if (!(key in rec)) observed.get(key)!.nullable = true\n }\n }\n\n const columns: ColumnSchema[] = []\n for (const [name, info] of observed) {\n if (!includeMeta && (name === '_v' || name === '_ts' || name === '_by')) continue\n const type: SqlType = info.types.size === 1\n ? [...info.types][0]!\n : 'text' // mixed types → fall back to text\n columns.push({ name, type, nullable: info.nullable })\n }\n if (includeMeta) {\n columns.push({ name: '_noydb_version', type: 'integer', nullable: true })\n columns.push({ name: '_noydb_ts', type: 'timestamp', nullable: true })\n }\n return columns\n}\n\nfunction inferType(value: unknown): SqlType {\n if (typeof value === 'boolean') return 'boolean'\n if (typeof value === 'number') return Number.isInteger(value) ? 'integer' : 'real'\n if (value instanceof Date) return 'timestamp'\n if (typeof value === 'string') {\n if (/^\\d{4}-\\d{2}-\\d{2}T/.test(value)) return 'timestamp'\n return 'text'\n }\n return 'jsonb'\n}\n\nfunction createTable(dialect: SqlDialect, table: string, schema: readonly ColumnSchema[]): string {\n const cols = schema.map(c => ` ${quoteIdent(dialect, c.name)} ${mapType(dialect, c.type)}${c.nullable ? '' : ' NOT NULL'}`)\n return `CREATE TABLE ${quoteIdent(dialect, table)} (\\n${cols.join(',\\n')}\\n);`\n}\n\nfunction mapType(dialect: SqlDialect, type: SqlType): string {\n const map: Record<SqlDialect, Record<SqlType, string>> = {\n postgres: { text: 'TEXT', integer: 'INTEGER', real: 'REAL', boolean: 'BOOLEAN', timestamp: 'TIMESTAMPTZ', jsonb: 'JSONB' },\n mysql: { text: 'TEXT', integer: 'BIGINT', real: 'DOUBLE', boolean: 'TINYINT(1)', timestamp: 'DATETIME', jsonb: 'JSON' },\n sqlite: { text: 'TEXT', integer: 'INTEGER', real: 'REAL', boolean: 'INTEGER', timestamp: 'TEXT', jsonb: 'TEXT' },\n }\n return map[dialect][type]\n}\n\nfunction insertRow(\n dialect: SqlDialect,\n table: string,\n schema: readonly ColumnSchema[],\n record: Record<string, unknown>,\n): string {\n const cols = schema.map(c => quoteIdent(dialect, c.name)).join(', ')\n const values = schema.map(c => formatLiteral(dialect, c.type, record[c.name])).join(', ')\n return `INSERT INTO ${quoteIdent(dialect, table)} (${cols}) VALUES (${values});`\n}\n\nfunction formatLiteral(dialect: SqlDialect, type: SqlType, value: unknown): string {\n if (value === null || value === undefined) return 'NULL'\n if (type === 'boolean') {\n if (dialect === 'mysql' || dialect === 'sqlite') return value ? '1' : '0'\n return value ? 'TRUE' : 'FALSE'\n }\n if (type === 'integer' || type === 'real') return stringifyScalar(value)\n if (type === 'timestamp') {\n const s = value instanceof Date ? value.toISOString() : stringifyScalar(value)\n return quoteString(dialect, s)\n }\n if (type === 'jsonb') {\n return quoteString(dialect, JSON.stringify(value))\n }\n return quoteString(dialect, stringifyScalar(value))\n}\n\nfunction stringifyScalar(value: unknown): string {\n if (typeof value === 'string') return value\n if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {\n return String(value)\n }\n if (value === null || value === undefined) return ''\n return JSON.stringify(value)\n}\n\nfunction quoteString(dialect: SqlDialect, s: string): string {\n // Both standard SQL and common dialects use '' to escape single quotes.\n const escaped = s.replace(/'/g, \"''\")\n return `'${escaped}'`\n}\n\nfunction quoteIdent(dialect: SqlDialect, name: string): string {\n if (dialect === 'mysql') return `\\`${name.replace(/`/g, '``')}\\``\n return `\"${name.replace(/\"/g, '\"\"')}\"`\n}\n\nfunction loadCommand(dialect: SqlDialect): string {\n if (dialect === 'postgres') return 'psql -f dump.sql'\n if (dialect === 'mysql') return 'mysql -u <user> -p < dump.sql'\n return 'sqlite3 <database>.db < dump.sql'\n}\n\nfunction stripMeta(record: Record<string, unknown>): Record<string, unknown> {\n const out: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(record)) {\n if (key === '_iv' || key === '_data' || key === '_noydb') continue\n out[key] = value\n }\n return out\n}\n"],"mappings":";AAoBA,SAAS,2BAAmE;AAgD5E,eAAsB,SAAS,OAAc,UAAwB,CAAC,GAAoB;AACxF,QAAM,gBAAgB,aAAa,KAAK;AAExC,QAAM,UAAU,QAAQ,WAAW;AACnC,QAAM,OAAO,QAAQ,QAAQ;AAC7B,QAAM,YAAY,QAAQ,eAAe,CAAC,MAAc;AACxD,QAAM,aAAa,CAAC,QAAQ,WAAW,QAAQ,QAAQ,WAAW;AAClE,QAAM,YAAY,QAAQ,UAAU,IAAI,IAAI,QAAQ,OAAO,IAAI;AAG/D,QAAM,UAAU,oBAAI,IAAuC;AAC3D,mBAAiB,SAAS,MAAM,aAAa,EAAE,aAAa,aAAa,CAAC,GAAG;AAC3E,QAAI,CAAC,cAAc,aAAa,CAAC,UAAU,IAAI,MAAM,UAAU,EAAG;AAClE,QAAI,UAAU,MAAM,QAAQ,IAAI,OAAK,UAAU,CAA4B,CAAC;AAG5E,QAAI,QAAQ,WAAW,UAAa,QAAQ,WAAW,OAAO;AAC5D,YAAM,OAA8B,MAAM,WAAW,MAAM,UAAU,EAAE,SAAS;AAChF,YAAM,iBAAiB,QAAQ,WAAW,OAAO,SAC7C,EAAE,aAAa,QAAQ,OAAO,YAAY;AAC9C,gBAAU,QAAQ,IAAI,CAAC,MAAM,oBAAoB,MAAM,GAAG,cAAc,CAAC;AAAA,IAC3E;AAEA,UAAM,SAAS,QAAQ,IAAI,MAAM,UAAU,KAAK,CAAC;AACjD,WAAO,KAAK,GAAG,OAAO;AACtB,YAAQ,IAAI,MAAM,YAAY,MAAM;AAAA,EACtC;AAEA,QAAM,QAAkB,CAAC;AACzB,QAAM,KAAK,gDAA6C,OAAO,eAAY,IAAI,EAAE;AACjF,QAAM,KAAK,+DAA0D,YAAY,OAAO,CAAC,EAAE;AAC3F,QAAM,KAAK,EAAE;AAEb,aAAW,CAAC,YAAY,OAAO,KAAK,SAAS;AAC3C,UAAM,QAAQ,UAAU,UAAU;AAClC,UAAM,SAAS,YAAY,SAAS,QAAQ,oBAAoB,IAAI;AACpE,QAAI,SAAS,aAAa;AACxB,YAAM,KAAK,YAAY,SAAS,OAAO,MAAM,CAAC;AAC9C,YAAM,KAAK,EAAE;AAAA,IACf;AACA,QAAI,SAAS,eAAe;AAC1B,iBAAW,OAAO,SAAS;AACzB,cAAM,KAAK,UAAU,SAAS,OAAO,QAAQ,GAAG,CAAC;AAAA,MACnD;AACA,YAAM,KAAK,EAAE;AAAA,IACf;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;AAEA,eAAsB,SAAS,OAAc,UAAgC,CAAC,GAAkB;AAC9F,QAAM,MAAM,MAAM,SAAS,OAAO,OAAO;AACzC,QAAM,WAAW,QAAQ,YAAY;AACrC,QAAM,OAAO,IAAI,KAAK,CAAC,GAAG,GAAG,EAAE,MAAM,gCAAgC,CAAC;AACtE,QAAM,MAAM,IAAI,gBAAgB,IAAI;AACpC,QAAM,IAAI,SAAS,cAAc,GAAG;AACpC,IAAE,OAAO;AACT,IAAE,WAAW;AACb,IAAE,MAAM;AACR,MAAI,gBAAgB,GAAG;AACzB;AAEA,eAAsB,MAAM,OAAc,MAAc,SAA2C;AACjG,MAAI,QAAQ,qBAAqB,MAAM;AACrC,UAAM,IAAI;AAAA,MACR;AAAA,IAEF;AAAA,EACF;AACA,QAAM,MAAM,MAAM,SAAS,OAAO,OAAO;AACzC,QAAM,EAAE,UAAU,IAAI,MAAM,OAAO,aAAkB;AACrD,QAAM,UAAU,MAAM,KAAK,OAAO;AACpC;AAYA,SAAS,YAAY,SAA6C,aAAsC;AACtG,QAAM,WAAW,oBAAI,IAAwD;AAC7E,QAAM,SAAS,QAAQ,MAAM,GAAG,GAAG;AACnC,aAAW,OAAO,QAAQ;AACxB,eAAW,OAAO,OAAO,KAAK,GAAG,GAAG;AAClC,UAAI,CAAC,SAAS,IAAI,GAAG,EAAG,UAAS,IAAI,KAAK,EAAE,OAAO,oBAAI,IAAI,GAAG,UAAU,MAAM,CAAC;AAC/E,YAAM,OAAO,SAAS,IAAI,GAAG;AAC7B,YAAM,QAAQ,IAAI,GAAG;AACrB,UAAI,UAAU,QAAQ,UAAU,QAAW;AACzC,aAAK,WAAW;AAChB;AAAA,MACF;AACA,WAAK,MAAM,IAAI,UAAU,KAAK,CAAC;AAAA,IACjC;AAEA,eAAW,OAAO,SAAS,KAAK,GAAG;AACjC,UAAI,EAAE,OAAO,KAAM,UAAS,IAAI,GAAG,EAAG,WAAW;AAAA,IACnD;AAAA,EACF;AAEA,QAAM,UAA0B,CAAC;AACjC,aAAW,CAAC,MAAM,IAAI,KAAK,UAAU;AACnC,QAAI,CAAC,gBAAgB,SAAS,QAAQ,SAAS,SAAS,SAAS,OAAQ;AACzE,UAAM,OAAgB,KAAK,MAAM,SAAS,IACtC,CAAC,GAAG,KAAK,KAAK,EAAE,CAAC,IACjB;AACJ,YAAQ,KAAK,EAAE,MAAM,MAAM,UAAU,KAAK,SAAS,CAAC;AAAA,EACtD;AACA,MAAI,aAAa;AACf,YAAQ,KAAK,EAAE,MAAM,kBAAkB,MAAM,WAAW,UAAU,KAAK,CAAC;AACxE,YAAQ,KAAK,EAAE,MAAM,aAAa,MAAM,aAAa,UAAU,KAAK,CAAC;AAAA,EACvE;AACA,SAAO;AACT;AAEA,SAAS,UAAU,OAAyB;AAC1C,MAAI,OAAO,UAAU,UAAW,QAAO;AACvC,MAAI,OAAO,UAAU,SAAU,QAAO,OAAO,UAAU,KAAK,IAAI,YAAY;AAC5E,MAAI,iBAAiB,KAAM,QAAO;AAClC,MAAI,OAAO,UAAU,UAAU;AAC7B,QAAI,sBAAsB,KAAK,KAAK,EAAG,QAAO;AAC9C,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,SAAS,YAAY,SAAqB,OAAe,QAAyC;AAChG,QAAM,OAAO,OAAO,IAAI,OAAK,KAAK,WAAW,SAAS,EAAE,IAAI,CAAC,IAAI,QAAQ,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,WAAW,KAAK,WAAW,EAAE;AAC3H,SAAO,gBAAgB,WAAW,SAAS,KAAK,CAAC;AAAA,EAAO,KAAK,KAAK,KAAK,CAAC;AAAA;AAC1E;AAEA,SAAS,QAAQ,SAAqB,MAAuB;AAC3D,QAAM,MAAmD;AAAA,IACvD,UAAU,EAAE,MAAM,QAAQ,SAAS,WAAW,MAAM,QAAQ,SAAS,WAAW,WAAW,eAAe,OAAO,QAAQ;AAAA,IACzH,OAAU,EAAE,MAAM,QAAQ,SAAS,UAAU,MAAM,UAAU,SAAS,cAAc,WAAW,YAAY,OAAO,OAAO;AAAA,IACzH,QAAU,EAAE,MAAM,QAAQ,SAAS,WAAW,MAAM,QAAQ,SAAS,WAAW,WAAW,QAAQ,OAAO,OAAO;AAAA,EACnH;AACA,SAAO,IAAI,OAAO,EAAE,IAAI;AAC1B;AAEA,SAAS,UACP,SACA,OACA,QACA,QACQ;AACR,QAAM,OAAO,OAAO,IAAI,OAAK,WAAW,SAAS,EAAE,IAAI,CAAC,EAAE,KAAK,IAAI;AACnE,QAAM,SAAS,OAAO,IAAI,OAAK,cAAc,SAAS,EAAE,MAAM,OAAO,EAAE,IAAI,CAAC,CAAC,EAAE,KAAK,IAAI;AACxF,SAAO,eAAe,WAAW,SAAS,KAAK,CAAC,KAAK,IAAI,aAAa,MAAM;AAC9E;AAEA,SAAS,cAAc,SAAqB,MAAe,OAAwB;AACjF,MAAI,UAAU,QAAQ,UAAU,OAAW,QAAO;AAClD,MAAI,SAAS,WAAW;AACtB,QAAI,YAAY,WAAW,YAAY,SAAU,QAAO,QAAQ,MAAM;AACtE,WAAO,QAAQ,SAAS;AAAA,EAC1B;AACA,MAAI,SAAS,aAAa,SAAS,OAAQ,QAAO,gBAAgB,KAAK;AACvE,MAAI,SAAS,aAAa;AACxB,UAAM,IAAI,iBAAiB,OAAO,MAAM,YAAY,IAAI,gBAAgB,KAAK;AAC7E,WAAO,YAAY,SAAS,CAAC;AAAA,EAC/B;AACA,MAAI,SAAS,SAAS;AACpB,WAAO,YAAY,SAAS,KAAK,UAAU,KAAK,CAAC;AAAA,EACnD;AACA,SAAO,YAAY,SAAS,gBAAgB,KAAK,CAAC;AACpD;AAEA,SAAS,gBAAgB,OAAwB;AAC/C,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI,OAAO,UAAU,YAAY,OAAO,UAAU,aAAa,OAAO,UAAU,UAAU;AACxF,WAAO,OAAO,KAAK;AAAA,EACrB;AACA,MAAI,UAAU,QAAQ,UAAU,OAAW,QAAO;AAClD,SAAO,KAAK,UAAU,KAAK;AAC7B;AAEA,SAAS,YAAY,SAAqB,GAAmB;AAE3D,QAAM,UAAU,EAAE,QAAQ,MAAM,IAAI;AACpC,SAAO,IAAI,OAAO;AACpB;AAEA,SAAS,WAAW,SAAqB,MAAsB;AAC7D,MAAI,YAAY,QAAS,QAAO,KAAK,KAAK,QAAQ,MAAM,IAAI,CAAC;AAC7D,SAAO,IAAI,KAAK,QAAQ,MAAM,IAAI,CAAC;AACrC;AAEA,SAAS,YAAY,SAA6B;AAChD,MAAI,YAAY,WAAY,QAAO;AACnC,MAAI,YAAY,QAAS,QAAO;AAChC,SAAO;AACT;AAEA,SAAS,UAAU,QAA0D;AAC3E,QAAM,MAA+B,CAAC;AACtC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,QAAI,QAAQ,SAAS,QAAQ,WAAW,QAAQ,SAAU;AAC1D,QAAI,GAAG,IAAI;AAAA,EACb;AACA,SAAO;AACT;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noy-db/as-sql",
|
|
3
|
-
"version": "0.3.0-pre.
|
|
3
|
+
"version": "0.3.0-pre.3",
|
|
4
4
|
"description": "SQL dump export for noy-db — decrypts records and emits dialect-aware CREATE TABLE + INSERT statements for postgres / mysql / sqlite. One-way migration helper. Gated by RFC #249 canExportPlaintext.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "vLannaAi <vicio@lanna.ai>",
|
|
@@ -32,11 +32,11 @@
|
|
|
32
32
|
"node": ">=22.0.0"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"@noy-db/hub": "0.3.0-pre.
|
|
35
|
+
"@noy-db/hub": "0.3.0-pre.3"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/node": "^22.0.0",
|
|
39
|
-
"@noy-db/hub": "0.3.0-pre.
|
|
39
|
+
"@noy-db/hub": "0.3.0-pre.3"
|
|
40
40
|
},
|
|
41
41
|
"keywords": [
|
|
42
42
|
"noy-db",
|