@pythia-software/query-table-codegen 0.5.0 → 0.6.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 +8 -0
- package/dist/{chunk-YQ663BVQ.js → chunk-ZQZDDNVV.js} +120 -36
- package/dist/chunk-ZQZDDNVV.js.map +1 -0
- package/dist/cli.js +12 -3
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-YQ663BVQ.js.map +0 -1
package/README.md
CHANGED
|
@@ -38,3 +38,11 @@ path with `--go-import` when using a fork or a vendored module.
|
|
|
38
38
|
|
|
39
39
|
The package also exports `generateTypeScript` and `generateGo` for build-tool
|
|
40
40
|
integrations.
|
|
41
|
+
|
|
42
|
+
## SQLite
|
|
43
|
+
|
|
44
|
+
Pass `--dialect sqlite` with `--go` (or `{ dialect: "sqlite" }` to `generateGo`)
|
|
45
|
+
to read `bindings.sqlite`. The default remains `postgres`; neither dialect falls
|
|
46
|
+
back to the other's expressions. SQLite bindings support `datetimeFormat`:
|
|
47
|
+
`rfc3339`, `utc-millis`, `unix-seconds`, and `unix-millis`. The frontend projection
|
|
48
|
+
continues to omit backend SQL. See [the SQLite guide](../../docs/backend-sqlite.md).
|
|
@@ -43,7 +43,8 @@ function asObject(value, label) {
|
|
|
43
43
|
return value;
|
|
44
44
|
}
|
|
45
45
|
function requiredString(value, label) {
|
|
46
|
-
if (typeof value !== "string" || !value)
|
|
46
|
+
if (typeof value !== "string" || !value)
|
|
47
|
+
throw new Error(`${label} must be a non-empty string`);
|
|
47
48
|
return value;
|
|
48
49
|
}
|
|
49
50
|
function goString(value) {
|
|
@@ -65,8 +66,12 @@ function orderTerms(value, label, fieldNames) {
|
|
|
65
66
|
return value.map((item, index) => {
|
|
66
67
|
const raw = asObject(item, `${label}[${index}]`);
|
|
67
68
|
const field = requiredString(raw.field, `${label}[${index}].field`);
|
|
68
|
-
if (!fieldNames.has(field))
|
|
69
|
-
|
|
69
|
+
if (!fieldNames.has(field))
|
|
70
|
+
throw new Error(
|
|
71
|
+
`${label}[${index}] references unknown field ${JSON.stringify(field)}`
|
|
72
|
+
);
|
|
73
|
+
if (raw.dir !== "asc" && raw.dir !== "desc")
|
|
74
|
+
throw new Error(`${label}[${index}].dir must be "asc" or "desc"`);
|
|
70
75
|
const term = { field, dir: raw.dir };
|
|
71
76
|
if (raw.nulls != null) {
|
|
72
77
|
if (raw.nulls !== "first" && raw.nulls !== "last") {
|
|
@@ -76,9 +81,12 @@ function orderTerms(value, label, fieldNames) {
|
|
|
76
81
|
}
|
|
77
82
|
if (raw.extract != null) {
|
|
78
83
|
const extract = asObject(raw.extract, `${label}[${index}].extract`);
|
|
79
|
-
if (typeof extract.regex !== "string")
|
|
84
|
+
if (typeof extract.regex !== "string")
|
|
85
|
+
throw new Error(`${label}[${index}].extract.regex must be a string`);
|
|
80
86
|
if (extract.regex.length > 1e4) {
|
|
81
|
-
throw new Error(
|
|
87
|
+
throw new Error(
|
|
88
|
+
`${label}[${index}].extract.regex exceeds 10000 characters`
|
|
89
|
+
);
|
|
82
90
|
}
|
|
83
91
|
term.extract = { regex: extract.regex };
|
|
84
92
|
}
|
|
@@ -94,28 +102,52 @@ function renderOrderTerms(terms) {
|
|
|
94
102
|
});
|
|
95
103
|
return `[]querytable.OrderBy{${members.join(", ")}}`;
|
|
96
104
|
}
|
|
97
|
-
function projectGoFields(doc) {
|
|
98
|
-
if (!Array.isArray(doc.fields))
|
|
99
|
-
|
|
105
|
+
function projectGoFields(doc, dialect) {
|
|
106
|
+
if (!Array.isArray(doc.fields))
|
|
107
|
+
throw new Error("schema.fields must be an array");
|
|
108
|
+
const rawFields = doc.fields.map(
|
|
109
|
+
(field, index) => asObject(field, `schema.fields[${index}]`)
|
|
110
|
+
);
|
|
100
111
|
const exprByName = /* @__PURE__ */ new Map();
|
|
101
112
|
for (const [index, field] of rawFields.entries()) {
|
|
102
113
|
const name = requiredString(field.name, `schema.fields[${index}].name`);
|
|
103
114
|
if (field.bindings == null) continue;
|
|
104
|
-
const bindings = asObject(
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
115
|
+
const bindings = asObject(
|
|
116
|
+
field.bindings,
|
|
117
|
+
`schema.fields[${index}].bindings`
|
|
118
|
+
);
|
|
119
|
+
if (bindings[dialect] == null) continue;
|
|
120
|
+
const binding = asObject(
|
|
121
|
+
bindings[dialect],
|
|
122
|
+
`schema.fields[${index}].bindings.${dialect}`
|
|
123
|
+
);
|
|
124
|
+
exprByName.set(
|
|
125
|
+
name,
|
|
126
|
+
requiredString(
|
|
127
|
+
binding.expr,
|
|
128
|
+
`schema.fields[${index}].bindings.${dialect}.expr`
|
|
129
|
+
)
|
|
130
|
+
);
|
|
108
131
|
}
|
|
109
132
|
const fields = [];
|
|
110
133
|
for (const [index, field] of rawFields.entries()) {
|
|
111
134
|
const name = requiredString(field.name, `schema.fields[${index}].name`);
|
|
112
135
|
const expr = exprByName.get(name);
|
|
113
136
|
if (!expr) continue;
|
|
114
|
-
const bindings = asObject(
|
|
115
|
-
|
|
116
|
-
|
|
137
|
+
const bindings = asObject(
|
|
138
|
+
field.bindings,
|
|
139
|
+
`schema.fields[${index}].bindings`
|
|
140
|
+
);
|
|
141
|
+
const binding = asObject(
|
|
142
|
+
bindings[dialect],
|
|
143
|
+
`schema.fields[${index}].bindings.${dialect}`
|
|
144
|
+
);
|
|
145
|
+
const kindName = typeof binding.kind === "string" ? binding.kind : requiredString(field.type, `schema.fields[${index}].type`);
|
|
117
146
|
const kind = GO_KINDS[kindName];
|
|
118
|
-
if (!kind)
|
|
147
|
+
if (!kind)
|
|
148
|
+
throw new Error(
|
|
149
|
+
`field ${JSON.stringify(name)} has unknown ${dialect} kind ${JSON.stringify(kindName)}`
|
|
150
|
+
);
|
|
119
151
|
let arrayCaseSensitive = false;
|
|
120
152
|
let serverFilter = true;
|
|
121
153
|
let filterOps;
|
|
@@ -123,37 +155,69 @@ function projectGoFields(doc) {
|
|
|
123
155
|
const filter = asObject(field.filter, `schema.fields[${index}].filter`);
|
|
124
156
|
arrayCaseSensitive = filter.arrayCaseSensitive === true;
|
|
125
157
|
if (typeof filter.enabled === "boolean") serverFilter = filter.enabled;
|
|
126
|
-
if (typeof filter.pushdown === "boolean")
|
|
158
|
+
if (typeof filter.pushdown === "boolean")
|
|
159
|
+
serverFilter = serverFilter && filter.pushdown;
|
|
127
160
|
if (filter.ops != null) {
|
|
128
161
|
if (!Array.isArray(filter.ops) || !filter.ops.every((op) => typeof op === "string")) {
|
|
129
|
-
throw new Error(
|
|
162
|
+
throw new Error(
|
|
163
|
+
`schema.fields[${index}].filter.ops must be an array of strings`
|
|
164
|
+
);
|
|
130
165
|
}
|
|
131
166
|
filterOps = [...filter.ops];
|
|
132
167
|
}
|
|
133
168
|
}
|
|
134
169
|
let sortable = true;
|
|
135
170
|
let sortExpr = expr;
|
|
171
|
+
let sqliteSortField;
|
|
136
172
|
if (field.sort != null) {
|
|
137
173
|
const sort = asObject(field.sort, `schema.fields[${index}].sort`);
|
|
138
174
|
if (typeof sort.enabled === "boolean") sortable = sort.enabled;
|
|
139
175
|
if (typeof sort.field === "string") {
|
|
140
176
|
const referencedExpr = exprByName.get(sort.field);
|
|
141
177
|
if (!referencedExpr) {
|
|
142
|
-
throw new Error(
|
|
178
|
+
throw new Error(
|
|
179
|
+
`field ${JSON.stringify(name)} sort target ${JSON.stringify(sort.field)} has no ${dialect} binding`
|
|
180
|
+
);
|
|
143
181
|
}
|
|
144
182
|
sortExpr = referencedExpr;
|
|
183
|
+
if (dialect === "sqlite") sqliteSortField = sort.field;
|
|
145
184
|
}
|
|
146
185
|
}
|
|
147
186
|
const projected = {
|
|
148
187
|
name,
|
|
149
188
|
kind,
|
|
150
189
|
expr,
|
|
151
|
-
synthetic:
|
|
190
|
+
synthetic: binding.synthetic === true,
|
|
152
191
|
serverFilter,
|
|
153
192
|
arrayCaseSensitive,
|
|
154
193
|
sortable,
|
|
155
|
-
sortExpr
|
|
194
|
+
sortExpr,
|
|
195
|
+
expressionNumeric: binding.expressionNumeric === true
|
|
156
196
|
};
|
|
197
|
+
if (sqliteSortField !== void 0) projected.sqliteSortField = sqliteSortField;
|
|
198
|
+
if (dialect === "sqlite" && binding.datetimeFormat != null) {
|
|
199
|
+
if (kindName !== "datetime" || !["rfc3339", "utc-millis", "unix-seconds", "unix-millis"].includes(
|
|
200
|
+
String(binding.datetimeFormat)
|
|
201
|
+
))
|
|
202
|
+
throw new Error(`field ${JSON.stringify(name)} has invalid SQLite datetimeFormat`);
|
|
203
|
+
projected.datetimeFormat = String(binding.datetimeFormat);
|
|
204
|
+
}
|
|
205
|
+
if (field.aggregate != null) {
|
|
206
|
+
const aggregate = asObject(
|
|
207
|
+
field.aggregate,
|
|
208
|
+
`schema.fields[${index}].aggregate`
|
|
209
|
+
);
|
|
210
|
+
if (typeof aggregate.groupable === "boolean")
|
|
211
|
+
projected.groupable = aggregate.groupable;
|
|
212
|
+
if (aggregate.measure === false) projected.aggregateOps = [];
|
|
213
|
+
else if (aggregate.ops != null) {
|
|
214
|
+
if (!Array.isArray(aggregate.ops) || !aggregate.ops.every((op) => typeof op === "string"))
|
|
215
|
+
throw new Error(
|
|
216
|
+
`schema.fields[${index}].aggregate.ops must be an array of strings`
|
|
217
|
+
);
|
|
218
|
+
projected.aggregateOps = [...aggregate.ops];
|
|
219
|
+
}
|
|
220
|
+
}
|
|
157
221
|
if (filterOps !== void 0) projected.filterOps = filterOps;
|
|
158
222
|
fields.push(projected);
|
|
159
223
|
}
|
|
@@ -177,24 +241,44 @@ function generateGo(input, options) {
|
|
|
177
241
|
const doc = asObject(input, "schema");
|
|
178
242
|
const name = requiredString(doc.name, "schema.name");
|
|
179
243
|
const idField = requiredString(doc.idField, "schema.idField");
|
|
180
|
-
const
|
|
244
|
+
const dialect = options.dialect ?? "postgres";
|
|
245
|
+
if (dialect !== "postgres" && dialect !== "sqlite") throw new Error("invalid SQL dialect");
|
|
246
|
+
const fields = projectGoFields(doc, dialect);
|
|
181
247
|
if (!fields.some((field) => field.name === idField)) {
|
|
182
|
-
throw new Error(
|
|
248
|
+
throw new Error(
|
|
249
|
+
`id field ${JSON.stringify(idField)} has no ${dialect} binding`
|
|
250
|
+
);
|
|
183
251
|
}
|
|
184
|
-
const fieldNames = new Set(
|
|
185
|
-
|
|
186
|
-
|
|
252
|
+
const fieldNames = new Set(
|
|
253
|
+
doc.fields.map(
|
|
254
|
+
(field) => requiredString(field.name, "schema field name")
|
|
255
|
+
)
|
|
256
|
+
);
|
|
257
|
+
const defaultSort = orderTerms(
|
|
258
|
+
doc.defaultSort,
|
|
259
|
+
"schema.defaultSort",
|
|
260
|
+
fieldNames
|
|
261
|
+
);
|
|
262
|
+
const tiebreakSort = orderTerms(
|
|
263
|
+
doc.tiebreakSort,
|
|
264
|
+
"schema.tiebreakSort",
|
|
265
|
+
fieldNames
|
|
266
|
+
);
|
|
187
267
|
const importPath = options.importPath ?? DEFAULT_GO_IMPORT;
|
|
188
|
-
if (!importPath || /[\s\x00-\x1f\x7f"]/u.test(importPath))
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const spacing = " ".repeat(longestFieldKey - key.length + 1);
|
|
194
|
-
const filterOps = field.filterOps ? `, FilterOps: []string{${field.filterOps.map(goString).join(", ")}}` : "";
|
|
195
|
-
return ` ${key}:${spacing}{Name: ${goString(field.name)}, Kind: querytable.${field.kind}, Expr: ${goString(field.expr)}, Synthetic: ${field.synthetic}, ServerFilter: ${field.serverFilter}${filterOps}${field.arrayCaseSensitive ? ", ArrayCaseSensitive: true" : ""}, Sortable: ${field.sortable}, SortExpr: ${goString(field.sortExpr)}},`;
|
|
196
|
-
}
|
|
268
|
+
if (!importPath || /[\s\x00-\x1f\x7f"]/u.test(importPath))
|
|
269
|
+
throw new Error("invalid Go import path");
|
|
270
|
+
const longestFieldKey = Math.max(
|
|
271
|
+
0,
|
|
272
|
+
...fields.map((field) => goString(field.name).length)
|
|
197
273
|
);
|
|
274
|
+
const fieldLines = fields.map((field) => {
|
|
275
|
+
const key = goString(field.name);
|
|
276
|
+
const spacing = " ".repeat(longestFieldKey - key.length + 1);
|
|
277
|
+
const filterOps = field.filterOps ? `, FilterOps: []string{${field.filterOps.map(goString).join(", ")}}` : "";
|
|
278
|
+
const aggregateOps = field.aggregateOps !== void 0 ? `, AggregateOps: []string{${field.aggregateOps.map(goString).join(", ")}}` : "";
|
|
279
|
+
const groupable = field.groupable !== void 0 ? `, Groupable: func() *bool { v := ${field.groupable}; return &v }()` : "";
|
|
280
|
+
return ` ${key}:${spacing}{Name: ${goString(field.name)}, Kind: querytable.${field.kind}, Expr: ${goString(field.expr)}, Synthetic: ${field.synthetic}${field.sqliteSortField ? `, SQLiteSortField: ${goString(field.sqliteSortField)}` : ""}${field.expressionNumeric ? ", ExpressionNumeric: true" : ""}${field.datetimeFormat ? `, SQLiteDatetimeFormat: ${goString(field.datetimeFormat)}` : ""}${aggregateOps}${groupable}, ServerFilter: ${field.serverFilter}${filterOps}${field.arrayCaseSensitive ? ", ArrayCaseSensitive: true" : ""}, Sortable: ${field.sortable}, SortExpr: ${goString(field.sortExpr)}},`;
|
|
281
|
+
});
|
|
198
282
|
return [
|
|
199
283
|
"// Code generated by query-table-codegen. DO NOT EDIT.",
|
|
200
284
|
`package ${options.packageName}`,
|
|
@@ -220,4 +304,4 @@ export {
|
|
|
220
304
|
generateTypeScript,
|
|
221
305
|
generateGo
|
|
222
306
|
};
|
|
223
|
-
//# sourceMappingURL=chunk-
|
|
307
|
+
//# sourceMappingURL=chunk-ZQZDDNVV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/generate.ts"],"sourcesContent":["import { loadSchema } from \"@pythia-software/query-table-core\";\n\nexport interface GoGenerationOptions {\n packageName: string;\n importPath?: string;\n dialect?: \"postgres\" | \"sqlite\";\n}\n\nconst DEFAULT_GO_IMPORT = \"github.com/Pythia-Software/query-table/backends/go\";\n\nconst GO_KINDS: Record<string, string> = {\n text: \"FieldText\",\n number: \"FieldNumber\",\n datetime: \"FieldDatetime\",\n bool: \"FieldBool\",\n enum: \"FieldEnum\",\n textarray: \"FieldTextArray\",\n};\n\nconst GO_KEYWORDS = new Set([\n \"break\",\n \"default\",\n \"func\",\n \"interface\",\n \"select\",\n \"case\",\n \"defer\",\n \"go\",\n \"map\",\n \"struct\",\n \"chan\",\n \"else\",\n \"goto\",\n \"package\",\n \"switch\",\n \"const\",\n \"fallthrough\",\n \"if\",\n \"range\",\n \"type\",\n \"continue\",\n \"for\",\n \"import\",\n \"return\",\n \"var\",\n]);\n\ntype JsonObject = Record<string, unknown>;\n\ninterface GoField {\n name: string;\n kind: string;\n expr: string;\n synthetic: boolean;\n serverFilter: boolean;\n arrayCaseSensitive: boolean;\n filterOps?: string[];\n sortable: boolean;\n sortExpr: string;\n sqliteSortField?: string;\n expressionNumeric: boolean;\n datetimeFormat?: string;\n aggregateOps?: string[];\n groupable?: boolean;\n}\n\ninterface OrderTerm {\n field: string;\n dir: \"asc\" | \"desc\";\n nulls?: \"first\" | \"last\";\n extract?: { regex: string };\n}\n\nfunction asObject(value: unknown, label: string): JsonObject {\n if (value == null || typeof value !== \"object\" || Array.isArray(value)) {\n throw new Error(`${label} must be an object`);\n }\n return value as JsonObject;\n}\n\nfunction requiredString(value: unknown, label: string): string {\n if (typeof value !== \"string\" || !value)\n throw new Error(`${label} must be a non-empty string`);\n return value;\n}\n\nfunction goString(value: string): string {\n return JSON.stringify(value)\n .replace(/\\u2028/g, \"\\\\u2028\")\n .replace(/\\u2029/g, \"\\\\u2029\");\n}\n\nfunction exportedName(name: string): string {\n const parts = name.split(/[^A-Za-z0-9]+/).filter(Boolean);\n const base =\n parts.map((part) => part[0]!.toUpperCase() + part.slice(1)).join(\"\") ||\n \"Generated\";\n return `${/^\\d/.test(base) ? `Schema${base}` : base}Schema`;\n}\n\nfunction validateGoPackage(packageName: string): void {\n if (\n !/^[A-Za-z_][A-Za-z0-9_]*$/.test(packageName) ||\n GO_KEYWORDS.has(packageName)\n ) {\n throw new Error(`invalid Go package name ${JSON.stringify(packageName)}`);\n }\n}\n\nfunction orderTerms(\n value: unknown,\n label: string,\n fieldNames: Set<string>,\n): OrderTerm[] {\n if (value == null) return [];\n if (!Array.isArray(value)) throw new Error(`${label} must be an array`);\n return value.map((item, index) => {\n const raw = asObject(item, `${label}[${index}]`);\n const field = requiredString(raw.field, `${label}[${index}].field`);\n if (!fieldNames.has(field))\n throw new Error(\n `${label}[${index}] references unknown field ${JSON.stringify(field)}`,\n );\n if (raw.dir !== \"asc\" && raw.dir !== \"desc\")\n throw new Error(`${label}[${index}].dir must be \"asc\" or \"desc\"`);\n const term: OrderTerm = { field, dir: raw.dir };\n if (raw.nulls != null) {\n if (raw.nulls !== \"first\" && raw.nulls !== \"last\") {\n throw new Error(`${label}[${index}].nulls must be \"first\" or \"last\"`);\n }\n term.nulls = raw.nulls;\n }\n if (raw.extract != null) {\n const extract = asObject(raw.extract, `${label}[${index}].extract`);\n if (typeof extract.regex !== \"string\")\n throw new Error(`${label}[${index}].extract.regex must be a string`);\n if (extract.regex.length > 10_000) {\n throw new Error(\n `${label}[${index}].extract.regex exceeds 10000 characters`,\n );\n }\n term.extract = { regex: extract.regex };\n }\n return term;\n });\n}\n\nfunction renderOrderTerms(terms: OrderTerm[]): string {\n if (terms.length === 0) return \"nil\";\n const members = terms.map((term) => {\n const nulls = term.nulls ? `, Nulls: ${goString(term.nulls)}` : \"\";\n const extract = term.extract\n ? `, Extract: &querytable.RegexExtract{Regex: ${goString(term.extract.regex)}}`\n : \"\";\n return `{Field: ${goString(term.field)}, Dir: ${goString(term.dir)}${nulls}${extract}}`;\n });\n return `[]querytable.OrderBy{${members.join(\", \")}}`;\n}\n\nfunction projectGoFields(doc: JsonObject, dialect: \"postgres\" | \"sqlite\"): GoField[] {\n if (!Array.isArray(doc.fields))\n throw new Error(\"schema.fields must be an array\");\n const rawFields = doc.fields.map((field, index) =>\n asObject(field, `schema.fields[${index}]`),\n );\n const exprByName = new Map<string, string>();\n\n for (const [index, field] of rawFields.entries()) {\n const name = requiredString(field.name, `schema.fields[${index}].name`);\n if (field.bindings == null) continue;\n const bindings = asObject(\n field.bindings,\n `schema.fields[${index}].bindings`,\n );\n if (bindings[dialect] == null) continue;\n const binding = asObject(\n bindings[dialect],\n `schema.fields[${index}].bindings.${dialect}`,\n );\n exprByName.set(\n name,\n requiredString(\n binding.expr,\n `schema.fields[${index}].bindings.${dialect}.expr`,\n ),\n );\n }\n\n const fields: GoField[] = [];\n for (const [index, field] of rawFields.entries()) {\n const name = requiredString(field.name, `schema.fields[${index}].name`);\n const expr = exprByName.get(name);\n if (!expr) continue;\n const bindings = asObject(\n field.bindings,\n `schema.fields[${index}].bindings`,\n );\n const binding = asObject(\n bindings[dialect],\n `schema.fields[${index}].bindings.${dialect}`,\n );\n const kindName =\n typeof binding.kind === \"string\"\n ? binding.kind\n : requiredString(field.type, `schema.fields[${index}].type`);\n const kind = GO_KINDS[kindName];\n if (!kind)\n throw new Error(\n `field ${JSON.stringify(name)} has unknown ${dialect} kind ${JSON.stringify(kindName)}`,\n );\n\n let arrayCaseSensitive = false;\n let serverFilter = true;\n let filterOps: string[] | undefined;\n if (field.filter != null) {\n const filter = asObject(field.filter, `schema.fields[${index}].filter`);\n arrayCaseSensitive = filter.arrayCaseSensitive === true;\n if (typeof filter.enabled === \"boolean\") serverFilter = filter.enabled;\n if (typeof filter.pushdown === \"boolean\")\n serverFilter = serverFilter && filter.pushdown;\n if (filter.ops != null) {\n if (\n !Array.isArray(filter.ops) ||\n !filter.ops.every((op) => typeof op === \"string\")\n ) {\n throw new Error(\n `schema.fields[${index}].filter.ops must be an array of strings`,\n );\n }\n filterOps = [...filter.ops] as string[];\n }\n }\n\n let sortable = true;\n let sortExpr = expr;\n let sqliteSortField: string | undefined;\n if (field.sort != null) {\n const sort = asObject(field.sort, `schema.fields[${index}].sort`);\n if (typeof sort.enabled === \"boolean\") sortable = sort.enabled;\n if (typeof sort.field === \"string\") {\n const referencedExpr = exprByName.get(sort.field);\n if (!referencedExpr) {\n throw new Error(\n `field ${JSON.stringify(name)} sort target ${JSON.stringify(sort.field)} has no ${dialect} binding`,\n );\n }\n sortExpr = referencedExpr;\n if (dialect === \"sqlite\") sqliteSortField = sort.field;\n }\n }\n\n const projected: GoField = {\n name,\n kind,\n expr,\n synthetic: binding.synthetic === true,\n serverFilter,\n arrayCaseSensitive,\n sortable,\n sortExpr,\n expressionNumeric: binding.expressionNumeric === true,\n };\n if (sqliteSortField !== undefined) projected.sqliteSortField = sqliteSortField;\n if (dialect === \"sqlite\" && binding.datetimeFormat != null) {\n if (\n kindName !== \"datetime\" ||\n ![\"rfc3339\", \"utc-millis\", \"unix-seconds\", \"unix-millis\"].includes(\n String(binding.datetimeFormat),\n )\n )\n throw new Error(`field ${JSON.stringify(name)} has invalid SQLite datetimeFormat`);\n projected.datetimeFormat = String(binding.datetimeFormat);\n }\n if (field.aggregate != null) {\n const aggregate = asObject(\n field.aggregate,\n `schema.fields[${index}].aggregate`,\n );\n if (typeof aggregate.groupable === \"boolean\")\n projected.groupable = aggregate.groupable;\n if (aggregate.measure === false) projected.aggregateOps = [];\n else if (aggregate.ops != null) {\n if (\n !Array.isArray(aggregate.ops) ||\n !aggregate.ops.every((op) => typeof op === \"string\")\n )\n throw new Error(\n `schema.fields[${index}].aggregate.ops must be an array of strings`,\n );\n projected.aggregateOps = [...aggregate.ops] as string[];\n }\n }\n if (filterOps !== undefined) projected.filterOps = filterOps;\n fields.push(projected);\n }\n return fields;\n}\n\n/** Generate the frontend projection. SQL bindings are deliberately omitted. */\nexport function generateTypeScript(input: unknown): string {\n const schema = loadSchema(input);\n const constant =\n exportedName(schema.name)\n .replace(/Schema$/, \"\")\n .toUpperCase() + \"_SCHEMA\";\n return [\n \"// Code generated by query-table-codegen. DO NOT EDIT.\",\n 'import type { FieldSchema } from \"@pythia-software/query-table-core\";',\n \"\",\n `export const ${constant}: FieldSchema = ${JSON.stringify(schema, null, 2)};`,\n `export const ${constant.replace(/_SCHEMA$/, \"_FIELDS\")} = ${constant}.fields;`,\n \"\",\n ].join(\"\\n\");\n}\n\n/** Generate a direct Go Schema literal with no runtime JSON parsing. */\nexport function generateGo(\n input: unknown,\n options: GoGenerationOptions,\n): string {\n validateGoPackage(options.packageName);\n loadSchema(input);\n const doc = asObject(input, \"schema\");\n const name = requiredString(doc.name, \"schema.name\");\n const idField = requiredString(doc.idField, \"schema.idField\");\n const dialect = options.dialect ?? \"postgres\";\n if (dialect !== \"postgres\" && dialect !== \"sqlite\") throw new Error(\"invalid SQL dialect\");\n const fields = projectGoFields(doc, dialect);\n if (!fields.some((field) => field.name === idField)) {\n throw new Error(\n `id field ${JSON.stringify(idField)} has no ${dialect} binding`,\n );\n }\n const fieldNames = new Set(\n (doc.fields as JsonObject[]).map((field) =>\n requiredString(field.name, \"schema field name\"),\n ),\n );\n const defaultSort = orderTerms(\n doc.defaultSort,\n \"schema.defaultSort\",\n fieldNames,\n );\n const tiebreakSort = orderTerms(\n doc.tiebreakSort,\n \"schema.tiebreakSort\",\n fieldNames,\n );\n const importPath = options.importPath ?? DEFAULT_GO_IMPORT;\n if (!importPath || /[\\s\\x00-\\x1f\\x7f\"]/u.test(importPath))\n throw new Error(\"invalid Go import path\");\n\n const longestFieldKey = Math.max(\n 0,\n ...fields.map((field) => goString(field.name).length),\n );\n const fieldLines = fields.map((field) => {\n const key = goString(field.name);\n const spacing = \" \".repeat(longestFieldKey - key.length + 1);\n const filterOps = field.filterOps\n ? `, FilterOps: []string{${field.filterOps.map(goString).join(\", \")}}`\n : \"\";\n const aggregateOps =\n field.aggregateOps !== undefined\n ? `, AggregateOps: []string{${field.aggregateOps.map(goString).join(\", \")}}`\n : \"\";\n const groupable =\n field.groupable !== undefined\n ? `, Groupable: func() *bool { v := ${field.groupable}; return &v }()`\n : \"\";\n return `\\t\\t\\t${key}:${spacing}{Name: ${goString(field.name)}, Kind: querytable.${field.kind}, Expr: ${goString(field.expr)}, Synthetic: ${field.synthetic}${field.sqliteSortField ? `, SQLiteSortField: ${goString(field.sqliteSortField)}` : \"\"}${field.expressionNumeric ? \", ExpressionNumeric: true\" : \"\"}${field.datetimeFormat ? `, SQLiteDatetimeFormat: ${goString(field.datetimeFormat)}` : \"\"}${aggregateOps}${groupable}, ServerFilter: ${field.serverFilter}${filterOps}${field.arrayCaseSensitive ? \", ArrayCaseSensitive: true\" : \"\"}, Sortable: ${field.sortable}, SortExpr: ${goString(field.sortExpr)}},`;\n });\n\n return [\n \"// Code generated by query-table-codegen. DO NOT EDIT.\",\n `package ${options.packageName}`,\n \"\",\n `import querytable ${goString(importPath)}`,\n \"\",\n `func ${exportedName(name)}() querytable.Schema {`,\n \"\\treturn querytable.Schema{\",\n `\\t\\tName: ${goString(name)},`,\n `\\t\\tIDField: ${goString(idField)},`,\n \"\\t\\tFields: map[string]querytable.FieldSpec{\",\n ...fieldLines,\n \"\\t\\t},\",\n `\\t\\tDefaultSort: ${renderOrderTerms(defaultSort)},`,\n `\\t\\tTiebreakSort: ${renderOrderTerms(tiebreakSort)},`,\n \"\\t}\",\n \"}\",\n \"\",\n ].join(\"\\n\");\n}\n"],"mappings":"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","names":[]}
|
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
generateGo,
|
|
4
4
|
generateTypeScript
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-ZQZDDNVV.js";
|
|
6
6
|
|
|
7
7
|
// src/cli.ts
|
|
8
8
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
@@ -18,6 +18,8 @@ Usage:
|
|
|
18
18
|
query-table-codegen <schema.json> --ts <output.ts>
|
|
19
19
|
query-table-codegen <schema.json> --go <output.go> --go-package <name> [--go-import <path>]
|
|
20
20
|
|
|
21
|
+
Use --dialect sqlite with --go to read bindings.sqlite (default: postgres).
|
|
22
|
+
|
|
21
23
|
Both --ts and --go may be supplied in one invocation.
|
|
22
24
|
`;
|
|
23
25
|
function optionValue(args, index, flag) {
|
|
@@ -46,6 +48,13 @@ function parseArgs(args) {
|
|
|
46
48
|
options.goPackage = optionValue(args, index, flag);
|
|
47
49
|
index += 1;
|
|
48
50
|
break;
|
|
51
|
+
case "--dialect": {
|
|
52
|
+
const value = optionValue(args, index, flag);
|
|
53
|
+
if (value !== "postgres" && value !== "sqlite") throw new Error("--dialect must be postgres or sqlite");
|
|
54
|
+
options.dialect = value;
|
|
55
|
+
index += 1;
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
49
58
|
case "--go-import":
|
|
50
59
|
options.goImport = optionValue(args, index, flag);
|
|
51
60
|
index += 1;
|
|
@@ -56,7 +65,7 @@ function parseArgs(args) {
|
|
|
56
65
|
}
|
|
57
66
|
if (!options.ts && !options.go) throw new Error("at least one of --ts or --go is required");
|
|
58
67
|
if (options.go && !options.goPackage) throw new Error("--go-package is required with --go");
|
|
59
|
-
if (!options.go && (options.goPackage || options.goImport)) throw new Error("--go-package/--go-import require --go");
|
|
68
|
+
if (!options.go && (options.goPackage || options.goImport || options.dialect)) throw new Error("--go-package/--go-import require --go");
|
|
60
69
|
return options;
|
|
61
70
|
}
|
|
62
71
|
async function writeGenerated(path, contents) {
|
|
@@ -89,7 +98,7 @@ async function main() {
|
|
|
89
98
|
if (tsPath && goPath && tsPath === goPath) throw new Error("--ts and --go must use different output paths");
|
|
90
99
|
const tsOutput = options.ts ? generateTypeScript(document) : void 0;
|
|
91
100
|
const goOptions = options.go ? options.goImport ? { packageName: options.goPackage, importPath: options.goImport } : { packageName: options.goPackage } : void 0;
|
|
92
|
-
const goOutput = goOptions ? generateGo(document, goOptions) : void 0;
|
|
101
|
+
const goOutput = goOptions ? generateGo(document, { ...goOptions, ...options.dialect ? { dialect: options.dialect } : {} }) : void 0;
|
|
93
102
|
await Promise.all([
|
|
94
103
|
...options.ts && tsOutput ? [writeGenerated(options.ts, tsOutput)] : [],
|
|
95
104
|
...options.go && goOutput ? [writeGenerated(options.go, goOutput)] : []
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, resolve } from \"node:path\";\nimport { generateGo, generateTypeScript } from \"./generate\";\n\nconst packageManifest = JSON.parse(await readFile(new URL(\"../package.json\", import.meta.url), \"utf8\")) as {\n version?: unknown;\n};\nif (typeof packageManifest.version !== \"string\" || !packageManifest.version) {\n throw new Error(\"package.json does not contain a valid version\");\n}\nconst VERSION = packageManifest.version;\n\ninterface CliOptions {\n input: string;\n ts?: string;\n go?: string;\n goPackage?: string;\n goImport?: string;\n}\n\nconst HELP = `query-table-codegen ${VERSION}\n\nUsage:\n query-table-codegen <schema.json> --ts <output.ts>\n query-table-codegen <schema.json> --go <output.go> --go-package <name> [--go-import <path>]\n\nBoth --ts and --go may be supplied in one invocation.\n`;\n\nfunction optionValue(args: string[], index: number, flag: string): string {\n const value = args[index + 1];\n if (!value || value.startsWith(\"--\")) throw new Error(`${flag} requires a value`);\n return value;\n}\n\nfunction parseArgs(args: string[]): CliOptions | \"help\" | \"version\" {\n if (args.includes(\"--help\") || args.includes(\"-h\")) return \"help\";\n if (args.includes(\"--version\") || args.includes(\"-v\")) return \"version\";\n const input = args[0];\n if (!input || input.startsWith(\"--\")) throw new Error(\"a schema JSON path is required\");\n const options: CliOptions = { input };\n\n for (let index = 1; index < args.length; index += 1) {\n const flag = args[index]!;\n switch (flag) {\n case \"--ts\":\n options.ts = optionValue(args, index, flag);\n index += 1;\n break;\n case \"--go\":\n options.go = optionValue(args, index, flag);\n index += 1;\n break;\n case \"--go-package\":\n options.goPackage = optionValue(args, index, flag);\n index += 1;\n break;\n case \"--go-import\":\n options.goImport = optionValue(args, index, flag);\n index += 1;\n break;\n default:\n throw new Error(`unknown option ${flag}`);\n }\n }\n\n if (!options.ts && !options.go) throw new Error(\"at least one of --ts or --go is required\");\n if (options.go && !options.goPackage) throw new Error(\"--go-package is required with --go\");\n if (!options.go && (options.goPackage || options.goImport)) throw new Error(\"--go-package/--go-import require --go\");\n return options;\n}\n\nasync function writeGenerated(path: string, contents: string): Promise<void> {\n const output = resolve(path);\n await mkdir(dirname(output), { recursive: true });\n await writeFile(output, contents, \"utf8\");\n}\n\nasync function main(): Promise<void> {\n const options = parseArgs(process.argv.slice(2));\n if (options === \"help\") {\n process.stdout.write(HELP);\n return;\n }\n if (options === \"version\") {\n process.stdout.write(`${VERSION}\\n`);\n return;\n }\n\n const source = await readFile(resolve(options.input), \"utf8\");\n let document: unknown;\n try {\n document = JSON.parse(source);\n } catch (error) {\n throw new Error(`invalid JSON in ${options.input}: ${(error as Error).message}`);\n }\n\n const inputPath = resolve(options.input);\n const tsPath = options.ts ? resolve(options.ts) : undefined;\n const goPath = options.go ? resolve(options.go) : undefined;\n if (tsPath === inputPath || goPath === inputPath) throw new Error(\"an output path must not overwrite the input schema\");\n if (tsPath && goPath && tsPath === goPath) throw new Error(\"--ts and --go must use different output paths\");\n\n const tsOutput = options.ts ? generateTypeScript(document) : undefined;\n const goOptions = options.go\n ? options.goImport\n ? { packageName: options.goPackage!, importPath: options.goImport }\n : { packageName: options.goPackage! }\n : undefined;\n const goOutput = goOptions ? generateGo(document, goOptions) : undefined;\n\n await Promise.all([\n ...(options.ts && tsOutput ? [writeGenerated(options.ts, tsOutput)] : []),\n ...(options.go && goOutput ? [writeGenerated(options.go, goOutput)] : []),\n ]);\n}\n\nvoid main().catch((error: unknown) => {\n process.stderr.write(`query-table-codegen: ${(error as Error).message}\\n`);\n process.exitCode = 1;\n});\n"],"mappings":";;;;;;;AAEA,SAAS,OAAO,UAAU,iBAAiB;AAC3C,SAAS,SAAS,eAAe;AAGjC,IAAM,kBAAkB,KAAK,MAAM,MAAM,SAAS,IAAI,IAAI,mBAAmB,YAAY,GAAG,GAAG,MAAM,CAAC;AAGtG,IAAI,OAAO,gBAAgB,YAAY,YAAY,CAAC,gBAAgB,SAAS;AAC3E,QAAM,IAAI,MAAM,+CAA+C;AACjE;AACA,IAAM,UAAU,gBAAgB;
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, resolve } from \"node:path\";\nimport { generateGo, generateTypeScript } from \"./generate\";\n\nconst packageManifest = JSON.parse(await readFile(new URL(\"../package.json\", import.meta.url), \"utf8\")) as {\n version?: unknown;\n};\nif (typeof packageManifest.version !== \"string\" || !packageManifest.version) {\n throw new Error(\"package.json does not contain a valid version\");\n}\nconst VERSION = packageManifest.version;\n\ninterface CliOptions {\n input: string;\n ts?: string;\n go?: string;\n goPackage?: string;\n goImport?: string;\n dialect?: \"postgres\" | \"sqlite\";\n}\n\nconst HELP = `query-table-codegen ${VERSION}\n\nUsage:\n query-table-codegen <schema.json> --ts <output.ts>\n query-table-codegen <schema.json> --go <output.go> --go-package <name> [--go-import <path>]\n\nUse --dialect sqlite with --go to read bindings.sqlite (default: postgres).\n\nBoth --ts and --go may be supplied in one invocation.\n`;\n\nfunction optionValue(args: string[], index: number, flag: string): string {\n const value = args[index + 1];\n if (!value || value.startsWith(\"--\")) throw new Error(`${flag} requires a value`);\n return value;\n}\n\nfunction parseArgs(args: string[]): CliOptions | \"help\" | \"version\" {\n if (args.includes(\"--help\") || args.includes(\"-h\")) return \"help\";\n if (args.includes(\"--version\") || args.includes(\"-v\")) return \"version\";\n const input = args[0];\n if (!input || input.startsWith(\"--\")) throw new Error(\"a schema JSON path is required\");\n const options: CliOptions = { input };\n\n for (let index = 1; index < args.length; index += 1) {\n const flag = args[index]!;\n switch (flag) {\n case \"--ts\":\n options.ts = optionValue(args, index, flag);\n index += 1;\n break;\n case \"--go\":\n options.go = optionValue(args, index, flag);\n index += 1;\n break;\n case \"--go-package\":\n options.goPackage = optionValue(args, index, flag);\n index += 1;\n break;\n case \"--dialect\": {\n const value = optionValue(args, index, flag);\n if (value !== \"postgres\" && value !== \"sqlite\") throw new Error(\"--dialect must be postgres or sqlite\");\n options.dialect = value;\n index += 1;\n break;\n }\n case \"--go-import\":\n options.goImport = optionValue(args, index, flag);\n index += 1;\n break;\n default:\n throw new Error(`unknown option ${flag}`);\n }\n }\n\n if (!options.ts && !options.go) throw new Error(\"at least one of --ts or --go is required\");\n if (options.go && !options.goPackage) throw new Error(\"--go-package is required with --go\");\n if (!options.go && (options.goPackage || options.goImport || options.dialect)) throw new Error(\"--go-package/--go-import require --go\");\n return options;\n}\n\nasync function writeGenerated(path: string, contents: string): Promise<void> {\n const output = resolve(path);\n await mkdir(dirname(output), { recursive: true });\n await writeFile(output, contents, \"utf8\");\n}\n\nasync function main(): Promise<void> {\n const options = parseArgs(process.argv.slice(2));\n if (options === \"help\") {\n process.stdout.write(HELP);\n return;\n }\n if (options === \"version\") {\n process.stdout.write(`${VERSION}\\n`);\n return;\n }\n\n const source = await readFile(resolve(options.input), \"utf8\");\n let document: unknown;\n try {\n document = JSON.parse(source);\n } catch (error) {\n throw new Error(`invalid JSON in ${options.input}: ${(error as Error).message}`);\n }\n\n const inputPath = resolve(options.input);\n const tsPath = options.ts ? resolve(options.ts) : undefined;\n const goPath = options.go ? resolve(options.go) : undefined;\n if (tsPath === inputPath || goPath === inputPath) throw new Error(\"an output path must not overwrite the input schema\");\n if (tsPath && goPath && tsPath === goPath) throw new Error(\"--ts and --go must use different output paths\");\n\n const tsOutput = options.ts ? generateTypeScript(document) : undefined;\n const goOptions = options.go\n ? options.goImport\n ? { packageName: options.goPackage!, importPath: options.goImport }\n : { packageName: options.goPackage! }\n : undefined;\n const goOutput = goOptions ? generateGo(document, { ...goOptions, ...(options.dialect ? { dialect: options.dialect } : {}) }) : undefined;\n\n await Promise.all([\n ...(options.ts && tsOutput ? [writeGenerated(options.ts, tsOutput)] : []),\n ...(options.go && goOutput ? [writeGenerated(options.go, goOutput)] : []),\n ]);\n}\n\nvoid main().catch((error: unknown) => {\n process.stderr.write(`query-table-codegen: ${(error as Error).message}\\n`);\n process.exitCode = 1;\n});\n"],"mappings":"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","names":[]}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pythia-software/query-table-codegen",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Generate the TS catalog + Go schema from a query-table JSON schema document.",
|
|
5
5
|
"keywords": ["codegen", "data-table", "json-schema", "typescript", "golang"],
|
|
6
6
|
"license": "MIT",
|
|
@@ -34,6 +34,6 @@
|
|
|
34
34
|
"typecheck": "tsc --noEmit"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@pythia-software/query-table-core": "0.
|
|
37
|
+
"@pythia-software/query-table-core": "0.6.0"
|
|
38
38
|
}
|
|
39
39
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/generate.ts"],"sourcesContent":["import { loadSchema } from \"@pythia-software/query-table-core\";\n\nexport interface GoGenerationOptions {\n packageName: string;\n importPath?: string;\n}\n\nconst DEFAULT_GO_IMPORT = \"github.com/Pythia-Software/query-table/backends/go\";\n\nconst GO_KINDS: Record<string, string> = {\n text: \"FieldText\",\n number: \"FieldNumber\",\n datetime: \"FieldDatetime\",\n bool: \"FieldBool\",\n enum: \"FieldEnum\",\n textarray: \"FieldTextArray\",\n};\n\nconst GO_KEYWORDS = new Set([\n \"break\",\n \"default\",\n \"func\",\n \"interface\",\n \"select\",\n \"case\",\n \"defer\",\n \"go\",\n \"map\",\n \"struct\",\n \"chan\",\n \"else\",\n \"goto\",\n \"package\",\n \"switch\",\n \"const\",\n \"fallthrough\",\n \"if\",\n \"range\",\n \"type\",\n \"continue\",\n \"for\",\n \"import\",\n \"return\",\n \"var\",\n]);\n\ntype JsonObject = Record<string, unknown>;\n\ninterface GoField {\n name: string;\n kind: string;\n expr: string;\n synthetic: boolean;\n serverFilter: boolean;\n arrayCaseSensitive: boolean;\n filterOps?: string[];\n sortable: boolean;\n sortExpr: string;\n}\n\ninterface OrderTerm {\n field: string;\n dir: \"asc\" | \"desc\";\n nulls?: \"first\" | \"last\";\n extract?: { regex: string };\n}\n\nfunction asObject(value: unknown, label: string): JsonObject {\n if (value == null || typeof value !== \"object\" || Array.isArray(value)) {\n throw new Error(`${label} must be an object`);\n }\n return value as JsonObject;\n}\n\nfunction requiredString(value: unknown, label: string): string {\n if (typeof value !== \"string\" || !value) throw new Error(`${label} must be a non-empty string`);\n return value;\n}\n\nfunction goString(value: string): string {\n return JSON.stringify(value).replace(/\\u2028/g, \"\\\\u2028\").replace(/\\u2029/g, \"\\\\u2029\");\n}\n\nfunction exportedName(name: string): string {\n const parts = name.split(/[^A-Za-z0-9]+/).filter(Boolean);\n const base = parts.map((part) => part[0]!.toUpperCase() + part.slice(1)).join(\"\") || \"Generated\";\n return `${/^\\d/.test(base) ? `Schema${base}` : base}Schema`;\n}\n\nfunction validateGoPackage(packageName: string): void {\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(packageName) || GO_KEYWORDS.has(packageName)) {\n throw new Error(`invalid Go package name ${JSON.stringify(packageName)}`);\n }\n}\n\nfunction orderTerms(value: unknown, label: string, fieldNames: Set<string>): OrderTerm[] {\n if (value == null) return [];\n if (!Array.isArray(value)) throw new Error(`${label} must be an array`);\n return value.map((item, index) => {\n const raw = asObject(item, `${label}[${index}]`);\n const field = requiredString(raw.field, `${label}[${index}].field`);\n if (!fieldNames.has(field)) throw new Error(`${label}[${index}] references unknown field ${JSON.stringify(field)}`);\n if (raw.dir !== \"asc\" && raw.dir !== \"desc\") throw new Error(`${label}[${index}].dir must be \"asc\" or \"desc\"`);\n const term: OrderTerm = { field, dir: raw.dir };\n if (raw.nulls != null) {\n if (raw.nulls !== \"first\" && raw.nulls !== \"last\") {\n throw new Error(`${label}[${index}].nulls must be \"first\" or \"last\"`);\n }\n term.nulls = raw.nulls;\n }\n if (raw.extract != null) {\n const extract = asObject(raw.extract, `${label}[${index}].extract`);\n if (typeof extract.regex !== \"string\") throw new Error(`${label}[${index}].extract.regex must be a string`);\n if (extract.regex.length > 10_000) {\n throw new Error(`${label}[${index}].extract.regex exceeds 10000 characters`);\n }\n term.extract = { regex: extract.regex };\n }\n return term;\n });\n}\n\nfunction renderOrderTerms(terms: OrderTerm[]): string {\n if (terms.length === 0) return \"nil\";\n const members = terms.map((term) => {\n const nulls = term.nulls ? `, Nulls: ${goString(term.nulls)}` : \"\";\n const extract = term.extract\n ? `, Extract: &querytable.RegexExtract{Regex: ${goString(term.extract.regex)}}`\n : \"\";\n return `{Field: ${goString(term.field)}, Dir: ${goString(term.dir)}${nulls}${extract}}`;\n });\n return `[]querytable.OrderBy{${members.join(\", \")}}`;\n}\n\nfunction projectGoFields(doc: JsonObject): GoField[] {\n if (!Array.isArray(doc.fields)) throw new Error(\"schema.fields must be an array\");\n const rawFields = doc.fields.map((field, index) => asObject(field, `schema.fields[${index}]`));\n const exprByName = new Map<string, string>();\n\n for (const [index, field] of rawFields.entries()) {\n const name = requiredString(field.name, `schema.fields[${index}].name`);\n if (field.bindings == null) continue;\n const bindings = asObject(field.bindings, `schema.fields[${index}].bindings`);\n if (bindings.postgres == null) continue;\n const postgres = asObject(bindings.postgres, `schema.fields[${index}].bindings.postgres`);\n exprByName.set(name, requiredString(postgres.expr, `schema.fields[${index}].bindings.postgres.expr`));\n }\n\n const fields: GoField[] = [];\n for (const [index, field] of rawFields.entries()) {\n const name = requiredString(field.name, `schema.fields[${index}].name`);\n const expr = exprByName.get(name);\n if (!expr) continue;\n const bindings = asObject(field.bindings, `schema.fields[${index}].bindings`);\n const postgres = asObject(bindings.postgres, `schema.fields[${index}].bindings.postgres`);\n const kindName = typeof postgres.kind === \"string\" ? postgres.kind : requiredString(field.type, `schema.fields[${index}].type`);\n const kind = GO_KINDS[kindName];\n if (!kind) throw new Error(`field ${JSON.stringify(name)} has unknown postgres kind ${JSON.stringify(kindName)}`);\n\n let arrayCaseSensitive = false;\n let serverFilter = true;\n let filterOps: string[] | undefined;\n if (field.filter != null) {\n const filter = asObject(field.filter, `schema.fields[${index}].filter`);\n arrayCaseSensitive = filter.arrayCaseSensitive === true;\n if (typeof filter.enabled === \"boolean\") serverFilter = filter.enabled;\n if (typeof filter.pushdown === \"boolean\") serverFilter = serverFilter && filter.pushdown;\n if (filter.ops != null) {\n if (!Array.isArray(filter.ops) || !filter.ops.every((op) => typeof op === \"string\")) {\n throw new Error(`schema.fields[${index}].filter.ops must be an array of strings`);\n }\n filterOps = [...filter.ops] as string[];\n }\n }\n\n let sortable = true;\n let sortExpr = expr;\n if (field.sort != null) {\n const sort = asObject(field.sort, `schema.fields[${index}].sort`);\n if (typeof sort.enabled === \"boolean\") sortable = sort.enabled;\n if (typeof sort.field === \"string\") {\n const referencedExpr = exprByName.get(sort.field);\n if (!referencedExpr) {\n throw new Error(`field ${JSON.stringify(name)} sort target ${JSON.stringify(sort.field)} has no postgres binding`);\n }\n sortExpr = referencedExpr;\n }\n }\n\n const projected: GoField = {\n name,\n kind,\n expr,\n synthetic: postgres.synthetic === true,\n serverFilter,\n arrayCaseSensitive,\n sortable,\n sortExpr,\n };\n if (filterOps !== undefined) projected.filterOps = filterOps;\n fields.push(projected);\n }\n return fields;\n}\n\n/** Generate the frontend projection. SQL bindings are deliberately omitted. */\nexport function generateTypeScript(input: unknown): string {\n const schema = loadSchema(input);\n const constant = exportedName(schema.name).replace(/Schema$/, \"\").toUpperCase() + \"_SCHEMA\";\n return [\n \"// Code generated by query-table-codegen. DO NOT EDIT.\",\n 'import type { FieldSchema } from \"@pythia-software/query-table-core\";',\n \"\",\n `export const ${constant}: FieldSchema = ${JSON.stringify(schema, null, 2)};`,\n `export const ${constant.replace(/_SCHEMA$/, \"_FIELDS\")} = ${constant}.fields;`,\n \"\",\n ].join(\"\\n\");\n}\n\n/** Generate a direct Go Schema literal with no runtime JSON parsing. */\nexport function generateGo(input: unknown, options: GoGenerationOptions): string {\n validateGoPackage(options.packageName);\n loadSchema(input);\n const doc = asObject(input, \"schema\");\n const name = requiredString(doc.name, \"schema.name\");\n const idField = requiredString(doc.idField, \"schema.idField\");\n const fields = projectGoFields(doc);\n if (!fields.some((field) => field.name === idField)) {\n throw new Error(`id field ${JSON.stringify(idField)} has no postgres binding`);\n }\n const fieldNames = new Set((doc.fields as JsonObject[]).map((field) => requiredString(field.name, \"schema field name\")));\n const defaultSort = orderTerms(doc.defaultSort, \"schema.defaultSort\", fieldNames);\n const tiebreakSort = orderTerms(doc.tiebreakSort, \"schema.tiebreakSort\", fieldNames);\n const importPath = options.importPath ?? DEFAULT_GO_IMPORT;\n if (!importPath || /[\\s\\x00-\\x1f\\x7f\"]/u.test(importPath)) throw new Error(\"invalid Go import path\");\n\n const longestFieldKey = Math.max(0, ...fields.map((field) => goString(field.name).length));\n const fieldLines = fields.map(\n (field) => {\n const key = goString(field.name);\n const spacing = \" \".repeat(longestFieldKey - key.length + 1);\n const filterOps = field.filterOps\n ? `, FilterOps: []string{${field.filterOps.map(goString).join(\", \")}}`\n : \"\";\n return `\\t\\t\\t${key}:${spacing}{Name: ${goString(field.name)}, Kind: querytable.${field.kind}, Expr: ${goString(field.expr)}, Synthetic: ${field.synthetic}, ServerFilter: ${field.serverFilter}${filterOps}${field.arrayCaseSensitive ? \", ArrayCaseSensitive: true\" : \"\"}, Sortable: ${field.sortable}, SortExpr: ${goString(field.sortExpr)}},`;\n },\n );\n\n return [\n \"// Code generated by query-table-codegen. DO NOT EDIT.\",\n `package ${options.packageName}`,\n \"\",\n `import querytable ${goString(importPath)}`,\n \"\",\n `func ${exportedName(name)}() querytable.Schema {`,\n \"\\treturn querytable.Schema{\",\n `\\t\\tName: ${goString(name)},`,\n `\\t\\tIDField: ${goString(idField)},`,\n \"\\t\\tFields: map[string]querytable.FieldSpec{\",\n ...fieldLines,\n \"\\t\\t},\",\n `\\t\\tDefaultSort: ${renderOrderTerms(defaultSort)},`,\n `\\t\\tTiebreakSort: ${renderOrderTerms(tiebreakSort)},`,\n \"\\t}\",\n \"}\",\n \"\",\n ].join(\"\\n\");\n}\n"],"mappings":"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","names":[]}
|