shapecraft 1.0.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 (112) hide show
  1. package/CLAUDE.md +227 -0
  2. package/README.md +22 -0
  3. package/apps/cli/node_modules/.bin/prettier +21 -0
  4. package/apps/cli/node_modules/.bin/tsc +21 -0
  5. package/apps/cli/node_modules/.bin/tsserver +21 -0
  6. package/apps/cli/node_modules/.bin/tsx +21 -0
  7. package/apps/cli/node_modules/.bin/vitest +21 -0
  8. package/apps/cli/package.json +47 -0
  9. package/apps/cli/src/index.ts +98 -0
  10. package/apps/cli/tsconfig.cjs.json +10 -0
  11. package/apps/cli/tsconfig.esm.json +10 -0
  12. package/apps/cli/tsconfig.json +22 -0
  13. package/package.json +16 -0
  14. package/packages/core/node_modules/.bin/prettier +21 -0
  15. package/packages/core/node_modules/.bin/tsc +21 -0
  16. package/packages/core/node_modules/.bin/tsserver +21 -0
  17. package/packages/core/node_modules/.bin/tsx +21 -0
  18. package/packages/core/node_modules/.bin/vitest +21 -0
  19. package/packages/core/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  20. package/packages/core/package.json +44 -0
  21. package/packages/core/src/common/array.test.ts +19 -0
  22. package/packages/core/src/common/array.ts +15 -0
  23. package/packages/core/src/common/index.ts +5 -0
  24. package/packages/core/src/common/is.ts +23 -0
  25. package/packages/core/src/common/object.ts +35 -0
  26. package/packages/core/src/common/phantom.ts +1 -0
  27. package/packages/core/src/common/result.ts +43 -0
  28. package/packages/core/src/common/string.ts +28 -0
  29. package/packages/core/src/common/types.ts +34 -0
  30. package/packages/core/src/index.ts +1 -0
  31. package/packages/core/src/shape/annotate.ts +139 -0
  32. package/packages/core/src/shape/annotation.ts +47 -0
  33. package/packages/core/src/shape/base.ts +71 -0
  34. package/packages/core/src/shape/builder.test.ts +728 -0
  35. package/packages/core/src/shape/builder.ts +475 -0
  36. package/packages/core/src/shape/error.ts +4 -0
  37. package/packages/core/src/shape/index.ts +3 -0
  38. package/packages/core/src/shape/number.ts +118 -0
  39. package/packages/core/src/shape/shape.test.ts +792 -0
  40. package/packages/core/src/shape/shape.ts +377 -0
  41. package/packages/core/src/shape/tags.ts +14 -0
  42. package/packages/core/src/shape/transforms/index.ts +3 -0
  43. package/packages/core/src/shape/transforms/json-schema/index.ts +2 -0
  44. package/packages/core/src/shape/transforms/json-schema/transform.test.ts +850 -0
  45. package/packages/core/src/shape/transforms/json-schema/transform.ts +882 -0
  46. package/packages/core/src/shape/transforms/json-schema/types.ts +132 -0
  47. package/packages/core/src/shape/transforms/sql/dialects/dialect.ts +89 -0
  48. package/packages/core/src/shape/transforms/sql/dialects/index.ts +14 -0
  49. package/packages/core/src/shape/transforms/sql/dialects/postgres.ts +392 -0
  50. package/packages/core/src/shape/transforms/sql/dialects/sqlite.ts +333 -0
  51. package/packages/core/src/shape/transforms/sql/from-sql.test.ts +704 -0
  52. package/packages/core/src/shape/transforms/sql/from-sql.ts +210 -0
  53. package/packages/core/src/shape/transforms/sql/index.ts +3 -0
  54. package/packages/core/src/shape/transforms/sql/options.ts +6 -0
  55. package/packages/core/src/shape/transforms/sql/parser/check-decoder.ts +457 -0
  56. package/packages/core/src/shape/transforms/sql/parser/create-domain.ts +105 -0
  57. package/packages/core/src/shape/transforms/sql/parser/create-table.ts +809 -0
  58. package/packages/core/src/shape/transforms/sql/parser/create-type.ts +91 -0
  59. package/packages/core/src/shape/transforms/sql/parser/cursor.ts +179 -0
  60. package/packages/core/src/shape/transforms/sql/parser/default-decoder.ts +129 -0
  61. package/packages/core/src/shape/transforms/sql/parser/lexer.ts +289 -0
  62. package/packages/core/src/shape/transforms/sql/parser/pg-types.ts +247 -0
  63. package/packages/core/src/shape/transforms/sql/parser/sqlite-types.ts +103 -0
  64. package/packages/core/src/shape/transforms/sql/parser/statements.ts +127 -0
  65. package/packages/core/src/shape/transforms/sql/parser/type-spec.ts +159 -0
  66. package/packages/core/src/shape/transforms/sql/transform.sqlite.test.ts +448 -0
  67. package/packages/core/src/shape/transforms/sql/transform.test.ts +880 -0
  68. package/packages/core/src/shape/transforms/sql/transform.ts +295 -0
  69. package/packages/core/src/shape/transforms/typescript/index.ts +1 -0
  70. package/packages/core/src/shape/transforms/typescript/transform.ts +211 -0
  71. package/packages/core/src/shape/tuple.test.ts +171 -0
  72. package/packages/core/src/shape/validate.ts +413 -0
  73. package/packages/core/tsconfig.cjs.json +11 -0
  74. package/packages/core/tsconfig.esm.json +10 -0
  75. package/packages/core/tsconfig.json +23 -0
  76. package/packages/samples/node_modules/.bin/prettier +21 -0
  77. package/packages/samples/node_modules/.bin/tsc +21 -0
  78. package/packages/samples/node_modules/.bin/tsserver +21 -0
  79. package/packages/samples/node_modules/.bin/tsx +21 -0
  80. package/packages/samples/node_modules/.bin/vitest +21 -0
  81. package/packages/samples/package.json +47 -0
  82. package/packages/samples/src/blog.ts +49 -0
  83. package/packages/samples/src/config.ts +50 -0
  84. package/packages/samples/src/ecommerce.ts +65 -0
  85. package/packages/samples/src/embeddings.ts +43 -0
  86. package/packages/samples/src/events.ts +52 -0
  87. package/packages/samples/src/geometry.ts +62 -0
  88. package/packages/samples/src/index.ts +9 -0
  89. package/packages/samples/src/relational.ts +17 -0
  90. package/packages/samples/src/tuples.ts +67 -0
  91. package/packages/samples/src/user.ts +9 -0
  92. package/packages/samples/tsconfig.cjs.json +11 -0
  93. package/packages/samples/tsconfig.esm.json +10 -0
  94. package/packages/samples/tsconfig.json +23 -0
  95. package/pnpm-workspace.yaml +3 -0
  96. package/test-data/json-schema/address.json +35 -0
  97. package/test-data/json-schema/array-of-things.json +36 -0
  98. package/test-data/json-schema/basic.json +21 -0
  99. package/test-data/json-schema/blog-post.json +29 -0
  100. package/test-data/json-schema/calendar.json +48 -0
  101. package/test-data/json-schema/complex-object-with-nested-properties.json +41 -0
  102. package/test-data/json-schema/ecommerce-complex.json +344 -0
  103. package/test-data/json-schema/ecommerce-system.json +27 -0
  104. package/test-data/json-schema/enumerated-values.json +11 -0
  105. package/test-data/json-schema/fstab-entry.json +92 -0
  106. package/test-data/json-schema/geographical-location.json +20 -0
  107. package/test-data/json-schema/health-record.json +41 -0
  108. package/test-data/json-schema/job-posting.json +33 -0
  109. package/test-data/json-schema/movie.json +35 -0
  110. package/test-data/json-schema/regular-expression-pattern.json +12 -0
  111. package/test-data/json-schema/user-profile.json +33 -0
  112. package/test-data/sql/ecommerce.sql +641 -0
@@ -0,0 +1,247 @@
1
+ import { annotate, shapes } from "../../../shape";
2
+ import { CompositeDef, EnumDef } from "./create-type";
3
+ import {
4
+ numberShapeForTag,
5
+ numIntArg,
6
+ parseTypeSpec,
7
+ ParsedTypeSpec,
8
+ } from "./type-spec";
9
+
10
+ // Re-export under the legacy names so existing callers keep working.
11
+ export { parseTypeSpec as parsePgType } from "./type-spec";
12
+ export type { ParsedTypeSpec as ParsedPgType } from "./type-spec";
13
+
14
+ export type Registries = {
15
+ enums: Map<string, EnumDef>;
16
+ composites: Map<string, CompositeDef>;
17
+ domains: Map<string, ResolvedDomain>;
18
+ };
19
+
20
+ // Resolved (post-pg-type-resolution) domain — after we've consumed the base
21
+ // type and folded the CHECK into an Annotation.
22
+ export type ResolvedDomain = {
23
+ schema?: string;
24
+ name: string;
25
+ shape: shapes.Shape;
26
+ };
27
+
28
+ const UUID_PATTERN_RE =
29
+ /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/;
30
+
31
+ export type ResolveResult = {
32
+ shape: shapes.Shape;
33
+ // True if this type implies an auto-incrementing column (SERIAL family)
34
+ autoIncrement?: boolean;
35
+ };
36
+
37
+ export const resolvePgType = (
38
+ spec: ParsedTypeSpec,
39
+ registries: Registries,
40
+ ): ResolveResult => {
41
+ if (spec.isArray) {
42
+ const inner = resolvePgType({ ...spec, isArray: false }, registries);
43
+ return { shape: shapes.array(inner.shape) };
44
+ }
45
+
46
+ // Domain lookup first (qualified or bare)
47
+ if (spec.qualified !== undefined) {
48
+ const d = registries.domains.get(spec.qualified);
49
+ if (d !== undefined) return { shape: d.shape };
50
+ }
51
+ {
52
+ const d = registries.domains.get(spec.baseName);
53
+ if (d !== undefined) return { shape: d.shape };
54
+ }
55
+
56
+ // Enum lookup (qualified or bare)
57
+ if (spec.qualified !== undefined) {
58
+ const e = registries.enums.get(spec.qualified);
59
+ if (e !== undefined) return { shape: enumToShape(e) };
60
+ }
61
+ {
62
+ const e = registries.enums.get(spec.baseName);
63
+ if (e !== undefined) return { shape: enumToShape(e) };
64
+ }
65
+
66
+ // Composite lookup: v1 falls back to unknown + format hint
67
+ if (spec.qualified !== undefined) {
68
+ const comp = registries.composites.get(spec.qualified);
69
+ if (comp !== undefined) {
70
+ return {
71
+ shape: annotate.as(shapes.unknown(), {
72
+ format: `pgtype:${spec.qualified}`,
73
+ }),
74
+ };
75
+ }
76
+ }
77
+
78
+ // Built-in types
79
+ const name = spec.baseName;
80
+
81
+ switch (name) {
82
+ case "uuid": {
83
+ const s = annotate.format(
84
+ annotate.pattern(shapes.string(), UUID_PATTERN_RE),
85
+ "uuid",
86
+ );
87
+ return { shape: s };
88
+ }
89
+ case "text": {
90
+ return { shape: shapes.string() };
91
+ }
92
+ case "citext": {
93
+ return {
94
+ shape: annotate.as(shapes.string(), { format: "pgtype:citext" }),
95
+ };
96
+ }
97
+ case "varchar":
98
+ case "character varying": {
99
+ const n = numIntArg(spec.args[0]);
100
+ if (n !== null) {
101
+ return { shape: annotate.max(shapes.string(), n) };
102
+ }
103
+ return { shape: shapes.string() };
104
+ }
105
+ case "char":
106
+ case "character":
107
+ case "bpchar": {
108
+ const n = numIntArg(spec.args[0]);
109
+ const len = n ?? 1;
110
+ return {
111
+ shape: annotate.min(annotate.max(shapes.string(), len), len),
112
+ };
113
+ }
114
+ case "smallint":
115
+ case "int2": {
116
+ return { shape: numberShapeForTag("int16") };
117
+ }
118
+ case "integer":
119
+ case "int":
120
+ case "int4": {
121
+ return { shape: numberShapeForTag("int32") };
122
+ }
123
+ case "bigint":
124
+ case "int8": {
125
+ return { shape: numberShapeForTag("int64") };
126
+ }
127
+ case "smallserial":
128
+ case "serial2": {
129
+ return { shape: numberShapeForTag("int16"), autoIncrement: true };
130
+ }
131
+ case "serial":
132
+ case "serial4": {
133
+ return { shape: numberShapeForTag("int32"), autoIncrement: true };
134
+ }
135
+ case "bigserial":
136
+ case "serial8": {
137
+ return { shape: numberShapeForTag("int64"), autoIncrement: true };
138
+ }
139
+ case "numeric":
140
+ case "decimal": {
141
+ const p = numIntArg(spec.args[0]);
142
+ const s = numIntArg(spec.args[1]);
143
+ if (p === 20 && s === 0) {
144
+ return { shape: numberShapeForTag("uint64") };
145
+ }
146
+ if (p !== null) {
147
+ const fmt =
148
+ s !== null ? `pgtype:numeric(${p},${s})` : `pgtype:numeric(${p})`;
149
+ return { shape: annotate.as(shapes.number(), { format: fmt }) };
150
+ }
151
+ return { shape: shapes.number() };
152
+ }
153
+ case "real":
154
+ case "float4": {
155
+ return { shape: numberShapeForTag("float32") };
156
+ }
157
+ case "double precision":
158
+ case "float8": {
159
+ return { shape: numberShapeForTag("float64") };
160
+ }
161
+ case "float": {
162
+ const p = numIntArg(spec.args[0]);
163
+ if (p !== null && p <= 24) return { shape: numberShapeForTag("float32") };
164
+ return { shape: numberShapeForTag("float64") };
165
+ }
166
+ case "boolean":
167
+ case "bool": {
168
+ return { shape: shapes.boolean() };
169
+ }
170
+ case "date":
171
+ case "time":
172
+ case "timetz":
173
+ case "timestamp":
174
+ case "timestamptz":
175
+ case "timestamp with time zone":
176
+ case "timestamp without time zone":
177
+ case "time with time zone":
178
+ case "time without time zone": {
179
+ const fmt = `pgtype:${name.replace(/ /g, "_")}`;
180
+ return { shape: annotate.as(shapes.date(), { format: fmt }) };
181
+ }
182
+ case "jsonb":
183
+ case "json": {
184
+ return {
185
+ shape: annotate.as(shapes.unknown(), { format: `pgtype:${name}` }),
186
+ };
187
+ }
188
+ case "bytea": {
189
+ return { shape: shapes.binary() };
190
+ }
191
+ case "vector": {
192
+ const n = numIntArg(spec.args[0]);
193
+ if (n !== null) return { shape: shapes.vector(n, shapes.float()) };
194
+ return { shape: shapes.vector(0, shapes.float()) };
195
+ }
196
+ case "tstzrange": {
197
+ return {
198
+ shape: annotate.as(shapes.range(shapes.date()), {
199
+ format: "pgtype:tstzrange",
200
+ }),
201
+ };
202
+ }
203
+ case "tsrange": {
204
+ return {
205
+ shape: annotate.as(shapes.range(shapes.date()), {
206
+ format: "pgtype:tsrange",
207
+ }),
208
+ };
209
+ }
210
+ case "daterange": {
211
+ return {
212
+ shape: annotate.as(shapes.range(shapes.date()), {
213
+ format: "pgtype:daterange",
214
+ }),
215
+ };
216
+ }
217
+ case "int4range": {
218
+ return { shape: shapes.range(shapes.int32()) };
219
+ }
220
+ case "int8range": {
221
+ return { shape: shapes.range(shapes.int64()) };
222
+ }
223
+ case "numrange": {
224
+ return { shape: shapes.range(shapes.number()) };
225
+ }
226
+ }
227
+
228
+ // Fall back: unknown + format hint preserving the original
229
+ const original =
230
+ spec.qualified !== undefined ? spec.qualified : spec.baseName;
231
+ const argsSuffix = spec.args.length > 0 ? `(${spec.args.join(",")})` : "";
232
+ return {
233
+ shape: annotate.as(shapes.unknown(), {
234
+ format: `pgtype:${original}${argsSuffix}`,
235
+ }),
236
+ };
237
+ };
238
+
239
+ const enumToShape = (e: EnumDef): shapes.Shape => {
240
+ const literals = e.values.map((v) => shapes.literal(v));
241
+ let u: shapes.Shape = shapes.union(literals);
242
+ u = annotate.titled(u, e.name);
243
+ if (e.schema !== undefined) {
244
+ u = annotate.as(u, { schema: e.schema });
245
+ }
246
+ return u;
247
+ };
@@ -0,0 +1,103 @@
1
+ import { annotate, shapes } from "../../../shape";
2
+ import { numIntArg, ParsedTypeSpec } from "./type-spec";
3
+
4
+ export type SqliteResolveResult = {
5
+ shape: shapes.Shape;
6
+ // True when the source type implies an auto-incrementing column. For SQLite
7
+ // this is left for the column-constraint parser to populate from
8
+ // `INTEGER PRIMARY KEY AUTOINCREMENT` — but we keep the field for symmetry
9
+ // with resolvePgType.
10
+ autoIncrement?: boolean;
11
+ };
12
+
13
+ // Resolve a SQLite type spec to a shape. SQLite has type affinity rather than
14
+ // a strict type system, so we accept many synonyms (VARCHAR, BOOLEAN, FLOAT,
15
+ // DATETIME, …) the engine itself accepts via affinity.
16
+ export const resolveSqliteType = (
17
+ spec: ParsedTypeSpec,
18
+ ): SqliteResolveResult => {
19
+ if (spec.isArray) {
20
+ // SQLite has no native array type; if we see `T[]` in input (unusual)
21
+ // fall back to a typed array shape.
22
+ const inner = resolveSqliteType({ ...spec, isArray: false });
23
+ return { shape: shapes.array(inner.shape) };
24
+ }
25
+
26
+ const name = spec.baseName.toLowerCase();
27
+
28
+ switch (name) {
29
+ case "integer":
30
+ case "int":
31
+ case "bigint":
32
+ case "int8":
33
+ case "int4":
34
+ case "int2":
35
+ case "smallint":
36
+ case "tinyint":
37
+ case "mediumint":
38
+ case "unsigned big int": {
39
+ return { shape: shapes.int64() };
40
+ }
41
+ case "text":
42
+ case "clob": {
43
+ return { shape: shapes.string() };
44
+ }
45
+ case "varchar":
46
+ case "character varying":
47
+ case "nvarchar":
48
+ case "varying character": {
49
+ const n = numIntArg(spec.args[0]);
50
+ if (n !== null) return { shape: annotate.max(shapes.string(), n) };
51
+ return { shape: shapes.string() };
52
+ }
53
+ case "char":
54
+ case "character":
55
+ case "nchar":
56
+ case "bpchar": {
57
+ const n = numIntArg(spec.args[0]);
58
+ const len = n ?? 1;
59
+ return {
60
+ shape: annotate.min(annotate.max(shapes.string(), len), len),
61
+ };
62
+ }
63
+ case "real":
64
+ case "float":
65
+ case "double":
66
+ case "double precision": {
67
+ return { shape: shapes.float64() };
68
+ }
69
+ case "numeric":
70
+ case "decimal": {
71
+ return { shape: shapes.number() };
72
+ }
73
+ case "boolean":
74
+ case "bool": {
75
+ return { shape: shapes.boolean() };
76
+ }
77
+ case "blob": {
78
+ return { shape: shapes.binary() };
79
+ }
80
+ case "date":
81
+ case "datetime":
82
+ case "timestamp":
83
+ case "timestamptz": {
84
+ return { shape: shapes.date() };
85
+ }
86
+ case "json":
87
+ case "jsonb": {
88
+ return {
89
+ shape: annotate.as(shapes.unknown(), { format: `sqlitetype:${name}` }),
90
+ };
91
+ }
92
+ }
93
+
94
+ // Fall back: unknown + format hint preserving the original.
95
+ const original =
96
+ spec.qualified !== undefined ? spec.qualified : spec.baseName;
97
+ const argsSuffix = spec.args.length > 0 ? `(${spec.args.join(",")})` : "";
98
+ return {
99
+ shape: annotate.as(shapes.unknown(), {
100
+ format: `sqlitetype:${original}${argsSuffix}`,
101
+ }),
102
+ };
103
+ };
@@ -0,0 +1,127 @@
1
+ import { Token } from "./lexer";
2
+
3
+ export type StatementKind =
4
+ | "createType"
5
+ | "createDomain"
6
+ | "createTable"
7
+ | "createTablePartitionOf"
8
+ | "skip";
9
+
10
+ export type Statement = {
11
+ kind: StatementKind;
12
+ tokens: Token[];
13
+ };
14
+
15
+ export const splitStatements = (tokens: Token[]): Token[][] => {
16
+ const out: Token[][] = [];
17
+ let current: Token[] = [];
18
+ let depth = 0;
19
+ for (const t of tokens) {
20
+ if (t.kind === "eof") break;
21
+ if (t.kind === "punct" && t.value === "(") depth += 1;
22
+ else if (t.kind === "punct" && t.value === ")") depth -= 1;
23
+ if (t.kind === "punct" && t.value === ";" && depth === 0) {
24
+ if (current.length > 0) out.push(current);
25
+ current = [];
26
+ continue;
27
+ }
28
+ current.push(t);
29
+ }
30
+ if (current.length > 0) out.push(current);
31
+ return out;
32
+ };
33
+
34
+ const peekIdent = (tokens: Token[], idx: number): string | null => {
35
+ const t = tokens[idx];
36
+ if (t === undefined) return null;
37
+ if (t.kind === "ident") return t.value;
38
+ return null;
39
+ };
40
+
41
+ export const classifyStatement = (tokens: Token[]): Statement => {
42
+ const k0 = peekIdent(tokens, 0);
43
+
44
+ if (k0 === "create") {
45
+ let k1 = peekIdent(tokens, 1);
46
+ // CREATE OR REPLACE ...
47
+ if (k1 === "or" && peekIdent(tokens, 2) === "replace") {
48
+ k1 = peekIdent(tokens, 3);
49
+ }
50
+ // CREATE [UNIQUE] INDEX ...
51
+ if (k1 === "unique" && peekIdent(tokens, 2) === "index")
52
+ return { kind: "skip", tokens };
53
+ if (k1 === "index") return { kind: "skip", tokens };
54
+ if (k1 === "trigger") return { kind: "skip", tokens };
55
+ if (k1 === "function") return { kind: "skip", tokens };
56
+ if (k1 === "procedure") return { kind: "skip", tokens };
57
+ if (k1 === "view") return { kind: "skip", tokens };
58
+ if (k1 === "materialized") return { kind: "skip", tokens };
59
+ if (k1 === "policy") return { kind: "skip", tokens };
60
+ if (k1 === "extension") return { kind: "skip", tokens };
61
+ if (k1 === "schema") return { kind: "skip", tokens };
62
+ if (k1 === "sequence") return { kind: "skip", tokens };
63
+ if (k1 === "language") return { kind: "skip", tokens };
64
+ if (k1 === "rule") return { kind: "skip", tokens };
65
+ if (k1 === "aggregate") return { kind: "skip", tokens };
66
+ if (k1 === "operator") return { kind: "skip", tokens };
67
+ if (k1 === "cast") return { kind: "skip", tokens };
68
+ if (k1 === "role") return { kind: "skip", tokens };
69
+ if (k1 === "user") return { kind: "skip", tokens };
70
+ if (k1 === "domain") return { kind: "createDomain", tokens };
71
+ if (k1 === "type") return { kind: "createType", tokens };
72
+ if (k1 === "virtual" && peekIdent(tokens, 2) === "table")
73
+ return { kind: "skip", tokens };
74
+ if (k1 === "table") {
75
+ // Look ahead for "PARTITION OF" to distinguish partition children
76
+ // (`CREATE TABLE name PARTITION OF parent ...`) from parents
77
+ // (which have `PARTITION BY` *inside or after* the column list).
78
+ // Children we skip; parents we parse normally (partition spec is dropped).
79
+ const partitionOfIdx = findPartitionOfIndex(tokens);
80
+ if (partitionOfIdx >= 0)
81
+ return { kind: "createTablePartitionOf", tokens };
82
+ return { kind: "createTable", tokens };
83
+ }
84
+ return { kind: "skip", tokens };
85
+ }
86
+
87
+ if (k0 === "alter") return { kind: "skip", tokens };
88
+ if (k0 === "insert") return { kind: "skip", tokens };
89
+ if (k0 === "select") return { kind: "skip", tokens };
90
+ if (k0 === "set") return { kind: "skip", tokens };
91
+ if (k0 === "comment") return { kind: "skip", tokens };
92
+ if (k0 === "grant") return { kind: "skip", tokens };
93
+ if (k0 === "revoke") return { kind: "skip", tokens };
94
+ if (k0 === "drop") return { kind: "skip", tokens };
95
+ if (k0 === "begin") return { kind: "skip", tokens };
96
+ if (k0 === "commit") return { kind: "skip", tokens };
97
+ if (k0 === "rollback") return { kind: "skip", tokens };
98
+ if (k0 === "with") return { kind: "skip", tokens };
99
+ if (k0 === "update") return { kind: "skip", tokens };
100
+ if (k0 === "delete") return { kind: "skip", tokens };
101
+ if (k0 === "truncate") return { kind: "skip", tokens };
102
+ if (k0 === "vacuum") return { kind: "skip", tokens };
103
+ if (k0 === "analyze") return { kind: "skip", tokens };
104
+ if (k0 === "explain") return { kind: "skip", tokens };
105
+ if (k0 === "refresh") return { kind: "skip", tokens };
106
+
107
+ return { kind: "skip", tokens };
108
+ };
109
+
110
+ // Search for `PARTITION OF` only outside parens (since CREATE TABLE columns
111
+ // could contain those keywords in weird places).
112
+ const findPartitionOfIndex = (tokens: Token[]): number => {
113
+ let depth = 0;
114
+ for (let i = 0; i < tokens.length; i += 1) {
115
+ const t = tokens[i]!;
116
+ if (t.kind === "punct" && t.value === "(") depth += 1;
117
+ else if (t.kind === "punct" && t.value === ")") depth -= 1;
118
+ if (
119
+ depth === 0 &&
120
+ t.kind === "ident" &&
121
+ t.value === "partition" &&
122
+ peekIdent(tokens, i + 1) === "of"
123
+ )
124
+ return i;
125
+ }
126
+ return -1;
127
+ };
@@ -0,0 +1,159 @@
1
+ import { shapes } from "../../../shape";
2
+ import { Token } from "./lexer";
3
+
4
+ // Type spec parsed from a sequence of tokens — base name (lowercased), optional
5
+ // qualified prefix (schema.name), parenthesised args, optional [] array suffix.
6
+ // This is dialect-agnostic; resolution to a shape happens in the per-dialect
7
+ // resolver.
8
+ export type ParsedTypeSpec = {
9
+ baseName: string;
10
+ qualified?: string;
11
+ args: string[];
12
+ isArray: boolean;
13
+ };
14
+
15
+ const MULTI_WORD_TYPES = new Set<string>([
16
+ "double precision",
17
+ "character varying",
18
+ "bit varying",
19
+ "timestamp with time zone",
20
+ "timestamp without time zone",
21
+ "time with time zone",
22
+ "time without time zone",
23
+ ]);
24
+
25
+ export const parseTypeSpec = (tokens: Token[]): ParsedTypeSpec => {
26
+ let i = 0;
27
+ const peek = (off = 0): Token | undefined => tokens[i + off];
28
+
29
+ const parts: string[] = [];
30
+ const t0 = tokens[i];
31
+ if (t0 === undefined || t0.kind !== "ident") {
32
+ return { baseName: "unknown", args: [], isArray: false };
33
+ }
34
+ parts.push(t0.value);
35
+ i += 1;
36
+ while (peek()?.kind === "punct" && peek()?.value === ".") {
37
+ i += 1;
38
+ const next = peek();
39
+ if (next === undefined || next.kind !== "ident") break;
40
+ parts.push(next.value);
41
+ i += 1;
42
+ }
43
+ let baseName = parts.join(".");
44
+ const qualified: string | undefined =
45
+ parts.length >= 2 ? parts.join(".") : undefined;
46
+
47
+ const second = peek();
48
+ if (parts.length === 1 && second?.kind === "ident" && !second.quoted) {
49
+ const combo = `${parts[0]} ${second.value}`;
50
+ const third = peek(2);
51
+ const fourth = peek(3);
52
+ if (
53
+ third?.kind === "ident" &&
54
+ fourth?.kind === "ident" &&
55
+ MULTI_WORD_TYPES.has(`${combo} ${third.value} ${fourth.value}`)
56
+ ) {
57
+ baseName = `${combo} ${third.value} ${fourth.value}`;
58
+ i += 3;
59
+ } else if (MULTI_WORD_TYPES.has(combo)) {
60
+ baseName = combo;
61
+ i += 1;
62
+ }
63
+ }
64
+
65
+ const args: string[] = [];
66
+ if (peek()?.kind === "punct" && peek()?.value === "(") {
67
+ i += 1;
68
+ let depth = 1;
69
+ let argBuf = "";
70
+ while (i < tokens.length && depth > 0) {
71
+ const t = tokens[i]!;
72
+ if (t.kind === "punct" && t.value === "(") {
73
+ depth += 1;
74
+ argBuf += "(";
75
+ } else if (t.kind === "punct" && t.value === ")") {
76
+ depth -= 1;
77
+ if (depth === 0) {
78
+ if (argBuf.length > 0) args.push(argBuf.trim());
79
+ argBuf = "";
80
+ i += 1;
81
+ break;
82
+ }
83
+ argBuf += ")";
84
+ } else if (t.kind === "punct" && t.value === "," && depth === 1) {
85
+ args.push(argBuf.trim());
86
+ argBuf = "";
87
+ } else if (t.kind === "string") {
88
+ argBuf += `'${t.value}'`;
89
+ } else {
90
+ argBuf += t.value;
91
+ }
92
+ i += 1;
93
+ }
94
+ }
95
+
96
+ let isArray = false;
97
+ if (
98
+ peek()?.kind === "punct" &&
99
+ peek()?.value === "[" &&
100
+ peek(1)?.kind === "punct" &&
101
+ peek(1)?.value === "]"
102
+ ) {
103
+ isArray = true;
104
+ }
105
+
106
+ return {
107
+ baseName,
108
+ ...(qualified !== undefined ? { qualified } : {}),
109
+ args,
110
+ isArray,
111
+ };
112
+ };
113
+
114
+ export const numIntArg = (s: string | undefined): number | null => {
115
+ if (s === undefined) return null;
116
+ const n = Number(s);
117
+ if (!isFinite(n) || Math.floor(n) !== n) return null;
118
+ return n;
119
+ };
120
+
121
+ export const numberShapeForTag = (
122
+ tag:
123
+ | "int8"
124
+ | "uint8"
125
+ | "int16"
126
+ | "uint16"
127
+ | "int32"
128
+ | "uint32"
129
+ | "int64"
130
+ | "uint64"
131
+ | "float"
132
+ | "float32"
133
+ | "float64",
134
+ ): shapes.ShapeNumber => {
135
+ switch (tag) {
136
+ case "int8":
137
+ return shapes.int8();
138
+ case "uint8":
139
+ return shapes.uint8();
140
+ case "int16":
141
+ return shapes.int16();
142
+ case "uint16":
143
+ return shapes.uint16();
144
+ case "int32":
145
+ return shapes.int32();
146
+ case "uint32":
147
+ return shapes.uint32();
148
+ case "int64":
149
+ return shapes.int64();
150
+ case "uint64":
151
+ return shapes.uint64();
152
+ case "float":
153
+ return shapes.float();
154
+ case "float32":
155
+ return shapes.float32();
156
+ case "float64":
157
+ return shapes.float64();
158
+ }
159
+ };