@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,198 @@
1
+ import { i as loadOrder } from "./chunks/plan-BAV2jEzl.js";
2
+ import { renderExpression } from "@happyvertical/sql/query";
3
+ import { getDatabase } from "@happyvertical/sql";
4
+ //#region src/testing.ts
5
+ var quote = (name) => `"${name.replace(/"/g, "\"\"")}"`;
6
+ var ROW_ID = "_row_id";
7
+ var SOURCE_ROW = "_source_row";
8
+ var COLUMN_TYPE = {
9
+ text: "TEXT",
10
+ choice: "TEXT",
11
+ date: "TEXT",
12
+ number: "REAL",
13
+ integer: "INTEGER",
14
+ money: "INTEGER",
15
+ boolean: "INTEGER",
16
+ reference: "INTEGER"
17
+ };
18
+ var EXPRESSION_TYPE = {
19
+ text: "text",
20
+ choice: "text",
21
+ number: "number",
22
+ integer: "number",
23
+ money: "number",
24
+ boolean: "boolean",
25
+ date: "date",
26
+ reference: "unknown"
27
+ };
28
+ function toStored(value) {
29
+ if (typeof value === "boolean") return value ? 1 : 0;
30
+ return value ?? null;
31
+ }
32
+ function fromStored(field, value) {
33
+ if (typeof value === "bigint") return Number(value);
34
+ if (typeof value === "string" && (field?.type === "integer" || field?.type === "money") && /^-?\d+$/.test(value)) return BigInt(value);
35
+ if (field?.type === "boolean" && (value === 0 || value === 1)) return value === 1;
36
+ return value ?? null;
37
+ }
38
+ async function createSqliteReferenceTarget(options = {}) {
39
+ const owned = options.db === void 0;
40
+ const db = options.db ?? await getDatabase({
41
+ type: "sqlite",
42
+ url: ":memory:"
43
+ });
44
+ const definitions = /* @__PURE__ */ new Map();
45
+ const reports = [];
46
+ const keyIndex = /* @__PURE__ */ new Map();
47
+ const definition = (key) => {
48
+ const found = definitions.get(key);
49
+ if (!found) throw new Error(`The target has no table "${key}".`);
50
+ return found;
51
+ };
52
+ const columnSql = (field, siblings) => {
53
+ const name = quote(field.key);
54
+ if (field.calculated !== void 0) {
55
+ const rendered = renderExpression(field.calculated, {
56
+ dialect: "sqlite",
57
+ mode: "ddl",
58
+ resolveColumn: (column) => ({
59
+ name: column,
60
+ type: EXPRESSION_TYPE[siblings.get(column)?.type ?? "text"]
61
+ })
62
+ });
63
+ if (rendered.values.length > 0) throw new Error("A generated column cannot bind parameters.");
64
+ return `${name} ${COLUMN_TYPE[field.type]} GENERATED ALWAYS AS (${rendered.sql}) STORED`;
65
+ }
66
+ if (field.reference) return `${name} INTEGER REFERENCES ${quote(field.reference.table)}(${quote(ROW_ID)})${field.required ? " NOT NULL" : ""}`;
67
+ return `${name} ${COLUMN_TYPE[field.type]}${field.required ? " NOT NULL" : ""}`;
68
+ };
69
+ const target = {
70
+ db,
71
+ appliedReports: () => [...reports],
72
+ async close() {
73
+ if (owned) await db.close?.();
74
+ },
75
+ async describe() {
76
+ const tables = await db.query("SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY name");
77
+ const out = [];
78
+ for (const row of tables.rows) {
79
+ const key = String(row.name);
80
+ const columns = await db.query(`SELECT name, type FROM pragma_table_xinfo('${key.replace(/'/g, "''")}') ORDER BY cid`);
81
+ out.push({
82
+ key,
83
+ fields: columns.rows.filter((column) => !String(column.name).startsWith("_")).map((column) => ({
84
+ key: String(column.name),
85
+ type: String(column.type)
86
+ })),
87
+ ...definitions.get(key)?.table.lookupKey ? { lookupKey: definitions.get(key)?.table.lookupKey } : {}
88
+ });
89
+ }
90
+ return { tables: out };
91
+ },
92
+ async applyStructure(plan) {
93
+ const byKey = new Map(plan.tables.map((table) => [table.key, table]));
94
+ for (const key of loadOrder(plan)) {
95
+ const table = byKey.get(key);
96
+ const siblings = new Map(table.fields.map((f) => [f.key, f]));
97
+ const columns = [
98
+ `${quote(ROW_ID)} INTEGER PRIMARY KEY AUTOINCREMENT`,
99
+ `${quote(SOURCE_ROW)} INTEGER NOT NULL`,
100
+ ...table.fields.map((field) => columnSql(field, siblings))
101
+ ];
102
+ await db.query(`CREATE TABLE ${quote(key)} (${columns.join(", ")})`);
103
+ if (table.lookupKey) await db.query(`CREATE UNIQUE INDEX ${quote(`ux_${key}_lookup`)} ON ${quote(key)} (${table.lookupKey.map(quote).join(", ")})`);
104
+ const stored = table.fields.filter((f) => f.calculated === void 0);
105
+ const names = [SOURCE_ROW, ...stored.map((f) => f.key)];
106
+ definitions.set(key, {
107
+ table,
108
+ fields: siblings,
109
+ stored,
110
+ insertSql: `INSERT INTO ${quote(key)} (${names.map(quote).join(", ")}) VALUES (${names.map((_, i) => `$${i + 1}`).join(", ")}) RETURNING ${quote(ROW_ID)} AS id`
111
+ });
112
+ }
113
+ for (const report of plan.reports ?? []) reports.push(report);
114
+ },
115
+ async load(batch) {
116
+ const def = definition(batch.table);
117
+ if (!db.transaction) throw new Error("The database does not support transactions.");
118
+ const added = [];
119
+ await db.transaction(async (tx) => {
120
+ for (const record of batch.records) {
121
+ const values = [record.sourceRow];
122
+ for (const field of def.stored) {
123
+ if (field.reference) {
124
+ const ref = record.references[field.key];
125
+ if (!ref) {
126
+ values.push(null);
127
+ continue;
128
+ }
129
+ const id = keyIndex.get(ref.table)?.get(JSON.stringify(ref.key));
130
+ if (id === void 0) throw new Error(`"${batch.table}" row ${record.sourceRow}: no "${ref.table}" row has key ${JSON.stringify(ref.key)}.`);
131
+ values.push(id);
132
+ continue;
133
+ }
134
+ values.push(toStored(record.values[field.key]));
135
+ }
136
+ const result = await tx.query(def.insertSql, values);
137
+ if (def.table.lookupKey) {
138
+ const key = def.table.lookupKey.map((name) => record.values[name] ?? null);
139
+ added.push([JSON.stringify(key), Number(result.rows[0].id)]);
140
+ }
141
+ }
142
+ });
143
+ if (added.length > 0) {
144
+ const index = keyIndex.get(batch.table) ?? /* @__PURE__ */ new Map();
145
+ for (const [key, id] of added) index.set(key, id);
146
+ keyIndex.set(batch.table, index);
147
+ }
148
+ return { loaded: batch.records.length };
149
+ },
150
+ async count(table) {
151
+ definition(table);
152
+ const result = await db.query(`SELECT COUNT(*) AS n FROM ${quote(table)}`);
153
+ return Number(result.rows[0].n);
154
+ },
155
+ async sum(table, field) {
156
+ const planField = definition(table).fields.get(field);
157
+ if (!planField) throw new Error(`"${table}" has no field "${field}".`);
158
+ if (planField.type === "integer" || planField.type === "money") {
159
+ let total = 0n;
160
+ for await (const row of target.values(table, [field])) {
161
+ const value = row.values[field];
162
+ if (value === null || value === void 0) continue;
163
+ if (typeof value !== "bigint" && (typeof value !== "number" || !Number.isSafeInteger(value))) return NaN;
164
+ total += BigInt(value);
165
+ }
166
+ return total <= BigInt(Number.MAX_SAFE_INTEGER) && total >= BigInt(Number.MIN_SAFE_INTEGER) ? Number(total) : total;
167
+ }
168
+ const result = await db.query(`SELECT SUM(${quote(field)}) AS total FROM ${quote(table)}`);
169
+ return Number(result.rows[0].total ?? 0);
170
+ },
171
+ async *values(table, fields, sourceRows) {
172
+ const def = definition(table);
173
+ for (const field of fields) if (!def.fields.has(field)) throw new Error(`"${table}" has no field "${field}".`);
174
+ const wanted = sourceRows ? [...new Set(sourceRows)].sort((a, b) => a - b) : void 0;
175
+ const chunks = [];
176
+ if (wanted) for (let at = 0; at < wanted.length; at += 500) chunks.push(wanted.slice(at, at + 500));
177
+ else chunks.push(void 0);
178
+ const select = [quote(SOURCE_ROW), ...fields.map((f) => `CASE WHEN typeof(${quote(f)}) = 'integer' AND (${quote(f)} > 9007199254740991 OR ${quote(f)} < -9007199254740991) THEN CAST(${quote(f)} AS TEXT) ELSE ${quote(f)} END AS ${quote(f)}`)].join(", ");
179
+ for (const chunk of chunks) {
180
+ const where = chunk ? ` WHERE ${quote(SOURCE_ROW)} IN (${chunk.map((_, i) => `$${i + 1}`).join(", ")})` : "";
181
+ const result = await db.query(`SELECT ${select} FROM ${quote(table)}${where} ORDER BY ${quote(SOURCE_ROW)}, ${quote(ROW_ID)}`, chunk ?? []);
182
+ for (const row of result.rows) {
183
+ const values = {};
184
+ for (const field of fields) values[field] = fromStored(def.fields.get(field), row[field]);
185
+ yield {
186
+ sourceRow: Number(row[SOURCE_ROW]),
187
+ values
188
+ };
189
+ }
190
+ }
191
+ }
192
+ };
193
+ return target;
194
+ }
195
+ //#endregion
196
+ export { createSqliteReferenceTarget };
197
+
198
+ //# sourceMappingURL=testing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.js","names":[],"sources":["../src/testing.ts"],"sourcesContent":["/**\n * Node-only reference target for tests: real in-process SQLite through\n * `@happyvertical/sql`, so calculated fields are generated columns evaluated\n * with database semantics. Never imported by the root or `/import` entry.\n */\nimport { type DatabaseInterface, getDatabase } from '@happyvertical/sql';\nimport { renderExpression } from '@happyvertical/sql/query';\nimport type { LoadBatch } from './import/load.js';\nimport {\n type ImportFieldType,\n type ImportPlan,\n loadOrder,\n type PlanField,\n type PlanReport,\n type PlanTable,\n} from './import/plan.js';\nimport type {\n ImportTarget,\n TargetRow,\n TargetStructure,\n} from './import/target.js';\n\nexport interface SqliteReferenceTarget extends ImportTarget {\n /** The database the target writes to. */\n readonly db: DatabaseInterface;\n /** Reports handed over by the plan, exactly as given. */\n appliedReports(): PlanReport[];\n close(): Promise<void>;\n}\n\nexport interface SqliteReferenceTargetOptions {\n /** An existing database to write into; the caller closes it. */\n db?: DatabaseInterface;\n}\n\nconst quote = (name: string): string => `\"${name.replace(/\"/g, '\"\"')}\"`;\n\n/** Hidden columns start with an underscore; plan keys cannot. */\nconst ROW_ID = '_row_id';\nconst SOURCE_ROW = '_source_row';\n\nconst COLUMN_TYPE: Record<ImportFieldType, string> = {\n text: 'TEXT',\n choice: 'TEXT',\n date: 'TEXT',\n number: 'REAL',\n integer: 'INTEGER',\n money: 'INTEGER',\n boolean: 'INTEGER',\n reference: 'INTEGER',\n};\n\nconst EXPRESSION_TYPE: Record<\n ImportFieldType,\n 'number' | 'text' | 'boolean' | 'date' | 'unknown'\n> = {\n text: 'text',\n choice: 'text',\n number: 'number',\n integer: 'number',\n money: 'number',\n boolean: 'boolean',\n date: 'date',\n reference: 'unknown',\n};\n\nfunction toStored(value: unknown): unknown {\n if (typeof value === 'boolean') return value ? 1 : 0;\n return value ?? null;\n}\n\nfunction fromStored(field: PlanField | undefined, value: unknown): unknown {\n if (typeof value === 'bigint') return Number(value);\n if (\n typeof value === 'string' &&\n (field?.type === 'integer' || field?.type === 'money') &&\n /^-?\\d+$/.test(value)\n ) {\n return BigInt(value);\n }\n if (field?.type === 'boolean' && (value === 0 || value === 1)) {\n return value === 1;\n }\n return value ?? null;\n}\n\ninterface TableDefinition {\n table: PlanTable;\n fields: Map<string, PlanField>;\n /** Fields that hold a stored value (everything but generated columns). */\n stored: PlanField[];\n /** The insert statement, built once per table. */\n insertSql: string;\n}\n\nexport async function createSqliteReferenceTarget(\n options: SqliteReferenceTargetOptions = {},\n): Promise<SqliteReferenceTarget> {\n const owned = options.db === undefined;\n const db =\n options.db ?? (await getDatabase({ type: 'sqlite', url: ':memory:' }));\n const definitions = new Map<string, TableDefinition>();\n const reports: PlanReport[] = [];\n /** table -> JSON of the lookup key -> hidden row id, for reference resolution. */\n const keyIndex = new Map<string, Map<string, number>>();\n\n const definition = (key: string): TableDefinition => {\n const found = definitions.get(key);\n if (!found) throw new Error(`The target has no table \"${key}\".`);\n return found;\n };\n\n const columnSql = (\n field: PlanField,\n siblings: ReadonlyMap<string, PlanField>,\n ): string => {\n const name = quote(field.key);\n if (field.calculated !== undefined) {\n const rendered = renderExpression(field.calculated, {\n dialect: 'sqlite',\n mode: 'ddl',\n resolveColumn: (column) => ({\n name: column,\n type: EXPRESSION_TYPE[siblings.get(column)?.type ?? 'text'],\n }),\n });\n if (rendered.values.length > 0) {\n throw new Error('A generated column cannot bind parameters.');\n }\n return `${name} ${COLUMN_TYPE[field.type]} GENERATED ALWAYS AS (${rendered.sql}) STORED`;\n }\n if (field.reference) {\n return `${name} INTEGER REFERENCES ${quote(field.reference.table)}(${quote(ROW_ID)})${field.required ? ' NOT NULL' : ''}`;\n }\n return `${name} ${COLUMN_TYPE[field.type]}${field.required ? ' NOT NULL' : ''}`;\n };\n\n const target: SqliteReferenceTarget = {\n db,\n appliedReports: () => [...reports],\n\n async close() {\n if (owned) await db.close?.();\n },\n\n async describe(): Promise<TargetStructure> {\n const tables = await db.query(\n \"SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY name\",\n );\n const out: TargetStructure['tables'] = [];\n for (const row of tables.rows) {\n const key = String(row.name);\n const columns = await db.query(\n `SELECT name, type FROM pragma_table_xinfo('${key.replace(/'/g, \"''\")}') ORDER BY cid`,\n );\n out.push({\n key,\n fields: columns.rows\n .filter((column) => !String(column.name).startsWith('_'))\n .map((column) => ({\n key: String(column.name),\n type: String(column.type),\n })),\n ...(definitions.get(key)?.table.lookupKey\n ? { lookupKey: definitions.get(key)?.table.lookupKey }\n : {}),\n });\n }\n return { tables: out };\n },\n\n async applyStructure(plan: ImportPlan): Promise<void> {\n const byKey = new Map(plan.tables.map((table) => [table.key, table]));\n for (const key of loadOrder(plan)) {\n const table = byKey.get(key) as PlanTable;\n const siblings = new Map(table.fields.map((f) => [f.key, f]));\n const columns = [\n `${quote(ROW_ID)} INTEGER PRIMARY KEY AUTOINCREMENT`,\n `${quote(SOURCE_ROW)} INTEGER NOT NULL`,\n ...table.fields.map((field) => columnSql(field, siblings)),\n ];\n await db.query(`CREATE TABLE ${quote(key)} (${columns.join(', ')})`);\n if (table.lookupKey) {\n await db.query(\n `CREATE UNIQUE INDEX ${quote(`ux_${key}_lookup`)} ON ${quote(key)} (${table.lookupKey.map(quote).join(', ')})`,\n );\n }\n const stored = table.fields.filter((f) => f.calculated === undefined);\n const names = [SOURCE_ROW, ...stored.map((f) => f.key)];\n definitions.set(key, {\n table,\n fields: siblings,\n stored,\n insertSql: `INSERT INTO ${quote(key)} (${names.map(quote).join(', ')}) VALUES (${names.map((_, i) => `$${i + 1}`).join(', ')}) RETURNING ${quote(ROW_ID)} AS id`,\n });\n }\n for (const report of plan.reports ?? []) reports.push(report);\n },\n\n async load(batch: LoadBatch): Promise<{ loaded: number }> {\n const def = definition(batch.table);\n if (!db.transaction) {\n throw new Error('The database does not support transactions.');\n }\n const added: Array<[string, number]> = [];\n await db.transaction(async (tx) => {\n for (const record of batch.records) {\n const values: unknown[] = [record.sourceRow];\n for (const field of def.stored) {\n if (field.reference) {\n const ref = record.references[field.key];\n if (!ref) {\n values.push(null);\n continue;\n }\n const id = keyIndex.get(ref.table)?.get(JSON.stringify(ref.key));\n if (id === undefined) {\n throw new Error(\n `\"${batch.table}\" row ${record.sourceRow}: no \"${ref.table}\" row has key ${JSON.stringify(ref.key)}.`,\n );\n }\n values.push(id);\n continue;\n }\n values.push(toStored(record.values[field.key]));\n }\n const result = await tx.query(def.insertSql, values);\n if (def.table.lookupKey) {\n const key = def.table.lookupKey.map(\n (name) => record.values[name] ?? null,\n );\n added.push([JSON.stringify(key), Number(result.rows[0].id)]);\n }\n }\n });\n if (added.length > 0) {\n const index = keyIndex.get(batch.table) ?? new Map<string, number>();\n for (const [key, id] of added) index.set(key, id);\n keyIndex.set(batch.table, index);\n }\n return { loaded: batch.records.length };\n },\n\n async count(table: string): Promise<number> {\n definition(table);\n const result = await db.query(\n `SELECT COUNT(*) AS n FROM ${quote(table)}`,\n );\n return Number(result.rows[0].n);\n },\n\n async sum(table: string, field: string): Promise<number | bigint> {\n const def = definition(table);\n const planField = def.fields.get(field);\n if (!planField) {\n throw new Error(`\"${table}\" has no field \"${field}\".`);\n }\n if (planField.type === 'integer' || planField.type === 'money') {\n // SQLite SUM can overflow int64 before later values cancel it out.\n let total = 0n;\n for await (const row of target.values(table, [field])) {\n const value = row.values[field];\n if (value === null || value === undefined) continue;\n if (\n typeof value !== 'bigint' &&\n (typeof value !== 'number' || !Number.isSafeInteger(value))\n ) {\n return Number.NaN;\n }\n total += BigInt(value);\n }\n return total <= BigInt(Number.MAX_SAFE_INTEGER) &&\n total >= BigInt(Number.MIN_SAFE_INTEGER)\n ? Number(total)\n : total;\n }\n const result = await db.query(\n `SELECT SUM(${quote(field)}) AS total FROM ${quote(table)}`,\n );\n return Number(result.rows[0].total ?? 0);\n },\n\n async *values(\n table: string,\n fields: string[],\n sourceRows?: number[],\n ): AsyncIterable<TargetRow> {\n const def = definition(table);\n for (const field of fields) {\n if (!def.fields.has(field)) {\n throw new Error(`\"${table}\" has no field \"${field}\".`);\n }\n }\n const wanted = sourceRows\n ? [...new Set(sourceRows)].sort((a, b) => a - b)\n : undefined;\n const chunks: Array<number[] | undefined> = [];\n if (wanted) {\n for (let at = 0; at < wanted.length; at += 500) {\n chunks.push(wanted.slice(at, at + 500));\n }\n } else chunks.push(undefined);\n // An integer beyond the safe range cannot cross the driver as a number;\n // hand it over as text and surface it as a bigint instead.\n const select = [\n quote(SOURCE_ROW),\n ...fields.map(\n (f) =>\n `CASE WHEN typeof(${quote(f)}) = 'integer' AND (${quote(f)} > 9007199254740991 OR ${quote(f)} < -9007199254740991) THEN CAST(${quote(f)} AS TEXT) ELSE ${quote(f)} END AS ${quote(f)}`,\n ),\n ].join(', ');\n for (const chunk of chunks) {\n const where = chunk\n ? ` WHERE ${quote(SOURCE_ROW)} IN (${chunk.map((_, i) => `$${i + 1}`).join(', ')})`\n : '';\n const result = await db.query(\n `SELECT ${select} FROM ${quote(table)}${where} ORDER BY ${quote(SOURCE_ROW)}, ${quote(ROW_ID)}`,\n chunk ?? [],\n );\n for (const row of result.rows) {\n const values: Record<string, unknown> = {};\n for (const field of fields) {\n values[field] = fromStored(def.fields.get(field), row[field]);\n }\n yield { sourceRow: Number(row[SOURCE_ROW]), values };\n }\n }\n },\n };\n return target;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,64 @@
1
1
  {
2
2
  "name": "@happyvertical/smrt-spreadsheets",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.61.0",
4
+ "description": "Spreadsheet capabilities for smrt, starting with validated import plans, workbook checks and batch loading through a target adapter",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "files": [
9
+ "dist",
10
+ "CLAUDE.md",
11
+ "AGENTS.md"
12
+ ],
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.js"
17
+ },
18
+ "./import": {
19
+ "types": "./dist/import/index.d.ts",
20
+ "import": "./dist/import/index.js"
21
+ },
22
+ "./testing": {
23
+ "types": "./dist/testing.d.ts",
24
+ "import": "./dist/testing.js"
25
+ }
26
+ },
27
+ "dependencies": {
28
+ "@happyvertical/spreadsheets": "^0.112.3",
29
+ "@happyvertical/sql": "^0.112.3"
30
+ },
31
+ "devDependencies": {
32
+ "@happyvertical/smrt-vitest": "0.61.0",
33
+ "@types/node": "24.13.6",
34
+ "typescript": "6.0.3",
35
+ "vite": "8.1.6",
36
+ "vitest": "4.1.10"
37
+ },
38
+ "keywords": [
39
+ "smrt",
40
+ "spreadsheet",
41
+ "import",
42
+ "xlsx",
43
+ "csv"
44
+ ],
45
+ "author": "HappyVertical",
46
+ "license": "MIT",
47
+ "publishConfig": {
48
+ "registry": "https://registry.npmjs.org",
49
+ "access": "public"
50
+ },
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "https://github.com/happyvertical/smrt.git",
54
+ "directory": "packages/spreadsheets"
55
+ },
56
+ "scripts": {
57
+ "build": "vite build --mode library",
58
+ "build:watch": "vite build --mode library --watch",
59
+ "clean": "rm -rf dist",
60
+ "test": "vitest run",
61
+ "test:watch": "vitest",
62
+ "typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.test.json"
63
+ }
6
64
  }