@tricoteuses/senat 3.0.1 → 3.0.2
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.
|
@@ -3,7 +3,6 @@ import { execFileSync } from "child_process";
|
|
|
3
3
|
import commandLineArgs from "command-line-args";
|
|
4
4
|
import fs from "fs-extra";
|
|
5
5
|
import { formatWithPrettier, makePgTsGenerator, markAsGenerated, processDatabase } from "kanel";
|
|
6
|
-
import { makeGenerateZodSchemas } from "kanel-zod";
|
|
7
6
|
import path from "path";
|
|
8
7
|
import StreamZip from "node-stream-zip";
|
|
9
8
|
import readline from "readline";
|
|
@@ -16,6 +15,7 @@ import { assertExistingDirectory, commonOptions } from "./shared/cli_helpers.js"
|
|
|
16
15
|
import { buildIncrementalDatasetImportSql, buildNormalizeStagingSchemaSql } from "./shared/incremental_import_sql.js";
|
|
17
16
|
import { buildGeneratedTableManifest, getGeneratedDefinitionPath, getGeneratedTableManifestPath, prefixedName, rawTypesDir, senatSchemaName, stagingSchemaName, stripDatasetPrefix, } from "./shared/prefixed_tables.js";
|
|
18
17
|
import { buildEnsureSchemaVersionTableSql, buildIncrementSchemaVersionSql, buildSchemaStructureFingerprintQuery, } from "./shared/schema_version.js";
|
|
18
|
+
import { makeGenerateZodSchemas } from "./shared/make_generate_zod_schemas.js";
|
|
19
19
|
import { buildExportStagingMetadataStatementsQuery } from "./shared/staging_metadata_sql.js";
|
|
20
20
|
import { isCopyFromStdinLine, rewriteLineForStagingImport } from "./shared/staging_import.js";
|
|
21
21
|
const badWindows1252CharacterRegex = /[\u0080-\u009f]/g;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { TableColumn, TableDetails } from "extract-pg-schema";
|
|
2
|
+
import { type Details, type Path, type PgTsGeneratorContext, type PgTsPreRenderHook, type TypeMap } from "kanel";
|
|
3
|
+
type GenerateFor = "selector" | "initializer" | "mutator" | undefined;
|
|
4
|
+
type ZodSchemaMetadata = {
|
|
5
|
+
comment?: string[];
|
|
6
|
+
name: string;
|
|
7
|
+
path: Path;
|
|
8
|
+
};
|
|
9
|
+
type ZodIdentifierMetadata = {
|
|
10
|
+
comment?: string[];
|
|
11
|
+
name: string;
|
|
12
|
+
};
|
|
13
|
+
type GenerateZodSchemasConfig = {
|
|
14
|
+
castToSchema?: boolean;
|
|
15
|
+
getZodIdentifierMetadata?: (column: TableColumn, details: TableDetails, context: PgTsGeneratorContext) => ZodIdentifierMetadata;
|
|
16
|
+
getZodSchemaMetadata?: (details: Details, generateFor: GenerateFor, context: PgTsGeneratorContext) => ZodSchemaMetadata;
|
|
17
|
+
zodTypeMap?: TypeMap;
|
|
18
|
+
};
|
|
19
|
+
export declare function makeGenerateZodSchemas(config?: GenerateZodSchemasConfig): PgTsPreRenderHook;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { escapeName, resolveType, useKanelContext, } from "kanel";
|
|
2
|
+
import { defaultGetZodIdentifierMetadata, defaultGetZodSchemaMetadata, defaultZodTypeMap } from "kanel-zod";
|
|
3
|
+
const zImport = {
|
|
4
|
+
asName: undefined,
|
|
5
|
+
importAsType: false,
|
|
6
|
+
isAbsolute: true,
|
|
7
|
+
isDefault: false,
|
|
8
|
+
name: "z",
|
|
9
|
+
path: "zod",
|
|
10
|
+
};
|
|
11
|
+
function appendTsDeclaration(output, path, declaration) {
|
|
12
|
+
const file = output[path];
|
|
13
|
+
if (!file || file.fileType !== "typescript") {
|
|
14
|
+
throw new Error(`Path ${path} is not a typescript file`);
|
|
15
|
+
}
|
|
16
|
+
file.declarations.push(declaration);
|
|
17
|
+
}
|
|
18
|
+
function toMetaQualifier(comment) {
|
|
19
|
+
if (!comment || comment.length === 0) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
const description = comment.join(" ").replace(/\n/g, " ");
|
|
23
|
+
return `meta({ description: ${JSON.stringify(description)} })`;
|
|
24
|
+
}
|
|
25
|
+
function generateProperties(details, generateFor, nonCompositeTypeImports, compositeTypeImports, identifierTypeImports, zodTypeMap, context) {
|
|
26
|
+
const properties = details.kind === "compositeType" ? [...details.attributes] : [...details.columns];
|
|
27
|
+
const sortedProperties = context.propertySortFunction ? properties.sort(context.propertySortFunction) : properties;
|
|
28
|
+
return sortedProperties.map((property) => {
|
|
29
|
+
const { comment, name, nullableOverride, optionalOverride, typeOverride } = context.getPropertyMetadata(property, details, generateFor);
|
|
30
|
+
const canBeOptional = property.isNullable || property.defaultValue || property.isIdentity;
|
|
31
|
+
const resolvedType = typeOverride ?? resolveType(property, details);
|
|
32
|
+
let zodType = "z.unknown()";
|
|
33
|
+
const typeImports = [];
|
|
34
|
+
if (typeof resolvedType !== "string" && `${details.schemaName}.${resolvedType.name}` in identifierTypeImports) {
|
|
35
|
+
const typeImport = identifierTypeImports[`${details.schemaName}.${resolvedType.name}`];
|
|
36
|
+
typeImports.push(typeImport);
|
|
37
|
+
zodType = typeImport.name;
|
|
38
|
+
}
|
|
39
|
+
else if (property.type.fullName in zodTypeMap) {
|
|
40
|
+
const mappedType = zodTypeMap[property.type.fullName];
|
|
41
|
+
if (typeof mappedType === "string") {
|
|
42
|
+
zodType = mappedType;
|
|
43
|
+
if ("dimensions" in property) {
|
|
44
|
+
for (let i = property.dimensions || 0; i > 0; i -= 1) {
|
|
45
|
+
zodType = `${zodType}.array()`;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
zodType = mappedType.name;
|
|
51
|
+
typeImports.push(...mappedType.typeImports);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
else if (property.type.fullName in nonCompositeTypeImports) {
|
|
55
|
+
const typeImport = nonCompositeTypeImports[property.type.fullName];
|
|
56
|
+
typeImports.push(typeImport);
|
|
57
|
+
zodType = typeImport.name;
|
|
58
|
+
}
|
|
59
|
+
else if (property.type.fullName in compositeTypeImports) {
|
|
60
|
+
const typeImport = compositeTypeImports[property.type.fullName];
|
|
61
|
+
typeImports.push(typeImport);
|
|
62
|
+
zodType = typeImport.name;
|
|
63
|
+
if (property.isArray) {
|
|
64
|
+
zodType = `${zodType}.array()`;
|
|
65
|
+
}
|
|
66
|
+
if (property.isNullable) {
|
|
67
|
+
zodType = `${zodType}.nullable()`;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
console.error(`kanel-zod: Unknown type for ${name}.${property.name}: ${property.type.fullName}`);
|
|
72
|
+
}
|
|
73
|
+
let isOptional;
|
|
74
|
+
if (optionalOverride === undefined) {
|
|
75
|
+
switch (generateFor) {
|
|
76
|
+
case "selector":
|
|
77
|
+
isOptional = false;
|
|
78
|
+
break;
|
|
79
|
+
case "initializer":
|
|
80
|
+
isOptional = Boolean(canBeOptional);
|
|
81
|
+
break;
|
|
82
|
+
case "mutator":
|
|
83
|
+
isOptional = true;
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
isOptional = optionalOverride;
|
|
89
|
+
}
|
|
90
|
+
const qualifiers = [];
|
|
91
|
+
if (isOptional) {
|
|
92
|
+
qualifiers.push("optional()");
|
|
93
|
+
}
|
|
94
|
+
if (nullableOverride ?? property.isNullable) {
|
|
95
|
+
qualifiers.push("nullable()");
|
|
96
|
+
}
|
|
97
|
+
const metaQualifier = toMetaQualifier(comment);
|
|
98
|
+
if (metaQualifier) {
|
|
99
|
+
qualifiers.push(metaQualifier);
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
name,
|
|
103
|
+
typeImports,
|
|
104
|
+
value: qualifiers.length > 0 ? `${zodType}.${qualifiers.join(".")}` : zodType,
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
function buildCompositeDeclaration(details, generateFor, nonCompositeTypeImports, compositeTypeImports, identifierTypeImports, getZodSchemaMetadata, zodTypeMap, castToSchema, context) {
|
|
109
|
+
const { comment, name } = getZodSchemaMetadata(details, generateFor, context);
|
|
110
|
+
const { name: typescriptTypeName } = context.getMetadata(details, generateFor);
|
|
111
|
+
const properties = generateProperties(details, generateFor, nonCompositeTypeImports, compositeTypeImports, identifierTypeImports, zodTypeMap, context);
|
|
112
|
+
const typeImports = [zImport];
|
|
113
|
+
const value = ["z.object({", ...properties.map((property) => ` ${escapeName(property.name)}: ${property.value},`)];
|
|
114
|
+
if (castToSchema) {
|
|
115
|
+
value.push(`}) as unknown as z.Schema<${typescriptTypeName}>`);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
value.push("})");
|
|
119
|
+
}
|
|
120
|
+
properties.forEach((property) => {
|
|
121
|
+
typeImports.push(...property.typeImports);
|
|
122
|
+
});
|
|
123
|
+
return {
|
|
124
|
+
comment,
|
|
125
|
+
declarationType: "constant",
|
|
126
|
+
exportAs: "named",
|
|
127
|
+
name,
|
|
128
|
+
type: undefined,
|
|
129
|
+
typeImports,
|
|
130
|
+
value,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function buildIdentifierDeclarations(details, getZodIdentifierMetadata, zodTypeMap, castToSchema, nonCompositeTypeImports, context) {
|
|
134
|
+
if (!context.generateIdentifierType) {
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
const generateIdentifierType = context.generateIdentifierType;
|
|
138
|
+
return details.columns
|
|
139
|
+
.filter((column) => column.isPrimaryKey && !column.reference)
|
|
140
|
+
.map((column) => {
|
|
141
|
+
const typescriptDeclaration = generateIdentifierType(column, details);
|
|
142
|
+
const { comment, name } = getZodIdentifierMetadata(column, details, context);
|
|
143
|
+
let zodType = "z.unknown()";
|
|
144
|
+
const typeImports = [zImport];
|
|
145
|
+
if (column.type.fullName in zodTypeMap) {
|
|
146
|
+
const mappedType = zodTypeMap[column.type.fullName];
|
|
147
|
+
if (typeof mappedType === "string") {
|
|
148
|
+
zodType = mappedType;
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
zodType = mappedType.name;
|
|
152
|
+
typeImports.push(...mappedType.typeImports);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
else if (column.type.fullName in nonCompositeTypeImports) {
|
|
156
|
+
const typeImport = nonCompositeTypeImports[column.type.fullName];
|
|
157
|
+
zodType = typeImport.name;
|
|
158
|
+
typeImports.push(typeImport);
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
declaration: {
|
|
162
|
+
comment,
|
|
163
|
+
declarationType: "constant",
|
|
164
|
+
exportAs: "named",
|
|
165
|
+
name,
|
|
166
|
+
type: undefined,
|
|
167
|
+
typeImports,
|
|
168
|
+
value: castToSchema
|
|
169
|
+
? `${zodType} as unknown as z.Schema<${typescriptDeclaration.name}>`
|
|
170
|
+
: `${zodType}.transform(value => value as ${typescriptDeclaration.name})`,
|
|
171
|
+
},
|
|
172
|
+
name,
|
|
173
|
+
originalName: typescriptDeclaration.name,
|
|
174
|
+
};
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
export function makeGenerateZodSchemas(config = {}) {
|
|
178
|
+
return async (outputAcc, context) => {
|
|
179
|
+
const kanelContext = useKanelContext();
|
|
180
|
+
const output = { ...outputAcc };
|
|
181
|
+
const getZodSchemaMetadata = config.getZodSchemaMetadata ?? defaultGetZodSchemaMetadata;
|
|
182
|
+
const getZodIdentifierMetadata = config.getZodIdentifierMetadata ?? defaultGetZodIdentifierMetadata;
|
|
183
|
+
const zodTypeMap = { ...defaultZodTypeMap, ...(config.zodTypeMap ?? {}) };
|
|
184
|
+
const castToSchema = config.castToSchema ?? true;
|
|
185
|
+
const nonCompositeTypeImports = {};
|
|
186
|
+
const identifierTypeImports = {};
|
|
187
|
+
const compositeTypeImports = {};
|
|
188
|
+
for (const schema of Object.values(kanelContext.schemas)) {
|
|
189
|
+
schema.enums?.forEach((enumDetails) => {
|
|
190
|
+
const { name, path } = getZodSchemaMetadata(enumDetails, undefined, context);
|
|
191
|
+
appendTsDeclaration(output, path, {
|
|
192
|
+
comment: [`Zod schema for ${enumDetails.name}`],
|
|
193
|
+
declarationType: "constant",
|
|
194
|
+
exportAs: "named",
|
|
195
|
+
name,
|
|
196
|
+
type: undefined,
|
|
197
|
+
typeImports: [zImport],
|
|
198
|
+
value: ["z.enum([", ...enumDetails.values.map((value) => ` '${value}',`), "])"],
|
|
199
|
+
});
|
|
200
|
+
nonCompositeTypeImports[`${enumDetails.schemaName}.${enumDetails.name}`] = {
|
|
201
|
+
asName: undefined,
|
|
202
|
+
importAsType: false,
|
|
203
|
+
isAbsolute: false,
|
|
204
|
+
isDefault: false,
|
|
205
|
+
name,
|
|
206
|
+
path,
|
|
207
|
+
};
|
|
208
|
+
});
|
|
209
|
+
schema.ranges?.forEach((rangeDetails) => {
|
|
210
|
+
const { name, path } = getZodSchemaMetadata(rangeDetails, undefined, context);
|
|
211
|
+
appendTsDeclaration(output, path, {
|
|
212
|
+
comment: [`Zod schema for ${rangeDetails.name}`],
|
|
213
|
+
declarationType: "constant",
|
|
214
|
+
exportAs: "named",
|
|
215
|
+
name,
|
|
216
|
+
type: undefined,
|
|
217
|
+
typeImports: [zImport],
|
|
218
|
+
value: "z.string()",
|
|
219
|
+
});
|
|
220
|
+
nonCompositeTypeImports[`${rangeDetails.schemaName}.${rangeDetails.name}`] = {
|
|
221
|
+
asName: undefined,
|
|
222
|
+
importAsType: false,
|
|
223
|
+
isAbsolute: false,
|
|
224
|
+
isDefault: false,
|
|
225
|
+
name,
|
|
226
|
+
path,
|
|
227
|
+
};
|
|
228
|
+
});
|
|
229
|
+
schema.domains?.forEach((domainDetails) => {
|
|
230
|
+
const { name, path } = getZodSchemaMetadata(domainDetails, undefined, context);
|
|
231
|
+
const domainType = zodTypeMap[domainDetails.innerType] ?? "z.unknown()";
|
|
232
|
+
appendTsDeclaration(output, path, {
|
|
233
|
+
comment: [`Zod schema for ${domainDetails.name}`],
|
|
234
|
+
declarationType: "constant",
|
|
235
|
+
exportAs: "named",
|
|
236
|
+
name,
|
|
237
|
+
type: undefined,
|
|
238
|
+
typeImports: [zImport, ...(typeof domainType === "string" ? [] : domainType.typeImports)],
|
|
239
|
+
value: typeof domainType === "string" ? domainType : domainType.name,
|
|
240
|
+
});
|
|
241
|
+
nonCompositeTypeImports[`${domainDetails.schemaName}.${domainDetails.name}`] = {
|
|
242
|
+
asName: undefined,
|
|
243
|
+
importAsType: false,
|
|
244
|
+
isAbsolute: false,
|
|
245
|
+
isDefault: false,
|
|
246
|
+
name,
|
|
247
|
+
path,
|
|
248
|
+
};
|
|
249
|
+
});
|
|
250
|
+
schema.tables?.forEach((tableDetails) => {
|
|
251
|
+
const { path } = getZodSchemaMetadata(tableDetails, undefined, context);
|
|
252
|
+
const declarations = buildIdentifierDeclarations(tableDetails, getZodIdentifierMetadata, zodTypeMap, castToSchema, nonCompositeTypeImports, context);
|
|
253
|
+
declarations.forEach(({ declaration, name, originalName }) => {
|
|
254
|
+
appendTsDeclaration(output, path, declaration);
|
|
255
|
+
identifierTypeImports[`${tableDetails.schemaName}.${originalName}`] = {
|
|
256
|
+
asName: undefined,
|
|
257
|
+
importAsType: false,
|
|
258
|
+
isAbsolute: false,
|
|
259
|
+
isDefault: false,
|
|
260
|
+
name,
|
|
261
|
+
path,
|
|
262
|
+
};
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
[
|
|
266
|
+
...(schema.tables ?? []),
|
|
267
|
+
...(schema.views ?? []),
|
|
268
|
+
...(schema.materializedViews ?? []),
|
|
269
|
+
...(schema.compositeTypes ?? []),
|
|
270
|
+
].forEach((compositeDetails) => {
|
|
271
|
+
const { name, path } = getZodSchemaMetadata(compositeDetails, undefined, context);
|
|
272
|
+
compositeTypeImports[`${compositeDetails.schemaName}.${compositeDetails.name}`] = {
|
|
273
|
+
asName: undefined,
|
|
274
|
+
importAsType: false,
|
|
275
|
+
isAbsolute: false,
|
|
276
|
+
isDefault: false,
|
|
277
|
+
name,
|
|
278
|
+
path,
|
|
279
|
+
};
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
for (const schema of Object.values(kanelContext.schemas)) {
|
|
283
|
+
;
|
|
284
|
+
[
|
|
285
|
+
...(schema.tables ?? []),
|
|
286
|
+
...(schema.views ?? []),
|
|
287
|
+
...(schema.materializedViews ?? []),
|
|
288
|
+
...(schema.compositeTypes ?? []),
|
|
289
|
+
].forEach((compositeDetails) => {
|
|
290
|
+
const { path } = getZodSchemaMetadata(compositeDetails, undefined, context);
|
|
291
|
+
appendTsDeclaration(output, path, buildCompositeDeclaration(compositeDetails, "selector", nonCompositeTypeImports, compositeTypeImports, identifierTypeImports, getZodSchemaMetadata, zodTypeMap, castToSchema, context));
|
|
292
|
+
if (compositeDetails.kind === "table") {
|
|
293
|
+
appendTsDeclaration(output, path, buildCompositeDeclaration(compositeDetails, "initializer", nonCompositeTypeImports, compositeTypeImports, identifierTypeImports, getZodSchemaMetadata, zodTypeMap, castToSchema, context));
|
|
294
|
+
appendTsDeclaration(output, path, buildCompositeDeclaration(compositeDetails, "mutator", nonCompositeTypeImports, compositeTypeImports, identifierTypeImports, getZodSchemaMetadata, zodTypeMap, castToSchema, context));
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
return output;
|
|
299
|
+
};
|
|
300
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tricoteuses/senat",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.2",
|
|
4
4
|
"description": "Handle French Sénat's open data",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"France",
|
|
@@ -46,7 +46,6 @@
|
|
|
46
46
|
"access": "public"
|
|
47
47
|
},
|
|
48
48
|
"scripts": {
|
|
49
|
-
"postinstall": "patch-package",
|
|
50
49
|
"build": "tsc",
|
|
51
50
|
"build:types": "tsc --emitDeclarationOnly",
|
|
52
51
|
"data:convert_data": "tsx src/scripts/convert_data.ts",
|
|
@@ -101,8 +100,6 @@
|
|
|
101
100
|
"globals": "^17.0.0",
|
|
102
101
|
"kanel": "^4.0.1",
|
|
103
102
|
"kanel-zod": "^4.0.0",
|
|
104
|
-
"patch-package": "^8.0.1",
|
|
105
|
-
"postinstall-postinstall": "^2.1.0",
|
|
106
103
|
"prettier": "^3.5.3",
|
|
107
104
|
"tsx": "^4.21.0",
|
|
108
105
|
"typescript": "^5.9.3",
|