@noy-db/as-csv 0.6.0 → 0.7.0-pre.0

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 CHANGED
@@ -1,4 +1,6 @@
1
- import { VaultDiff, Vault } from '@noy-db/hub';
1
+ import { Vault } from '@noy-db/hub';
2
+ import { ImportPlan, NoydbFormat } from '@noy-db/hub/as';
3
+ export { ImportPolicy } from '@noy-db/hub/as';
2
4
 
3
5
  /**
4
6
  * **@noy-db/as-csv** — CSV plaintext export for noy-db.
@@ -9,7 +11,7 @@ import { VaultDiff, Vault } from '@noy-db/hub';
9
11
  * escape embedded quotes by doubling them).
10
12
  *
11
13
  * **Authorization.** Every call is gated by the invoking keyring's
12
- * `canExportPlaintext` capability — plaintext crossings of the
14
+ * `assertCanExport('plaintext', …)` gate — plaintext crossings of the
13
15
  * library boundary require an explicit grant from the vault owner
14
16
  *. The package calls `vault.assertCanExport('plaintext',
15
17
  * 'csv')` before decrypting anything.
@@ -23,45 +25,15 @@ import { VaultDiff, Vault } from '@noy-db/hub';
23
25
  * @packageDocumentation
24
26
  */
25
27
 
26
- interface AsCSVOptions {
28
+ interface AsCSVOptions extends AsCSVFormatOptions {
27
29
  /**
28
- * Collection to export. Must be in the caller's read ACL; otherwise
29
- * the resulting CSV will be empty (ACL-scoping applies at the
30
- * `exportStream` layer).
30
+ * Collections to export. Was a single `collection`; now plural and a READ
31
+ * concern, because hub owns the read — `vault.export(fmt, { collections })`.
31
32
  */
32
- readonly collection: string;
33
- /**
34
- * Explicit column list. When omitted, columns are inferred from
35
- * the union of keys across all records, in first-record-wins
36
- * order. Specify explicitly for deterministic exports or when the
37
- * source data has sparse fields.
38
- */
39
- readonly columns?: readonly string[];
40
- /**
41
- * Row separator. Default `'\n'` (LF). Use `'\r\n'` for Windows-
42
- * friendly output (Excel prefers CRLF but accepts LF).
43
- */
44
- readonly eol?: '\n' | '\r\n';
45
- /**
46
- * Apply the hub's `applyListProjection` read-projection before
47
- * serialising rows. `true` redacts only `classifiedFields` (mask /
48
- * omit / rider, per the field's preset). The object form additionally
49
- * redacts fields carrying a plain `fieldMeta` `sensitivity: 'pii' |
50
- * 'secret'` tag, per `sensitivity: 'omit' | 'mask'`.
51
- *
52
- * Caveat: `describe()` reflects the declarations of *this session's*
53
- * collection instance — redaction only takes effect when the
54
- * collection was opened (this call or earlier in the session) with
55
- * its `classifiedFields` / `fieldMeta` options. This is presentation-
56
- * layer redaction; it never affects what's on disk. Sealed handles
57
- * are unaffected either way — they always serialize as `'[sealed]'`,
58
- * so ciphertext never leaks regardless of this option.
59
- *
60
- * Rider companion fields (e.g. `pan_last4`) remain visible as their own
61
- * columns — they are safe write-time projections.
62
- */
63
- readonly redact?: boolean | {
64
- readonly sensitivity: 'omit' | 'mask';
33
+ readonly collections?: readonly string[];
34
+ /** Redact before encoding. Hub applies the projection; the format never sees it. */
35
+ readonly redact?: true | {
36
+ readonly sensitivity?: string;
65
37
  };
66
38
  }
67
39
  interface AsCSVWriteOptions extends AsCSVOptions {
@@ -77,55 +49,45 @@ interface AsCSVDownloadOptions extends AsCSVOptions {
77
49
  readonly filename?: string;
78
50
  }
79
51
  /**
80
- * Serialise a collection as a CSV string. Pure operation no side
81
- * effects beyond the authorization check + audit ledger write.
82
- */
83
- declare function toString(vault: Vault, options: AsCSVOptions): Promise<string>;
84
- /**
85
- * Browser download — wraps `toString()` in a `Blob` + triggers the
52
+ * Browser download wraps `vault.export(asCsv())` in a `Blob` + triggers the
86
53
  * browser's download prompt. Tier 2 egress per the pattern doc.
87
54
  *
88
55
  * Requires a browser-like environment with `URL.createObjectURL` and
89
56
  * `document.createElement`. No-op in headless environments; use
90
- * `toString()` there instead.
57
+ * `vault.export(asCsv())` there instead.
91
58
  */
92
- declare function download(vault: Vault, options: AsCSVDownloadOptions): Promise<void>;
59
+ declare function download(vault: Vault, options?: AsCSVDownloadOptions): Promise<void>;
93
60
  /**
94
- * Node file-write persists the CSV to the filesystem. Requires
95
- * explicit `acknowledgeRisks: true` because the plaintext file
96
- * outlives the current process (Tier 3 egress).
61
+ * Node file write. Still here, and not in hub, for a measured reason:
62
+ * `check-architecture`'s `hub-portable` rule forbids Node builtins in
63
+ * `hub/src` because hub must run in a browser, Worker, Deno and Bun. The gate,
64
+ * the read and the redaction all moved; these three lines are the part that
65
+ * legitimately differs per runtime.
97
66
  */
98
67
  declare function write(vault: Vault, path: string, options: AsCSVWriteOptions): Promise<void>;
99
- type ImportPolicy = 'merge' | 'replace' | 'insert-only';
100
- interface AsCSVImportOptions {
101
- /** Target collection. CSV has no native collection grouping. Required. */
102
- readonly collection: string;
103
- /**
104
- * Optional column type hints. When omitted, every cell is parsed as
105
- * a string. Number / boolean cells are auto-detected when the hint
106
- * matches: `'1'` `1`, `'true'` `true`, etc.
107
- */
108
- readonly columnTypes?: Record<string, 'string' | 'number' | 'boolean'>;
109
- /** Field on each record that carries its id. Default `'id'`. */
110
- readonly idKey?: string;
111
- /** Reconciliation policy. Default `'merge'`. */
112
- readonly policy?: ImportPolicy;
113
- }
114
- interface AsCSVImportPlan {
115
- readonly plan: VaultDiff;
116
- readonly policy: ImportPolicy;
117
- apply(): Promise<void>;
68
+
69
+ /** @deprecated Use `ImportPlan` from `@noy-db/hub/as` — this is now an alias. */
70
+ type AsCSVImportPlan = ImportPlan;
71
+ /** Options a CSV format instance carries. Read concerns live on `vault.export`. */
72
+ interface AsCSVFormatOptions {
73
+ /** Line ending. Default `'\n'`. */
74
+ readonly eol?: string;
75
+ /** Explicit column list. Omitted: inferred from the records. */
76
+ readonly columns?: readonly string[];
77
+ /** Per-column coercion on decode. */
78
+ readonly columnTypes?: Readonly<Record<string, 'string' | 'number' | 'boolean'>>;
118
79
  }
119
80
  /**
120
- * Parse RFC-4180 CSV into records and build an import plan for one
121
- * collection. The first row is the header; subsequent rows are
122
- * records. Quoted fields, embedded commas, embedded `""`, and
123
- * CRLF line endings all round-trip with `as-csv.toString()`.
81
+ * The CSV format the `as-*` port instance.
82
+ *
83
+ * ```ts
84
+ * const csv = await vault.export(asCsv(), { collections: ['invoices'] })
85
+ * const plan = await vault.import(asCsv(), csv, { collection: 'invoices' })
86
+ * await plan.apply()
87
+ * ```
124
88
  *
125
- * Cells are returned as strings unless overridden via `columnTypes`.
126
- * For the common case of numeric ids ("1001" → 1001), pass
127
- * `columnTypes: { id: 'number' }`.
89
+ * Requires `formatsStrategy: withFormats()` on `createNoydb`.
128
90
  */
129
- declare function fromString(vault: Vault, csv: string, options: AsCSVImportOptions): Promise<AsCSVImportPlan>;
91
+ declare function asCsv(options?: AsCSVFormatOptions): NoydbFormat<string>;
130
92
 
131
- export { type AsCSVDownloadOptions, type AsCSVImportOptions, type AsCSVImportPlan, type AsCSVOptions, type AsCSVWriteOptions, type ImportPolicy, download, fromString, toString, write };
93
+ export { type AsCSVDownloadOptions, type AsCSVFormatOptions, type AsCSVImportPlan, type AsCSVOptions, type AsCSVWriteOptions, asCsv, download, write };
package/dist/index.js CHANGED
@@ -1,37 +1,20 @@
1
1
  // src/index.ts
2
- import { applyListProjection, diffVault } from "@noy-db/hub";
3
- async function toString(vault, options) {
4
- vault.assertCanExport("plaintext", "csv");
2
+ function encodeCsv(chunks, options) {
5
3
  const eol = options.eol ?? "\n";
6
- const collection = options.collection;
7
- let records = [];
8
- for await (const chunk of vault.exportStream({ granularity: "collection" })) {
9
- if (chunk.collection === collection) {
10
- records.push(...chunk.records);
11
- break;
12
- }
13
- }
14
- if (options.redact !== void 0 && options.redact !== false) {
15
- const desc = vault.collection(collection).describe();
16
- const projectionOpts = options.redact === true ? void 0 : { sensitivity: options.redact.sensitivity };
17
- records = records.map((r) => applyListProjection(desc, r, projectionOpts));
18
- }
4
+ const records = chunks.flatMap((c) => c.records);
19
5
  const columns = options.columns ?? inferColumns(records);
20
- if (columns.length === 0) {
21
- return "";
22
- }
6
+ if (columns.length === 0) return "";
23
7
  const lines = [columns.map(escapeField).join(",")];
24
8
  for (const record of records) {
25
- const row = columns.map((c) => escapeField(record[c]));
26
- lines.push(row.join(","));
9
+ lines.push(columns.map((c) => escapeField(record[c])).join(","));
27
10
  }
28
11
  return lines.join(eol);
29
12
  }
30
- async function download(vault, options) {
31
- const csv = await toString(vault, options);
32
- const filename = options.filename ?? `${options.collection}.csv`;
33
- const blob = new Blob([csv], { type: "text/csv;charset=utf-8" });
34
- const url = URL.createObjectURL(blob);
13
+ async function download(vault, options = {}) {
14
+ const fmt = asCsv(options);
15
+ const csv = await vault.export(fmt, readOpts(options));
16
+ const filename = options.filename ?? `${options.collections?.[0] ?? "export"}.${fmt.extension}`;
17
+ const url = URL.createObjectURL(new Blob([csv], { type: fmt.mimeType }));
35
18
  const a = document.createElement("a");
36
19
  a.href = url;
37
20
  a.download = filename;
@@ -41,12 +24,18 @@ async function download(vault, options) {
41
24
  async function write(vault, path, options) {
42
25
  if (options.acknowledgeRisks !== true) {
43
26
  throw new Error(
44
- `as-csv.write: acknowledgeRisks: true is required for on-disk plaintext output. This call creates a persistent plaintext copy of your data outside noy-db's encrypted storage \u2014 see docs/patterns/as-exports.md \xA7"The three tiers of \\"plaintext out\\""`
27
+ "as-csv.write: acknowledgeRisks: true is required for on-disk plaintext output. See docs/patterns/as-exports.md - the three tiers of plaintext out."
45
28
  );
46
29
  }
47
- const csv = await toString(vault, options);
30
+ const csv = await vault.export(asCsv(options), readOpts(options));
48
31
  const { writeFile } = await import("fs/promises");
49
- await writeFile(path, csv, "utf-8");
32
+ await writeFile(path, csv, "utf8");
33
+ }
34
+ function readOpts(o) {
35
+ return {
36
+ ...o.collections ? { collections: o.collections } : {},
37
+ ...o.redact !== void 0 ? { redact: o.redact } : {}
38
+ };
50
39
  }
51
40
  function escapeField(value) {
52
41
  if (value === null || value === void 0) return "";
@@ -73,15 +62,10 @@ function inferColumns(records) {
73
62
  }
74
63
  return columns;
75
64
  }
76
- async function fromString(vault, csv, options) {
77
- vault.assertCanImport("plaintext", "csv");
78
- const policy = options.policy ?? "merge";
79
- const idKey = options.idKey ?? "id";
65
+ function decodeCsv(csv, options) {
80
66
  const types = options.columnTypes ?? {};
81
67
  const rows = parseCSV(csv);
82
- if (rows.length === 0) {
83
- return emptyPlan(vault, options.collection, policy, idKey);
84
- }
68
+ if (rows.length === 0) return [{ collection: "", records: [] }];
85
69
  const header = rows[0] ?? [];
86
70
  const records = [];
87
71
  for (let r = 1; r < rows.length; r++) {
@@ -90,43 +74,22 @@ async function fromString(vault, csv, options) {
90
74
  const record = {};
91
75
  for (let c = 0; c < header.length; c++) {
92
76
  const col = header[c] ?? "";
93
- const cell = row[c] ?? "";
94
- record[col] = coerceCell(cell, types[col]);
77
+ record[col] = coerceCell(row[c] ?? "", types[col]);
95
78
  }
96
79
  records.push(record);
97
80
  }
98
- const plan = await diffVault(vault, { [options.collection]: records }, {
99
- collections: [options.collection],
100
- idKey
101
- });
81
+ return [{ collection: "", records }];
82
+ }
83
+ function asCsv(options = {}) {
102
84
  return {
103
- plan,
104
- policy,
105
- async apply() {
106
- await vault.noydb.transaction((tx) => {
107
- const txVault = tx.vault(vault.name);
108
- for (const entry of plan.added) {
109
- txVault.collection(entry.collection).put(entry.id, entry.record, { reason: "import:csv" });
110
- }
111
- if (policy !== "insert-only") {
112
- for (const entry of plan.modified) {
113
- txVault.collection(entry.collection).put(entry.id, entry.record, { reason: "import:csv" });
114
- }
115
- }
116
- if (policy === "replace") {
117
- for (const entry of plan.deleted) {
118
- txVault.collection(entry.collection).delete(entry.id);
119
- }
120
- }
121
- });
122
- }
85
+ id: "csv",
86
+ extension: "csv",
87
+ mimeType: "text/csv;charset=utf-8",
88
+ tier: "plaintext",
89
+ encode: (chunks) => encodeCsv(chunks, options),
90
+ decode: (input) => decodeCsv(input, options)
123
91
  };
124
92
  }
125
- async function emptyPlan(vault, collection, policy, idKey) {
126
- const plan = await diffVault(vault, { [collection]: [] }, { collections: [collection], idKey });
127
- return { plan, policy, async apply() {
128
- } };
129
- }
130
93
  function coerceCell(cell, type) {
131
94
  if (type === "number") {
132
95
  if (cell === "") return void 0;
@@ -200,9 +163,8 @@ function parseCSV(input) {
200
163
  return rows;
201
164
  }
202
165
  export {
166
+ asCsv,
203
167
  download,
204
- fromString,
205
- toString,
206
168
  write
207
169
  };
208
170
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * **@noy-db/as-csv** — CSV plaintext export for noy-db.\n *\n * Decrypts records from a single collection and formats them as\n * comma-separated values suitable for spreadsheet import. RFC 4180\n * escaping (quote fields containing commas, quotes, or newlines;\n * escape embedded quotes by doubling them).\n *\n * **Authorization.** Every call is gated by the invoking keyring's\n * `canExportPlaintext` capability — plaintext crossings of the\n * library boundary require an explicit grant from the vault owner\n *. The package calls `vault.assertCanExport('plaintext',\n * 'csv')` before decrypting anything.\n *\n * **Scope.** One collection per call. Multi-collection + attachments\n * → use `@noy-db/as-zip`. Structured JSON → `@noy-db/as-json`.\n * Excel with dictionary-label expansion → `@noy-db/as-xlsx`.\n *\n * See [`docs/patterns/as-exports.md`](https://github.com/vLannaAi/noy-db/blob/main/docs/patterns/as-exports.md).\n *\n * @packageDocumentation\n */\n\nimport { applyListProjection, diffVault, type Vault, type CollectionDescription, type VaultDiff } from '@noy-db/hub'\n\nexport interface AsCSVOptions {\n /**\n * Collection to export. Must be in the caller's read ACL; otherwise\n * the resulting CSV will be empty (ACL-scoping applies at the\n * `exportStream` layer).\n */\n readonly collection: string\n\n /**\n * Explicit column list. When omitted, columns are inferred from\n * the union of keys across all records, in first-record-wins\n * order. Specify explicitly for deterministic exports or when the\n * source data has sparse fields.\n */\n readonly columns?: readonly string[]\n\n /**\n * Row separator. Default `'\\n'` (LF). Use `'\\r\\n'` for Windows-\n * friendly output (Excel prefers CRLF but accepts LF).\n */\n readonly eol?: '\\n' | '\\r\\n'\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 own\n * columns — they are safe write-time projections.\n */\n readonly redact?: boolean | { readonly sensitivity: 'omit' | 'mask' }\n}\n\nexport interface AsCSVWriteOptions extends AsCSVOptions {\n /**\n * Required for Node file-write calls — consumer acknowledgement\n * that plaintext bytes will persist on disk past the current\n * process lifetime (Tier 3 risk per `docs/patterns/as-exports.md`).\n */\n readonly acknowledgeRisks: true\n}\n\nexport interface AsCSVDownloadOptions extends AsCSVOptions {\n /** Filename offered to the browser. Default `'<collection>.csv'`. */\n readonly filename?: string\n}\n\n/**\n * Serialise a collection as a CSV string. Pure operation — no side\n * effects beyond the authorization check + audit ledger write.\n */\nexport async function toString(vault: Vault, options: AsCSVOptions): Promise<string> {\n vault.assertCanExport('plaintext', 'csv')\n\n const eol = options.eol ?? '\\n'\n const collection = options.collection\n\n // Pull the one collection via exportStream in collection granularity.\n let records: unknown[] = []\n for await (const chunk of vault.exportStream({ granularity: 'collection' })) {\n if (chunk.collection === collection) {\n records.push(...chunk.records)\n break\n }\n }\n\n if (options.redact !== undefined && options.redact !== false) {\n const desc: CollectionDescription = vault.collection(collection).describe()\n const projectionOpts = options.redact === true ? undefined\n : { sensitivity: options.redact.sensitivity }\n records = records.map((r) => applyListProjection(desc, r as Record<string, unknown>, projectionOpts))\n }\n\n // Determine columns.\n const columns = options.columns ?? inferColumns(records)\n if (columns.length === 0) {\n // Empty collection or no accessible records — emit header-only csv.\n return ''\n }\n\n // Build header + rows\n const lines: string[] = [columns.map(escapeField).join(',')]\n for (const record of records) {\n const row = columns.map(c => escapeField((record as Record<string, unknown>)[c]))\n lines.push(row.join(','))\n }\n return lines.join(eol)\n}\n\n/**\n * Browser download — wraps `toString()` in a `Blob` + triggers the\n * browser's download prompt. Tier 2 egress per the pattern doc.\n *\n * Requires a browser-like environment with `URL.createObjectURL` and\n * `document.createElement`. No-op in headless environments; use\n * `toString()` there instead.\n */\nexport async function download(vault: Vault, options: AsCSVDownloadOptions): Promise<void> {\n const csv = await toString(vault, options)\n const filename = options.filename ?? `${options.collection}.csv`\n const blob = new Blob([csv], { type: 'text/csv;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\n/**\n * Node file-write — persists the CSV to the filesystem. Requires\n * explicit `acknowledgeRisks: true` because the plaintext file\n * outlives the current process (Tier 3 egress).\n */\nexport async function write(\n vault: Vault,\n path: string,\n options: AsCSVWriteOptions,\n): Promise<void> {\n if (options.acknowledgeRisks !== true) {\n throw new Error(\n 'as-csv.write: acknowledgeRisks: true is required for on-disk plaintext output. ' +\n 'This call creates a persistent plaintext copy of your data outside noy-db\\'s ' +\n 'encrypted storage — see docs/patterns/as-exports.md §\"The three tiers of \\\\\"plaintext out\\\\\"\"',\n )\n }\n const csv = await toString(vault, options)\n // Defer the node:fs import so this package remains browser-safe.\n const { writeFile } = await import('node:fs/promises')\n await writeFile(path, csv, 'utf-8')\n}\n\n// ── CSV formatting internals ───────────────────────────────────────────\n\n/**\n * RFC 4180 escaping: wrap a field in double quotes if it contains\n * comma, double quote, CR, or LF. Embedded double quotes become `\"\"`.\n * Other values stringify naturally.\n */\nfunction escapeField(value: unknown): string {\n if (value === null || value === undefined) return ''\n if (typeof value === 'number' || typeof value === 'boolean') return String(value)\n if (value instanceof Date) return value.toISOString()\n const s =\n typeof value === 'string' ? value : JSON.stringify(value)\n if (/[\",\\r\\n]/.test(s)) {\n return `\"${s.replace(/\"/g, '\"\"')}\"`\n }\n return s\n}\n\n/**\n * Derive column list from the records array, preserving first-\n * encountered-wins ordering. An explicit `options.columns` bypasses\n * this.\n */\nfunction inferColumns(records: readonly unknown[]): string[] {\n const columns: string[] = []\n const seen = new Set<string>()\n for (const r of records) {\n if (r && typeof r === 'object') {\n for (const key of Object.keys(r)) {\n if (!seen.has(key)) {\n seen.add(key)\n columns.push(key)\n }\n }\n }\n }\n return columns\n}\n\n// ─── Reader ─────────────────────────────────────────────\n\nexport type ImportPolicy = 'merge' | 'replace' | 'insert-only'\n\nexport interface AsCSVImportOptions {\n /** Target collection. CSV has no native collection grouping. Required. */\n readonly collection: string\n /**\n * Optional column type hints. When omitted, every cell is parsed as\n * a string. Number / boolean cells are auto-detected when the hint\n * matches: `'1'` → `1`, `'true'` → `true`, etc.\n */\n readonly columnTypes?: Record<string, 'string' | 'number' | 'boolean'>\n /** Field on each record that carries its id. Default `'id'`. */\n readonly idKey?: string\n /** Reconciliation policy. Default `'merge'`. */\n readonly policy?: ImportPolicy\n}\n\nexport interface AsCSVImportPlan {\n readonly plan: VaultDiff\n readonly policy: ImportPolicy\n apply(): Promise<void>\n}\n\n/**\n * Parse RFC-4180 CSV into records and build an import plan for one\n * collection. The first row is the header; subsequent rows are\n * records. Quoted fields, embedded commas, embedded `\"\"`, and\n * CRLF line endings all round-trip with `as-csv.toString()`.\n *\n * Cells are returned as strings unless overridden via `columnTypes`.\n * For the common case of numeric ids (\"1001\" → 1001), pass\n * `columnTypes: { id: 'number' }`.\n */\nexport async function fromString(\n vault: Vault,\n csv: string,\n options: AsCSVImportOptions,\n): Promise<AsCSVImportPlan> {\n vault.assertCanImport('plaintext', 'csv')\n const policy: ImportPolicy = options.policy ?? 'merge'\n const idKey = options.idKey ?? 'id'\n const types = options.columnTypes ?? {}\n\n const rows = parseCSV(csv)\n if (rows.length === 0) {\n return emptyPlan(vault, options.collection, policy, idKey)\n }\n const header = rows[0] ?? []\n const records: Record<string, unknown>[] = []\n for (let r = 1; r < rows.length; r++) {\n const row = rows[r]!\n if (row.length === 1 && row[0] === '') continue // ignore blank lines\n const record: Record<string, unknown> = {}\n for (let c = 0; c < header.length; c++) {\n const col = header[c] ?? ''\n const cell = row[c] ?? ''\n record[col] = coerceCell(cell, types[col])\n }\n records.push(record)\n }\n\n const plan = await diffVault(vault, { [options.collection]: records }, {\n collections: [options.collection],\n idKey,\n })\n\n return {\n plan,\n policy,\n async apply(): Promise<void> {\n // Routes through the transactionsStrategy seam — vault.noydb.transaction()\n // throws a clear error pointing at withTransactions() when the\n // strategy is not opted in. Atomicity ensures a partial failure\n // rolls back every executed put.\n await vault.noydb.transaction((tx) => {\n const txVault = tx.vault(vault.name)\n for (const entry of plan.added) {\n txVault.collection(entry.collection).put(entry.id, entry.record, { reason: 'import:csv' })\n }\n if (policy !== 'insert-only') {\n for (const entry of plan.modified) {\n txVault.collection(entry.collection).put(entry.id, entry.record, { reason: 'import:csv' })\n }\n }\n if (policy === 'replace') {\n for (const entry of plan.deleted) {\n txVault.collection(entry.collection).delete(entry.id)\n }\n }\n })\n },\n }\n}\n\nasync function emptyPlan(\n vault: Vault,\n collection: string,\n policy: ImportPolicy,\n idKey: string,\n): Promise<AsCSVImportPlan> {\n const plan = await diffVault(vault, { [collection]: [] }, { collections: [collection], idKey })\n return { plan, policy, async apply() { /* nothing to do */ } }\n}\n\nfunction coerceCell(cell: string, type?: 'string' | 'number' | 'boolean'): unknown {\n if (type === 'number') {\n if (cell === '') return undefined\n const n = Number(cell)\n return Number.isFinite(n) ? n : cell\n }\n if (type === 'boolean') {\n if (cell === 'true') return true\n if (cell === 'false') return false\n return cell\n }\n return cell\n}\n\n/**\n * Minimal RFC-4180 CSV parser. Recognises:\n * - Comma-separated fields\n * - Quoted fields with embedded commas, newlines, and `\"\"` escapes\n * - Both CRLF and LF row endings\n *\n * Returns a 2D string array. The caller maps the first row to a\n * header and the rest to records.\n */\nfunction parseCSV(input: string): string[][] {\n const rows: string[][] = []\n let row: string[] = []\n let field = ''\n let inQuotes = false\n let i = 0\n\n while (i < input.length) {\n const ch = input[i]!\n if (inQuotes) {\n if (ch === '\"') {\n if (input[i + 1] === '\"') {\n field += '\"'\n i += 2\n continue\n }\n inQuotes = false\n i++\n continue\n }\n field += ch\n i++\n continue\n }\n if (ch === '\"') {\n inQuotes = true\n i++\n continue\n }\n if (ch === ',') {\n row.push(field)\n field = ''\n i++\n continue\n }\n if (ch === '\\r' && input[i + 1] === '\\n') {\n row.push(field)\n rows.push(row)\n row = []\n field = ''\n i += 2\n continue\n }\n if (ch === '\\n' || ch === '\\r') {\n row.push(field)\n rows.push(row)\n row = []\n field = ''\n i++\n continue\n }\n field += ch\n i++\n }\n\n // Final field / row.\n if (field !== '' || row.length > 0) {\n row.push(field)\n rows.push(row)\n }\n\n return rows\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * **@noy-db/as-csv** — CSV plaintext export for noy-db.\n *\n * Decrypts records from a single collection and formats them as\n * comma-separated values suitable for spreadsheet import. RFC 4180\n * escaping (quote fields containing commas, quotes, or newlines;\n * escape embedded quotes by doubling them).\n *\n * **Authorization.** Every call is gated by the invoking keyring's\n * `assertCanExport('plaintext', …)` gate — plaintext crossings of the\n * library boundary require an explicit grant from the vault owner\n *. The package calls `vault.assertCanExport('plaintext',\n * 'csv')` before decrypting anything.\n *\n * **Scope.** One collection per call. Multi-collection + attachments\n * → use `@noy-db/as-zip`. Structured JSON → `@noy-db/as-json`.\n * Excel with dictionary-label expansion → `@noy-db/as-xlsx`.\n *\n * See [`docs/patterns/as-exports.md`](https://github.com/vLannaAi/noy-db/blob/main/docs/patterns/as-exports.md).\n *\n * @packageDocumentation\n */\n\nimport type { Vault } from '@noy-db/hub'\n\n\nexport interface AsCSVOptions extends AsCSVFormatOptions {\n /**\n * Collections to export. Was a single `collection`; now plural and a READ\n * concern, because hub owns the read — `vault.export(fmt, { collections })`.\n */\n readonly collections?: readonly string[]\n /** Redact before encoding. Hub applies the projection; the format never sees it. */\n readonly redact?: true | { readonly sensitivity?: string }\n}\n\nexport interface AsCSVWriteOptions extends AsCSVOptions {\n /**\n * Required for Node file-write calls — consumer acknowledgement\n * that plaintext bytes will persist on disk past the current\n * process lifetime (Tier 3 risk per `docs/patterns/as-exports.md`).\n */\n readonly acknowledgeRisks: true\n}\n\nexport interface AsCSVDownloadOptions extends AsCSVOptions {\n /** Filename offered to the browser. Default `'<collection>.csv'`. */\n readonly filename?: string\n}\n\n/**\n * Serialise a collection as a CSV string. Pure operation — no side\n * effects beyond the authorization check + audit ledger write.\n */\n/**\n * The pure encoder. Receives records — already gated and already redacted by\n * hub — and returns CSV. It has no vault, which is what makes the export gate\n * unskippable rather than merely checked (ADR 0004).\n */\nfunction encodeCsv(chunks: readonly ExportChunk[], options: AsCSVFormatOptions): string {\n const eol = options.eol ?? '\\n'\n const records: unknown[] = chunks.flatMap((c) => c.records)\n const columns = options.columns ?? inferColumns(records)\n if (columns.length === 0) return ''\n const lines: string[] = [columns.map(escapeField).join(',')]\n for (const record of records) {\n lines.push(columns.map((c) => escapeField((record as Record<string, unknown>)[c])).join(','))\n }\n return lines.join(eol)\n}\n\n/**\n * Browser download — wraps `vault.export(asCsv())` in a `Blob` + triggers the\n * browser's download prompt. Tier 2 egress per the pattern doc.\n *\n * Requires a browser-like environment with `URL.createObjectURL` and\n * `document.createElement`. No-op in headless environments; use\n * `vault.export(asCsv())` there instead.\n */\nexport async function download(vault: Vault, options: AsCSVDownloadOptions = {}): Promise<void> {\n const fmt = asCsv(options)\n const csv = await vault.export(fmt, readOpts(options))\n const filename = options.filename ?? `${options.collections?.[0] ?? 'export'}.${fmt.extension}`\n const url = URL.createObjectURL(new Blob([csv], { type: fmt.mimeType }))\n const a = document.createElement('a')\n a.href = url\n a.download = filename\n a.click()\n URL.revokeObjectURL(url)\n}\n\n/**\n * Node file write. Still here, and not in hub, for a measured reason:\n * `check-architecture`'s `hub-portable` rule forbids Node builtins in\n * `hub/src` because hub must run in a browser, Worker, Deno and Bun. The gate,\n * the read and the redaction all moved; these three lines are the part that\n * legitimately differs per runtime.\n */\nexport async function write(\n vault: Vault,\n path: string,\n options: AsCSVWriteOptions,\n): Promise<void> {\n if (options.acknowledgeRisks !== true) {\n throw new Error(\n 'as-csv.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 csv = await vault.export(asCsv(options), readOpts(options))\n const { writeFile } = await import('node:fs/promises')\n await writeFile(path, csv, 'utf8')\n}\n\n/** Only the keys that are actually set — `exactOptionalPropertyTypes` is on. */\nfunction readOpts(o: AsCSVOptions): FormatExportOptions {\n return {\n ...(o.collections ? { collections: o.collections } : {}),\n ...(o.redact !== undefined ? { redact: o.redact } : {}),\n }\n}\n\nfunction escapeField(value: unknown): string {\n if (value === null || value === undefined) return ''\n if (typeof value === 'number' || typeof value === 'boolean') return String(value)\n if (value instanceof Date) return value.toISOString()\n const s =\n typeof value === 'string' ? value : JSON.stringify(value)\n if (/[\",\\r\\n]/.test(s)) {\n return `\"${s.replace(/\"/g, '\"\"')}\"`\n }\n return s\n}\n\n/**\n * Derive column list from the records array, preserving first-\n * encountered-wins ordering. An explicit `options.columns` bypasses\n * this.\n */\nfunction inferColumns(records: readonly unknown[]): string[] {\n const columns: string[] = []\n const seen = new Set<string>()\n for (const r of records) {\n if (r && typeof r === 'object') {\n for (const key of Object.keys(r)) {\n if (!seen.has(key)) {\n seen.add(key)\n columns.push(key)\n }\n }\n }\n }\n return columns\n}\n\n// ─── Reader ─────────────────────────────────────────────\n\n// Hub-owned as of 0.7 (ADR 0004). This line replaced a local declaration that\n// existed identically in six as-* packages, with nothing comparing them.\nimport type {\n ImportPolicy,\n ImportPlan,\n NoydbFormat,\n DecodedChunk,\n ExportChunk,\n FormatExportOptions,\n} from '@noy-db/hub/as'\nexport type { ImportPolicy }\n\n/** @deprecated Use `ImportPlan` from `@noy-db/hub/as` — this is now an alias. */\nexport type AsCSVImportPlan = ImportPlan\n\n/**\n * Parse RFC-4180 CSV into records and build an import plan for one\n * collection. The first row is the header; subsequent rows are\n * records. Quoted fields, embedded commas, embedded `\"\"`, and\n * CRLF line endings all round-trip through `asCsv()`.\n *\n * Cells are returned as strings unless overridden via `columnTypes`.\n * For the common case of numeric ids (\"1001\" → 1001), pass\n * `columnTypes: { id: 'number' }`.\n */\n/**\n * The pure decoder. Bytes in, records out — no vault, no gate, no diff. Hub\n * gates, plans against the live vault, and owns `apply()`.\n */\nfunction decodeCsv(csv: string, options: AsCSVFormatOptions): readonly DecodedChunk[] {\n const types = options.columnTypes ?? {}\n const rows = parseCSV(csv)\n if (rows.length === 0) return [{ collection: '', records: [] }]\n const header = rows[0] ?? []\n const records: Record<string, unknown>[] = []\n for (let r = 1; r < rows.length; r++) {\n const row = rows[r]!\n if (row.length === 1 && row[0] === '') continue\n const record: Record<string, unknown> = {}\n for (let c = 0; c < header.length; c++) {\n const col = header[c] ?? ''\n record[col] = coerceCell(row[c] ?? '', types[col])\n }\n records.push(record)\n }\n // No collection name: CSV carries none. Hub resolves it from\n // `vault.import(fmt, csv, { collection })`.\n return [{ collection: '', records }]\n}\n\n/** Options a CSV format instance carries. Read concerns live on `vault.export`. */\nexport interface AsCSVFormatOptions {\n /** Line ending. Default `'\\n'`. */\n readonly eol?: string\n /** Explicit column list. Omitted: inferred from the records. */\n readonly columns?: readonly string[]\n /** Per-column coercion on decode. */\n readonly columnTypes?: Readonly<Record<string, 'string' | 'number' | 'boolean'>>\n}\n\n/**\n * The CSV format — the `as-*` port instance.\n *\n * ```ts\n * const csv = await vault.export(asCsv(), { collections: ['invoices'] })\n * const plan = await vault.import(asCsv(), csv, { collection: 'invoices' })\n * await plan.apply()\n * ```\n *\n * Requires `formatsStrategy: withFormats()` on `createNoydb`.\n */\nexport function asCsv(options: AsCSVFormatOptions = {}): NoydbFormat<string> {\n return {\n id: 'csv',\n extension: 'csv',\n mimeType: 'text/csv;charset=utf-8',\n tier: 'plaintext',\n encode: (chunks) => encodeCsv(chunks, options),\n decode: (input) => decodeCsv(input, options),\n }\n}\n\nfunction coerceCell(cell: string, type?: 'string' | 'number' | 'boolean'): unknown {\n if (type === 'number') {\n if (cell === '') return undefined\n const n = Number(cell)\n return Number.isFinite(n) ? n : cell\n }\n if (type === 'boolean') {\n if (cell === 'true') return true\n if (cell === 'false') return false\n return cell\n }\n return cell\n}\n\n/**\n * Minimal RFC-4180 CSV parser. Recognises:\n * - Comma-separated fields\n * - Quoted fields with embedded commas, newlines, and `\"\"` escapes\n * - Both CRLF and LF row endings\n *\n * Returns a 2D string array. The caller maps the first row to a\n * header and the rest to records.\n */\nfunction parseCSV(input: string): string[][] {\n const rows: string[][] = []\n let row: string[] = []\n let field = ''\n let inQuotes = false\n let i = 0\n\n while (i < input.length) {\n const ch = input[i]!\n if (inQuotes) {\n if (ch === '\"') {\n if (input[i + 1] === '\"') {\n field += '\"'\n i += 2\n continue\n }\n inQuotes = false\n i++\n continue\n }\n field += ch\n i++\n continue\n }\n if (ch === '\"') {\n inQuotes = true\n i++\n continue\n }\n if (ch === ',') {\n row.push(field)\n field = ''\n i++\n continue\n }\n if (ch === '\\r' && input[i + 1] === '\\n') {\n row.push(field)\n rows.push(row)\n row = []\n field = ''\n i += 2\n continue\n }\n if (ch === '\\n' || ch === '\\r') {\n row.push(field)\n rows.push(row)\n row = []\n field = ''\n i++\n continue\n }\n field += ch\n i++\n }\n\n // Final field / row.\n if (field !== '' || row.length > 0) {\n row.push(field)\n rows.push(row)\n }\n\n return rows\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@noy-db/as-csv",
3
- "version": "0.6.0",
4
- "description": "CSV plaintext export for noy-db — decrypts records and formats as comma-separated values. Gated by the RFC #249 `canExportPlaintext` capability bit; writes an audit-ledger entry on every call. Part of the @noy-db/as-* portable-artefact family (plaintext tier).",
3
+ "version": "0.7.0-pre.0",
4
+ "description": "CSV plaintext export for noy-db — decrypts records and formats as comma-separated values. Gated by the RFC #249 `assertCanExport('plaintext', …)` gate; writes an audit-ledger entry on every call. Part of the @noy-db/as-* portable-artefact family (plaintext tier).",
5
5
  "license": "MIT",
6
6
  "author": "vLannaAi <vicio@lanna.ai>",
7
7
  "homepage": "https://github.com/vLannaAi/noy-db/tree/main/packages/as-csv#readme",
@@ -32,11 +32,11 @@
32
32
  "node": ">=22.0.0"
33
33
  },
34
34
  "peerDependencies": {
35
- "@noy-db/hub": "0.6.0"
35
+ "@noy-db/hub": "0.7.0-pre.0"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/node": "^22.0.0",
39
- "@noy-db/hub": "0.6.0"
39
+ "@noy-db/hub": "0.7.0-pre.0"
40
40
  },
41
41
  "keywords": [
42
42
  "noy-db",