@an5/orm 1.0.9 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -3
- package/dist/config.d.ts +9 -1
- package/dist/config.js +13 -18
- package/dist/generator/src/api.d.ts +35 -0
- package/dist/generator/src/api.js +58 -0
- package/dist/generator/src/config.d.ts +162 -0
- package/dist/generator/src/config.js +354 -0
- package/dist/generator/src/dialect.d.ts +88 -0
- package/dist/generator/src/dialect.js +318 -0
- package/dist/generator/src/dotnet-generator.d.ts +8 -0
- package/dist/generator/src/dotnet-generator.js +282 -101
- package/dist/generator/src/field-types.d.ts +96 -0
- package/dist/generator/src/field-types.js +433 -0
- package/dist/generator/src/golang-generator.d.ts +36 -0
- package/dist/generator/src/golang-generator.js +1229 -0
- package/dist/generator/src/index.js +69 -36
- package/dist/generator/src/metadata-generator.js +2 -1
- package/dist/generator/src/parser.d.ts +28 -2
- package/dist/generator/src/parser.js +92 -90
- package/dist/generator/src/push-apply.d.ts +37 -0
- package/dist/generator/src/push-apply.js +109 -0
- package/dist/generator/src/push-schema.d.ts +93 -0
- package/dist/generator/src/push-schema.js +189 -0
- package/dist/generator/src/python-generator.d.ts +19 -1
- package/dist/generator/src/python-generator.js +147 -64
- package/dist/generator/src/rust-generator.d.ts +38 -0
- package/dist/generator/src/rust-generator.js +971 -0
- package/dist/generator/src/suggest.d.ts +17 -0
- package/dist/generator/src/suggest.js +45 -0
- package/dist/generator/src/type-kinds.d.ts +27 -0
- package/dist/generator/src/type-kinds.js +72 -0
- package/dist/generator/src/types.d.ts +33 -0
- package/dist/generator/src/types.js +39 -0
- package/dist/migration-core.d.ts +16 -1
- package/dist/migration-core.js +60 -21
- package/dist/orm.d.ts +147 -0
- package/dist/orm.js +1654 -0
- package/dist/provider-support.d.ts +27 -0
- package/dist/provider-support.js +50 -0
- package/generator/src/api.ts +45 -0
- package/generator/src/config.ts +496 -0
- package/generator/src/dialect.ts +415 -0
- package/generator/src/dotnet-generator.ts +277 -88
- package/generator/src/field-types.ts +473 -0
- package/generator/src/golang-generator.ts +89 -64
- package/generator/src/index.ts +71 -36
- package/generator/src/metadata-generator.ts +2 -2
- package/generator/src/parser.ts +122 -90
- package/generator/src/push-apply.ts +148 -0
- package/generator/src/push-schema.ts +250 -0
- package/generator/src/python-generator.ts +118 -62
- package/generator/src/rust-generator.ts +974 -0
- package/generator/src/suggest.ts +41 -0
- package/generator/src/type-kinds.ts +78 -0
- package/generator/src/types.ts +48 -0
- package/package.json +26 -6
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
export type Provider = 'mssql' | 'postgres' | 'mysql' | 'sqlite' | 'googlesheets';
|
|
2
|
+
/** Order used when printing issues and when suggesting a provider. */
|
|
3
|
+
export declare const PROVIDERS: readonly Provider[];
|
|
4
|
+
/**
|
|
5
|
+
* SQL Server is the default provider: it is the only one `db:push`, `db:pull`
|
|
6
|
+
* and `db:migrate` actually support, and it is what existing `.an5` schemas
|
|
7
|
+
* were written for. With no connection string, treat the schema as SQL Server —
|
|
8
|
+
* the same fallback `an5Adapter` uses.
|
|
9
|
+
*/
|
|
10
|
+
export declare const DEFAULT_PROVIDER: Provider;
|
|
11
|
+
/** Display name, used in error messages. */
|
|
12
|
+
export declare const PROVIDER_LABELS: Record<Provider, string>;
|
|
13
|
+
/** The TypeScript type a field is generated as. */
|
|
14
|
+
export type TsType = 'string' | 'number' | 'number | bigint' | 'boolean' | 'Date' | 'Buffer' | 'any' | 'number[] | string';
|
|
15
|
+
/**
|
|
16
|
+
* Valid column types per provider: what is written in `.an5` → the TypeScript
|
|
17
|
+
* type generated for it.
|
|
18
|
+
*
|
|
19
|
+
* Keys are stored upper case and lookups are case-insensitive. This table is
|
|
20
|
+
* the single source of truth — nowhere else is allowed to enumerate types.
|
|
21
|
+
*/
|
|
22
|
+
export declare const PROVIDER_FIELD_TYPES: Record<Provider, Readonly<Record<string, TsType>>>;
|
|
23
|
+
/** The type table of one provider. */
|
|
24
|
+
export declare function fieldTypesFor(provider: Provider): Readonly<Record<string, TsType>>;
|
|
25
|
+
/**
|
|
26
|
+
* A column type for a TypeScript type, valid on `provider`.
|
|
27
|
+
*
|
|
28
|
+
* Unknown TypeScript types fall back to that provider's string type, which is the
|
|
29
|
+
* only choice that can hold any value.
|
|
30
|
+
*/
|
|
31
|
+
export declare function defaultSqlTypeForTs(tsType: string, provider?: Provider): string;
|
|
32
|
+
/** One bad field type, with everything needed to print it. */
|
|
33
|
+
export interface FieldTypeIssue {
|
|
34
|
+
/** `Model.field`, or just the field name when there is no model. */
|
|
35
|
+
path: string;
|
|
36
|
+
message: string;
|
|
37
|
+
}
|
|
38
|
+
/** Every bad field type reported at once, instead of stopping at the first. */
|
|
39
|
+
export declare class FieldTypeError extends Error {
|
|
40
|
+
readonly provider: Provider;
|
|
41
|
+
readonly issues: FieldTypeIssue[];
|
|
42
|
+
constructor(provider: Provider, issues: FieldTypeIssue[]);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Splits `NVARCHAR(255)` into name and parameters.
|
|
46
|
+
*
|
|
47
|
+
* The name keeps every word, because `DOUBLE PRECISION` and
|
|
48
|
+
* `TIMESTAMP WITH TIME ZONE` are single entries in the PostgreSQL table. It is
|
|
49
|
+
* upper-cased and whitespace-collapsed here so lookups do not depend on how the
|
|
50
|
+
* schema was written.
|
|
51
|
+
*/
|
|
52
|
+
export declare function splitTypeParams(raw: string): {
|
|
53
|
+
base: string;
|
|
54
|
+
params: string;
|
|
55
|
+
};
|
|
56
|
+
export interface ResolvedFieldType {
|
|
57
|
+
/** Normalised type name, e.g. `DOUBLE PRECISION`. */
|
|
58
|
+
base: string;
|
|
59
|
+
/** The parameters in parentheses, e.g. `255` for `NVARCHAR(255)`. */
|
|
60
|
+
params: string;
|
|
61
|
+
/** The first word of `base`, used to guess "which type was meant". */
|
|
62
|
+
head: string;
|
|
63
|
+
/** The matching TypeScript type. */
|
|
64
|
+
ts: TsType;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Looks a type up in the provider's table, or null when the provider has no such
|
|
68
|
+
* type.
|
|
69
|
+
*
|
|
70
|
+
* Called before deciding whether a field is a relation, so an upper-case type
|
|
71
|
+
* that happens to match a model name (`TEXT`, `NAME`) still becomes a column.
|
|
72
|
+
*/
|
|
73
|
+
export declare function resolveFieldType(raw: string, provider: Provider): ResolvedFieldType | null;
|
|
74
|
+
/**
|
|
75
|
+
* Message for a type the provider does not have.
|
|
76
|
+
*
|
|
77
|
+
* `modelNames` holds the models in the schema: an upper-case token may be a
|
|
78
|
+
* model name (a relation) rather than a mistyped type, so the message has to
|
|
79
|
+
* name both possibilities instead of guessing.
|
|
80
|
+
*/
|
|
81
|
+
export declare function unknownFieldTypeMessage(raw: string, provider: Provider, modelNames?: readonly string[]): string;
|
|
82
|
+
/**
|
|
83
|
+
* The head of a field line: the column name and type, with the `@attributes`
|
|
84
|
+
* that may follow removed.
|
|
85
|
+
*
|
|
86
|
+
* The type is joined from every remaining token instead of just the second one,
|
|
87
|
+
* because a multi-word type (`DOUBLE PRECISION`, `TIMESTAMP WITH TIME ZONE`)
|
|
88
|
+
* truncated to its first word matches no table. Returns null when the line has
|
|
89
|
+
* no name or no type.
|
|
90
|
+
*/
|
|
91
|
+
export declare function readFieldLineHead(line: string): {
|
|
92
|
+
name: string;
|
|
93
|
+
type: string;
|
|
94
|
+
isArray: boolean;
|
|
95
|
+
isOptional: boolean;
|
|
96
|
+
} | null;
|
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FieldTypeError = exports.PROVIDER_FIELD_TYPES = exports.PROVIDER_LABELS = exports.DEFAULT_PROVIDER = exports.PROVIDERS = void 0;
|
|
4
|
+
exports.fieldTypesFor = fieldTypesFor;
|
|
5
|
+
exports.defaultSqlTypeForTs = defaultSqlTypeForTs;
|
|
6
|
+
exports.splitTypeParams = splitTypeParams;
|
|
7
|
+
exports.resolveFieldType = resolveFieldType;
|
|
8
|
+
exports.unknownFieldTypeMessage = unknownFieldTypeMessage;
|
|
9
|
+
exports.readFieldLineHead = readFieldLineHead;
|
|
10
|
+
/**
|
|
11
|
+
* Field types per database provider.
|
|
12
|
+
*
|
|
13
|
+
* There used to be one `AN5_TO_TS` table shared by every provider, plus a
|
|
14
|
+
* separate `AN5_TYPES` list for push/migrate — and the two disagreed. So
|
|
15
|
+
* `INTEGER`/`BOOLEAN`/`BLOB` (SQLite-only names) were generated into the client
|
|
16
|
+
* and then dropped silently on push, while another provider's types (`SERIAL`,
|
|
17
|
+
* `JSONB`) passed validation and only died at DDL time.
|
|
18
|
+
*
|
|
19
|
+
* Each provider now has its own table, which is both the list of allowed types
|
|
20
|
+
* and the mapping to a TypeScript type. The provider comes from the connection
|
|
21
|
+
* string — see `detectProvider` in `./config`.
|
|
22
|
+
*/
|
|
23
|
+
const suggest_1 = require("./suggest");
|
|
24
|
+
/** Order used when printing issues and when suggesting a provider. */
|
|
25
|
+
exports.PROVIDERS = ['mssql', 'postgres', 'mysql', 'sqlite', 'googlesheets'];
|
|
26
|
+
/**
|
|
27
|
+
* SQL Server is the default provider: it is the only one `db:push`, `db:pull`
|
|
28
|
+
* and `db:migrate` actually support, and it is what existing `.an5` schemas
|
|
29
|
+
* were written for. With no connection string, treat the schema as SQL Server —
|
|
30
|
+
* the same fallback `an5Adapter` uses.
|
|
31
|
+
*/
|
|
32
|
+
exports.DEFAULT_PROVIDER = 'mssql';
|
|
33
|
+
/** Display name, used in error messages. */
|
|
34
|
+
exports.PROVIDER_LABELS = {
|
|
35
|
+
mssql: 'SQL Server',
|
|
36
|
+
postgres: 'PostgreSQL',
|
|
37
|
+
mysql: 'MySQL',
|
|
38
|
+
sqlite: 'SQLite',
|
|
39
|
+
googlesheets: 'Google Sheets',
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Valid column types per provider: what is written in `.an5` → the TypeScript
|
|
43
|
+
* type generated for it.
|
|
44
|
+
*
|
|
45
|
+
* Keys are stored upper case and lookups are case-insensitive. This table is
|
|
46
|
+
* the single source of truth — nowhere else is allowed to enumerate types.
|
|
47
|
+
*/
|
|
48
|
+
exports.PROVIDER_FIELD_TYPES = {
|
|
49
|
+
// ─── SQL Server ────────────────────────────────────────────────────────────
|
|
50
|
+
mssql: {
|
|
51
|
+
// Strings
|
|
52
|
+
NVARCHAR: 'string',
|
|
53
|
+
VARCHAR: 'string',
|
|
54
|
+
CHAR: 'string',
|
|
55
|
+
NCHAR: 'string',
|
|
56
|
+
TEXT: 'string',
|
|
57
|
+
NTEXT: 'string',
|
|
58
|
+
XML: 'string',
|
|
59
|
+
// Numbers
|
|
60
|
+
INT: 'number',
|
|
61
|
+
SMALLINT: 'number',
|
|
62
|
+
TINYINT: 'number',
|
|
63
|
+
BIGINT: 'number | bigint',
|
|
64
|
+
FLOAT: 'number',
|
|
65
|
+
REAL: 'number',
|
|
66
|
+
DECIMAL: 'number',
|
|
67
|
+
NUMERIC: 'number',
|
|
68
|
+
MONEY: 'number',
|
|
69
|
+
SMALLMONEY: 'number',
|
|
70
|
+
// Boolean
|
|
71
|
+
BIT: 'boolean',
|
|
72
|
+
// Date and time
|
|
73
|
+
DATE: 'Date',
|
|
74
|
+
DATETIME: 'Date',
|
|
75
|
+
DATETIME2: 'Date',
|
|
76
|
+
SMALLDATETIME: 'Date',
|
|
77
|
+
DATETIMEOFFSET: 'Date',
|
|
78
|
+
TIME: 'Date',
|
|
79
|
+
// Binary
|
|
80
|
+
VARBINARY: 'Buffer',
|
|
81
|
+
BINARY: 'Buffer',
|
|
82
|
+
IMAGE: 'Buffer',
|
|
83
|
+
ROWVERSION: 'Buffer',
|
|
84
|
+
// `TIMESTAMP` is the legacy name of `ROWVERSION`, i.e. a rowversion — writing
|
|
85
|
+
// TIMESTAMP in a SQL Server schema does not mean a point in time.
|
|
86
|
+
TIMESTAMP: 'Buffer',
|
|
87
|
+
// Everything else
|
|
88
|
+
UNIQUEIDENTIFIER: 'string',
|
|
89
|
+
// `sysname` is an alias for NVARCHAR(128) and is what db:pull writes for a
|
|
90
|
+
// column declared that way, so it has to be valid or a pulled schema could
|
|
91
|
+
// not be generated again.
|
|
92
|
+
SYSNAME: 'string',
|
|
93
|
+
SQL_VARIANT: 'any',
|
|
94
|
+
HIERARCHYID: 'string',
|
|
95
|
+
GEOGRAPHY: 'string',
|
|
96
|
+
GEOMETRY: 'string',
|
|
97
|
+
VECTOR: 'number[] | string',
|
|
98
|
+
},
|
|
99
|
+
// ─── PostgreSQL ────────────────────────────────────────────────────────────
|
|
100
|
+
postgres: {
|
|
101
|
+
// Strings. `NAME` and `USER` are real PostgreSQL types but are deliberately
|
|
102
|
+
// left out: a model called `Name`/`User` is far more likely, and listing
|
|
103
|
+
// them here would turn `user User @relation(...)` into a USER column.
|
|
104
|
+
VARCHAR: 'string',
|
|
105
|
+
CHARACTER: 'string',
|
|
106
|
+
'CHARACTER VARYING': 'string',
|
|
107
|
+
CHAR: 'string',
|
|
108
|
+
TEXT: 'string',
|
|
109
|
+
UUID: 'string',
|
|
110
|
+
XML: 'string',
|
|
111
|
+
JSON: 'any',
|
|
112
|
+
JSONB: 'any',
|
|
113
|
+
INET: 'string',
|
|
114
|
+
CIDR: 'string',
|
|
115
|
+
// Numbers. `INT` is not valid on PostgreSQL (it must be `INTEGER`/`INT4`),
|
|
116
|
+
// so it is absent here — a schema using `INT` is rejected instead of dying
|
|
117
|
+
// at DDL time.
|
|
118
|
+
SMALLINT: 'number',
|
|
119
|
+
INT2: 'number',
|
|
120
|
+
INT4: 'number',
|
|
121
|
+
INTEGER: 'number',
|
|
122
|
+
INT8: 'number | bigint',
|
|
123
|
+
BIGINT: 'number | bigint',
|
|
124
|
+
SMALLSERIAL: 'number',
|
|
125
|
+
SERIAL: 'number',
|
|
126
|
+
BIGSERIAL: 'number | bigint',
|
|
127
|
+
DECIMAL: 'number',
|
|
128
|
+
NUMERIC: 'number',
|
|
129
|
+
REAL: 'number',
|
|
130
|
+
'DOUBLE PRECISION': 'number',
|
|
131
|
+
MONEY: 'number',
|
|
132
|
+
// Boolean
|
|
133
|
+
BOOLEAN: 'boolean',
|
|
134
|
+
BOOL: 'boolean',
|
|
135
|
+
// Date and time
|
|
136
|
+
DATE: 'Date',
|
|
137
|
+
TIME: 'Date',
|
|
138
|
+
TIMETZ: 'Date',
|
|
139
|
+
'TIME WITH TIME ZONE': 'Date',
|
|
140
|
+
'TIME WITHOUT TIME ZONE': 'Date',
|
|
141
|
+
TIMESTAMP: 'Date',
|
|
142
|
+
TIMESTAMPTZ: 'Date',
|
|
143
|
+
'TIMESTAMP WITH TIME ZONE': 'Date',
|
|
144
|
+
'TIMESTAMP WITHOUT TIME ZONE': 'Date',
|
|
145
|
+
INTERVAL: 'string',
|
|
146
|
+
// Binary
|
|
147
|
+
BYTEA: 'Buffer',
|
|
148
|
+
// Requires an extension: PostGIS for geometry, pgvector for vectors.
|
|
149
|
+
GEOGRAPHY: 'string',
|
|
150
|
+
GEOMETRY: 'string',
|
|
151
|
+
VECTOR: 'number[] | string',
|
|
152
|
+
},
|
|
153
|
+
// ─── MySQL / MariaDB ───────────────────────────────────────────────────────
|
|
154
|
+
mysql: {
|
|
155
|
+
// Strings
|
|
156
|
+
CHAR: 'string',
|
|
157
|
+
VARCHAR: 'string',
|
|
158
|
+
NCHAR: 'string',
|
|
159
|
+
NVARCHAR: 'string',
|
|
160
|
+
TINYTEXT: 'string',
|
|
161
|
+
TEXT: 'string',
|
|
162
|
+
MEDIUMTEXT: 'string',
|
|
163
|
+
LONGTEXT: 'string',
|
|
164
|
+
ENUM: 'string',
|
|
165
|
+
SET: 'string',
|
|
166
|
+
// Numbers
|
|
167
|
+
TINYINT: 'number',
|
|
168
|
+
SMALLINT: 'number',
|
|
169
|
+
MEDIUMINT: 'number',
|
|
170
|
+
INT: 'number',
|
|
171
|
+
INTEGER: 'number',
|
|
172
|
+
BIGINT: 'number | bigint',
|
|
173
|
+
// `SERIAL` is an alias for `BIGINT UNSIGNED AUTO_INCREMENT`.
|
|
174
|
+
SERIAL: 'number | bigint',
|
|
175
|
+
DECIMAL: 'number',
|
|
176
|
+
NUMERIC: 'number',
|
|
177
|
+
FIXED: 'number',
|
|
178
|
+
FLOAT: 'number',
|
|
179
|
+
DOUBLE: 'number',
|
|
180
|
+
'DOUBLE PRECISION': 'number',
|
|
181
|
+
REAL: 'number',
|
|
182
|
+
// Boolean
|
|
183
|
+
BOOLEAN: 'boolean',
|
|
184
|
+
BOOL: 'boolean',
|
|
185
|
+
// `BIT(n)` is an n-bit field on MySQL, but it is a flag in practice and every
|
|
186
|
+
// other dialect's BIT is a boolean here. Mapping it to a number made the
|
|
187
|
+
// TypeScript client say `number` while the Go, Python, Rust and .NET clients
|
|
188
|
+
// said `bool` for the same column.
|
|
189
|
+
BIT: 'boolean',
|
|
190
|
+
// Date and time
|
|
191
|
+
DATE: 'Date',
|
|
192
|
+
DATETIME: 'Date',
|
|
193
|
+
TIMESTAMP: 'Date',
|
|
194
|
+
TIME: 'Date',
|
|
195
|
+
YEAR: 'number',
|
|
196
|
+
// Binary
|
|
197
|
+
BINARY: 'Buffer',
|
|
198
|
+
VARBINARY: 'Buffer',
|
|
199
|
+
TINYBLOB: 'Buffer',
|
|
200
|
+
BLOB: 'Buffer',
|
|
201
|
+
MEDIUMBLOB: 'Buffer',
|
|
202
|
+
LONGBLOB: 'Buffer',
|
|
203
|
+
// Everything else. MySQL has no UUID type (MariaDB does), so UUID is
|
|
204
|
+
// intentionally absent.
|
|
205
|
+
JSON: 'any',
|
|
206
|
+
GEOMETRY: 'string',
|
|
207
|
+
POINT: 'string',
|
|
208
|
+
LINESTRING: 'string',
|
|
209
|
+
POLYGON: 'string',
|
|
210
|
+
},
|
|
211
|
+
// ─── SQLite ────────────────────────────────────────────────────────────────
|
|
212
|
+
// SQLite does not enforce types: the declared name only picks an affinity, so
|
|
213
|
+
// this list is "names in common use whose affinity is derivable" rather than
|
|
214
|
+
// an engine enum. The point that matters: `INTEGER`/`BOOLEAN`/`BLOB` exist
|
|
215
|
+
// only here — SQL Server has no INTEGER, so a schema using it and pushed to
|
|
216
|
+
// SQL Server is now rejected.
|
|
217
|
+
sqlite: {
|
|
218
|
+
// Integers
|
|
219
|
+
INTEGER: 'number',
|
|
220
|
+
INT: 'number',
|
|
221
|
+
INT2: 'number',
|
|
222
|
+
INT8: 'number | bigint',
|
|
223
|
+
TINYINT: 'number',
|
|
224
|
+
SMALLINT: 'number',
|
|
225
|
+
MEDIUMINT: 'number',
|
|
226
|
+
BIGINT: 'number | bigint',
|
|
227
|
+
'UNSIGNED BIG INT': 'number | bigint',
|
|
228
|
+
// Reals
|
|
229
|
+
REAL: 'number',
|
|
230
|
+
DOUBLE: 'number',
|
|
231
|
+
'DOUBLE PRECISION': 'number',
|
|
232
|
+
FLOAT: 'number',
|
|
233
|
+
NUMERIC: 'number',
|
|
234
|
+
DECIMAL: 'number',
|
|
235
|
+
// Boolean
|
|
236
|
+
BOOLEAN: 'boolean',
|
|
237
|
+
BOOL: 'boolean',
|
|
238
|
+
// Strings
|
|
239
|
+
TEXT: 'string',
|
|
240
|
+
CLOB: 'string',
|
|
241
|
+
VARCHAR: 'string',
|
|
242
|
+
NVARCHAR: 'string',
|
|
243
|
+
CHAR: 'string',
|
|
244
|
+
NCHAR: 'string',
|
|
245
|
+
CHARACTER: 'string',
|
|
246
|
+
'VARYING CHARACTER': 'string',
|
|
247
|
+
// Binary
|
|
248
|
+
BLOB: 'Buffer',
|
|
249
|
+
// Date and time — stored as TEXT and converted on read (see `SqliteEngine`).
|
|
250
|
+
DATE: 'Date',
|
|
251
|
+
DATETIME: 'Date',
|
|
252
|
+
TIMESTAMP: 'Date',
|
|
253
|
+
TIME: 'Date',
|
|
254
|
+
YEAR: 'number',
|
|
255
|
+
// Everything else
|
|
256
|
+
JSON: 'any',
|
|
257
|
+
UUID: 'string',
|
|
258
|
+
VECTOR: 'number[] | string',
|
|
259
|
+
},
|
|
260
|
+
// ─── Google Sheets ─────────────────────────────────────────────────────────
|
|
261
|
+
// Sheets has no column types: every cell is a text/number/boolean value and
|
|
262
|
+
// the adapter coerces it from the generated TypeScript type (`coerceCell`).
|
|
263
|
+
// So this list only accepts portable types — the ones the adapter still knows
|
|
264
|
+
// what to coerce into.
|
|
265
|
+
googlesheets: {
|
|
266
|
+
// Strings
|
|
267
|
+
STRING: 'string',
|
|
268
|
+
TEXT: 'string',
|
|
269
|
+
VARCHAR: 'string',
|
|
270
|
+
NVARCHAR: 'string',
|
|
271
|
+
CHAR: 'string',
|
|
272
|
+
NCHAR: 'string',
|
|
273
|
+
CLOB: 'string',
|
|
274
|
+
// Numbers
|
|
275
|
+
INT: 'number',
|
|
276
|
+
INTEGER: 'number',
|
|
277
|
+
SMALLINT: 'number',
|
|
278
|
+
TINYINT: 'number',
|
|
279
|
+
BIGINT: 'number | bigint',
|
|
280
|
+
FLOAT: 'number',
|
|
281
|
+
DOUBLE: 'number',
|
|
282
|
+
DECIMAL: 'number',
|
|
283
|
+
NUMERIC: 'number',
|
|
284
|
+
REAL: 'number',
|
|
285
|
+
// Boolean
|
|
286
|
+
BOOLEAN: 'boolean',
|
|
287
|
+
BOOL: 'boolean',
|
|
288
|
+
// Date and time
|
|
289
|
+
DATE: 'Date',
|
|
290
|
+
DATETIME: 'Date',
|
|
291
|
+
TIMESTAMP: 'Date',
|
|
292
|
+
TIME: 'Date',
|
|
293
|
+
// Binary
|
|
294
|
+
BYTES: 'Buffer',
|
|
295
|
+
BLOB: 'Buffer',
|
|
296
|
+
// Everything else
|
|
297
|
+
VECTOR: 'number[] | string',
|
|
298
|
+
},
|
|
299
|
+
};
|
|
300
|
+
/** The type table of one provider. */
|
|
301
|
+
function fieldTypesFor(provider) {
|
|
302
|
+
return exports.PROVIDER_FIELD_TYPES[provider];
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* The column type to use when only the TypeScript type is known.
|
|
306
|
+
*
|
|
307
|
+
* For tooling that has to invent a type name — the agent reading a client generated
|
|
308
|
+
* before the metadata carried `sql` — and the table is per provider on purpose:
|
|
309
|
+
* `NVARCHAR` exists only on SQL Server and `TIMESTAMP` is a rowversion there, so
|
|
310
|
+
* naming a type from the wrong provider produces SQL the database rejects.
|
|
311
|
+
*/
|
|
312
|
+
const DEFAULT_TYPE_FOR_TS = {
|
|
313
|
+
mssql: { string: 'NVARCHAR(255)', number: 'INT', bigint: 'BIGINT', boolean: 'BIT', Date: 'DATETIME2', Buffer: 'VARBINARY(MAX)', any: 'NVARCHAR(MAX)', vector: 'VECTOR' },
|
|
314
|
+
postgres: { string: 'VARCHAR(255)', number: 'INTEGER', bigint: 'BIGINT', boolean: 'BOOLEAN', Date: 'TIMESTAMPTZ', Buffer: 'BYTEA', any: 'JSONB', vector: 'VECTOR' },
|
|
315
|
+
mysql: { string: 'VARCHAR(255)', number: 'INT', bigint: 'BIGINT', boolean: 'BOOLEAN', Date: 'DATETIME', Buffer: 'BLOB', any: 'JSON', vector: 'JSON' },
|
|
316
|
+
// SQLite stores what it is given: dates as text, which is what the adapter reads
|
|
317
|
+
// back and converts, and numbers as a real.
|
|
318
|
+
sqlite: { string: 'TEXT', number: 'REAL', bigint: 'INTEGER', boolean: 'BOOLEAN', Date: 'TEXT', Buffer: 'BLOB', any: 'TEXT', vector: 'TEXT' },
|
|
319
|
+
googlesheets: { string: 'TEXT', number: 'INTEGER', bigint: 'INTEGER', boolean: 'BOOLEAN', Date: 'TEXT', Buffer: 'BYTES', any: 'TEXT', vector: 'TEXT' },
|
|
320
|
+
};
|
|
321
|
+
/**
|
|
322
|
+
* A column type for a TypeScript type, valid on `provider`.
|
|
323
|
+
*
|
|
324
|
+
* Unknown TypeScript types fall back to that provider's string type, which is the
|
|
325
|
+
* only choice that can hold any value.
|
|
326
|
+
*/
|
|
327
|
+
function defaultSqlTypeForTs(tsType, provider = exports.DEFAULT_PROVIDER) {
|
|
328
|
+
const byProvider = DEFAULT_TYPE_FOR_TS[provider];
|
|
329
|
+
const raw = tsType.trim();
|
|
330
|
+
// A `[]` means the vector type, which the generators spell `number[] | string`; it
|
|
331
|
+
// has to be recognised before the brackets are stripped, or it reads as a number.
|
|
332
|
+
if (raw.includes('[]'))
|
|
333
|
+
return byProvider.vector;
|
|
334
|
+
const key = raw.replace(/[?\]]/g, '').trim();
|
|
335
|
+
if (byProvider[key])
|
|
336
|
+
return byProvider[key];
|
|
337
|
+
if (key.startsWith('number'))
|
|
338
|
+
return byProvider.bigint;
|
|
339
|
+
if (key.startsWith('Date'))
|
|
340
|
+
return byProvider.Date;
|
|
341
|
+
if (key.startsWith('boolean') || key.startsWith('bool'))
|
|
342
|
+
return byProvider.boolean;
|
|
343
|
+
if (key.startsWith('Buffer'))
|
|
344
|
+
return byProvider.Buffer;
|
|
345
|
+
if (key === 'any')
|
|
346
|
+
return byProvider.any;
|
|
347
|
+
return byProvider.string;
|
|
348
|
+
}
|
|
349
|
+
/** Every bad field type reported at once, instead of stopping at the first. */
|
|
350
|
+
class FieldTypeError extends Error {
|
|
351
|
+
constructor(provider, issues) {
|
|
352
|
+
super(`Invalid field types for ${exports.PROVIDER_LABELS[provider]}`);
|
|
353
|
+
this.name = 'FieldTypeError';
|
|
354
|
+
this.provider = provider;
|
|
355
|
+
this.issues = issues;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
exports.FieldTypeError = FieldTypeError;
|
|
359
|
+
/**
|
|
360
|
+
* Splits `NVARCHAR(255)` into name and parameters.
|
|
361
|
+
*
|
|
362
|
+
* The name keeps every word, because `DOUBLE PRECISION` and
|
|
363
|
+
* `TIMESTAMP WITH TIME ZONE` are single entries in the PostgreSQL table. It is
|
|
364
|
+
* upper-cased and whitespace-collapsed here so lookups do not depend on how the
|
|
365
|
+
* schema was written.
|
|
366
|
+
*/
|
|
367
|
+
function splitTypeParams(raw) {
|
|
368
|
+
const normalized = raw.trim().toUpperCase().replace(/\s+/g, ' ');
|
|
369
|
+
const match = normalized.match(/^([A-Z0-9_]+(?:\s+[A-Z0-9_]+)*)\s*(?:\(([^)]*)\))?$/);
|
|
370
|
+
if (!match)
|
|
371
|
+
return { base: normalized, params: '' };
|
|
372
|
+
return { base: match[1], params: match[2] ?? '' };
|
|
373
|
+
}
|
|
374
|
+
/** Drops the `[]` (array) and `?` (nullable) suffixes. */
|
|
375
|
+
function stripTypeSuffixes(raw) {
|
|
376
|
+
return raw.replace(/\[\]$/, '').replace(/\?$/, '');
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Looks a type up in the provider's table, or null when the provider has no such
|
|
380
|
+
* type.
|
|
381
|
+
*
|
|
382
|
+
* Called before deciding whether a field is a relation, so an upper-case type
|
|
383
|
+
* that happens to match a model name (`TEXT`, `NAME`) still becomes a column.
|
|
384
|
+
*/
|
|
385
|
+
function resolveFieldType(raw, provider) {
|
|
386
|
+
const { base, params } = splitTypeParams(raw);
|
|
387
|
+
const ts = fieldTypesFor(provider)[base];
|
|
388
|
+
if (ts === undefined)
|
|
389
|
+
return null;
|
|
390
|
+
return { base, params, head: base.split(' ')[0], ts };
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Message for a type the provider does not have.
|
|
394
|
+
*
|
|
395
|
+
* `modelNames` holds the models in the schema: an upper-case token may be a
|
|
396
|
+
* model name (a relation) rather than a mistyped type, so the message has to
|
|
397
|
+
* name both possibilities instead of guessing.
|
|
398
|
+
*/
|
|
399
|
+
function unknownFieldTypeMessage(raw, provider, modelNames = []) {
|
|
400
|
+
const label = `unknown type "${raw.trim()}" for ${exports.PROVIDER_LABELS[provider]}`;
|
|
401
|
+
const { base } = splitTypeParams(raw);
|
|
402
|
+
const typeHint = (0, suggest_1.suggest)(base.split(' ')[0], Object.keys(fieldTypesFor(provider)));
|
|
403
|
+
if (typeHint)
|
|
404
|
+
return `${label}; did you mean "${typeHint}"?`;
|
|
405
|
+
const modelHint = (0, suggest_1.suggest)(raw.trim(), modelNames);
|
|
406
|
+
if (modelHint) {
|
|
407
|
+
return `${label}, and no model named "${raw.trim()}" here; did you mean the model "${modelHint}"?`;
|
|
408
|
+
}
|
|
409
|
+
return `${label}, and no model named "${raw.trim()}" in this schema`;
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* The head of a field line: the column name and type, with the `@attributes`
|
|
413
|
+
* that may follow removed.
|
|
414
|
+
*
|
|
415
|
+
* The type is joined from every remaining token instead of just the second one,
|
|
416
|
+
* because a multi-word type (`DOUBLE PRECISION`, `TIMESTAMP WITH TIME ZONE`)
|
|
417
|
+
* truncated to its first word matches no table. Returns null when the line has
|
|
418
|
+
* no name or no type.
|
|
419
|
+
*/
|
|
420
|
+
function readFieldLineHead(line) {
|
|
421
|
+
const attributeStart = line.indexOf('@');
|
|
422
|
+
const tokens = (attributeStart === -1 ? line : line.slice(0, attributeStart)).trim().split(/\s+/);
|
|
423
|
+
const name = tokens[0];
|
|
424
|
+
const rawType = tokens.slice(1).join(' ');
|
|
425
|
+
if (!name || !rawType)
|
|
426
|
+
return null;
|
|
427
|
+
return {
|
|
428
|
+
name,
|
|
429
|
+
type: stripTypeSuffixes(rawType),
|
|
430
|
+
isArray: rawType.endsWith('[]'),
|
|
431
|
+
isOptional: rawType.endsWith('?'),
|
|
432
|
+
};
|
|
433
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Model } from './types';
|
|
2
|
+
export declare class GolangGenerator {
|
|
3
|
+
private outputDir;
|
|
4
|
+
/** The database being generated for; decides types like `TIMESTAMP`. */
|
|
5
|
+
private provider;
|
|
6
|
+
constructor(outputDir: string);
|
|
7
|
+
generate(models: Model[]): void;
|
|
8
|
+
/**
|
|
9
|
+
* The Go type for a field.
|
|
10
|
+
*
|
|
11
|
+
* Driven by the declared type, not the generated TypeScript one: that collapses
|
|
12
|
+
* `INT`, `FLOAT` and `DECIMAL` into `number`, which matched none of the branches
|
|
13
|
+
* below and made every numeric column a `string`.
|
|
14
|
+
*/
|
|
15
|
+
private mapGoType;
|
|
16
|
+
private generateModelFile;
|
|
17
|
+
private generateModelStruct;
|
|
18
|
+
private generateMetadataGo;
|
|
19
|
+
private generateConfigGo;
|
|
20
|
+
private generateClientGo;
|
|
21
|
+
private generateClientHeader;
|
|
22
|
+
private generateBaseOrmTypes;
|
|
23
|
+
private generateDbContext;
|
|
24
|
+
private generateTableClientStruct;
|
|
25
|
+
private generateQueryAndScan;
|
|
26
|
+
private generateWhereBuilder;
|
|
27
|
+
private generateCrudMethods;
|
|
28
|
+
private generateVectorSearch;
|
|
29
|
+
private generateHelpers;
|
|
30
|
+
private generateModelOrmTypes;
|
|
31
|
+
private getGoFilterType;
|
|
32
|
+
private getAllPropertyVariations;
|
|
33
|
+
private capitalize;
|
|
34
|
+
private toCamelCase;
|
|
35
|
+
private toSnakeCase;
|
|
36
|
+
}
|