duck-types 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -1
- package/README.md +14 -39
- package/dist/cli.js +1 -0
- package/dist/cli.js.map +1 -1
- package/package.json +14 -12
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,16 @@ All notable changes to this project are documented here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.1.1] — 2026-10-25
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- README: before/after examples no longer use side-by-side tables, which did
|
|
14
|
+
not fit on GitHub or npm.
|
|
15
|
+
- `duck-types generate` explains itself when it finds no `sql()` calls at all:
|
|
16
|
+
which globs it scanned, under which directory, and that `sql` must be
|
|
17
|
+
imported from `duck-types`.
|
|
18
|
+
|
|
9
19
|
## [0.1.0] — 2026-10-24
|
|
10
20
|
|
|
11
21
|
First release.
|
|
@@ -30,5 +40,6 @@ First release.
|
|
|
30
40
|
`MAP`, `UNION` and `ENUM` resolved exactly; every column and nested element
|
|
31
41
|
is nullable.
|
|
32
42
|
|
|
33
|
-
[Unreleased]: https://github.com/gavricci/duck-types/compare/v0.1.
|
|
43
|
+
[Unreleased]: https://github.com/gavricci/duck-types/compare/v0.1.1...HEAD
|
|
44
|
+
[0.1.1]: https://github.com/gavricci/duck-types/compare/v0.1.0...v0.1.1
|
|
34
45
|
[0.1.0]: https://github.com/gavricci/duck-types/releases/tag/v0.1.0
|
package/README.md
CHANGED
|
@@ -26,74 +26,49 @@ Run `duck-types generate` once (or in `--watch`), and every `sql('...')` in the
|
|
|
26
26
|
|
|
27
27
|
**Hand-written row types** — the interface, the cast and the SQL drift apart; nothing checks them.
|
|
28
28
|
|
|
29
|
-
<table><tr><td>
|
|
30
|
-
|
|
31
29
|
```ts
|
|
30
|
+
// before
|
|
32
31
|
interface OrderRow { id: number; total: number }
|
|
33
32
|
|
|
34
|
-
const reader = await conn.runAndReadAll(
|
|
35
|
-
'select id, total from orders'
|
|
36
|
-
);
|
|
33
|
+
const reader = await conn.runAndReadAll('select id, total from orders');
|
|
37
34
|
const rows = reader.getRowObjectsJS() as OrderRow[];
|
|
38
|
-
```
|
|
39
35
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
```ts
|
|
36
|
+
// after — the type is produced by DuckDB and regenerated on change
|
|
43
37
|
const orders = sql('select id, total from orders');
|
|
44
38
|
|
|
45
39
|
const rows = await db.all(orders);
|
|
46
|
-
// { id: number | null; total: number | null }[]
|
|
47
|
-
// — produced by DuckDB, regenerated on change
|
|
40
|
+
// ^? { id: number | null; total: number | null }[]
|
|
48
41
|
```
|
|
49
42
|
|
|
50
|
-
</td></tr></table>
|
|
51
|
-
|
|
52
43
|
**Value normalisers** — every project grows its own `instanceof DuckDBDecimalValue` branch.
|
|
53
44
|
|
|
54
|
-
<table><tr><td>
|
|
55
|
-
|
|
56
45
|
```ts
|
|
46
|
+
// before
|
|
57
47
|
const rows = reader.getRowObjects().map((r) => ({
|
|
58
48
|
total: (r.total as DuckDBDecimalValue).toDouble(),
|
|
59
|
-
placed_at: new Date(Number(
|
|
60
|
-
(r.placed_at as DuckDBTimestampValue).micros / 1000n
|
|
61
|
-
)),
|
|
49
|
+
placed_at: new Date(Number((r.placed_at as DuckDBTimestampValue).micros / 1000n)),
|
|
62
50
|
}));
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
</td><td>
|
|
66
51
|
|
|
67
|
-
|
|
52
|
+
// after — the @duckdb/node-api JS converter, and the types say what it returns
|
|
68
53
|
const rows = await db.all(orders);
|
|
69
|
-
rows[0].total;
|
|
70
|
-
rows[0].placed_at;
|
|
71
|
-
// the @duckdb/node-api JS converter,
|
|
72
|
-
// and the types say what it returns
|
|
54
|
+
rows[0].total; // number
|
|
55
|
+
rows[0].placed_at; // Date
|
|
73
56
|
```
|
|
74
57
|
|
|
75
|
-
</td></tr></table>
|
|
76
|
-
|
|
77
58
|
**Caught by `tsc`, not in production** — typos in columns and parameters are compile errors.
|
|
78
59
|
|
|
79
|
-
<table><tr><td>
|
|
80
|
-
|
|
81
60
|
```ts
|
|
61
|
+
// before
|
|
82
62
|
const rows = reader.getRowObjectsJS();
|
|
83
|
-
rows[0].totl;
|
|
84
|
-
prepared.bind({ name: 'ann' });
|
|
85
|
-
```
|
|
63
|
+
rows[0].totl; // undefined at runtime
|
|
64
|
+
prepared.bind({ name: 'ann' }); // throws at runtime
|
|
86
65
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
```ts
|
|
66
|
+
// after
|
|
90
67
|
const rows = await db.all(ordersFor, { customer: 'ann' });
|
|
91
|
-
rows[0].totl;
|
|
68
|
+
rows[0].totl; // error: no property 'totl'
|
|
92
69
|
await db.all(ordersFor, { name: 'ann' }); // error: expected { customer }
|
|
93
70
|
```
|
|
94
71
|
|
|
95
|
-
</td></tr></table>
|
|
96
|
-
|
|
97
72
|
## In the editor
|
|
98
73
|
|
|
99
74
|

|
package/dist/cli.js
CHANGED
|
@@ -240,6 +240,7 @@ async function main(argv, options = {}) {
|
|
|
240
240
|
} else await writeFile(out, result.declarations);
|
|
241
241
|
const typed = result.queries.length - result.diagnostics.length;
|
|
242
242
|
stderr(`${rel(cwd, out)}: ${typed} of ${result.queries.length} queries typed`);
|
|
243
|
+
if (result.queries.length === 0) stderr(`no sql() calls found in ${generateOptions.include.join(" ")} under ${cwd}; sql must be imported from 'duck-types' (run from the project directory, or pass --cwd)`);
|
|
243
244
|
return result.diagnostics.length > 0 ? 1 : 0;
|
|
244
245
|
};
|
|
245
246
|
const code = await runOnce();
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","names":[],"sources":["../src/generate/emit.ts","../src/generate/scan.ts","../src/generate/index.ts","../src/cli/main.ts","../src/cli.ts"],"sourcesContent":["import type { QueryDescription } from '../describe.js';\nimport { nullable, propertyKey, stringLiteral, tsTypeFor } from '../types/emit.js';\n\nexport interface GeneratedEntry {\n text: string;\n description: QueryDescription;\n}\n\n/** Renders the `duck-types.d.ts` module augmentation for the given queries. */\nexport function emitDeclarations(entries: readonly GeneratedEntry[]): string {\n const sorted = [...entries].sort((a, b) => (a.text < b.text ? -1 : a.text > b.text ? 1 : 0));\n const lines: string[] = [\n '// Generated by duck-types. Do not edit.',\n \"import type { JS } from '@duckdb/node-api';\",\n '',\n \"declare module 'duck-types' {\",\n ' interface Queries {',\n ];\n for (const { text, description } of sorted) {\n lines.push(` ${stringLiteral(text)}: {`);\n lines.push(` row: ${rowType(description)};`);\n lines.push(` params: ${paramsType(description)};`);\n lines.push(' };');\n }\n lines.push(' }', '}', '');\n return lines.join('\\n');\n}\n\nexport function rowType(description: QueryDescription): string {\n if (description.columns.length === 0) {\n return '{}';\n }\n const entries = description.columns.map(\n (c) =>\n `${propertyKey(c.key)}: ${c.type === null ? 'unknown' : nullable(tsTypeFor(c.type, 'output'))}`,\n );\n return `{ ${entries.join('; ')} }`;\n}\n\nexport function paramsType(description: QueryDescription): string {\n const { params, positional } = description;\n if (params.length === 0) {\n return '[]';\n }\n const render = (p: QueryDescription['params'][number]) =>\n p.type === null ? 'unknown' : nullable(tsTypeFor(p.type, 'input'));\n if (positional) {\n return `[${params.map(render).join(', ')}]`;\n }\n return `{ ${params.map((p) => `${propertyKey(p.name)}: ${render(p)}`).join('; ')} }`;\n}\n","import { readFile } from 'node:fs/promises';\nimport { parse } from '@babel/parser';\nimport type { Node, SourceLocation } from '@babel/types';\n\nexport interface FoundQuery {\n text: string;\n file: string;\n line: number;\n column: number;\n}\n\n/**\n * Finds every `sql('...')` call in a TypeScript source file whose `sql` is\n * imported from `duck-types`, and returns the literal text passed to it.\n *\n * Only string literals and `${}`-free template literals count: anything else\n * cannot be typed by name, so the generator ignores it the way the type\n * lookup would.\n */\nexport async function scanFile(file: string): Promise<FoundQuery[]> {\n return scanSource(await readFile(file, 'utf8'), file);\n}\n\nexport function scanSource(code: string, file: string): FoundQuery[] {\n const ast = parse(code, {\n sourceType: 'module',\n plugins: ['typescript', ...(file.endsWith('.tsx') ? (['jsx'] as const) : [])],\n errorRecovery: true,\n });\n const names = sqlBindings(ast.program.body);\n if (names.size === 0) {\n return [];\n }\n const found: FoundQuery[] = [];\n walk(ast.program, (node) => {\n if (\n node.type !== 'CallExpression' ||\n node.callee.type !== 'Identifier' ||\n !names.has(node.callee.name) ||\n node.arguments.length !== 1\n ) {\n return;\n }\n const arg = node.arguments[0];\n if (!arg) {\n return;\n }\n let text: string | undefined;\n if (arg.type === 'StringLiteral') {\n text = arg.value;\n } else if (arg.type === 'TemplateLiteral' && arg.expressions.length === 0) {\n text = arg.quasis[0]?.value.cooked ?? undefined;\n }\n if (text !== undefined) {\n found.push({ text, file, ...position(arg.loc) });\n }\n });\n return found;\n}\n\n/** Local names bound to duck-types's `sql`: `import { sql }` and `import { sql as q }`. */\nfunction sqlBindings(statements: readonly Node[]): Set<string> {\n const names = new Set<string>();\n for (const statement of statements) {\n if (statement.type !== 'ImportDeclaration' || statement.source.value !== 'duck-types') {\n continue;\n }\n for (const specifier of statement.specifiers) {\n if (specifier.type !== 'ImportSpecifier') {\n continue;\n }\n const imported =\n specifier.imported.type === 'Identifier'\n ? specifier.imported.name\n : specifier.imported.value;\n if (imported === 'sql') {\n names.add(specifier.local.name);\n }\n }\n }\n return names;\n}\n\nfunction position(loc: SourceLocation | null | undefined): { line: number; column: number } {\n return { line: loc?.start.line ?? 0, column: (loc?.start.column ?? -1) + 1 };\n}\n\nfunction walk(node: Node, visit: (node: Node) => void): void {\n visit(node);\n for (const key of Object.keys(node)) {\n if (key === 'loc' || key === 'leadingComments' || key === 'trailingComments') {\n continue;\n }\n const child = (node as unknown as Record<string, unknown>)[key];\n if (Array.isArray(child)) {\n for (const item of child) {\n if (isNode(item)) {\n walk(item, visit);\n }\n }\n } else if (isNode(child)) {\n walk(child, visit);\n }\n }\n}\n\nfunction isNode(value: unknown): value is Node {\n return typeof value === 'object' && value !== null && typeof (value as Node).type === 'string';\n}\n","import { readFile } from 'node:fs/promises';\nimport { resolve } from 'node:path';\nimport { DuckDBInstance } from '@duckdb/node-api';\nimport { glob } from 'tinyglobby';\nimport { describeQuery } from '../describe.js';\nimport { emitDeclarations, type GeneratedEntry } from './emit.js';\nimport { type FoundQuery, scanFile } from './scan.js';\n\nexport interface GenerateOptions {\n /** Globs of source files to scan. */\n include: string[];\n /** Database file to describe queries against; in-memory when omitted. */\n database?: string | undefined;\n /** SQL file to run first, e.g. the schema DDL. */\n init?: string | undefined;\n /** Directory globs, `database` and `init` are resolved against; also where relative file paths in SQL point. */\n cwd?: string | undefined;\n}\n\nexport interface Diagnostic {\n query: FoundQuery;\n message: string;\n}\n\nexport interface GenerateResult {\n declarations: string;\n queries: FoundQuery[];\n diagnostics: Diagnostic[];\n}\n\nexport async function generate(options: GenerateOptions): Promise<GenerateResult> {\n const cwd = options.cwd ?? process.cwd();\n const files = await glob(options.include, {\n cwd,\n absolute: true,\n ignore: ['**/node_modules/**'],\n });\n const queries = (await Promise.all(files.sort().map(scanFile))).flat();\n\n const instance = await DuckDBInstance.create(\n options.database ? resolve(cwd, options.database) : ':memory:',\n { access_mode: options.database ? 'READ_ONLY' : 'READ_WRITE' },\n );\n const connection = await instance.connect();\n try {\n // Relative paths in SQL (`read_parquet('data/x.parquet')`) resolve against\n // the process cwd; point DuckDB at the project directory instead.\n await connection.run(`set file_search_path = ${quoted(cwd)}`);\n if (options.init) {\n await connection.run(await readFile(resolve(cwd, options.init), 'utf8'));\n }\n const entries = new Map<string, GeneratedEntry>();\n const diagnostics: Diagnostic[] = [];\n for (const query of queries) {\n if (entries.has(query.text)) {\n continue;\n }\n try {\n const description = await describeQuery(connection, query.text);\n entries.set(query.text, { text: query.text, description });\n for (const p of description.params) {\n if (p.type === null) {\n diagnostics.push({\n query,\n message: `cannot infer the type of parameter $${p.name}; add a cast, e.g. $${p.name}::integer`,\n });\n }\n }\n } catch (error) {\n diagnostics.push({ query, message: describeError(error) });\n }\n }\n return { declarations: emitDeclarations([...entries.values()]), queries, diagnostics };\n } finally {\n connection.closeSync();\n instance.closeSync();\n }\n}\n\nfunction describeError(error: unknown): string {\n const message = (error as Error).message.split('\\n')[0] ?? String(error);\n if (/No files found that match/.test(message)) {\n return `${message} (relative paths resolve against the project directory; pass --cwd to change it)`;\n }\n return message;\n}\n\nfunction quoted(value: string): string {\n return `'${value.replaceAll(\"'\", \"''\")}'`;\n}\n","import { watch } from 'node:fs';\nimport { readFile, writeFile } from 'node:fs/promises';\nimport { dirname, relative, resolve, sep } from 'node:path';\nimport { parseArgs } from 'node:util';\nimport { type GenerateOptions, generate } from '../generate/index.js';\n\nexport const USAGE = `Usage: duck-types generate [globs...] [options]\n\nScans TypeScript sources for sql('...') calls, describes each query against\nDuckDB, and writes their row and parameter types as a module augmentation.\n\nOptions:\n -d, --database <file> DuckDB file to describe queries against (opened read-only)\n -i, --init <file> SQL to run first, e.g. schema DDL (in-memory by default)\n -o, --out <file> Output file (default: duck-types.d.ts)\n -c, --cwd <dir> Project directory (default: current directory)\n --check Exit 1 if the output file is out of date; write nothing\n -w, --watch Regenerate when source files change\n -h, --help\n\nDefaults come from duck-types.config.json in the project directory when present:\n { \"include\": [\"src/**/*.ts\"], \"database\": \"...\", \"init\": \"...\", \"out\": \"...\" }\nDefault globs: src/**/*.ts src/**/*.tsx`;\n\nexport interface Config {\n include?: string[];\n database?: string;\n init?: string;\n out?: string;\n}\n\nexport interface MainOptions {\n cwd?: string;\n stdout?: (line: string) => void;\n stderr?: (line: string) => void;\n /** Stops `--watch`. */\n signal?: AbortSignal;\n}\n\nexport async function main(argv: string[], options: MainOptions = {}): Promise<number> {\n const stdout = options.stdout ?? ((line) => console.log(line));\n const stderr = options.stderr ?? ((line) => console.error(line));\n\n const { values, positionals } = parseArgs({\n args: argv,\n allowPositionals: true,\n options: {\n database: { type: 'string', short: 'd' },\n init: { type: 'string', short: 'i' },\n out: { type: 'string', short: 'o' },\n cwd: { type: 'string', short: 'c' },\n check: { type: 'boolean', default: false },\n watch: { type: 'boolean', short: 'w', default: false },\n help: { type: 'boolean', short: 'h', default: false },\n },\n });\n const [command, ...globs] = positionals;\n if (values.help || command !== 'generate') {\n stdout(USAGE);\n return values.help ? 0 : 1;\n }\n\n const cwd = resolve(options.cwd ?? process.cwd(), values.cwd ?? '.');\n const config = await readConfig(cwd);\n const generateOptions: GenerateOptions = {\n include: globs.length > 0 ? globs : (config.include ?? ['src/**/*.ts', 'src/**/*.tsx']),\n database: values.database ?? config.database,\n init: values.init ?? config.init,\n cwd,\n };\n const out = resolve(cwd, values.out ?? config.out ?? 'duck-types.d.ts');\n\n const runOnce = async (): Promise<number> => {\n const result = await generate(generateOptions);\n for (const { query, message } of result.diagnostics) {\n stderr(`${rel(cwd, query.file)}:${query.line}:${query.column}: ${message}`);\n }\n if (values.check) {\n const current = await readFile(out, 'utf8').catch(() => null);\n if (current !== result.declarations) {\n stderr(`${rel(cwd, out)} is out of date; run duck-types generate`);\n return 1;\n }\n } else {\n await writeFile(out, result.declarations);\n }\n const typed = result.queries.length - result.diagnostics.length;\n stderr(`${rel(cwd, out)}: ${typed} of ${result.queries.length} queries typed`);\n return result.diagnostics.length > 0 ? 1 : 0;\n };\n\n const code = await runOnce();\n if (!values.watch) {\n return code;\n }\n await watchAndRerun(cwd, generateOptions.include, out, runOnce, options.signal);\n return 0;\n}\n\n/** A path relative to `cwd`, with `/` separators on every platform so messages are stable. */\nfunction rel(cwd: string, path: string): string {\n return relative(cwd, path).split(sep).join('/');\n}\n\nasync function readConfig(cwd: string): Promise<Config> {\n const text = await readFile(resolve(cwd, 'duck-types.config.json'), 'utf8').catch(() => null);\n return text === null ? {} : (JSON.parse(text) as Config);\n}\n\n/** Watches the directories the globs start in and reruns on any change, debounced. */\nfunction watchAndRerun(\n cwd: string,\n include: string[],\n out: string,\n rerun: () => Promise<number>,\n signal?: AbortSignal,\n): Promise<void> {\n const roots = new Set(include.map((g) => resolve(cwd, globRoot(g))));\n return new Promise((done) => {\n let timer: NodeJS.Timeout | undefined;\n let running = Promise.resolve();\n const watchers = [...roots].map((root) =>\n watch(root, { recursive: true }, (_event, file) => {\n if (file && resolve(root, file) === out) {\n return;\n }\n clearTimeout(timer);\n timer = setTimeout(() => {\n running = running.then(() => rerun().then(() => undefined));\n }, 100);\n }),\n );\n const stop = () => {\n clearTimeout(timer);\n for (const w of watchers) {\n w.close();\n }\n running.then(done);\n };\n if (signal?.aborted) {\n stop();\n } else {\n signal?.addEventListener('abort', stop, { once: true });\n }\n });\n}\n\n/** The literal directory prefix of a glob: `src/**\\/*.ts` → `src`, `*.ts` → `.`. */\nexport function globRoot(pattern: string): string {\n const parts = pattern.split('/');\n const literal: string[] = [];\n for (const part of parts) {\n if (/[*?[{]/.test(part)) {\n break;\n }\n literal.push(part);\n }\n if (literal.length === parts.length) {\n return dirname(pattern);\n }\n return literal.length > 0 ? literal.join('/') : '.';\n}\n","#!/usr/bin/env node\nimport { main } from './cli/main.js';\n\nconst controller = new AbortController();\nprocess.once('SIGINT', () => controller.abort());\nprocess.exitCode = await main(process.argv.slice(2), { signal: controller.signal });\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.js","names":[],"sources":["../src/generate/emit.ts","../src/generate/scan.ts","../src/generate/index.ts","../src/cli/main.ts","../src/cli.ts"],"sourcesContent":["import type { QueryDescription } from '../describe.js';\nimport { nullable, propertyKey, stringLiteral, tsTypeFor } from '../types/emit.js';\n\nexport interface GeneratedEntry {\n text: string;\n description: QueryDescription;\n}\n\n/** Renders the `duck-types.d.ts` module augmentation for the given queries. */\nexport function emitDeclarations(entries: readonly GeneratedEntry[]): string {\n const sorted = [...entries].sort((a, b) => (a.text < b.text ? -1 : a.text > b.text ? 1 : 0));\n const lines: string[] = [\n '// Generated by duck-types. Do not edit.',\n \"import type { JS } from '@duckdb/node-api';\",\n '',\n \"declare module 'duck-types' {\",\n ' interface Queries {',\n ];\n for (const { text, description } of sorted) {\n lines.push(` ${stringLiteral(text)}: {`);\n lines.push(` row: ${rowType(description)};`);\n lines.push(` params: ${paramsType(description)};`);\n lines.push(' };');\n }\n lines.push(' }', '}', '');\n return lines.join('\\n');\n}\n\nexport function rowType(description: QueryDescription): string {\n if (description.columns.length === 0) {\n return '{}';\n }\n const entries = description.columns.map(\n (c) =>\n `${propertyKey(c.key)}: ${c.type === null ? 'unknown' : nullable(tsTypeFor(c.type, 'output'))}`,\n );\n return `{ ${entries.join('; ')} }`;\n}\n\nexport function paramsType(description: QueryDescription): string {\n const { params, positional } = description;\n if (params.length === 0) {\n return '[]';\n }\n const render = (p: QueryDescription['params'][number]) =>\n p.type === null ? 'unknown' : nullable(tsTypeFor(p.type, 'input'));\n if (positional) {\n return `[${params.map(render).join(', ')}]`;\n }\n return `{ ${params.map((p) => `${propertyKey(p.name)}: ${render(p)}`).join('; ')} }`;\n}\n","import { readFile } from 'node:fs/promises';\nimport { parse } from '@babel/parser';\nimport type { Node, SourceLocation } from '@babel/types';\n\nexport interface FoundQuery {\n text: string;\n file: string;\n line: number;\n column: number;\n}\n\n/**\n * Finds every `sql('...')` call in a TypeScript source file whose `sql` is\n * imported from `duck-types`, and returns the literal text passed to it.\n *\n * Only string literals and `${}`-free template literals count: anything else\n * cannot be typed by name, so the generator ignores it the way the type\n * lookup would.\n */\nexport async function scanFile(file: string): Promise<FoundQuery[]> {\n return scanSource(await readFile(file, 'utf8'), file);\n}\n\nexport function scanSource(code: string, file: string): FoundQuery[] {\n const ast = parse(code, {\n sourceType: 'module',\n plugins: ['typescript', ...(file.endsWith('.tsx') ? (['jsx'] as const) : [])],\n errorRecovery: true,\n });\n const names = sqlBindings(ast.program.body);\n if (names.size === 0) {\n return [];\n }\n const found: FoundQuery[] = [];\n walk(ast.program, (node) => {\n if (\n node.type !== 'CallExpression' ||\n node.callee.type !== 'Identifier' ||\n !names.has(node.callee.name) ||\n node.arguments.length !== 1\n ) {\n return;\n }\n const arg = node.arguments[0];\n if (!arg) {\n return;\n }\n let text: string | undefined;\n if (arg.type === 'StringLiteral') {\n text = arg.value;\n } else if (arg.type === 'TemplateLiteral' && arg.expressions.length === 0) {\n text = arg.quasis[0]?.value.cooked ?? undefined;\n }\n if (text !== undefined) {\n found.push({ text, file, ...position(arg.loc) });\n }\n });\n return found;\n}\n\n/** Local names bound to duck-types's `sql`: `import { sql }` and `import { sql as q }`. */\nfunction sqlBindings(statements: readonly Node[]): Set<string> {\n const names = new Set<string>();\n for (const statement of statements) {\n if (statement.type !== 'ImportDeclaration' || statement.source.value !== 'duck-types') {\n continue;\n }\n for (const specifier of statement.specifiers) {\n if (specifier.type !== 'ImportSpecifier') {\n continue;\n }\n const imported =\n specifier.imported.type === 'Identifier'\n ? specifier.imported.name\n : specifier.imported.value;\n if (imported === 'sql') {\n names.add(specifier.local.name);\n }\n }\n }\n return names;\n}\n\nfunction position(loc: SourceLocation | null | undefined): { line: number; column: number } {\n return { line: loc?.start.line ?? 0, column: (loc?.start.column ?? -1) + 1 };\n}\n\nfunction walk(node: Node, visit: (node: Node) => void): void {\n visit(node);\n for (const key of Object.keys(node)) {\n if (key === 'loc' || key === 'leadingComments' || key === 'trailingComments') {\n continue;\n }\n const child = (node as unknown as Record<string, unknown>)[key];\n if (Array.isArray(child)) {\n for (const item of child) {\n if (isNode(item)) {\n walk(item, visit);\n }\n }\n } else if (isNode(child)) {\n walk(child, visit);\n }\n }\n}\n\nfunction isNode(value: unknown): value is Node {\n return typeof value === 'object' && value !== null && typeof (value as Node).type === 'string';\n}\n","import { readFile } from 'node:fs/promises';\nimport { resolve } from 'node:path';\nimport { DuckDBInstance } from '@duckdb/node-api';\nimport { glob } from 'tinyglobby';\nimport { describeQuery } from '../describe.js';\nimport { emitDeclarations, type GeneratedEntry } from './emit.js';\nimport { type FoundQuery, scanFile } from './scan.js';\n\nexport interface GenerateOptions {\n /** Globs of source files to scan. */\n include: string[];\n /** Database file to describe queries against; in-memory when omitted. */\n database?: string | undefined;\n /** SQL file to run first, e.g. the schema DDL. */\n init?: string | undefined;\n /** Directory globs, `database` and `init` are resolved against; also where relative file paths in SQL point. */\n cwd?: string | undefined;\n}\n\nexport interface Diagnostic {\n query: FoundQuery;\n message: string;\n}\n\nexport interface GenerateResult {\n declarations: string;\n queries: FoundQuery[];\n diagnostics: Diagnostic[];\n}\n\nexport async function generate(options: GenerateOptions): Promise<GenerateResult> {\n const cwd = options.cwd ?? process.cwd();\n const files = await glob(options.include, {\n cwd,\n absolute: true,\n ignore: ['**/node_modules/**'],\n });\n const queries = (await Promise.all(files.sort().map(scanFile))).flat();\n\n const instance = await DuckDBInstance.create(\n options.database ? resolve(cwd, options.database) : ':memory:',\n { access_mode: options.database ? 'READ_ONLY' : 'READ_WRITE' },\n );\n const connection = await instance.connect();\n try {\n // Relative paths in SQL (`read_parquet('data/x.parquet')`) resolve against\n // the process cwd; point DuckDB at the project directory instead.\n await connection.run(`set file_search_path = ${quoted(cwd)}`);\n if (options.init) {\n await connection.run(await readFile(resolve(cwd, options.init), 'utf8'));\n }\n const entries = new Map<string, GeneratedEntry>();\n const diagnostics: Diagnostic[] = [];\n for (const query of queries) {\n if (entries.has(query.text)) {\n continue;\n }\n try {\n const description = await describeQuery(connection, query.text);\n entries.set(query.text, { text: query.text, description });\n for (const p of description.params) {\n if (p.type === null) {\n diagnostics.push({\n query,\n message: `cannot infer the type of parameter $${p.name}; add a cast, e.g. $${p.name}::integer`,\n });\n }\n }\n } catch (error) {\n diagnostics.push({ query, message: describeError(error) });\n }\n }\n return { declarations: emitDeclarations([...entries.values()]), queries, diagnostics };\n } finally {\n connection.closeSync();\n instance.closeSync();\n }\n}\n\nfunction describeError(error: unknown): string {\n const message = (error as Error).message.split('\\n')[0] ?? String(error);\n if (/No files found that match/.test(message)) {\n return `${message} (relative paths resolve against the project directory; pass --cwd to change it)`;\n }\n return message;\n}\n\nfunction quoted(value: string): string {\n return `'${value.replaceAll(\"'\", \"''\")}'`;\n}\n","import { watch } from 'node:fs';\nimport { readFile, writeFile } from 'node:fs/promises';\nimport { dirname, relative, resolve, sep } from 'node:path';\nimport { parseArgs } from 'node:util';\nimport { type GenerateOptions, generate } from '../generate/index.js';\n\nexport const USAGE = `Usage: duck-types generate [globs...] [options]\n\nScans TypeScript sources for sql('...') calls, describes each query against\nDuckDB, and writes their row and parameter types as a module augmentation.\n\nOptions:\n -d, --database <file> DuckDB file to describe queries against (opened read-only)\n -i, --init <file> SQL to run first, e.g. schema DDL (in-memory by default)\n -o, --out <file> Output file (default: duck-types.d.ts)\n -c, --cwd <dir> Project directory (default: current directory)\n --check Exit 1 if the output file is out of date; write nothing\n -w, --watch Regenerate when source files change\n -h, --help\n\nDefaults come from duck-types.config.json in the project directory when present:\n { \"include\": [\"src/**/*.ts\"], \"database\": \"...\", \"init\": \"...\", \"out\": \"...\" }\nDefault globs: src/**/*.ts src/**/*.tsx`;\n\nexport interface Config {\n include?: string[];\n database?: string;\n init?: string;\n out?: string;\n}\n\nexport interface MainOptions {\n cwd?: string;\n stdout?: (line: string) => void;\n stderr?: (line: string) => void;\n /** Stops `--watch`. */\n signal?: AbortSignal;\n}\n\nexport async function main(argv: string[], options: MainOptions = {}): Promise<number> {\n const stdout = options.stdout ?? ((line) => console.log(line));\n const stderr = options.stderr ?? ((line) => console.error(line));\n\n const { values, positionals } = parseArgs({\n args: argv,\n allowPositionals: true,\n options: {\n database: { type: 'string', short: 'd' },\n init: { type: 'string', short: 'i' },\n out: { type: 'string', short: 'o' },\n cwd: { type: 'string', short: 'c' },\n check: { type: 'boolean', default: false },\n watch: { type: 'boolean', short: 'w', default: false },\n help: { type: 'boolean', short: 'h', default: false },\n },\n });\n const [command, ...globs] = positionals;\n if (values.help || command !== 'generate') {\n stdout(USAGE);\n return values.help ? 0 : 1;\n }\n\n const cwd = resolve(options.cwd ?? process.cwd(), values.cwd ?? '.');\n const config = await readConfig(cwd);\n const generateOptions: GenerateOptions = {\n include: globs.length > 0 ? globs : (config.include ?? ['src/**/*.ts', 'src/**/*.tsx']),\n database: values.database ?? config.database,\n init: values.init ?? config.init,\n cwd,\n };\n const out = resolve(cwd, values.out ?? config.out ?? 'duck-types.d.ts');\n\n const runOnce = async (): Promise<number> => {\n const result = await generate(generateOptions);\n for (const { query, message } of result.diagnostics) {\n stderr(`${rel(cwd, query.file)}:${query.line}:${query.column}: ${message}`);\n }\n if (values.check) {\n const current = await readFile(out, 'utf8').catch(() => null);\n if (current !== result.declarations) {\n stderr(`${rel(cwd, out)} is out of date; run duck-types generate`);\n return 1;\n }\n } else {\n await writeFile(out, result.declarations);\n }\n const typed = result.queries.length - result.diagnostics.length;\n stderr(`${rel(cwd, out)}: ${typed} of ${result.queries.length} queries typed`);\n if (result.queries.length === 0) {\n stderr(\n `no sql() calls found in ${generateOptions.include.join(' ')} under ${cwd}; ` +\n \"sql must be imported from 'duck-types' (run from the project directory, or pass --cwd)\",\n );\n }\n return result.diagnostics.length > 0 ? 1 : 0;\n };\n\n const code = await runOnce();\n if (!values.watch) {\n return code;\n }\n await watchAndRerun(cwd, generateOptions.include, out, runOnce, options.signal);\n return 0;\n}\n\n/** A path relative to `cwd`, with `/` separators on every platform so messages are stable. */\nfunction rel(cwd: string, path: string): string {\n return relative(cwd, path).split(sep).join('/');\n}\n\nasync function readConfig(cwd: string): Promise<Config> {\n const text = await readFile(resolve(cwd, 'duck-types.config.json'), 'utf8').catch(() => null);\n return text === null ? {} : (JSON.parse(text) as Config);\n}\n\n/** Watches the directories the globs start in and reruns on any change, debounced. */\nfunction watchAndRerun(\n cwd: string,\n include: string[],\n out: string,\n rerun: () => Promise<number>,\n signal?: AbortSignal,\n): Promise<void> {\n const roots = new Set(include.map((g) => resolve(cwd, globRoot(g))));\n return new Promise((done) => {\n let timer: NodeJS.Timeout | undefined;\n let running = Promise.resolve();\n const watchers = [...roots].map((root) =>\n watch(root, { recursive: true }, (_event, file) => {\n if (file && resolve(root, file) === out) {\n return;\n }\n clearTimeout(timer);\n timer = setTimeout(() => {\n running = running.then(() => rerun().then(() => undefined));\n }, 100);\n }),\n );\n const stop = () => {\n clearTimeout(timer);\n for (const w of watchers) {\n w.close();\n }\n running.then(done);\n };\n if (signal?.aborted) {\n stop();\n } else {\n signal?.addEventListener('abort', stop, { once: true });\n }\n });\n}\n\n/** The literal directory prefix of a glob: `src/**\\/*.ts` → `src`, `*.ts` → `.`. */\nexport function globRoot(pattern: string): string {\n const parts = pattern.split('/');\n const literal: string[] = [];\n for (const part of parts) {\n if (/[*?[{]/.test(part)) {\n break;\n }\n literal.push(part);\n }\n if (literal.length === parts.length) {\n return dirname(pattern);\n }\n return literal.length > 0 ? literal.join('/') : '.';\n}\n","#!/usr/bin/env node\nimport { main } from './cli/main.js';\n\nconst controller = new AbortController();\nprocess.once('SIGINT', () => controller.abort());\nprocess.exitCode = await main(process.argv.slice(2), { signal: controller.signal });\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "duck-types",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Type-safe DuckDB client for TypeScript — row types inferred from your SQL, plain JS values out of the box.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"author": "
|
|
6
|
+
"author": "Alex Gavrilov",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "git+https://github.com/gavricci/duck-types.git"
|
|
@@ -39,6 +39,17 @@
|
|
|
39
39
|
"engines": {
|
|
40
40
|
"node": ">=20"
|
|
41
41
|
},
|
|
42
|
+
"packageManager": "pnpm@10.33.0",
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsdown",
|
|
45
|
+
"dev": "tsdown --watch",
|
|
46
|
+
"test": "vitest run",
|
|
47
|
+
"test:watch": "vitest",
|
|
48
|
+
"typecheck": "tsc --noEmit",
|
|
49
|
+
"lint": "biome check .",
|
|
50
|
+
"lint:fix": "biome check --write .",
|
|
51
|
+
"prepublishOnly": "pnpm typecheck && pnpm test && pnpm build"
|
|
52
|
+
},
|
|
42
53
|
"peerDependencies": {
|
|
43
54
|
"@duckdb/node-api": ">=2.0.0-r.1"
|
|
44
55
|
},
|
|
@@ -61,14 +72,5 @@
|
|
|
61
72
|
},
|
|
62
73
|
"publishConfig": {
|
|
63
74
|
"access": "public"
|
|
64
|
-
},
|
|
65
|
-
"scripts": {
|
|
66
|
-
"build": "tsdown",
|
|
67
|
-
"dev": "tsdown --watch",
|
|
68
|
-
"test": "vitest run",
|
|
69
|
-
"test:watch": "vitest",
|
|
70
|
-
"typecheck": "tsc --noEmit",
|
|
71
|
-
"lint": "biome check .",
|
|
72
|
-
"lint:fix": "biome check --write ."
|
|
73
75
|
}
|
|
74
|
-
}
|
|
76
|
+
}
|