@kequtech/tenon-orm 0.3.0 → 0.4.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 (63) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/README.md +27 -5
  3. package/dist/affected-rows.js +0 -1
  4. package/dist/baseline.js +3 -5
  5. package/dist/check/contracts.d.ts +6 -0
  6. package/dist/check/contracts.js +206 -0
  7. package/dist/check/project.d.ts +18 -0
  8. package/dist/check/project.js +224 -0
  9. package/dist/check/source.d.ts +51 -0
  10. package/dist/check/source.js +629 -0
  11. package/dist/check/types.d.ts +36 -0
  12. package/dist/check/types.js +1 -0
  13. package/dist/check.d.ts +4 -0
  14. package/dist/check.js +147 -0
  15. package/dist/cli.js +38 -9
  16. package/dist/connection.d.ts +5 -12
  17. package/dist/connection.js +12 -121
  18. package/dist/database.js +5 -8
  19. package/dist/describe.js +10 -3
  20. package/dist/generate.d.ts +2 -3
  21. package/dist/generate.js +26 -24
  22. package/dist/generation/catalog.d.ts +5 -6
  23. package/dist/generation/catalog.js +7 -9
  24. package/dist/generation/render.d.ts +3 -0
  25. package/dist/generation/render.js +60 -0
  26. package/dist/generation/schema-summary.d.ts +3 -3
  27. package/dist/generation/schema-summary.js +2 -2
  28. package/dist/index.d.ts +2 -0
  29. package/dist/index.js +2 -0
  30. package/dist/inspection/catalog.d.ts +3 -6
  31. package/dist/inspection/catalog.js +9 -18
  32. package/dist/inspection/cli.d.ts +12 -0
  33. package/dist/inspection/cli.js +9 -4
  34. package/dist/inspection/connection.d.ts +2 -3
  35. package/dist/inspection/connection.js +20 -22
  36. package/dist/inspection/options.js +0 -6
  37. package/dist/inspection/result.d.ts +3 -2
  38. package/dist/inspection/result.js +13 -26
  39. package/dist/inspection/types.d.ts +6 -4
  40. package/dist/migrations/connection.d.ts +3 -3
  41. package/dist/migrations/connection.js +14 -21
  42. package/dist/migrations/history.d.ts +4 -4
  43. package/dist/migrations/history.js +5 -5
  44. package/dist/migrations.js +7 -9
  45. package/dist/query-options.d.ts +1 -1
  46. package/dist/query.js +1 -1
  47. package/dist/transaction.d.ts +2 -2
  48. package/dist/transaction.js +21 -77
  49. package/dist/types.d.ts +1 -1
  50. package/docs/adoption-guide.md +64 -12
  51. package/docs/agent-guide.md +57 -27
  52. package/docs/human-guide.md +53 -18
  53. package/docs/mutations.md +22 -9
  54. package/docs/query-checking.md +200 -0
  55. package/docs/query-inspection.md +40 -13
  56. package/docs/reference.md +330 -108
  57. package/package.json +11 -7
  58. package/dist/driver/types.d.ts +0 -10
  59. package/dist/driver/types.js +0 -49
  60. package/dist/generation/table-types.d.ts +0 -2
  61. package/dist/generation/table-types.js +0 -96
  62. package/dist/inspection/protocol.d.ts +0 -7
  63. package/dist/inspection/protocol.js +0 -45
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kequtech/tenon-orm",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "license": "ISC",
5
5
  "type": "module",
6
6
  "description": "A PostgreSQL ORM designed for coding agents, with explicit SQL, typed queries, and migrations",
@@ -28,6 +28,10 @@
28
28
  ".": {
29
29
  "types": "./dist/index.d.ts",
30
30
  "import": "./dist/index.js"
31
+ },
32
+ "./check": {
33
+ "types": "./dist/check.d.ts",
34
+ "import": "./dist/check.js"
31
35
  }
32
36
  },
33
37
  "files": [
@@ -53,21 +57,21 @@
53
57
  "format:check": "prettier --check .",
54
58
  "benchmark": "npm run build && node scripts/benchmark.mjs"
55
59
  },
56
- "dependencies": {
57
- "pg": "8.23.1",
58
- "@types/pg": "8.23.1"
59
- },
60
60
  "devDependencies": {
61
+ "@js-temporal/polyfill": "^0.5.1",
61
62
  "@types/node": "^24.19.2",
62
63
  "prettier": "^3.9.10",
63
- "typescript": "~6.0.2",
64
64
  "typescript-next": "npm:typescript@^7.0.2"
65
65
  },
66
66
  "bin": {
67
- "tenon": "./dist/cli.js"
67
+ "tenon": "dist/cli.js"
68
68
  },
69
69
  "prettier": {
70
70
  "singleQuote": true,
71
71
  "printWidth": 100
72
+ },
73
+ "dependencies": {
74
+ "@kequtech/pgclear": "^0.3.0",
75
+ "typescript": "~6.0.2"
72
76
  }
73
77
  }
@@ -1,10 +0,0 @@
1
- /** pg default representations, shared by generation and query inspection. */
2
- export type ValueTypes = Readonly<{
3
- read: string;
4
- write: string;
5
- }>;
6
- type TypeMapping = string | ValueTypes;
7
- export declare const arrayTypes: Readonly<Record<string, TypeMapping>>;
8
- export declare const scalarTypes: Readonly<Record<string, TypeMapping>>;
9
- export declare function valueTypes(mapping: TypeMapping): ValueTypes;
10
- export {};
@@ -1,49 +0,0 @@
1
- // Arrays use verified built-in OIDs: their parsers can differ from scalar parsers.
2
- export const arrayTypes = {
3
- '1000': 'boolean',
4
- '1001': 'import("node:buffer").Buffer',
5
- '1005': 'number',
6
- '1007': 'number',
7
- '1021': 'number',
8
- '1022': 'number',
9
- '1028': 'number',
10
- '1016': { read: 'string', write: 'string | bigint' },
11
- '1231': { read: 'number', write: 'string | number' },
12
- '1009': 'string',
13
- '1014': 'string',
14
- '1015': 'string',
15
- '1183': 'string',
16
- '1270': 'string',
17
- '2951': 'string',
18
- '1182': { read: 'globalThis.Date | number', write: 'globalThis.Date | string' },
19
- '1115': { read: 'globalThis.Date | number', write: 'globalThis.Date | string' },
20
- '1185': { read: 'globalThis.Date | number', write: 'globalThis.Date | string' },
21
- '199': { read: 'unknown', write: 'string' },
22
- '3807': { read: 'unknown', write: 'string' },
23
- };
24
- export const scalarTypes = {
25
- int2: 'number',
26
- int4: 'number',
27
- oid: 'number',
28
- float4: 'number',
29
- float8: 'number',
30
- bool: 'boolean',
31
- text: 'string',
32
- name: 'string',
33
- varchar: 'string',
34
- bpchar: 'string',
35
- time: 'string',
36
- timetz: 'string',
37
- uuid: 'string',
38
- date: { read: 'globalThis.Date | number', write: 'globalThis.Date | string' },
39
- timestamp: { read: 'globalThis.Date | number', write: 'globalThis.Date | string' },
40
- timestamptz: { read: 'globalThis.Date | number', write: 'globalThis.Date | string' },
41
- int8: { read: 'string', write: 'string | bigint' },
42
- numeric: { read: 'string', write: 'string | number' },
43
- json: { read: 'unknown', write: 'string' },
44
- jsonb: { read: 'unknown', write: 'string' },
45
- bytea: 'import("node:buffer").Buffer',
46
- };
47
- export function valueTypes(mapping) {
48
- return typeof mapping === 'string' ? { read: mapping, write: mapping } : mapping;
49
- }
@@ -1,2 +0,0 @@
1
- import type { TableRow } from './catalog.js';
2
- export declare function renderTableTypes(tables: readonly TableRow[], namespace: string, onWarning: (message: string) => void): string;
@@ -1,96 +0,0 @@
1
- import { arrayTypes, scalarTypes, valueTypes } from '../driver/types.js';
2
- // Every generated table remains accessible through Rows["exact SQL name"].
3
- const reservedTypeNames = new Set(`
4
- as await break case catch class const continue debugger default delete do else enum
5
- export extends false finally for function if implements import in instanceof
6
- interface let new null package private protected public return static super
7
- switch this throw true try typeof var void while with yield any bigint boolean
8
- intrinsic never number object string symbol undefined unknown
9
- Rows globalThis PgArray
10
- `
11
- .trim()
12
- .split(/\s+/));
13
- function canExportRowName(name) {
14
- return (/^[$_\p{ID_Start}][$_\u200c\u200d\p{ID_Continue}]*$/u.test(name) && !reservedTypeNames.has(name));
15
- }
16
- function isSupportedArray(value) {
17
- return Object.hasOwn(arrayTypes, value.oid);
18
- }
19
- function columnTypes(value, column, onWarning) {
20
- let types;
21
- if (isSupportedArray(value)) {
22
- const element = valueTypes(arrayTypes[value.oid]);
23
- types = { read: `PgArray<${element.read}>`, write: `PgArray<${element.write}>` };
24
- }
25
- else if (value.typeKind === 'e') {
26
- const choice = value.choices?.map((choice) => JSON.stringify(choice)).join(' | ') || 'never';
27
- types = { read: choice, write: choice };
28
- }
29
- else {
30
- const type = value.typeSchema === 'pg_catalog' && Object.hasOwn(scalarTypes, value.type)
31
- ? scalarTypes[value.type]
32
- : undefined;
33
- if (type) {
34
- types = valueTypes(type);
35
- }
36
- else {
37
- const underlying = value.declaredOid === value.oid
38
- ? ''
39
- : ` with underlying type ${JSON.stringify(value.typeSchema)}.${JSON.stringify(value.type)} (OID ${value.oid})`;
40
- onWarning(`Column ${column} uses PostgreSQL type ${JSON.stringify(value.declaredSchema)}.${JSON.stringify(value.declaredType)} (OID ${value.declaredOid})${underlying} without a verified pg mapping; generated as unknown for reads and never for non-null writes.`);
41
- types = { read: 'unknown', write: 'never' };
42
- }
43
- }
44
- // unknown already includes null, including for nullable JSON and unmapped types.
45
- return {
46
- read: `${types.read}${value.nullable && types.read !== 'unknown' ? ' | null' : ''}`,
47
- write: `${types.write}${value.writeNullable ? ' | null' : ''}`,
48
- };
49
- }
50
- export function renderTableTypes(tables, namespace, onWarning) {
51
- const definitions = tables.map((table) => {
52
- const tableName = JSON.stringify(table.name);
53
- const columns = table.columns.map((column) => {
54
- const columnName = JSON.stringify(column.name);
55
- const path = `${JSON.stringify(namespace)}.${tableName}.${columnName}`;
56
- const types = columnTypes(column, path, onWarning);
57
- const requiredOnInsert = column.writable && !column.hasDefault && !column.writeNullable;
58
- const optionalInputType = column.writable ? `${types.write} | undefined` : 'never';
59
- return {
60
- row: ` ${columnName}: ${types.read};`,
61
- insert: ` ${columnName}${requiredOnInsert ? '' : '?'}: ${requiredOnInsert ? types.write : optionalInputType};`,
62
- update: ` ${columnName}?: ${optionalInputType};`,
63
- };
64
- });
65
- return { tableName, columns };
66
- });
67
- const render = (kind, indent = '') => definitions
68
- .map(({ tableName, columns }) => `${indent} ${tableName}: {
69
- ${columns.map((column) => indent + column[kind]).join('\n')}
70
- ${indent} };`)
71
- .join('\n');
72
- const aliases = tables
73
- .filter((table) => canExportRowName(table.name))
74
- .map((table) => `export type ${table.name} = Rows[${JSON.stringify(table.name)}];`);
75
- const arrays = tables.some((table) => table.columns.some(isSupportedArray))
76
- ? 'export type PgArray<T> = (T | null | PgArray<T>)[];\n\n'
77
- : '';
78
- return `${arrays}// Full database rows. Compose query types with Pick, indexed access, and intersections.
79
- export type Rows = {
80
- ${render('row')}
81
- };
82
-
83
- export declare namespace Rows {
84
- // Bound inputs for sql.insertValues. Missing/undefined fields use database defaults.
85
- type Insert = {
86
- ${render('insert', ' ')}
87
- };
88
-
89
- // Bound inputs for sql.assignments. Missing/undefined fields are omitted.
90
- type Update = {
91
- ${render('update', ' ')}
92
- };
93
- }
94
- ${aliases.join('\n')}
95
- `;
96
- }
@@ -1,7 +0,0 @@
1
- import type { Client, FieldDef } from 'pg';
2
- export interface Description {
3
- parameters: number[];
4
- fields: FieldDef[];
5
- }
6
- /** Describe through pg custom query dispatch; send no Bind or Execute messages. */
7
- export declare function describe(client: Client, text: string): Promise<Description>;
@@ -1,45 +0,0 @@
1
- /** Describe through pg custom query dispatch; send no Bind or Execute messages. */
2
- export function describe(client, text) {
3
- return new Promise((resolve, reject) => {
4
- let connection;
5
- const description = { parameters: [], fields: [] };
6
- const parameters = (message) => {
7
- description.parameters = message.dataTypeIDs;
8
- };
9
- const cleanup = () => connection?.removeListener('parameterDescription', parameters);
10
- const query = {
11
- // pg wraps callback to manage query_timeout, including clearing its timer.
12
- callback(error) {
13
- cleanup();
14
- if (error)
15
- reject(error);
16
- else
17
- resolve(description);
18
- },
19
- submit(wire) {
20
- connection = wire;
21
- wire.once('parameterDescription', parameters);
22
- try {
23
- wire.parse({ name: '', text, types: [] }, false);
24
- wire.describe({ type: 'S', name: '' }, false);
25
- wire.sync();
26
- }
27
- catch (error) {
28
- return error instanceof Error
29
- ? error
30
- : new Error('Describe dispatch failed', { cause: error });
31
- }
32
- },
33
- handleRowDescription(message) {
34
- description.fields = message.fields;
35
- },
36
- handleReadyForQuery() {
37
- query.callback(null);
38
- },
39
- handleError(error) {
40
- query.callback(error);
41
- },
42
- };
43
- client.query(query);
44
- });
45
- }