@lakeql/schema-generator 0.4.1 → 0.4.3

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.
@@ -54,7 +54,10 @@ type PartitioningValue = boolean | string;
54
54
  interface MutationConfig {
55
55
  /** Load strategy for the write pipeline. */
56
56
  loadStrategy: LoadStrategy;
57
- /** Storage adapter type. Defaults to "s3". */
57
+ /**
58
+ * Storage adapter type.
59
+ * @default "s3"
60
+ */
58
61
  type: StorageType;
59
62
  /** S3/MinIO bucket name for endpoint data. */
60
63
  bucket: string;
@@ -64,9 +67,15 @@ interface MutationConfig {
64
67
  region?: string;
65
68
  /** Optional custom endpoint. Required for MinIO, optional for S3. */
66
69
  endpoint?: string;
67
- /** Partitioning mode. Default: true */
70
+ /**
71
+ * Partitioning mode. `true` partitions by write timestamp, `false` disables partitioning, or a string for field-based/custom partitioning.
72
+ * @default true
73
+ */
68
74
  partitioning?: PartitioningValue;
69
- /** Partition path granularity. Default: "year/month/day" */
75
+ /**
76
+ * Partition path granularity. One of `"year"`, `"year/month"`, or `"year/month/day"`.
77
+ * @default "year/month/day"
78
+ */
70
79
  partitioningFormat?: string;
71
80
  }
72
81
  /**
@@ -137,10 +146,21 @@ type FieldValidation = {
137
146
  * Field-level options for mutation input validation.
138
147
  */
139
148
  interface FieldOptions {
140
- /** Whether the field is required in mutation input. Default: false. */
149
+ /**
150
+ * Whether the field is required in mutation input.
151
+ * @default false
152
+ */
141
153
  required?: boolean;
142
- /** Validation refinements to apply via Zod. */
154
+ /**
155
+ * Validation refinements to apply via Zod at runtime.
156
+ * @default []
157
+ */
143
158
  validations?: FieldValidation[];
159
+ /**
160
+ * Whether the field is read-only. Read-only fields appear in the query schema and Hive DDL but are excluded from the mutation input type.
161
+ * @default false
162
+ */
163
+ readOnly?: boolean;
144
164
  }
145
165
  /**
146
166
  * Zod schema for field validation refinements.
@@ -1 +1 @@
1
- import{z as e}from"zod";const t=/^[a-zA-Z_][a-zA-Z0-9_]{0,63}$/u,n=/^[a-zA-Z_][a-zA-Z0-9_]{0,127}$/u,r=[`full_load`,`full_load_append`,`append`],i=[`s3`,`minio`],a=[`year`,`year/month`,`year/month/day`],o=[`year`,`month`,`day`,`hour`,`minute`,`second`];function s(e){if(!(e.includes(`/`)||e.includes(`:`)))return t.test(e)?null:`Invalid partition field name "${e}". Must match pattern: letters/underscores, no leading digit, max 64 chars.`;let n=e.split(`/`);for(let r of n){if(r===``)return`Invalid partition format: empty segment found in "${e}".`;if(r.includes(`:`)){let e=r.split(`:`);if(e.length!==2)return`Invalid partition segment "${r}": expected format "fieldname:component".`;let[n,i]=e;if(!t.test(n))return`Invalid field name "${n}" in partition segment "${r}".`;if(!o.includes(i))return`Invalid date component "${i}" in partition segment "${r}". Expected one of: ${o.join(`, `)}.`}else if(!t.test(r))return`Invalid field name "${r}" in partition format.`}return null}const c=e.object({loadStrategy:e.enum(r),type:e.enum(i).default(`s3`),bucket:e.string().min(1),basePath:e.string().min(1),region:e.string().optional(),endpoint:e.string().optional(),partitioning:e.union([e.boolean(),e.string().min(1)]).optional(),partitioningFormat:e.string().optional()}).superRefine((e,t)=>{if(e.type===`minio`&&!e.endpoint&&t.addIssue({code:`custom`,path:[`endpoint`],message:`Endpoint is required when storage type is 'minio'. Provide the MinIO server URL (e.g. http://minio:9000).`}),e.partitioning!==!1){if(typeof e.partitioning==`string`){let n=s(e.partitioning);if(n){t.addIssue({code:`custom`,path:[`partitioning`],message:n});return}if(e.partitioning.includes(`/`)||e.partitioning.includes(`:`))return}e.partitioningFormat!==void 0&&!a.includes(e.partitioningFormat)&&t.addIssue({code:`invalid_value`,values:[...a],path:[`partitioningFormat`],message:`Invalid partitioning format. Expected one of: ${a.join(`, `)}`})}}),l=e.union([e.literal(!1),c]),u=[`String`,`Integer`,`Float`,`Boolean`,`Date`,`DateTime`],d=e.union([e.object({type:e.literal(`email`)}),e.object({type:e.literal(`url`)}),e.object({type:e.literal(`uuid`)}),e.object({type:e.literal(`min`),value:e.number()}),e.object({type:e.literal(`max`),value:e.number()}),e.object({type:e.literal(`regex`),pattern:e.string()})]),f=e.object({required:e.boolean().optional(),validations:e.array(d).optional()}),p=e.lazy(()=>e.discriminatedUnion(`type`,[e.object({name:e.string().regex(t),type:e.literal(`String`),options:f.optional()}),e.object({name:e.string().regex(t),type:e.literal(`Integer`),options:f.optional()}),e.object({name:e.string().regex(t),type:e.literal(`Float`),options:f.optional()}),e.object({name:e.string().regex(t),type:e.literal(`Boolean`),options:f.optional()}),e.object({name:e.string().regex(t),type:e.literal(`Date`),options:f.optional()}),e.object({name:e.string().regex(t),type:e.literal(`DateTime`),options:f.optional()}),e.object({name:e.string().regex(t),type:e.literal(`Object`),fields:e.array(p).min(1),options:f.optional()}),e.object({name:e.string().regex(t),type:e.literal(`Array`),items:e.union([e.object({type:e.enum(u)}),e.object({type:e.literal(`Object`),fields:e.array(p).min(1)})]),options:f.optional()})])),m=e.object({version:e.literal(`1.0`),tableName:e.string().regex(n),catalog:e.string().regex(n),schema:e.string().regex(n),fields:e.array(p),mutation:l.optional()});function h(e,t=[]){let n=[],r=new Set;for(let i of e)r.has(i.name)?n.push({name:i.name,path:t}):r.add(i.name),i.type===`Object`&&i.fields&&n.push(...h(i.fields,[...t,i.name])),i.type===`Array`&&i.items?.type===`Object`&&i.items.fields&&n.push(...h(i.items.fields,[...t,i.name,`items`]));return n}export{m as endpointDefinitionSchema,p as fieldDefinitionSchema,t as fieldNamePattern,f as fieldOptionsSchema,d as fieldValidationSchema,h as findDuplicateFieldNames,r as loadStrategies,n as metadataFieldPattern,c as mutationConfigSchema,l as mutationSchema,o as partitioningComponents,a as partitioningFormats,u as primitiveTypes,i as storageTypes,s as validatePartitioningFormat};
1
+ import{z as e}from"zod";const t=/^[a-zA-Z_][a-zA-Z0-9_]{0,63}$/u,n=/^[a-zA-Z_][a-zA-Z0-9_]{0,127}$/u,r=[`full_load`,`full_load_append`,`append`],i=[`s3`,`minio`],a=[`year`,`year/month`,`year/month/day`],o=[`year`,`month`,`day`,`hour`,`minute`,`second`];function s(e){if(!(e.includes(`/`)||e.includes(`:`)))return t.test(e)?null:`Invalid partition field name "${e}". Must match pattern: letters/underscores, no leading digit, max 64 chars.`;let n=e.split(`/`);for(let r of n){if(r===``)return`Invalid partition format: empty segment found in "${e}".`;if(r.includes(`:`)){let e=r.split(`:`);if(e.length!==2)return`Invalid partition segment "${r}": expected format "fieldname:component".`;let[n,i]=e;if(!t.test(n))return`Invalid field name "${n}" in partition segment "${r}".`;if(!o.includes(i))return`Invalid date component "${i}" in partition segment "${r}". Expected one of: ${o.join(`, `)}.`}else if(!t.test(r))return`Invalid field name "${r}" in partition format.`}return null}const c=e.object({loadStrategy:e.enum(r),type:e.enum(i).default(`s3`),bucket:e.string().min(1),basePath:e.string().min(1),region:e.string().optional(),endpoint:e.string().optional(),partitioning:e.union([e.boolean(),e.string().min(1)]).optional(),partitioningFormat:e.string().optional()}).superRefine((e,t)=>{if(e.type===`minio`&&!e.endpoint&&t.addIssue({code:`custom`,path:[`endpoint`],message:`Endpoint is required when storage type is 'minio'. Provide the MinIO server URL (e.g. http://minio:9000).`}),e.partitioning!==!1){if(typeof e.partitioning==`string`){let n=s(e.partitioning);if(n){t.addIssue({code:`custom`,path:[`partitioning`],message:n});return}if(e.partitioning.includes(`/`)||e.partitioning.includes(`:`))return}e.partitioningFormat!==void 0&&!a.includes(e.partitioningFormat)&&t.addIssue({code:`invalid_value`,values:[...a],path:[`partitioningFormat`],message:`Invalid partitioning format. Expected one of: ${a.join(`, `)}`})}}),l=e.union([e.literal(!1),c]),u=[`String`,`Integer`,`Float`,`Boolean`,`Date`,`DateTime`],d=e.union([e.object({type:e.literal(`email`)}),e.object({type:e.literal(`url`)}),e.object({type:e.literal(`uuid`)}),e.object({type:e.literal(`min`),value:e.number()}),e.object({type:e.literal(`max`),value:e.number()}),e.object({type:e.literal(`regex`),pattern:e.string()})]),f=e.object({required:e.boolean().optional(),validations:e.array(d).optional(),readOnly:e.boolean().optional()}),p=e.lazy(()=>e.discriminatedUnion(`type`,[e.object({name:e.string().regex(t),type:e.literal(`String`),options:f.optional()}),e.object({name:e.string().regex(t),type:e.literal(`Integer`),options:f.optional()}),e.object({name:e.string().regex(t),type:e.literal(`Float`),options:f.optional()}),e.object({name:e.string().regex(t),type:e.literal(`Boolean`),options:f.optional()}),e.object({name:e.string().regex(t),type:e.literal(`Date`),options:f.optional()}),e.object({name:e.string().regex(t),type:e.literal(`DateTime`),options:f.optional()}),e.object({name:e.string().regex(t),type:e.literal(`Object`),fields:e.array(p).min(1),options:f.optional()}),e.object({name:e.string().regex(t),type:e.literal(`Array`),items:e.union([e.object({type:e.enum(u)}),e.object({type:e.literal(`Object`),fields:e.array(p).min(1)})]),options:f.optional()})])),m=e.object({version:e.literal(`1.0`),tableName:e.string().regex(n),catalog:e.string().regex(n),schema:e.string().regex(n),fields:e.array(p),mutation:l.optional()});function h(e,t=[]){let n=[],r=new Set;for(let i of e)r.has(i.name)?n.push({name:i.name,path:t}):r.add(i.name),i.type===`Object`&&i.fields&&n.push(...h(i.fields,[...t,i.name])),i.type===`Array`&&i.items?.type===`Object`&&i.items.fields&&n.push(...h(i.items.fields,[...t,i.name,`items`]));return n}export{m as endpointDefinitionSchema,p as fieldDefinitionSchema,t as fieldNamePattern,f as fieldOptionsSchema,d as fieldValidationSchema,h as findDuplicateFieldNames,r as loadStrategies,n as metadataFieldPattern,c as mutationConfigSchema,l as mutationSchema,o as partitioningComponents,a as partitioningFormats,u as primitiveTypes,i as storageTypes,s as validatePartitioningFormat};
@@ -57,6 +57,10 @@ interface GenerateModelProps {
57
57
  /** Whether this is the root model definition. */
58
58
  isRoot: boolean;
59
59
  }
60
+ /**
61
+ * Sanitizes a raw field name into a valid GraphQL/TypeScript identifier-like key.
62
+ */
63
+ declare function sanitizeFieldName(rawFieldName: string): string;
60
64
  /**
61
65
  * Generates a GraphQL model definition from JSON Schema.
62
66
  */
@@ -90,4 +94,4 @@ declare const generateFieldDefinition: ({
90
94
  parent
91
95
  }: GenerateFieldDefinitionProps) => FieldDefinitionResponse;
92
96
  //#endregion
93
- export { CurrentModelResponse, FieldDefinitionResponse, FilterFieldInput, GenerateFieldDefinitionProps, GenerateModelProps, ModelResponse, generateFieldDefinition, generateModel };
97
+ export { CurrentModelResponse, FieldDefinitionResponse, FilterFieldInput, GenerateFieldDefinitionProps, GenerateModelProps, ModelResponse, generateFieldDefinition, generateModel, sanitizeFieldName };
@@ -1 +1 @@
1
- import e from"@lakeql/helpers/special-characters";import{capitalize as t,omit as n}from"lodash-es";const r=({source:a,name:o,parent:s,models:c={},isRoot:l=!0})=>{let u=n(a,[`additionalProperties`]),d={...c},f={};for(let[t,n]of Object.entries(u.properties)){let a=e(t).replace(`-`,`_`).replace(`.`,`_`).replace(/^(?<digit>\d)/u,`_$<digit>`),c=i({fieldDefinition:n,fieldName:a,parent:s??o,rawFieldName:t});f[a]={filter:c.filter,graphqlTplType:c.graphqlTplType,graphqlType:c.graphqlType,interfaceName:c.interfaceName,interfaceType:c.interfaceType,isArray:c.isArray??!1,name:a,nullable:!0,rawFieldName:t,transformed:a!==t},c.subModel!==!1&&Object.assign(d,r({isRoot:!1,models:d,name:a,parent:s??o,source:c.subModel}))}let p=l?o:`${s}_${t(o)}`;return d[p]={dateTimeFields:Object.values(f).filter(e=>e.graphqlType===`DateTime`).map(e=>e.rawFieldName),fields:f,interfaceName:`${p}Interface`,modelName:p,root:l,transformFields:Object.values(f).filter(e=>e.transformed).map(e=>[e.name,e.rawFieldName])},d},i=({fieldName:e,rawFieldName:n,fieldDefinition:r,parent:a})=>{let o=t(e);switch(r.type){case`string`:switch(r.format){case`date`:return{filter:!0,graphqlTplType:`'Date'`,graphqlType:`Date`,interfaceType:`Date`,subModel:!1};case`date-time`:return{filter:!0,graphqlTplType:`'DateTime'`,graphqlType:`DateTime`,interfaceType:`Date`,subModel:!1};default:return{filter:!0,graphqlTplType:`'String'`,graphqlType:`String`,interfaceType:`string`,subModel:!1}}case`number`:return{filter:!0,graphqlTplType:`'Float'`,graphqlType:`Float`,interfaceType:`number`,subModel:!1};case`integer`:return{filter:!0,graphqlTplType:`'Int'`,graphqlType:`Int`,interfaceType:`number`,subModel:!1};case`boolean`:return{filter:!0,graphqlTplType:`'Boolean'`,graphqlType:`Boolean`,interfaceType:`boolean`,subModel:!1};case`array`:if(r.items.type!==`object`){let t=i({fieldDefinition:r.items,fieldName:e,rawFieldName:n});return{filter:!1,graphqlTplType:`['${t.graphqlType}']`,graphqlType:`[${t.graphqlType}]`,interfaceType:`${t.interfaceType}[]`,isArray:!0,subModel:!1}}return{filter:!1,graphqlTplType:`[${a}_${o}]`,graphqlType:`[${a}_${o}]`,interfaceName:`${a}_${o}Interface[]`,interfaceType:`${a}_${o}[]`,isArray:!0,subModel:r.items};case`object`:return{filter:!1,graphqlTplType:`${a}_${o}`,graphqlType:`${a}_${o}`,interfaceName:`${a}_${o}Interface`,interfaceType:`${a}_${o}`,subModel:r};default:return{filter:!1,graphqlTplType:`Null`,graphqlType:`Null`,interfaceType:`null`,subModel:!1}}};export{i as generateFieldDefinition,r as generateModel};
1
+ import e from"@lakeql/helpers/special-characters";import{capitalize as t,omit as n}from"lodash-es";function r(t){let n=e(t).replaceAll(/[^a-zA-Z0-9_]/gu,`_`).replaceAll(/_+/gu,`_`).replaceAll(/^_+|_+$/gu,``);return n.length===0?`field`:n.replace(/^(?<digit>\d)/u,`_$<digit>`)}function i(e,t,n){if(!t.has(e))return t.add(e),e;throw Error(`Field name collision after normalization: "${n}" -> "${e}".`)}const a=({source:e,name:s,parent:c,models:l={},isRoot:u=!0})=>{let d=n(e,[`additionalProperties`]),f={...l},p=new Set,m={};for(let[e,t]of Object.entries(d.properties)){let n=i(r(e),p,e),l=o({fieldDefinition:t,fieldName:n,parent:c??s,rawFieldName:e});m[n]={filter:l.filter,graphqlTplType:l.graphqlTplType,graphqlType:l.graphqlType,interfaceName:l.interfaceName,interfaceType:l.interfaceType,isArray:l.isArray??!1,name:n,nullable:!0,rawFieldName:e,transformed:n!==e},l.subModel!==!1&&Object.assign(f,a({isRoot:!1,models:f,name:n,parent:c??s,source:l.subModel}))}let h=u?s:`${c}_${t(s)}`;return f[h]={dateTimeFields:Object.values(m).filter(e=>e.graphqlType===`DateTime`).map(e=>e.rawFieldName),fields:m,interfaceName:`${h}Interface`,modelName:h,root:u,transformFields:Object.values(m).filter(e=>e.transformed).map(e=>[e.name,e.rawFieldName])},f},o=({fieldName:e,rawFieldName:n,fieldDefinition:r,parent:i})=>{let a=t(e);switch(r.type){case`string`:switch(r.format){case`date`:return{filter:!0,graphqlTplType:`'Date'`,graphqlType:`Date`,interfaceType:`Date`,subModel:!1};case`date-time`:return{filter:!0,graphqlTplType:`'DateTime'`,graphqlType:`DateTime`,interfaceType:`Date`,subModel:!1};default:return{filter:!0,graphqlTplType:`'String'`,graphqlType:`String`,interfaceType:`string`,subModel:!1}}case`number`:return{filter:!0,graphqlTplType:`'Float'`,graphqlType:`Float`,interfaceType:`number`,subModel:!1};case`integer`:return{filter:!0,graphqlTplType:`'Int'`,graphqlType:`Int`,interfaceType:`number`,subModel:!1};case`boolean`:return{filter:!0,graphqlTplType:`'Boolean'`,graphqlType:`Boolean`,interfaceType:`boolean`,subModel:!1};case`array`:if(r.items.type!==`object`){let t=o({fieldDefinition:r.items,fieldName:e,rawFieldName:n});return{filter:!1,graphqlTplType:`['${t.graphqlType}']`,graphqlType:`[${t.graphqlType}]`,interfaceType:`${t.interfaceType}[]`,isArray:!0,subModel:!1}}return{filter:!1,graphqlTplType:`[${i}_${a}]`,graphqlType:`[${i}_${a}]`,interfaceName:`${i}_${a}Interface[]`,interfaceType:`${i}_${a}[]`,isArray:!0,subModel:r.items};case`object`:return{filter:!1,graphqlTplType:`${i}_${a}`,graphqlType:`${i}_${a}`,interfaceName:`${i}_${a}Interface`,interfaceType:`${i}_${a}`,subModel:r};default:return{filter:!1,graphqlTplType:`Null`,graphqlType:`Null`,interfaceType:`null`,subModel:!1}}};export{o as generateFieldDefinition,a as generateModel,r as sanitizeFieldName};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lakeql/schema-generator",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "Generates GraphQL schemas, Hive table definitions, and JSON schemas from Trino metadata",
5
5
  "keywords": [
6
6
  "codegen",
@@ -55,7 +55,7 @@
55
55
  "lodash-es": "4.18.1",
56
56
  "magic-regexp": "0.11.0",
57
57
  "zod": "4.4.3",
58
- "@lakeql/helpers": "0.1.3"
58
+ "@lakeql/helpers": "0.1.4"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@types/json-schema": "7.0.15",
@@ -63,7 +63,7 @@
63
63
  "@types/node": "24.13.2",
64
64
  "fast-check": "^4.8.0",
65
65
  "json-column-parser": "2.0.0",
66
- "tsdown": "0.22.2",
66
+ "tsdown": "0.22.3",
67
67
  "typescript": "6.0.3",
68
68
  "vitest": "4.1.9",
69
69
  "@lakeql/tsconfig": "0.1.0"