@happyvertical/smrt-spreadsheets 0.0.0-stage → 0.61.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.
Files changed (41) hide show
  1. package/AGENTS.md +75 -0
  2. package/CLAUDE.md +1 -0
  3. package/LICENSE +7 -0
  4. package/README.md +112 -2
  5. package/dist/chunks/plan-BAV2jEzl.js +429 -0
  6. package/dist/chunks/plan-BAV2jEzl.js.map +1 -0
  7. package/dist/import/checks.d.ts +52 -0
  8. package/dist/import/checks.d.ts.map +1 -0
  9. package/dist/import/coerce.d.ts +27 -0
  10. package/dist/import/coerce.d.ts.map +1 -0
  11. package/dist/import/errors.d.ts +16 -0
  12. package/dist/import/errors.d.ts.map +1 -0
  13. package/dist/import/evaluate.d.ts +45 -0
  14. package/dist/import/evaluate.d.ts.map +1 -0
  15. package/dist/import/findings.d.ts +33 -0
  16. package/dist/import/findings.d.ts.map +1 -0
  17. package/dist/import/index.d.ts +11 -0
  18. package/dist/import/index.d.ts.map +1 -0
  19. package/dist/import/index.js +975 -0
  20. package/dist/import/index.js.map +1 -0
  21. package/dist/import/limits.d.ts +19 -0
  22. package/dist/import/limits.d.ts.map +1 -0
  23. package/dist/import/load.d.ts +35 -0
  24. package/dist/import/load.d.ts.map +1 -0
  25. package/dist/import/plan.d.ts +81 -0
  26. package/dist/import/plan.d.ts.map +1 -0
  27. package/dist/import/run.d.ts +34 -0
  28. package/dist/import/run.d.ts.map +1 -0
  29. package/dist/import/target.d.ts +38 -0
  30. package/dist/import/target.d.ts.map +1 -0
  31. package/dist/index.d.ts +2 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +0 -0
  34. package/dist/manifest.json +9 -0
  35. package/dist/smrt-knowledge.json +57 -0
  36. package/dist/smrt-knowledge.okf.md +46 -0
  37. package/dist/testing.d.ts +21 -0
  38. package/dist/testing.d.ts.map +1 -0
  39. package/dist/testing.js +198 -0
  40. package/dist/testing.js.map +1 -0
  41. package/package.json +61 -3
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["column","cell","coerced"],"sources":["../../src/import/coerce.ts","../../src/import/limits.ts","../../src/import/evaluate.ts","../../src/import/findings.ts","../../src/import/checks.ts","../../src/import/load.ts","../../src/import/run.ts"],"sourcesContent":["import type { Cell } from '@happyvertical/spreadsheets';\nimport type { PlanField } from './plan.js';\n\nexport type CoerceFailureCode =\n | 'cell-error'\n | 'not-number'\n | 'not-integer'\n | 'unsafe-integer'\n | 'unsafe-number'\n | 'not-boolean'\n | 'not-date'\n | 'not-money'\n | 'not-choice'\n | 'not-coercible';\n\nexport type CoerceResult =\n | { ok: true; value: string | number | boolean | null }\n | { ok: false; code: CoerceFailureCode; detail: string };\n\nconst EMPTY: Cell = { value: null, type: 'empty' };\nconst DECIMAL = /^[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)$/;\nconst ISO_DATE =\n /^(\\d{4})-(\\d{2})-(\\d{2})(?:[T ]\\d{2}:\\d{2}(?::\\d{2}(?:\\.\\d+)?)?(?:Z|[+-]\\d{2}:?\\d{2})?)?$/;\n\n/** The shortest decimal that round-trips, never in exponent form. */\nexport function plainDecimal(value: number): string {\n const text = String(value);\n const match = /^(-?)(\\d+)(?:\\.(\\d+))?e([+-]\\d+)$/.exec(text);\n if (!match) return text;\n const [, sign, whole, fraction = '', exp] = match;\n const digits = whole + fraction;\n const point = whole.length + Number(exp);\n if (point <= 0) return `${sign}0.${'0'.repeat(-point)}${digits}`;\n if (point >= digits.length)\n return sign + digits + '0'.repeat(point - digits.length);\n return `${sign}${digits.slice(0, point)}.${digits.slice(point)}`;\n}\n\nfunction failure(code: CoerceFailureCode, detail: string): CoerceResult {\n return { ok: false, code, detail };\n}\n\nexport function isoDate(date: Date): string {\n return date.toISOString().slice(0, 10);\n}\n\n/** A short, bounded rendering of a cell for findings. */\nexport function describeCell(cell: Cell | undefined): string {\n if (!cell || cell.type === 'empty' || cell.value === null) {\n return cell?.type === 'error' ? (cell.error ?? '#VALUE!') : '';\n }\n const value = cell.value;\n const text =\n value instanceof Date\n ? value.toISOString()\n : typeof value === 'number'\n ? (cell.originalText ?? plainDecimal(value))\n : String(value);\n return text.length > 80 ? `${text.slice(0, 77)}...` : text;\n}\n\nexport function isBlankCell(cell: Cell | undefined, trim = true): boolean {\n if (!cell || cell.type === 'empty' || cell.value === null) {\n return cell?.type !== 'error';\n }\n if (cell.type === 'error') return false;\n if (typeof cell.value === 'string') {\n return trim ? cell.value.trim().length === 0 : cell.value.length === 0;\n }\n return false;\n}\n\n/** The text of a number cell: what the author typed if the source kept it. */\nfunction numberText(cell: Cell): string {\n return cell.originalText ?? plainDecimal(cell.value as number);\n}\n\n/** Digits of a number-like string, ignoring sign and the decimal point. */\nfunction digitCount(text: string): number {\n return text.replace(/[^0-9]/g, '').length;\n}\n\n/** `007` and `00.5` are codes, not numbers; `0`, `0.5` and `-0.5` are numbers. */\nexport function hasLeadingZeros(text: string): boolean {\n return /^[+-]?0\\d/.test(text.trim());\n}\n\nfunction decimalOf(\n cell: Cell,\n code: 'not-number' | 'not-integer' | 'not-money',\n): { ok: true; value: number } | { ok: false; result: CoerceResult } {\n if (cell.type === 'number' && typeof cell.value === 'number') {\n if (!Number.isFinite(cell.value)) {\n return { ok: false, result: failure(code, 'not a finite number') };\n }\n return { ok: true, value: cell.value };\n }\n if (cell.type === 'text' && typeof cell.value === 'string') {\n const text = cell.value.trim();\n if (!DECIMAL.test(text)) {\n return { ok: false, result: failure(code, `\"${text}\" is not a number`) };\n }\n if (hasLeadingZeros(text)) {\n return {\n ok: false,\n result: failure(code, `\"${text}\" has leading zeros; keep it as text`),\n };\n }\n if (digitCount(text) >= 16) {\n return {\n ok: false,\n result: failure(\n code === 'not-integer' ? 'unsafe-integer' : 'unsafe-number',\n `\"${text}\" has too many digits to hold as a number`,\n ),\n };\n }\n const value = Number(text);\n if (!Number.isFinite(value)) {\n return { ok: false, result: failure(code, 'not a finite number') };\n }\n return { ok: true, value };\n }\n return {\n ok: false,\n result: failure(code, `a ${cell.type} cell is not a number`),\n };\n}\n\nconst TRUE_WORDS = new Set(['true', 'yes', 'y', '1']);\nconst FALSE_WORDS = new Set(['false', 'no', 'n', '0']);\n\nfunction validDate(year: number, month: number, day: number): boolean {\n const probe = new Date(Date.UTC(year, month - 1, day));\n return (\n probe.getUTCFullYear() === year &&\n probe.getUTCMonth() === month - 1 &&\n probe.getUTCDate() === day\n );\n}\n\n/**\n * Turns one workbook cell into the value a plan field stores, or refuses with\n * a code. Nothing is guessed: a value that does not fit is refused, never\n * rounded, truncated or reinterpreted. A caller reading many cells passes the\n * field's choices as a set, so matching stays constant-time per cell.\n */\nexport function coerceCell(\n cell: Cell | undefined,\n field: PlanField,\n choiceSet?: ReadonlySet<string>,\n): CoerceResult {\n const c = cell ?? EMPTY;\n if (c.type === 'error') {\n return failure('cell-error', c.error ?? '#VALUE!');\n }\n if (field.type === 'reference') {\n return failure(\n 'not-coercible',\n 'reference values resolve through a lookup',\n );\n }\n if (isBlankCell(c, field.type !== 'text')) return { ok: true, value: null };\n\n switch (field.type) {\n case 'text': {\n if (typeof c.value === 'string') return { ok: true, value: c.value };\n if (c.type === 'number') return { ok: true, value: numberText(c) };\n if (c.value instanceof Date) {\n return { ok: true, value: isoDate(c.value) };\n }\n return { ok: true, value: String(c.value) };\n }\n case 'number': {\n const parsed = decimalOf(c, 'not-number');\n return parsed.ok ? { ok: true, value: parsed.value + 0 } : parsed.result;\n }\n case 'integer': {\n const parsed = decimalOf(c, 'not-integer');\n if (!parsed.ok) return parsed.result;\n if (!Number.isInteger(parsed.value)) {\n return failure(\n 'not-integer',\n `${plainDecimal(parsed.value)} is not whole`,\n );\n }\n if (!Number.isSafeInteger(parsed.value)) {\n return failure('unsafe-integer', 'beyond the safe integer range');\n }\n return { ok: true, value: parsed.value + 0 };\n }\n case 'boolean': {\n if (c.type === 'boolean') return { ok: true, value: c.value as boolean };\n const word =\n c.type === 'number' && typeof c.value === 'number'\n ? String(c.value)\n : typeof c.value === 'string'\n ? c.value.trim().toLowerCase()\n : '';\n if (TRUE_WORDS.has(word)) return { ok: true, value: true };\n if (FALSE_WORDS.has(word)) return { ok: true, value: false };\n return failure('not-boolean', `\"${describeCell(c)}\" is not yes/no`);\n }\n case 'date': {\n if (c.value instanceof Date) {\n return { ok: true, value: isoDate(c.value) };\n }\n if (c.type === 'text' && typeof c.value === 'string') {\n const match = ISO_DATE.exec(c.value.trim());\n if (match && validDate(+match[1], +match[2], +match[3])) {\n return { ok: true, value: `${match[1]}-${match[2]}-${match[3]}` };\n }\n }\n return failure('not-date', `\"${describeCell(c)}\" is not a date`);\n }\n case 'money': {\n const parsed = decimalOf(c, 'not-money');\n if (!parsed.ok) return parsed.result;\n const scale = field.money?.scale ?? 0;\n const minor = parsed.value * 10 ** scale;\n const rounded = Math.round(minor);\n if (\n !Number.isFinite(minor) ||\n Math.abs(minor - rounded) > 1e-6 ||\n !Number.isSafeInteger(rounded)\n ) {\n return failure(\n 'not-money',\n `${plainDecimal(parsed.value)} does not fit ${scale} decimal places`,\n );\n }\n return { ok: true, value: rounded + 0 };\n }\n case 'choice': {\n const text =\n typeof c.value === 'string'\n ? c.value.trim()\n : c.type === 'number'\n ? numberText(c)\n : String(c.value);\n if (choiceSet ? choiceSet.has(text) : field.choices?.includes(text))\n return { ok: true, value: text };\n return failure('not-choice', `\"${text}\" is not one of the choices`);\n }\n }\n}\n","/**\n * PROVISIONAL (owner decision pending): the most cells a workbook may hold\n * before an import is refused. One named constant so the decision is one edit.\n */\nexport const DEFAULT_MAX_CELLS = 250_000;\n\nexport const DEFAULT_MAX_ROWS_PER_TABLE = 10_000;\n\n/**\n * Units of work one evaluation may spend: one per occupied cell indexed, plus\n * one per occupied row and mapped column read. Bounds synchronous time for any\n * plan and workbook the other limits admit.\n */\nexport const DEFAULT_MAX_WORK = 20_000_000;\n\nexport interface ImportLimits {\n maxCells: number;\n maxRowsPerTable: number;\n maxWork: number;\n}\n\nexport const DEFAULT_IMPORT_LIMITS: Readonly<ImportLimits> = Object.freeze({\n maxCells: DEFAULT_MAX_CELLS,\n maxRowsPerTable: DEFAULT_MAX_ROWS_PER_TABLE,\n maxWork: DEFAULT_MAX_WORK,\n});\n","import {\n address,\n type Cell,\n range as parseA1Range,\n type Range,\n type Sheet,\n type Workbook,\n} from '@happyvertical/spreadsheets';\nimport {\n coerceCell,\n describeCell,\n hasLeadingZeros,\n isBlankCell,\n} from './coerce.js';\nimport { ImportRunError } from './errors.js';\nimport type { FindingSink } from './findings.js';\nimport { DEFAULT_MAX_WORK } from './limits.js';\nimport type { ImportPlan, PlanField, PlanTable } from './plan.js';\n\nexport interface PlannedRecord {\n /** Absolute sheet row the record came from (first one, for distinct tables). */\n sourceRow: number;\n /** Column and typed values by field key. Calculated and reference fields are absent. */\n values: Record<string, unknown>;\n /** Resolved-by-key references; a blank reference is absent. */\n references: Record<string, { table: string; key: unknown[] }>;\n}\n\nexport interface EvaluatedTable {\n table: PlanTable;\n /** False when the source could not be read at all; `rows` is then empty. */\n readable: boolean;\n rows: PlannedRecord[];\n /** Data rows examined. */\n read: number;\n /** Rows left out: skipRows and rows with every mapped cell blank. */\n skipped: number;\n /** Rows refused by a finding; they never appear in `rows`. */\n rejected: number;\n}\n\n/** Highest occupied row of a sheet. Never cached: workbooks are mutable. */\nexport function lastOccupiedRow(sheet: Sheet): number {\n let last = 0;\n for (const [row] of sheet.cells.rows()) last = Math.max(last, row);\n return last;\n}\n\ntype IndexedRows = Array<[number, ReadonlyMap<number, Cell>]>;\n\n/** Spends work units against one ceiling; shared by every step of one import. */\nexport class WorkBudget {\n used = 0;\n\n constructor(readonly maxWork: number) {}\n\n charge(units: number): void {\n this.used += units;\n if (this.used > this.maxWork) {\n throw new ImportRunError(\n 'work-over-limit',\n `Reading the workbook needs more than ${this.maxWork} units of work.`,\n { maxWork: this.maxWork },\n );\n }\n }\n}\n\n/**\n * State of one evaluation call: the occupied rows of each sheet, and the\n * lookups that do not depend on a row, resolved once. Nothing outlives the\n * call, so a workbook that changes between calls is always read afresh.\n */\nclass Evaluation {\n private readonly rows = new Map<Sheet, IndexedRows>();\n /** table key -> field key -> field. */\n readonly fields = new Map<string, Map<string, PlanField>>();\n /** Fields with choices -> their choices as a set. */\n readonly choiceSets = new Map<PlanField, ReadonlySet<string>>();\n\n constructor(\n plan: ImportPlan,\n readonly budget: WorkBudget,\n ) {\n for (const table of plan.tables) {\n budget.charge(1 + table.fields.length);\n const byKey = new Map<string, PlanField>();\n for (const field of table.fields) {\n byKey.set(field.key, field);\n if (field.choices) {\n budget.charge(field.choices.length);\n this.choiceSets.set(field, new Set(field.choices));\n }\n }\n this.fields.set(table.key, byKey);\n }\n }\n\n charge(units: number): void {\n this.budget.charge(units);\n }\n\n occupied(sheet: Sheet): IndexedRows {\n let known = this.rows.get(sheet);\n if (!known) {\n this.charge(sheet.cells.size);\n known = [...sheet.cells.rows()];\n this.rows.set(sheet, known);\n }\n return known;\n }\n}\n\n/** Index of the first entry whose row is at least `row`. */\nfunction lowerBound(rows: IndexedRows, row: number): number {\n let low = 0;\n let high = rows.length;\n while (low < high) {\n const mid = (low + high) >> 1;\n if (rows[mid][0] < row) low = mid + 1;\n else high = mid;\n }\n return low;\n}\n\n/** The sheet columns a field reads from. */\nexport function fieldColumns(field: PlanField): number[] {\n const columns: number[] = [];\n if (field.column !== undefined) columns.push(field.column);\n if (field.reference) columns.push(...field.reference.columns);\n if (field.cachedColumn !== undefined) columns.push(field.cachedColumn);\n return columns;\n}\n\nfunction digitsAsNumber(cell: Cell | undefined): string | undefined {\n if (!cell) return undefined;\n let text: string | undefined;\n if (cell.type === 'text' && typeof cell.value === 'string') {\n text = cell.value.trim();\n } else if (cell.type === 'number' && typeof cell.value === 'number') {\n text = cell.originalText;\n if (text === undefined) {\n return Number.isInteger(cell.value) && Math.abs(cell.value) >= 1e15\n ? String(cell.value)\n : undefined;\n }\n }\n if (text === undefined || !/^[+-]?\\d+$/.test(text)) return undefined;\n const digits = text.replace(/^[+-]/, '');\n if (digits.length >= 16) return text;\n if (hasLeadingZeros(text)) return text;\n return undefined;\n}\n\nfunction keyText(values: readonly unknown[]): string {\n return JSON.stringify(values);\n}\n\n/**\n * Reads every table of the plan from the workbook, typing each cell. Findings\n * go to the sink; refused rows are counted and dropped.\n */\nexport function evaluatePlan(\n plan: ImportPlan,\n workbook: Workbook,\n sink: FindingSink,\n budget: WorkBudget = new WorkBudget(DEFAULT_MAX_WORK),\n): Map<string, EvaluatedTable> {\n const evaluation = new Evaluation(plan, budget);\n const result = new Map<string, EvaluatedTable>();\n for (const table of plan.tables) {\n result.set(table.key, evaluateTable(table, workbook, sink, evaluation));\n }\n return result;\n}\n\nfunction evaluateTable(\n table: PlanTable,\n workbook: Workbook,\n sink: FindingSink,\n evaluation: Evaluation,\n): EvaluatedTable {\n const out: EvaluatedTable = {\n table,\n readable: false,\n rows: [],\n read: 0,\n skipped: 0,\n rejected: 0,\n };\n const { sheet: sheetName, range: a1, headerRow } = table.source;\n const sheet = workbook.sheets.find(\n (candidate) => candidate.name === sheetName,\n );\n if (!sheet) {\n sink.add({\n code: 'sheet-missing',\n severity: 'error',\n table: table.key,\n sheet: sheetName,\n message: `Sheet \"${sheetName}\" is not in the workbook.`,\n });\n return out;\n }\n let box: Range;\n try {\n box = parseA1Range(a1);\n } catch {\n sink.add({\n code: 'range-invalid',\n severity: 'error',\n table: table.key,\n sheet: sheetName,\n message: `Range \"${a1}\" is not a valid A1 range.`,\n });\n return out;\n }\n let structural = false;\n if (headerRow < box.start.row || headerRow > box.end.row) {\n structural = true;\n sink.add({\n code: 'header-outside-range',\n severity: 'error',\n table: table.key,\n sheet: sheetName,\n row: headerRow,\n message: `Header row ${headerRow} is outside ${a1}.`,\n });\n }\n for (const field of table.fields) {\n for (const column of fieldColumns(field)) {\n if (column < box.start.col || column > box.end.col) {\n structural = true;\n sink.add({\n code: 'column-outside-range',\n severity: 'error',\n table: table.key,\n field: field.key,\n sheet: sheetName,\n column,\n message: `Column ${column} of field \"${field.key}\" is outside ${a1}.`,\n });\n }\n }\n }\n if (structural) return out;\n out.readable = true;\n\n const skip = new Set(table.source.skipRows ?? []);\n const first = Math.max(headerRow + 1, box.start.row);\n const occupiedRows = evaluation.occupied(sheet);\n const lastRow =\n occupiedRows.length > 0 ? occupiedRows[occupiedRows.length - 1][0] : 0;\n const last = Math.min(box.end.row, lastRow);\n const total = Math.max(0, last - first + 1);\n const mapped = new Set(table.fields.flatMap(fieldColumns));\n const readFields = table.fields.filter(\n (field) => field.column !== undefined || field.reference,\n );\n // Cached calculated cells also establish row presence.\n const sources = [...mapped];\n evaluation.charge(1 + table.fields.length + mapped.size);\n // Everything that does not depend on the row is resolved here, once.\n const keyFieldsOf = new Map<PlanField, Array<PlanField | undefined>>();\n for (const field of readFields) {\n if (!field.reference) continue;\n const target = evaluation.fields.get(field.reference.table);\n keyFieldsOf.set(\n field,\n field.reference.by.map((key) => target?.get(key)),\n );\n }\n const seen = new Set<string>();\n const unmapped = new Map<number, { count: number; formula: boolean }>();\n\n // Only occupied rows are visited; the empty rows between are counted, not walked.\n let occupied = 0;\n for (\n let at = lowerBound(occupiedRows, first);\n at < occupiedRows.length && occupiedRows[at][0] <= last;\n at++\n ) {\n const [row, rowCells] = occupiedRows[at];\n occupied++;\n evaluation.charge(1 + rowCells.size + sources.length);\n if (skip.has(row)) {\n out.skipped++;\n continue;\n }\n // Columns nothing maps: remember what they hold, for one warning each.\n // A distinct table is an extraction of some columns, so the rest are expected.\n for (const [column, cell] of rowCells) {\n if (\n table.source.distinct ||\n mapped.has(column) ||\n column < box.start.col ||\n column > box.end.col\n ) {\n continue;\n }\n if (isBlankCell(cell)) continue;\n const entry = unmapped.get(column) ?? { count: 0, formula: false };\n entry.count++;\n if (cell.formula) entry.formula = true;\n unmapped.set(column, entry);\n }\n if (sources.every((column) => isBlankCell(rowCells.get(column)))) {\n out.skipped++;\n continue;\n }\n\n const record: PlannedRecord = {\n sourceRow: row,\n values: {},\n references: {},\n };\n let refused = false;\n const refuse = (\n code:\n | 'coerce-failed'\n | 'required-blank'\n | 'digits-as-number'\n | 'reference-unresolved',\n field: PlanField,\n column: number,\n message: string,\n expected: string,\n ): void => {\n refused = true;\n const cell = sheet.cells.get(row, column);\n sink.add({\n code,\n severity: 'error',\n table: table.key,\n field: field.key,\n sheet: sheetName,\n cell: address({ row, col: column }),\n row,\n expected,\n actual: describeCell(cell),\n message,\n });\n };\n\n for (const field of readFields) {\n if (field.reference) {\n const parts: unknown[] = [];\n let blanks = 0;\n let failed = false;\n const keyFields = keyFieldsOf.get(field) ?? [];\n field.reference.columns.forEach((column, index) => {\n const keyField = keyFields[index];\n const cell = rowCells.get(column);\n if (!keyField) {\n failed = true;\n return;\n }\n const coerced = coerceCell(\n cell,\n keyField,\n evaluation.choiceSets.get(keyField),\n );\n if (!coerced.ok) {\n failed = true;\n refuse(\n 'coerce-failed',\n field,\n column,\n `Row ${row}: ${coerced.detail} [${coerced.code}]`,\n keyField.type,\n );\n return;\n }\n if (coerced.value === null) blanks++;\n parts.push(coerced.value);\n });\n if (failed) continue;\n if (blanks === parts.length) {\n if (field.required) {\n refuse(\n 'required-blank',\n field,\n field.reference.columns[0],\n `Row ${row}: \"${field.key}\" is required.`,\n 'a value',\n );\n }\n continue;\n }\n if (blanks > 0) {\n refuse(\n 'reference-unresolved',\n field,\n field.reference.columns[0],\n `Row ${row}: the key for \"${field.key}\" is partly blank.`,\n field.reference.table,\n );\n continue;\n }\n record.references[field.key] = {\n table: field.reference.table,\n key: parts,\n };\n continue;\n }\n const column = field.column as number;\n const cell = rowCells.get(column);\n if (field.type === 'number' || field.type === 'integer') {\n const digits = digitsAsNumber(cell);\n if (digits !== undefined) {\n refuse(\n 'digits-as-number',\n field,\n column,\n `Row ${row}: \"${digits}\" looks like an identifier; map \"${field.key}\" as text.`,\n 'text',\n );\n continue;\n }\n }\n const coerced = coerceCell(cell, field, evaluation.choiceSets.get(field));\n if (!coerced.ok) {\n refuse(\n 'coerce-failed',\n field,\n column,\n `Row ${row}: ${coerced.detail} [${coerced.code}]`,\n field.type,\n );\n continue;\n }\n if (coerced.value === null && field.required) {\n refuse(\n 'required-blank',\n field,\n column,\n `Row ${row}: \"${field.key}\" is required.`,\n 'a value',\n );\n continue;\n }\n record.values[field.key] = coerced.value;\n }\n if (refused) {\n out.rejected++;\n continue;\n }\n if (table.source.distinct) {\n const identity = keyText(\n readFields.map((field) =>\n field.reference\n ? (record.references[field.key]?.key ?? null)\n : (record.values[field.key] ?? null),\n ),\n );\n if (seen.has(identity)) continue;\n seen.add(identity);\n }\n out.rows.push(record);\n }\n evaluation.charge(unmapped.size);\n out.read = total;\n out.skipped += total - occupied;\n\n const headerLabel = (column: number): string =>\n describeCell(sheet.cells.get(headerRow, column));\n for (const [column, entry] of [...unmapped].sort(([a], [b]) => a - b)) {\n if (entry.formula) continue;\n sink.add({\n code: 'unmapped-column',\n severity: 'warning',\n table: table.key,\n sheet: sheetName,\n cell: address({ row: headerRow, col: column }),\n column,\n actual: headerLabel(column),\n message: `Column ${column} (\"${headerLabel(column)}\") holds ${entry.count} values that no field uses.`,\n });\n }\n return out;\n}\n\nexport { keyText };\n","/** What checks report. A finding is data: nothing here throws or writes. */\n\nexport type FindingCode =\n | 'sheet-missing'\n | 'range-invalid'\n | 'header-outside-range'\n | 'column-outside-range'\n | 'coerce-failed'\n | 'required-blank'\n | 'digits-as-number'\n | 'lookup-key-not-unique'\n | 'lookup-key-blank'\n | 'reference-unresolved'\n | 'unmapped-column'\n | 'sum-unsafe'\n | 'row-count-mismatch'\n | 'sum-mismatch'\n | 'calculated-invalid'\n | 'calculated-mismatch';\n\nexport interface Finding {\n code: FindingCode;\n severity: 'error' | 'warning';\n table?: string;\n field?: string;\n sheet?: string;\n /** A1 address of the offending cell. */\n cell?: string;\n /** Absolute sheet row. */\n row?: number;\n /** 1-based sheet column, for findings about a whole column. */\n column?: number;\n expected?: string | number | boolean | null;\n actual?: string | number | boolean | null;\n /** Total findings of this code for this table and field, examples included. */\n count: number;\n message: string;\n}\n\nexport type FindingInput = Omit<Finding, 'count'>;\n\n/** At most this many examples are kept per (code, table, field). */\nexport const MAX_EXAMPLES = 20;\n\n/** Collects findings, keeping a bounded number of examples and a total. */\nexport class FindingSink {\n private readonly groups = new Map<\n string,\n { total: number; items: FindingInput[] }\n >();\n\n add(finding: FindingInput): void {\n const key = `${finding.code}\\u0000${finding.table ?? ''}\\u0000${finding.field ?? ''}`;\n let group = this.groups.get(key);\n if (!group) {\n group = { total: 0, items: [] };\n this.groups.set(key, group);\n }\n group.total++;\n if (group.items.length < MAX_EXAMPLES) group.items.push(finding);\n }\n\n /** Insertion order of first occurrence, then row order within a group. */\n list(): Finding[] {\n const out: Finding[] = [];\n for (const group of this.groups.values()) {\n for (const item of group.items) out.push({ ...item, count: group.total });\n }\n return out;\n }\n\n hasErrors(): boolean {\n for (const group of this.groups.values()) {\n if (group.items.some((item) => item.severity === 'error')) return true;\n }\n return false;\n }\n}\n\nexport function hasErrors(findings: readonly Finding[]): boolean {\n return findings.some((finding) => finding.severity === 'error');\n}\n","import {\n address,\n type Cell,\n type Workbook,\n type WorkbookProfile,\n} from '@happyvertical/spreadsheets';\nimport { describeCell, isBlankCell, isoDate } from './coerce.js';\nimport {\n type EvaluatedTable,\n evaluatePlan,\n keyText,\n type PlannedRecord,\n WorkBudget,\n} from './evaluate.js';\nimport { type Finding, FindingSink, hasErrors } from './findings.js';\nimport { DEFAULT_IMPORT_LIMITS, type ImportLimits } from './limits.js';\nimport type { ImportPlan, PlanField, PlanTable } from './plan.js';\nimport type { ImportTarget } from './target.js';\n\n/** Numbers a table summed per field, for the after-load comparison. */\nexport interface TableFacts {\n /** Data rows examined. */\n read: number;\n /** Rows left out: skipRows and rows with every mapped cell blank. */\n skipped: number;\n /** Rows refused by an error finding. */\n rejected: number;\n /** Records that will be loaded. */\n planned: number;\n}\n\nexport interface ImportFacts {\n tables: Record<string, TableFacts>;\n /** table -> field -> sum. Money and integers in exact minor units/integers. */\n sums: Record<string, Record<string, number>>;\n}\n\nexport interface CheckResult {\n /** No error findings. */\n ok: boolean;\n findings: Finding[];\n}\n\nexport interface PlanCheckResult extends CheckResult {\n facts: ImportFacts;\n /** Work units this check spent against `maxWork`. */\n work: number;\n}\n\nconst SUMMED: ReadonlySet<string> = new Set(['number', 'integer', 'money']);\n\n/** Neumaier-compensated sum: the error does not grow with the row count. */\nexport function compensatedSum(values: Iterable<number>): number {\n let sum = 0;\n let compensation = 0;\n for (const value of values) {\n const next = sum + value;\n compensation +=\n Math.abs(sum) >= Math.abs(value)\n ? sum - next + value\n : value - next + sum;\n sum = next;\n }\n return sum + compensation;\n}\n\nfunction columnSum(\n rows: readonly PlannedRecord[],\n field: PlanField,\n): { value: number; safe: boolean } {\n if (field.type === 'number') {\n return {\n value: compensatedSum(\n rows.map((row) => Number(row.values[field.key] ?? 0)),\n ),\n safe: true,\n };\n }\n let total = 0n;\n for (const row of rows) {\n const value = row.values[field.key];\n if (typeof value === 'number') total += BigInt(value);\n }\n const safe =\n total <= BigInt(Number.MAX_SAFE_INTEGER) &&\n total >= BigInt(Number.MIN_SAFE_INTEGER);\n return { value: Number(total), safe };\n}\n\n/** Findings that need the rows of more than one table. */\nfunction crossChecks(\n tables: ReadonlyMap<string, EvaluatedTable>,\n sink: FindingSink,\n budget: WorkBudget,\n): void {\n const keyIndex = new Map<string, Set<string>>();\n for (const evaluated of tables.values()) {\n const { table } = evaluated;\n if (!table.lookupKey) continue;\n const keys = new Set<string>();\n const firstRow = new Map<string, number>();\n const columnOf = new Map(table.fields.map((f) => [f.key, f.column]));\n budget.charge(\n 1 + table.fields.length + evaluated.rows.length * table.lookupKey.length,\n );\n for (const row of evaluated.rows) {\n const parts = table.lookupKey.map((key) => row.values[key] ?? null);\n const blankIndex = parts.indexOf(null);\n if (blankIndex >= 0) {\n const field = table.lookupKey[blankIndex];\n const column = columnOf.get(field);\n sink.add({\n code: 'lookup-key-blank',\n severity: 'error',\n table: table.key,\n field,\n sheet: table.source.sheet,\n row: row.sourceRow,\n ...(column !== undefined\n ? { cell: address({ row: row.sourceRow, col: column }) }\n : {}),\n message: `Row ${row.sourceRow}: lookup key field \"${field}\" is blank.`,\n });\n continue;\n }\n const text = keyText(parts);\n const earlier = firstRow.get(text);\n if (earlier !== undefined) {\n sink.add({\n code: 'lookup-key-not-unique',\n severity: 'error',\n table: table.key,\n field: table.lookupKey.join(','),\n sheet: table.source.sheet,\n row: row.sourceRow,\n actual: parts.join(' | '),\n message: `Row ${row.sourceRow}: lookup key (${parts.join(', ')}) already appears on row ${earlier}.`,\n });\n continue;\n }\n firstRow.set(text, row.sourceRow);\n keys.add(text);\n }\n keyIndex.set(table.key, keys);\n }\n\n for (const evaluated of tables.values()) {\n const { table } = evaluated;\n for (const field of table.fields) {\n if (!field.reference) continue;\n const known = keyIndex.get(field.reference.table) ?? new Set<string>();\n budget.charge(1 + evaluated.rows.length);\n for (const row of evaluated.rows) {\n const ref = row.references[field.key];\n if (!ref || known.has(keyText(ref.key))) continue;\n const column = field.reference.columns[0];\n sink.add({\n code: 'reference-unresolved',\n severity: 'error',\n table: table.key,\n field: field.key,\n sheet: table.source.sheet,\n cell: address({ row: row.sourceRow, col: column }),\n row: row.sourceRow,\n expected: field.reference.table,\n actual: ref.key.join(' | '),\n message: `Row ${row.sourceRow}: no ${field.reference.table} row has key (${ref.key.join(', ')}).`,\n });\n }\n }\n }\n}\n\n/**\n * Checks a plan against a workbook before anything is written. The plan must\n * already be valid ({@link validatePlan}); `profile` is accepted for callers\n * that hold a workbook profile and is not needed to decide anything here.\n */\nexport function checkPlan(\n plan: ImportPlan,\n workbook: Workbook,\n _profile?: WorkbookProfile,\n limits: Partial<ImportLimits> = {},\n budget: WorkBudget = new WorkBudget(\n limits.maxWork ?? DEFAULT_IMPORT_LIMITS.maxWork,\n ),\n): PlanCheckResult {\n const sink = new FindingSink();\n const tables = evaluatePlan(plan, workbook, sink, budget);\n crossChecks(tables, sink, budget);\n\n const facts: ImportFacts = { tables: {}, sums: {} };\n for (const [key, evaluated] of tables) {\n facts.tables[key] = {\n read: evaluated.read,\n skipped: evaluated.skipped,\n rejected: evaluated.rejected,\n planned: evaluated.rows.length,\n };\n const sums: Record<string, number> = {};\n for (const field of evaluated.table.fields) {\n if (field.column === undefined || !SUMMED.has(field.type)) continue;\n budget.charge(1 + evaluated.rows.length);\n const sum = columnSum(evaluated.rows, field);\n if (!sum.safe) {\n sink.add({\n code: 'sum-unsafe',\n severity: 'error',\n table: key,\n field: field.key,\n message: `The column sum of \"${field.key}\" exceeds the safe integer range.`,\n });\n }\n sums[field.key] = sum.value;\n }\n facts.sums[key] = sums;\n }\n const findings = sink.list();\n return { ok: !hasErrors(findings), findings, facts, work: budget.used };\n}\n\nfunction relativelyEqual(a: number, b: number, tolerance = 1e-9): boolean {\n if (!Number.isFinite(a) || !Number.isFinite(b)) return false;\n if (a === b) return true;\n return Math.abs(a - b) <= tolerance * Math.max(Math.abs(a), Math.abs(b));\n}\n\nfunction asNumber(value: unknown): number | undefined {\n if (typeof value === 'number') return value;\n if (typeof value === 'bigint') return Number(value);\n if (typeof value === 'string' && value.trim() !== '') {\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : undefined;\n }\n return undefined;\n}\n\nfunction asBoolean(value: unknown): boolean | undefined {\n if (typeof value === 'boolean') return value;\n if (value === 1 || value === '1') return true;\n if (value === 0 || value === '0') return false;\n return undefined;\n}\n\nfunction asIsoDate(value: unknown): string | undefined {\n if (value instanceof Date) return isoDate(value);\n if (typeof value === 'string')\n return /^\\d{4}-\\d{2}-\\d{2}/.test(value) ? value.slice(0, 10) : undefined;\n return undefined;\n}\n\n/** Why a stored calculated value cannot be a valid value of its field, if it cannot. */\nfunction invalidCalculated(\n field: PlanField,\n actual: unknown,\n): string | undefined {\n if (actual === null || actual === undefined) return undefined;\n const whole = field.type === 'integer' || field.type === 'money';\n if (!whole && field.type !== 'number') return undefined;\n if (typeof actual === 'bigint') {\n return actual <= BigInt(Number.MAX_SAFE_INTEGER) &&\n actual >= BigInt(Number.MIN_SAFE_INTEGER)\n ? undefined\n : `${actual} is beyond the safe integer range`;\n }\n const n = actual;\n if (typeof n !== 'number' || !Number.isFinite(n)) {\n return `${String(actual)} is not a finite number`;\n }\n if (whole && !Number.isInteger(n)) return `${n} is not a whole number`;\n if (whole && !Number.isSafeInteger(n))\n return `${n} is beyond the safe integer range`;\n return undefined;\n}\n\n/** Does the target's value for a calculated field agree with the workbook's cached result? */\nfunction cachedAgrees(\n field: PlanField,\n cached: Cell | undefined,\n actual: unknown,\n): boolean {\n const cachedBlank = isBlankCell(cached, false) || cached?.type === 'error';\n if (cachedBlank) return actual === null || actual === undefined;\n if (actual === null || actual === undefined) return false;\n const value = cached?.value;\n switch (field.type) {\n case 'number': {\n const a = asNumber(actual);\n return (\n typeof value === 'number' &&\n a !== undefined &&\n relativelyEqual(value, a)\n );\n }\n // Integers and money are exact: no tolerance, however large the value.\n case 'integer': {\n const a = asNumber(actual);\n return typeof value === 'number' && a !== undefined && value === a;\n }\n case 'money': {\n const a = asNumber(actual);\n if (typeof value !== 'number' || a === undefined) return false;\n const minor = value * 10 ** (field.money?.scale ?? 0);\n const rounded = Math.round(minor);\n // A cached amount finer than a minor unit cannot equal a stored integer.\n return Math.abs(minor - rounded) <= 1e-6 && rounded === a;\n }\n case 'boolean':\n return typeof value === 'boolean' && asBoolean(actual) === value;\n case 'date':\n return value instanceof Date && asIsoDate(actual) === isoDate(value);\n default:\n return typeof value === 'string'\n ? value === String(actual)\n : describeCell(cached) === String(actual);\n }\n}\n\n/**\n * The most work `checkLoaded` can spend for these planned counts. `runImport`\n * reserves it before its first call on the target, so verification can never\n * exhaust the budget after something was written.\n */\nexport function verificationWork(plan: ImportPlan, facts: ImportFacts): number {\n let work = 0;\n for (const table of plan.tables) {\n const planned = facts.tables[table.key]?.planned ?? 0;\n const calculated = table.fields.filter((f) => f.calculated !== undefined);\n const summed = table.fields.filter(\n (f) => f.column !== undefined && SUMMED.has(f.type),\n );\n work += 1 + summed.length;\n if (calculated.length > 0) work += (planned + 1) * (1 + calculated.length);\n }\n return work;\n}\n\n/**\n * After a load: row counts, column sums and every calculated field with a\n * cached column, read back from the target and compared with the workbook.\n */\nexport async function checkLoaded(\n plan: ImportPlan,\n workbook: Workbook,\n target: ImportTarget,\n facts: ImportFacts,\n budget: WorkBudget = new WorkBudget(Number.POSITIVE_INFINITY),\n): Promise<CheckResult> {\n const sink = new FindingSink();\n for (const table of plan.tables) {\n const planned = facts.tables[table.key]?.planned ?? 0;\n const count = await target.count(table.key);\n if (count !== planned) {\n sink.add({\n code: 'row-count-mismatch',\n severity: 'error',\n table: table.key,\n expected: planned,\n actual: count,\n message: `\"${table.key}\" holds ${count} rows; the workbook plans ${planned}.`,\n });\n }\n for (const field of table.fields) {\n if (field.column === undefined || !SUMMED.has(field.type)) continue;\n const expected = facts.sums[table.key]?.[field.key];\n if (expected === undefined) continue;\n const actual = Number(await target.sum(table.key, field.key));\n const equal =\n field.type === 'number'\n ? relativelyEqual(expected, actual)\n : expected === actual;\n if (!equal) {\n sink.add({\n code: 'sum-mismatch',\n severity: 'error',\n table: table.key,\n field: field.key,\n expected,\n actual,\n message: `\"${table.key}.${field.key}\" sums to ${actual}; the workbook sums to ${expected}.`,\n });\n }\n }\n await checkCalculated(table, workbook, target, sink, budget, planned + 1);\n }\n const findings = sink.list();\n return { ok: !hasErrors(findings), findings };\n}\n\nasync function checkCalculated(\n table: PlanTable,\n workbook: Workbook,\n target: ImportTarget,\n sink: FindingSink,\n budget: WorkBudget,\n maxRows: number,\n): Promise<void> {\n const sheet = workbook.sheets.find((s) => s.name === table.source.sheet);\n const calculated = table.fields.filter((f) => f.calculated !== undefined);\n if (calculated.length === 0) return;\n // One pass over the stored rows for every calculated field of the table.\n // Bounded by planned rows + 1: a target holding more is a row-count-mismatch\n // (reported from count()), never unbounded work.\n let seen = 0;\n for await (const row of target.values(\n table.key,\n calculated.map((f) => f.key),\n )) {\n if (++seen > maxRows) break;\n budget.charge(1 + calculated.length);\n for (const field of calculated) {\n const column = field.cachedColumn;\n const actual = row.values[field.key];\n const shown =\n actual === null || actual === undefined\n ? null\n : typeof actual === 'object'\n ? JSON.stringify(actual)\n : typeof actual === 'bigint'\n ? String(actual)\n : (actual as string | number | boolean);\n const invalid = invalidCalculated(field, actual);\n if (invalid !== undefined) {\n sink.add({\n code: 'calculated-invalid',\n severity: 'error',\n table: table.key,\n field: field.key,\n row: row.sourceRow,\n actual: shown,\n message: `Row ${row.sourceRow}: \"${field.key}\" holds ${invalid}.`,\n });\n continue;\n }\n if (column === undefined || !sheet) continue;\n const cached = sheet.cells.get(row.sourceRow, column);\n if (cachedAgrees(field, cached, actual)) continue;\n sink.add({\n code: 'calculated-mismatch',\n severity: 'error',\n table: table.key,\n field: field.key,\n sheet: table.source.sheet,\n cell: address({ row: row.sourceRow, col: column }),\n row: row.sourceRow,\n expected: describeCell(cached),\n actual: shown,\n message: `Row ${row.sourceRow}: \"${field.key}\" is ${String(actual)} in the target; the workbook cached ${describeCell(cached)}.`,\n });\n }\n }\n}\n","import type { Sheet, Workbook } from '@happyvertical/spreadsheets';\nimport { range as parseA1Range } from '@happyvertical/spreadsheets';\nimport { ImportRunError } from './errors.js';\nimport {\n type EvaluatedTable,\n evaluatePlan,\n lastOccupiedRow,\n type PlannedRecord,\n WorkBudget,\n} from './evaluate.js';\nimport { FindingSink } from './findings.js';\nimport { DEFAULT_IMPORT_LIMITS, type ImportLimits } from './limits.js';\nimport { type ImportPlan, loadOrder } from './plan.js';\n\nexport const DEFAULT_BATCH_SIZE = 500;\n\nexport type LoadRecord = PlannedRecord;\n\nexport interface LoadBatch {\n table: string;\n /** Position of this batch within its table, from 0. */\n index: number;\n records: LoadRecord[];\n}\n\nexport interface LoadPlan {\n /** Table keys, referenced tables first. */\n order: string[];\n batches(): Iterable<LoadBatch>;\n}\n\n/**\n * Refuses a workbook that is too big, before any row is read or written.\n * With a plan, rows are counted per planned table; without one, per sheet.\n */\nexport function admit(\n workbook: Workbook,\n limits: Partial<ImportLimits> = {},\n plan?: ImportPlan,\n): void {\n const { maxCells, maxRowsPerTable }: ImportLimits = {\n ...DEFAULT_IMPORT_LIMITS,\n ...limits,\n };\n let cells = 0;\n for (const sheet of workbook.sheets) {\n cells += sheet.cells.size;\n if (cells > maxCells) {\n throw new ImportRunError(\n 'cells-over-limit',\n `The workbook holds more than ${maxCells} cells.`,\n { maxCells },\n );\n }\n }\n const over = (what: string, rows: number): never => {\n throw new ImportRunError(\n 'rows-over-limit',\n `${what} holds ${rows} rows; the limit is ${maxRowsPerTable}.`,\n { maxRowsPerTable, rows },\n );\n };\n // Scanned once per sheet per call: many tables may share one sheet.\n const lastRows = new Map<Sheet, number>();\n const lastRowOf = (sheet: Sheet): number => {\n let known = lastRows.get(sheet);\n if (known === undefined) {\n known = lastOccupiedRow(sheet);\n lastRows.set(sheet, known);\n }\n return known;\n };\n if (plan) {\n const sheets = new Map(workbook.sheets.map((sheet) => [sheet.name, sheet]));\n for (const table of plan.tables) {\n const sheet = sheets.get(table.source.sheet);\n if (!sheet) continue;\n let box: ReturnType<typeof parseA1Range>;\n try {\n box = parseA1Range(table.source.range);\n } catch {\n continue;\n }\n const first = Math.max(table.source.headerRow + 1, box.start.row);\n const last = Math.min(box.end.row, lastRowOf(sheet));\n const rows = last - first + 1;\n if (rows > maxRowsPerTable) over(`Table \"${table.key}\"`, rows);\n }\n return;\n }\n for (const sheet of workbook.sheets) {\n const rows = lastRowOf(sheet);\n if (rows > maxRowsPerTable) over(`Sheet \"${sheet.name}\"`, rows);\n }\n}\n\n/**\n * Turns a plan and its workbook into batches of typed records. Referenced\n * tables come first; `distinct` tables keep each combination once, in first-seen\n * order; calculated fields are never in `values`; rows that failed to type were\n * errors in `checkPlan` and never appear.\n */\nexport function planLoad(\n plan: ImportPlan,\n workbook: Workbook,\n options: {\n batchSize?: number;\n limits?: Partial<ImportLimits>;\n /** Share one budget across the steps of one import. */\n budget?: WorkBudget;\n } = {},\n): LoadPlan {\n const batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;\n if (!Number.isSafeInteger(batchSize) || batchSize < 1) {\n throw new RangeError('batchSize must be a positive integer');\n }\n const sink = new FindingSink();\n const tables = evaluatePlan(\n plan,\n workbook,\n sink,\n options.budget ??\n new WorkBudget(options.limits?.maxWork ?? DEFAULT_IMPORT_LIMITS.maxWork),\n );\n const unreadable = [...tables.values()].filter((t) => !t.readable);\n if (unreadable.length > 0) {\n throw new ImportRunError(\n 'source-invalid',\n `The source of ${unreadable.map((t) => `\"${t.table.key}\"`).join(', ')} cannot be read; run checkPlan.`,\n { findings: sink.list().filter((f) => f.severity === 'error') },\n );\n }\n const order = loadOrder(plan);\n return {\n order,\n *batches(): Iterable<LoadBatch> {\n for (const key of order) {\n const evaluated = tables.get(key) as EvaluatedTable;\n for (\n let at = 0, index = 0;\n at < evaluated.rows.length;\n at += batchSize\n ) {\n yield {\n table: key,\n index: index++,\n records: evaluated.rows.slice(at, at + batchSize),\n };\n }\n }\n },\n };\n}\n","import type { Workbook } from '@happyvertical/spreadsheets';\nimport {\n checkLoaded,\n checkPlan,\n type ImportFacts,\n verificationWork,\n} from './checks.js';\nimport { ImportRunError } from './errors.js';\nimport { WorkBudget } from './evaluate.js';\nimport { type Finding, hasErrors } from './findings.js';\nimport { DEFAULT_IMPORT_LIMITS, type ImportLimits } from './limits.js';\nimport { admit, planLoad } from './load.js';\nimport { type ImportPlan, validatePlan } from './plan.js';\nimport type { ImportTarget } from './target.js';\n\nexport interface RunImportOptions {\n /** A plan to validate; the validated copy is what runs. */\n plan: unknown;\n workbook: Workbook;\n target: ImportTarget;\n limits?: Partial<ImportLimits>;\n batchSize?: number;\n}\n\nexport interface RunImportResult {\n /** True when nothing is an error: the data is loaded and proven. */\n ok: boolean;\n findings: Finding[];\n facts: ImportFacts;\n /** Rows the target reports loading, per table. Empty when nothing was written. */\n loaded: Record<string, number>;\n plan: ImportPlan;\n /** Work units spent, verification reservation included. */\n work: number;\n}\n\n/**\n * First import of a workbook into an empty target: validate, admit, check,\n * plan the whole load, reserve the read-back work, and only then touch the\n * target: build structure, load every batch, prove the result by reading it\n * back. `work-over-limit` is therefore thrown before the first target call or\n * not at all. Adapter errors propagate untouched.\n */\nexport async function runImport(\n options: RunImportOptions,\n): Promise<RunImportResult> {\n const plan = validatePlan(options.plan);\n admit(options.workbook, options.limits, plan);\n\n // One ceiling for every step: checking, planning and verifying.\n const budget = new WorkBudget(\n options.limits?.maxWork ?? DEFAULT_IMPORT_LIMITS.maxWork,\n );\n const planned = checkPlan(\n plan,\n options.workbook,\n undefined,\n options.limits,\n budget,\n );\n if (!planned.ok) {\n return {\n ok: false,\n findings: planned.findings,\n facts: planned.facts,\n loaded: {},\n plan,\n work: budget.used,\n };\n }\n\n // Everything that can exhaust the budget happens before the first call on\n // the target: plan the whole load now, and reserve the read-back work.\n const load = planLoad(plan, options.workbook, {\n batchSize: options.batchSize,\n limits: options.limits,\n budget,\n });\n const batches = [...load.batches()];\n budget.charge(verificationWork(plan, planned.facts));\n\n const structure = await options.target.describe();\n const existing = new Set(structure.tables.map((table) => table.key));\n const clash = plan.tables.filter((table) => existing.has(table.key));\n if (clash.length > 0) {\n throw new ImportRunError(\n 'target-not-empty',\n `The target already has ${clash.map((t) => `\"${t.key}\"`).join(', ')}; this import is first-time only.`,\n { tables: clash.map((table) => table.key) },\n );\n }\n\n await options.target.applyStructure(plan);\n const loaded: Record<string, number> = {};\n for (const table of load.order) loaded[table] = 0;\n for (const batch of batches) {\n const result = await options.target.load(batch);\n loaded[batch.table] += result.loaded;\n }\n\n // Verification draws from the reservation: its own bounded accounting, no throw.\n const verified = await checkLoaded(\n plan,\n options.workbook,\n options.target,\n planned.facts,\n );\n const findings = [...planned.findings, ...verified.findings];\n return {\n ok: !hasErrors(findings),\n findings,\n facts: planned.facts,\n loaded,\n plan,\n work: budget.used,\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * PROVISIONAL (owner decision pending): the most cells a workbook may hold
3
+ * before an import is refused. One named constant so the decision is one edit.
4
+ */
5
+ export declare const DEFAULT_MAX_CELLS = 250000;
6
+ export declare const DEFAULT_MAX_ROWS_PER_TABLE = 10000;
7
+ /**
8
+ * Units of work one evaluation may spend: one per occupied cell indexed, plus
9
+ * one per occupied row and mapped column read. Bounds synchronous time for any
10
+ * plan and workbook the other limits admit.
11
+ */
12
+ export declare const DEFAULT_MAX_WORK = 20000000;
13
+ export interface ImportLimits {
14
+ maxCells: number;
15
+ maxRowsPerTable: number;
16
+ maxWork: number;
17
+ }
18
+ export declare const DEFAULT_IMPORT_LIMITS: Readonly<ImportLimits>;
19
+ //# sourceMappingURL=limits.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../../src/import/limits.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,iBAAiB,SAAU,CAAC;AAEzC,eAAO,MAAM,0BAA0B,QAAS,CAAC;AAEjD;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,WAAa,CAAC;AAE3C,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,eAAO,MAAM,qBAAqB,EAAE,QAAQ,CAAC,YAAY,CAIvD,CAAC"}
@@ -0,0 +1,35 @@
1
+ import type { Workbook } from '@happyvertical/spreadsheets';
2
+ import { type PlannedRecord, WorkBudget } from './evaluate.js';
3
+ import { type ImportLimits } from './limits.js';
4
+ import { type ImportPlan } from './plan.js';
5
+ export declare const DEFAULT_BATCH_SIZE = 500;
6
+ export type LoadRecord = PlannedRecord;
7
+ export interface LoadBatch {
8
+ table: string;
9
+ /** Position of this batch within its table, from 0. */
10
+ index: number;
11
+ records: LoadRecord[];
12
+ }
13
+ export interface LoadPlan {
14
+ /** Table keys, referenced tables first. */
15
+ order: string[];
16
+ batches(): Iterable<LoadBatch>;
17
+ }
18
+ /**
19
+ * Refuses a workbook that is too big, before any row is read or written.
20
+ * With a plan, rows are counted per planned table; without one, per sheet.
21
+ */
22
+ export declare function admit(workbook: Workbook, limits?: Partial<ImportLimits>, plan?: ImportPlan): void;
23
+ /**
24
+ * Turns a plan and its workbook into batches of typed records. Referenced
25
+ * tables come first; `distinct` tables keep each combination once, in first-seen
26
+ * order; calculated fields are never in `values`; rows that failed to type were
27
+ * errors in `checkPlan` and never appear.
28
+ */
29
+ export declare function planLoad(plan: ImportPlan, workbook: Workbook, options?: {
30
+ batchSize?: number;
31
+ limits?: Partial<ImportLimits>;
32
+ /** Share one budget across the steps of one import. */
33
+ budget?: WorkBudget;
34
+ }): LoadPlan;
35
+ //# sourceMappingURL=load.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load.d.ts","sourceRoot":"","sources":["../../src/import/load.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAS,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAGnE,OAAO,EAIL,KAAK,aAAa,EAClB,UAAU,EACX,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAyB,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,KAAK,UAAU,EAAa,MAAM,WAAW,CAAC;AAEvD,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAEtC,MAAM,MAAM,UAAU,GAAG,aAAa,CAAC;AAEvC,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,UAAU,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,QAAQ;IACvB,2CAA2C;IAC3C,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,IAAI,QAAQ,CAAC,SAAS,CAAC,CAAC;CAChC;AAED;;;GAGG;AACH,wBAAgB,KAAK,CACnB,QAAQ,EAAE,QAAQ,EAClB,MAAM,GAAE,OAAO,CAAC,YAAY,CAAM,EAClC,IAAI,CAAC,EAAE,UAAU,GAChB,IAAI,CAuDN;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CACtB,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,QAAQ,EAClB,OAAO,GAAE;IACP,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IAC/B,uDAAuD;IACvD,MAAM,CAAC,EAAE,UAAU,CAAC;CAChB,GACL,QAAQ,CAyCV"}
@@ -0,0 +1,81 @@
1
+ import { type AggregateOperand } from '@happyvertical/sql/query';
2
+ export type ImportFieldType = 'text' | 'number' | 'integer' | 'boolean' | 'date' | 'money' | 'choice' | 'reference';
3
+ export declare const IMPORT_FIELD_TYPES: readonly ImportFieldType[];
4
+ export type { AggregateOperand };
5
+ export interface PlanReference {
6
+ /** Key of the referenced table. */
7
+ table: string;
8
+ /** Field keys of the referenced table; must equal its `lookupKey`. */
9
+ by: string[];
10
+ /** 1-based sheet columns whose values are matched, in `by` order. */
11
+ columns: number[];
12
+ }
13
+ export interface PlanField {
14
+ key: string;
15
+ label: string;
16
+ type: ImportFieldType;
17
+ required?: boolean;
18
+ /** 1-based sheet column the value comes from. */
19
+ column?: number;
20
+ /** SDK expression over sibling field keys; the field has no column. */
21
+ calculated?: AggregateOperand;
22
+ /** For calculated fields: the column holding the workbook's cached result. */
23
+ cachedColumn?: number;
24
+ reference?: PlanReference;
25
+ choices?: string[];
26
+ /** Stored as integer minor units: `value * 10^scale`. */
27
+ money?: {
28
+ currency: string;
29
+ scale: number;
30
+ };
31
+ }
32
+ export interface PlanTable {
33
+ key: string;
34
+ label: string;
35
+ source: {
36
+ sheet: string;
37
+ /** A1 range holding the header row and the data rows. */
38
+ range: string;
39
+ /** Absolute sheet row of the header. */
40
+ headerRow: number;
41
+ /** Absolute sheet rows to leave out (totals, notes). */
42
+ skipRows?: number[];
43
+ /** Rows are the distinct combinations of the mapped columns. */
44
+ distinct?: boolean;
45
+ };
46
+ fields: PlanField[];
47
+ /** Field keys that identify a row (reference target, re-import match). */
48
+ lookupKey?: string[];
49
+ titleField?: string;
50
+ }
51
+ /** Opaque to this package; passed to the target as given. */
52
+ export interface PlanReport {
53
+ key: string;
54
+ label: string;
55
+ spec: unknown;
56
+ }
57
+ export interface ImportPlan {
58
+ version: 1;
59
+ tables: PlanTable[];
60
+ reports?: PlanReport[];
61
+ }
62
+ export interface PlanLimits {
63
+ maxTables: number;
64
+ maxFieldsPerTable: number;
65
+ maxPlanBytes: number;
66
+ maxReports: number;
67
+ maxChoices: number;
68
+ maxSkipRows: number;
69
+ maxExpressionNodes: number;
70
+ }
71
+ export declare const DEFAULT_PLAN_LIMITS: Readonly<PlanLimits>;
72
+ export declare const PLAN_KEY_PATTERN: RegExp;
73
+ /**
74
+ * Validates a plan and returns a clean copy. Throws {@link ImportPlanError}.
75
+ * A plan is data: it names no function, path or SQL string, and an unknown key
76
+ * anywhere refuses the whole plan.
77
+ */
78
+ export declare function validatePlan(input: unknown, overrides?: Partial<PlanLimits>): ImportPlan;
79
+ /** Table keys with referenced tables before the tables that reference them. */
80
+ export declare function loadOrder(plan: ImportPlan): string[];
81
+ //# sourceMappingURL=plan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../src/import/plan.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,gBAAgB,EAGtB,MAAM,0BAA0B,CAAC;AAGlC,MAAM,MAAM,eAAe,GACvB,MAAM,GACN,QAAQ,GACR,SAAS,GACT,SAAS,GACT,MAAM,GACN,OAAO,GACP,QAAQ,GACR,WAAW,CAAC;AAEhB,eAAO,MAAM,kBAAkB,EAAE,SAAS,eAAe,EASvD,CAAC;AAEH,YAAY,EAAE,gBAAgB,EAAE,CAAC;AAEjC,MAAM,WAAW,aAAa;IAC5B,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,EAAE,EAAE,MAAM,EAAE,CAAC;IACb,qEAAqE;IACrE,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,eAAe,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iDAAiD;IACjD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,8EAA8E;IAC9E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,yDAAyD;IACzD,KAAK,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7C;AAED,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE;QACN,KAAK,EAAE,MAAM,CAAC;QACd,yDAAyD;QACzD,KAAK,EAAE,MAAM,CAAC;QACd,wCAAwC;QACxC,SAAS,EAAE,MAAM,CAAC;QAClB,wDAAwD;QACxD,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QACpB,gEAAgE;QAChE,QAAQ,CAAC,EAAE,OAAO,CAAC;KACpB,CAAC;IACF,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,6DAA6D;AAC7D,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,CAAC,CAAC;IACX,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,OAAO,CAAC,EAAE,UAAU,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,mBAAmB,EAAE,QAAQ,CAAC,UAAU,CAQnD,CAAC;AAEH,eAAO,MAAM,gBAAgB,QAA2B,CAAC;AAqlBzD;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,OAAO,EACd,SAAS,GAAE,OAAO,CAAC,UAAU,CAAM,GAClC,UAAU,CA6CZ;AAED,+EAA+E;AAC/E,wBAAgB,SAAS,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,EAAE,CAgBpD"}
@@ -0,0 +1,34 @@
1
+ import type { Workbook } from '@happyvertical/spreadsheets';
2
+ import { type ImportFacts } from './checks.js';
3
+ import { type Finding } from './findings.js';
4
+ import { type ImportLimits } from './limits.js';
5
+ import { type ImportPlan } from './plan.js';
6
+ import type { ImportTarget } from './target.js';
7
+ export interface RunImportOptions {
8
+ /** A plan to validate; the validated copy is what runs. */
9
+ plan: unknown;
10
+ workbook: Workbook;
11
+ target: ImportTarget;
12
+ limits?: Partial<ImportLimits>;
13
+ batchSize?: number;
14
+ }
15
+ export interface RunImportResult {
16
+ /** True when nothing is an error: the data is loaded and proven. */
17
+ ok: boolean;
18
+ findings: Finding[];
19
+ facts: ImportFacts;
20
+ /** Rows the target reports loading, per table. Empty when nothing was written. */
21
+ loaded: Record<string, number>;
22
+ plan: ImportPlan;
23
+ /** Work units spent, verification reservation included. */
24
+ work: number;
25
+ }
26
+ /**
27
+ * First import of a workbook into an empty target: validate, admit, check,
28
+ * plan the whole load, reserve the read-back work, and only then touch the
29
+ * target: build structure, load every batch, prove the result by reading it
30
+ * back. `work-over-limit` is therefore thrown before the first target call or
31
+ * not at all. Adapter errors propagate untouched.
32
+ */
33
+ export declare function runImport(options: RunImportOptions): Promise<RunImportResult>;
34
+ //# sourceMappingURL=run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/import/run.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAGL,KAAK,WAAW,EAEjB,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,KAAK,OAAO,EAAa,MAAM,eAAe,CAAC;AACxD,OAAO,EAAyB,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAEvE,OAAO,EAAE,KAAK,UAAU,EAAgB,MAAM,WAAW,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,WAAW,gBAAgB;IAC/B,2DAA2D;IAC3D,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,oEAAoE;IACpE,EAAE,EAAE,OAAO,CAAC;IACZ,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,KAAK,EAAE,WAAW,CAAC;IACnB,kFAAkF;IAClF,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,IAAI,EAAE,UAAU,CAAC;IACjB,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;GAMG;AACH,wBAAsB,SAAS,CAC7B,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,eAAe,CAAC,CAuE1B"}
@@ -0,0 +1,38 @@
1
+ import type { LoadBatch } from './load.js';
2
+ import type { ImportPlan } from './plan.js';
3
+ /** What a target already holds. Only keys matter to the import. */
4
+ export interface TargetStructure {
5
+ tables: Array<{
6
+ key: string;
7
+ fields: Array<{
8
+ key: string;
9
+ type?: string;
10
+ }>;
11
+ lookupKey?: string[];
12
+ }>;
13
+ }
14
+ export interface TargetRow {
15
+ /** Absolute sheet row the target kept for the record. */
16
+ sourceRow: number;
17
+ values: Record<string, unknown>;
18
+ }
19
+ /**
20
+ * The product-owned half of an import. This package decides what to load and
21
+ * proves it arrived; a target decides where it goes. Nothing in the contract
22
+ * names a product, a database or a model layer.
23
+ */
24
+ export interface ImportTarget {
25
+ describe(): Promise<TargetStructure>;
26
+ /** Creates tables, fields, calculated fields and reports. */
27
+ applyStructure(plan: ImportPlan): Promise<void>;
28
+ /** Resolves references by lookup key. All or nothing per batch. */
29
+ load(batch: LoadBatch): Promise<{
30
+ loaded: number;
31
+ }>;
32
+ count(table: string): Promise<number>;
33
+ /** Money in minor units. Integer/money totals beyond the safe range stay bigint. */
34
+ sum(table: string, field: string): Promise<number | bigint>;
35
+ /** Stored values in source-row order, calculated fields included. */
36
+ values(table: string, fields: string[], sourceRows?: number[]): AsyncIterable<TargetRow>;
37
+ }
38
+ //# sourceMappingURL=target.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"target.d.ts","sourceRoot":"","sources":["../../src/import/target.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE5C,mEAAmE;AACnE,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,KAAK,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;QACZ,MAAM,EAAE,KAAK,CAAC;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAC9C,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;KACtB,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,SAAS;IACxB,yDAAyD;IACzD,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,IAAI,OAAO,CAAC,eAAe,CAAC,CAAC;IACrC,6DAA6D;IAC7D,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,mEAAmE;IACnE,IAAI,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpD,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACtC,oFAAoF;IACpF,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;IAC5D,qEAAqE;IACrE,MAAM,CACJ,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EAAE,EAChB,UAAU,CAAC,EAAE,MAAM,EAAE,GACpB,aAAa,CAAC,SAAS,CAAC,CAAC;CAC7B"}
@@ -0,0 +1,2 @@
1
+ export type * from './import/index.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mBAAmB,mBAAmB,CAAC"}
package/dist/index.js ADDED
File without changes
@@ -0,0 +1,9 @@
1
+ {
2
+ "version": "1.0.0",
3
+ "timestamp": 0,
4
+ "packageName": "@happyvertical/smrt-spreadsheets",
5
+ "packageVersion": "0.61.0",
6
+ "objects": {},
7
+ "moduleType": "smrt",
8
+ "stampsConstructors": true
9
+ }
@@ -0,0 +1,57 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "sensitiveFieldsExcluded": true,
4
+ "generatedAt": "1970-01-01T00:00:00.000Z",
5
+ "packageName": "@happyvertical/smrt-spreadsheets",
6
+ "packageVersion": "0.61.0",
7
+ "sourceManifestPath": "dist/manifest.json",
8
+ "agentDocPath": "AGENTS.md",
9
+ "sourceHashes": {
10
+ "manifest": "a47505699c6510900be12a0df732bc97a43ff67f652281d7aa1e6137b97308f4",
11
+ "packageJson": "66032241629c47205379d4091160da8c8917f2042be1b29bb335e4c132cdbc4d",
12
+ "agents": "ac8aa22db5f7f33f53baf38079bafcb2d0bea2842709ea25b36b99ca3f814e84"
13
+ },
14
+ "exports": [
15
+ ".",
16
+ "./import",
17
+ "./testing"
18
+ ],
19
+ "dependencies": {
20
+ "@happyvertical/spreadsheets": "catalog:",
21
+ "@happyvertical/sql": "catalog:",
22
+ "@happyvertical/smrt-vitest": "workspace:*",
23
+ "@types/node": "24.13.6",
24
+ "typescript": "6.0.3",
25
+ "vite": "8.1.6",
26
+ "vitest": "4.1.10"
27
+ },
28
+ "smrtDependencies": [
29
+ "@happyvertical/smrt-vitest"
30
+ ],
31
+ "sdkDependencies": [
32
+ "@happyvertical/sql"
33
+ ],
34
+ "tags": [
35
+ "cross-package",
36
+ "csv",
37
+ "import",
38
+ "smrt",
39
+ "spreadsheet",
40
+ "xlsx"
41
+ ],
42
+ "risks": [
43
+ "sensitive-fields-excluded"
44
+ ],
45
+ "objects": [],
46
+ "surfaces": [],
47
+ "prompts": [],
48
+ "relationshipsV2": {
49
+ "foreignKeyFields": 0,
50
+ "crossPackageRefFields": 0,
51
+ "junctionCollections": 0,
52
+ "hierarchicalObjects": 0,
53
+ "polymorphicAssociations": 0,
54
+ "uuidColumns": 0
55
+ },
56
+ "agentDoc": "# @happyvertical/smrt-spreadsheets\n\nsmrt's spreadsheet package, starting with import: the deterministic half of\n\"build an app from a workbook\" (#4969). A model, elsewhere and later, proposes an `ImportPlan`; this package validates it,\nchecks it against the workbook, loads the data through an `ImportTarget`, and\nproves the result. Two products implement the target, so nothing here may\nname either. No `@smrt()` objects, no tables, no Svelte.\n\n`./export`, saved mapping and re-import are planned, not present.\n\n## Entries\n\n- Root (`.`): types only (`export type * from './import/index.js'`); no runtime\n code or values. Keep it types only as new capabilities arrive.\n- `./import`: browser-safe and pure. Imports `@happyvertical/spreadsheets` and\n `@happyvertical/sql/query` only, never the sql root or a `node:` built-in\n (`browser-safe.test.ts` walks and bundles the graph).\n- `./testing`: Node-only `createSqliteReferenceTarget()`, real in-process SQLite\n through `@happyvertical/sql`. Calculated fields are generated columns made\n by `renderExpression`, so they run on database semantics. Never import it\n from the root or import entry.\n\n## Source map\n\nImport implementation and tests live under `src/import/`; `src/import/index.ts`\nexports the import surface. `src/index.ts` is the type-only root, and\n`src/testing.ts` implements the Node-only target.\n\n- `src/import/plan.ts`: `validatePlan` (closed shapes, JSON-cloned input, limits),\n `loadOrder`. Calculated expressions may read column fields only.\n- `src/import/coerce.ts`: `coerceCell`, the one typing rule. Nothing is guessed.\n- `src/import/evaluate.ts`: reads and types every table; shared by `checkPlan` and\n `planLoad`. A row that fails never reaches a record.\n- `src/import/checks.ts`: `checkPlan` (before writing) and `checkLoaded` (after).\n- `src/import/load.ts`: `admit` (limits) and `planLoad` (ordered, batched records).\n- `src/import/run.ts`: `runImport`. Adapter errors propagate; it catches nothing.\n\n## Invariants\n\n- First import only: `runImport` refuses a target that has any planned table\n (`target-not-empty`). Re-import matching is a later slice.\n- `checkPlan` errors stop the run before `describe()` is called; nothing is written.\n- `DEFAULT_MAX_CELLS` (250,000) is PROVISIONAL, owner decision pending; keep it\n one constant. Per-table rows default to 10,000.\n- Money is integer minor units (`value * 10^scale`, within 1e-6 of an integer or\n refused). Integer sums are exact (BigInt); number sums are compensated.\n- `distinct` tables skip the unmapped-column warning: they extract columns by design.\n- Work is bounded: evaluation visits occupied rows and mapped columns only, never\n range width x height, and spends from `maxWork` (`DEFAULT_MAX_WORK`); over it,\n `work-over-limit`. Row indexes live for one evaluation call, never across calls:\n a workbook is mutable and is re-read every time.\n- Anything that does not depend on a row (field-by-key maps, reference key fields,\n choice sets, column lists, insert SQL) is resolved once before rows are visited;\n never `.find`/`.includes` over plan data inside a row or cell loop. Every\n per-row, per-cell and per-reference unit is charged to one `WorkBudget` that\n `runImport` shares across checking, planning and verifying. `work-over-limit`\n is thrown before the first call on the target (`describe()` included) or never:\n `runImport` plans every batch up front and reserves the read-back work\n (`verificationWork`) before touching the target; read-back is bounded by planned\n rows + 1, and extra rows are a `row-count-mismatch`, never a budget error.\n- `checkLoaded` value-checks every calculated integer, money and number field\n (`calculated-invalid`: not a whole safe integer / not finite) with or without a\n `cachedColumn`. Integer and money comparisons are exact; tolerance is for `number`.\n- `ok:false` after a load is a verdict, not a repair: the caller discards the draft\n target. There is no rollback or recreation logic here.\n- Findings keep at most 20 examples per (code, table, field) with a total `count`.\n- Plan keys are `^[a-z][a-z0-9_]{0,62}$`; SQL keywords are fine, names that exist\n on every object (`constructor`, `prototype`, ...) are refused. The reference\n target quotes every identifier.\n\n## Validation\n\n`pnpm --filter @happyvertical/smrt-spreadsheets build|typecheck|test`.\nTests build workbooks in code with the SDK's `createWorkbook`/`createSheet`;\nno binary fixtures, no containers.\n"
57
+ }
@@ -0,0 +1,46 @@
1
+ ---
2
+ type: "Component"
3
+ title: "@happyvertical/smrt-spreadsheets"
4
+ description: "Spreadsheet capabilities for smrt, starting with validated import plans, workbook checks and batch loading through a target adapter"
5
+ status: "stable"
6
+ generated:
7
+ by: "smrt/scripts/generate-knowledge-okf.ts"
8
+ at: "2026-11-13T20:49:38Z"
9
+ audience: "internal"
10
+ evidence_level: "manifest-described"
11
+ source_revision: "sha256:61a92004ee2d2045d0e31aaa82db787b560cd539c617617e11751bbef6051797"
12
+ sources:
13
+ - id: "manifest"
14
+ resource: "https://github.com/happyvertical/smrt/blob/main/packages/spreadsheets/package.json"
15
+ title: "@happyvertical/smrt-spreadsheets package manifest"
16
+ - id: "agents"
17
+ resource: "https://github.com/happyvertical/smrt/blob/main/packages/spreadsheets/AGENTS.md"
18
+ title: "@happyvertical/smrt-spreadsheets packages/spreadsheets/AGENTS.md"
19
+ ---
20
+
21
+ # @happyvertical/smrt-spreadsheets
22
+
23
+ ## Package
24
+
25
+ - Directory: `packages/spreadsheets`
26
+ - Private: no
27
+ - Object source: `domain-artifact`
28
+ - Evidence level: `manifest-described`
29
+
30
+ ## Documentation
31
+
32
+ - `packages/spreadsheets/AGENTS.md`
33
+
34
+ ## Framework dependencies
35
+
36
+ - [smrt-vitest](/components/smrt-vitest.md)
37
+
38
+ ## SDK dependencies
39
+
40
+ - `@happyvertical/sql`
41
+
42
+ ## Exports
43
+
44
+ - `.`
45
+ - `./import`
46
+ - `./testing`
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Node-only reference target for tests: real in-process SQLite through
3
+ * `@happyvertical/sql`, so calculated fields are generated columns evaluated
4
+ * with database semantics. Never imported by the root or `/import` entry.
5
+ */
6
+ import { type DatabaseInterface } from '@happyvertical/sql';
7
+ import { type PlanReport } from './import/plan.js';
8
+ import type { ImportTarget } from './import/target.js';
9
+ export interface SqliteReferenceTarget extends ImportTarget {
10
+ /** The database the target writes to. */
11
+ readonly db: DatabaseInterface;
12
+ /** Reports handed over by the plan, exactly as given. */
13
+ appliedReports(): PlanReport[];
14
+ close(): Promise<void>;
15
+ }
16
+ export interface SqliteReferenceTargetOptions {
17
+ /** An existing database to write into; the caller closes it. */
18
+ db?: DatabaseInterface;
19
+ }
20
+ export declare function createSqliteReferenceTarget(options?: SqliteReferenceTargetOptions): Promise<SqliteReferenceTarget>;
21
+ //# sourceMappingURL=testing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,KAAK,iBAAiB,EAAe,MAAM,oBAAoB,CAAC;AAGzE,OAAO,EAKL,KAAK,UAAU,EAEhB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EACV,YAAY,EAGb,MAAM,oBAAoB,CAAC;AAE5B,MAAM,WAAW,qBAAsB,SAAQ,YAAY;IACzD,yCAAyC;IACzC,QAAQ,CAAC,EAAE,EAAE,iBAAiB,CAAC;IAC/B,yDAAyD;IACzD,cAAc,IAAI,UAAU,EAAE,CAAC;IAC/B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,MAAM,WAAW,4BAA4B;IAC3C,gEAAgE;IAChE,EAAE,CAAC,EAAE,iBAAiB,CAAC;CACxB;AA8DD,wBAAsB,2BAA2B,CAC/C,OAAO,GAAE,4BAAiC,GACzC,OAAO,CAAC,qBAAqB,CAAC,CAyOhC"}