@schema2sheet/core 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.
@@ -0,0 +1,113 @@
1
+ //#region src/types.d.ts
2
+ type SchemaType = "string" | "number" | "integer" | "boolean" | "object" | "array";
3
+ type OnDeleteAction = "cascade" | "restrict" | "set_null";
4
+ type ColumnRef = {
5
+ table: string;
6
+ field?: string;
7
+ expandKey?: string;
8
+ onDelete?: OnDeleteAction;
9
+ source?: "header" | "metadata" | "auto";
10
+ };
11
+ type ColumnItems = {
12
+ type: SchemaType;
13
+ format?: string;
14
+ enum?: string[];
15
+ ref?: ColumnRef;
16
+ };
17
+ type ColumnSchema = {
18
+ name: string;
19
+ required: boolean;
20
+ index: number;
21
+ type?: SchemaType;
22
+ format?: string;
23
+ enum?: string[];
24
+ minimum?: number;
25
+ maximum?: number;
26
+ pattern?: string;
27
+ multipleOf?: number;
28
+ validation?: unknown;
29
+ extensions?: Record<string, unknown>;
30
+ source?: string;
31
+ description?: string;
32
+ ref?: ColumnRef;
33
+ items?: ColumnItems;
34
+ };
35
+ type TableSchema = {
36
+ name: string;
37
+ sheetId?: number;
38
+ columns: ColumnSchema[];
39
+ };
40
+ type SchemaDocument = {
41
+ version: number;
42
+ spreadsheetId: string;
43
+ generatedAt: string;
44
+ tables: TableSchema[];
45
+ };
46
+ type FetchSchemaOptions = {
47
+ spreadsheetId: string;
48
+ excludePrefixes?: string[];
49
+ includeHidden?: boolean;
50
+ scanRows: number;
51
+ metadataKey?: string | null;
52
+ enumLimit?: number;
53
+ debug?: boolean;
54
+ headerRow?: number;
55
+ };
56
+ type GenerateOasOptions = FetchSchemaOptions & {
57
+ title: string;
58
+ version: string;
59
+ outputPath: string;
60
+ schemaPath: string;
61
+ hashPath: string;
62
+ pathPrefix: string;
63
+ };
64
+ type HeaderTypeHint = {
65
+ type?: SchemaType;
66
+ format?: string;
67
+ ref?: ColumnRef;
68
+ items?: ColumnItems;
69
+ };
70
+ type HeaderSpec = {
71
+ name: string;
72
+ required: boolean;
73
+ raw: string;
74
+ typeHint?: HeaderTypeHint;
75
+ };
76
+ type OpenApiDocument = {
77
+ openapi: string;
78
+ info: {
79
+ title: string;
80
+ version: string;
81
+ };
82
+ paths: Record<string, unknown>;
83
+ components: Record<string, unknown>;
84
+ };
85
+ type RgbColor = {
86
+ red: number;
87
+ green: number;
88
+ blue: number;
89
+ };
90
+ type EnsureSheetHeaderOptions = {
91
+ spreadsheetId: string;
92
+ sheetName: string;
93
+ headers: string[];
94
+ force?: boolean;
95
+ createIfMissing?: boolean;
96
+ manualText?: string;
97
+ headerColor?: RgbColor;
98
+ };
99
+ type EnsureSheetHeaderResult = {
100
+ sheetId: number;
101
+ created: boolean;
102
+ updated: boolean;
103
+ previousHeader?: string[];
104
+ };
105
+ //#endregion
106
+ //#region src/openapi.d.ts
107
+ declare function buildOpenApi(schema: SchemaDocument, options: GenerateOasOptions): OpenApiDocument;
108
+ //#endregion
109
+ //#region src/schema.d.ts
110
+ declare function fetchSchema(options: FetchSchemaOptions): Promise<SchemaDocument>;
111
+ //#endregion
112
+ export { ColumnItems, ColumnRef, ColumnSchema, EnsureSheetHeaderOptions, EnsureSheetHeaderResult, FetchSchemaOptions, GenerateOasOptions, HeaderSpec, HeaderTypeHint, OnDeleteAction, OpenApiDocument, RgbColor, SchemaDocument, SchemaType, TableSchema, buildOpenApi, fetchSchema };
113
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/openapi.ts","../src/schema.ts"],"mappings":";KAAY,UAAA;AAAA,KAQA,cAAA;AAAA,KAEA,SAAA;EACX,KAAA;EACA,KAAA;EACA,SAAA;EACA,QAAA,GAAW,cAAA;EACX,MAAA;AAAA;AAAA,KAGW,WAAA;EACX,IAAA,EAAM,UAAA;EACN,MAAA;EACA,IAAA;EACA,GAAA,GAAM,SAAA;AAAA;AAAA,KAGK,YAAA;EACX,IAAA;EACA,QAAA;EACA,KAAA;EACA,IAAA,GAAO,UAAA;EACP,MAAA;EACA,IAAA;EACA,OAAA;EACA,OAAA;EACA,OAAA;EACA,UAAA;EACA,UAAA;EACA,UAAA,GAAa,MAAA;EACb,MAAA;EACA,WAAA;EACA,GAAA,GAAM,SAAA;EACN,KAAA,GAAQ,WAAA;AAAA;AAAA,KAGG,WAAA;EACX,IAAA;EACA,OAAA;EACA,OAAA,EAAS,YAAA;AAAA;AAAA,KAGE,cAAA;EACX,OAAA;EACA,aAAA;EACA,WAAA;EACA,MAAA,EAAQ,WAAA;AAAA;AAAA,KAGG,kBAAA;EACX,aAAA;EACA,eAAA;EACA,aAAA;EACA,QAAA;EACA,WAAA;EACA,SAAA;EACA,KAAA;EACA,SAAA;AAAA;AAAA,KAGW,kBAAA,GAAqB,kBAAA;EAChC,KAAA;EACA,OAAA;EACA,UAAA;EACA,UAAA;EACA,QAAA;EACA,UAAA;AAAA;AAAA,KAGW,cAAA;EACX,IAAA,GAAO,UAAA;EACP,MAAA;EACA,GAAA,GAAM,SAAA;EACN,KAAA,GAAQ,WAAA;AAAA;AAAA,KAGG,UAAA;EACX,IAAA;EACA,QAAA;EACA,GAAA;EACA,QAAA,GAAW,cAAA;AAAA;AAAA,KAGA,eAAA;EACX,OAAA;EACA,IAAA;IACC,KAAA;IACA,OAAA;EAAA;EAED,KAAA,EAAO,MAAA;EACP,UAAA,EAAY,MAAA;AAAA;AAAA,KAGD,QAAA;EACX,GAAA;EACA,KAAA;EACA,IAAA;AAAA;AAAA,KAGW,wBAAA;EACX,aAAA;EACA,SAAA;EACA,OAAA;EACA,KAAA;EACA,eAAA;EACA,UAAA;EACA,WAAA,GAAc,QAAA;AAAA;AAAA,KAGH,uBAAA;EACX,OAAA;EACA,OAAA;EACA,OAAA;EACA,cAAA;AAAA;;;iBC/Ge,YAAA,CACf,MAAA,EAAQ,cAAA,EACR,OAAA,EAAS,kBAAA,GACP,eAAA;;;iBCyCmB,WAAA,CACrB,OAAA,EAAS,kBAAA,GACP,OAAA,CAAQ,cAAA"}
package/dist/index.mjs ADDED
@@ -0,0 +1,1039 @@
1
+ import { google } from "googleapis";
2
+
3
+ //#region src/utils/helpers.ts
4
+ function toSchemaName(name) {
5
+ const cleaned = String(name).replace(/[^A-Za-z0-9]+/g, " ").trim();
6
+ if (!cleaned) return "Table";
7
+ return cleaned.split(" ").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
8
+ }
9
+ function normalizePathPrefix(prefix) {
10
+ if (!prefix) return "";
11
+ let value = String(prefix).trim();
12
+ if (!value || value === "/") return "";
13
+ if (!value.startsWith("/")) value = `/${value}`;
14
+ if (value.endsWith("/")) value = value.slice(0, -1);
15
+ return value;
16
+ }
17
+ function joinPath(prefix, segment) {
18
+ return `${normalizePathPrefix(prefix)}/${String(segment).replace(/^\//, "")}`.replace("//", "/");
19
+ }
20
+ function escapeSheetName(name) {
21
+ return `'${String(name).replace(/'/g, "''")}'`;
22
+ }
23
+
24
+ //#endregion
25
+ //#region src/utils/parser.ts
26
+ const TYPE_ALIASES = {
27
+ uuid: {
28
+ type: "string",
29
+ format: "uuid"
30
+ },
31
+ email: {
32
+ type: "string",
33
+ format: "email"
34
+ },
35
+ uri: {
36
+ type: "string",
37
+ format: "uri"
38
+ },
39
+ url: {
40
+ type: "string",
41
+ format: "uri"
42
+ },
43
+ date: {
44
+ type: "string",
45
+ format: "date"
46
+ },
47
+ "date-time": {
48
+ type: "string",
49
+ format: "date-time"
50
+ },
51
+ time: {
52
+ type: "string",
53
+ format: "time"
54
+ },
55
+ currency: {
56
+ type: "number",
57
+ format: "currency"
58
+ },
59
+ percent: {
60
+ type: "number",
61
+ format: "percent"
62
+ },
63
+ json: { type: "object" },
64
+ int32: {
65
+ type: "integer",
66
+ format: "int32"
67
+ },
68
+ int64: {
69
+ type: "integer",
70
+ format: "int64"
71
+ },
72
+ float: {
73
+ type: "number",
74
+ format: "float"
75
+ },
76
+ double: {
77
+ type: "number",
78
+ format: "double"
79
+ },
80
+ enum: {
81
+ type: "string",
82
+ format: "enum"
83
+ }
84
+ };
85
+ const PRIMITIVE_TYPES = new Set([
86
+ "string",
87
+ "number",
88
+ "integer",
89
+ "boolean",
90
+ "object",
91
+ "array"
92
+ ]);
93
+ function parseHeaderValue(value) {
94
+ if (!value) return null;
95
+ const trimmed = String(value).trim();
96
+ if (!trimmed) return null;
97
+ const colonIndex = trimmed.indexOf(":");
98
+ let namePart = colonIndex >= 0 ? trimmed.slice(0, colonIndex).trim() : trimmed;
99
+ let typePart = colonIndex >= 0 ? trimmed.slice(colonIndex + 1).trim() : "";
100
+ let required = false;
101
+ if (namePart.endsWith("*")) {
102
+ required = true;
103
+ namePart = namePart.slice(0, -1).trim();
104
+ }
105
+ if (typePart.endsWith("*")) {
106
+ required = true;
107
+ typePart = typePart.slice(0, -1).trim();
108
+ }
109
+ if (!namePart) return null;
110
+ const typeHint = typePart ? parseHeaderType(typePart) : void 0;
111
+ return {
112
+ name: namePart,
113
+ required,
114
+ raw: trimmed,
115
+ typeHint
116
+ };
117
+ }
118
+ function normalizeEnumValue(value) {
119
+ if (value == null) return null;
120
+ const text = String(value).trim();
121
+ if (!text) return null;
122
+ if (text.startsWith("\"") && text.endsWith("\"") || text.startsWith("'") && text.endsWith("'")) return text.slice(1, -1);
123
+ return text;
124
+ }
125
+ function parseJsonMaybe(value) {
126
+ if (typeof value !== "string") return null;
127
+ try {
128
+ return JSON.parse(value);
129
+ } catch {
130
+ return null;
131
+ }
132
+ }
133
+ function parseHeaderType(typeValue) {
134
+ const trimmed = String(typeValue).trim();
135
+ if (!trimmed) return void 0;
136
+ const isArray = trimmed.endsWith("[]");
137
+ const rawSpec = isArray ? trimmed.slice(0, -2).trim() : trimmed;
138
+ const ref = parseRefSpec(rawSpec);
139
+ if (ref) {
140
+ const itemHint = {
141
+ type: "string",
142
+ format: "uuid",
143
+ ref
144
+ };
145
+ if (isArray) return {
146
+ type: "array",
147
+ items: {
148
+ type: itemHint.type,
149
+ format: itemHint.format,
150
+ ref: itemHint.ref
151
+ }
152
+ };
153
+ return itemHint;
154
+ }
155
+ const tokenHint = parseTypeToken(rawSpec);
156
+ if (!tokenHint) return void 0;
157
+ if (!isArray) return tokenHint;
158
+ return {
159
+ type: "array",
160
+ items: {
161
+ type: tokenHint.type ?? "string",
162
+ format: tokenHint.format
163
+ }
164
+ };
165
+ }
166
+ function parseTypeToken(value) {
167
+ const token = String(value).trim().toLowerCase();
168
+ if (!token) return void 0;
169
+ const alias = TYPE_ALIASES[token];
170
+ if (alias) return { ...alias };
171
+ if (PRIMITIVE_TYPES.has(token)) return { type: token };
172
+ }
173
+ const ON_DELETE_ACTIONS = new Set([
174
+ "cascade",
175
+ "restrict",
176
+ "set_null"
177
+ ]);
178
+ function parseRefSpec(value) {
179
+ const match = value.match(/^(ref|fk)\((.+)\)$/i);
180
+ if (!match) return null;
181
+ const body = match[2].trim();
182
+ if (!body) return null;
183
+ const parts = body.split(",").map((p) => p.trim());
184
+ const target = parts[0];
185
+ if (!target) return null;
186
+ const [tableRaw, fieldRaw] = target.split(".", 2);
187
+ const table = tableRaw?.trim();
188
+ if (!table) return null;
189
+ const field = fieldRaw?.trim() || "id";
190
+ let expandKey;
191
+ let onDelete;
192
+ for (let i = 1; i < parts.length; i++) {
193
+ const part = parts[i];
194
+ if (!part) continue;
195
+ if (ON_DELETE_ACTIONS.has(part)) onDelete = part;
196
+ else if (!expandKey) expandKey = part;
197
+ }
198
+ return {
199
+ table,
200
+ field,
201
+ expandKey,
202
+ onDelete
203
+ };
204
+ }
205
+
206
+ //#endregion
207
+ //#region src/openapi.ts
208
+ function buildOpenApi(schema, options) {
209
+ const components = { schemas: { ErrorResponse: {
210
+ type: "object",
211
+ properties: {
212
+ code: { type: "string" },
213
+ message: { type: "string" },
214
+ details: { type: "object" }
215
+ },
216
+ required: ["code", "message"]
217
+ } } };
218
+ const paths = {};
219
+ const tableNameToSchema = /* @__PURE__ */ new Map();
220
+ for (const table of schema.tables) {
221
+ const schemaName = toSchemaName(table.name);
222
+ tableNameToSchema.set(table.name, schemaName);
223
+ }
224
+ for (const table of schema.tables) {
225
+ const schemaName = tableNameToSchema.get(table.name) ?? toSchemaName(table.name);
226
+ const listPath = joinPath(options.pathPrefix, table.name);
227
+ const itemPath = `${listPath}/{id}`;
228
+ const schemas = buildTableSchemas(table, schemaName, tableNameToSchema);
229
+ Object.assign(components.schemas, schemas);
230
+ paths[listPath] = {
231
+ get: {
232
+ operationId: `${table.name}_list`,
233
+ description: "Filters: exact field=value, contains field__contains=value, range field__gte / field__lte.",
234
+ parameters: [{
235
+ name: "sort",
236
+ in: "query",
237
+ schema: { type: "string" },
238
+ description: "Sort by field. Use -field for desc."
239
+ }, {
240
+ name: "expand",
241
+ in: "query",
242
+ schema: { type: "string" },
243
+ description: "Comma-separated relations to expand."
244
+ }],
245
+ responses: { 200: {
246
+ description: "OK",
247
+ content: { "application/json": { schema: {
248
+ type: "array",
249
+ items: { $ref: `#/components/schemas/${schemaName}` }
250
+ } } }
251
+ } }
252
+ },
253
+ post: {
254
+ operationId: `${table.name}_create`,
255
+ requestBody: {
256
+ required: true,
257
+ content: { "application/json": { schema: { $ref: `#/components/schemas/${schemaName}Create` } } }
258
+ },
259
+ responses: {
260
+ 201: {
261
+ description: "Created",
262
+ content: { "application/json": { schema: { $ref: `#/components/schemas/${schemaName}` } } }
263
+ },
264
+ 400: {
265
+ description: "Validation error",
266
+ content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } }
267
+ }
268
+ }
269
+ }
270
+ };
271
+ paths[itemPath] = {
272
+ get: {
273
+ operationId: `${table.name}_get`,
274
+ parameters: [{
275
+ name: "id",
276
+ in: "path",
277
+ required: true,
278
+ schema: {
279
+ type: "string",
280
+ format: "uuid"
281
+ }
282
+ }, {
283
+ name: "expand",
284
+ in: "query",
285
+ schema: { type: "string" },
286
+ description: "Comma-separated relations to expand."
287
+ }],
288
+ responses: {
289
+ 200: {
290
+ description: "OK",
291
+ content: { "application/json": { schema: { $ref: `#/components/schemas/${schemaName}` } } }
292
+ },
293
+ 404: {
294
+ description: "Not found",
295
+ content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } }
296
+ }
297
+ }
298
+ },
299
+ put: {
300
+ operationId: `${table.name}_update`,
301
+ parameters: [{
302
+ name: "id",
303
+ in: "path",
304
+ required: true,
305
+ schema: {
306
+ type: "string",
307
+ format: "uuid"
308
+ }
309
+ }],
310
+ requestBody: {
311
+ required: true,
312
+ content: { "application/json": { schema: { $ref: `#/components/schemas/${schemaName}Create` } } }
313
+ },
314
+ responses: {
315
+ 200: {
316
+ description: "OK",
317
+ content: { "application/json": { schema: { $ref: `#/components/schemas/${schemaName}` } } }
318
+ },
319
+ 400: {
320
+ description: "Validation error",
321
+ content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } }
322
+ }
323
+ }
324
+ },
325
+ patch: {
326
+ operationId: `${table.name}_patch`,
327
+ parameters: [{
328
+ name: "id",
329
+ in: "path",
330
+ required: true,
331
+ schema: {
332
+ type: "string",
333
+ format: "uuid"
334
+ }
335
+ }],
336
+ requestBody: {
337
+ required: true,
338
+ content: { "application/json": { schema: { $ref: `#/components/schemas/${schemaName}Patch` } } }
339
+ },
340
+ responses: {
341
+ 200: {
342
+ description: "OK",
343
+ content: { "application/json": { schema: { $ref: `#/components/schemas/${schemaName}` } } }
344
+ },
345
+ 400: {
346
+ description: "Validation error (e.g. NOT NULL violated after patch)",
347
+ content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } }
348
+ }
349
+ }
350
+ },
351
+ delete: {
352
+ operationId: `${table.name}_delete`,
353
+ parameters: [{
354
+ name: "id",
355
+ in: "path",
356
+ required: true,
357
+ schema: {
358
+ type: "string",
359
+ format: "uuid"
360
+ }
361
+ }],
362
+ responses: {
363
+ 204: { description: "No Content" },
364
+ 404: {
365
+ description: "Not found",
366
+ content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } }
367
+ }
368
+ }
369
+ }
370
+ };
371
+ }
372
+ return {
373
+ openapi: "3.1.0",
374
+ info: {
375
+ title: options.title,
376
+ version: options.version
377
+ },
378
+ paths,
379
+ components
380
+ };
381
+ }
382
+ function buildTableSchemas(table, schemaName, tableNameToSchema) {
383
+ const properties = {};
384
+ const required = [];
385
+ for (const column of table.columns) {
386
+ properties[column.name] = columnToSchema(column);
387
+ if (column.required) required.push(column.name);
388
+ }
389
+ for (const column of table.columns) {
390
+ if (!column.ref) continue;
391
+ const expandKey = column.ref.expandKey || column.ref.table;
392
+ if (!expandKey) continue;
393
+ const refSchemaName = tableNameToSchema.get(column.ref.table);
394
+ if (!refSchemaName) continue;
395
+ properties[expandKey] = {
396
+ $ref: `#/components/schemas/${refSchemaName}`,
397
+ description: `Included when expand=${expandKey}.`
398
+ };
399
+ }
400
+ const createProperties = { ...properties };
401
+ const patchProperties = { ...properties };
402
+ delete patchProperties.id;
403
+ for (const column of table.columns) if (column.ref) {
404
+ const expandKey = column.ref.expandKey || column.ref.table;
405
+ if (expandKey) delete createProperties[expandKey];
406
+ if (expandKey) delete patchProperties[expandKey];
407
+ }
408
+ const createRequired = required.filter((name) => name !== "id");
409
+ return {
410
+ [schemaName]: {
411
+ type: "object",
412
+ properties,
413
+ required
414
+ },
415
+ [`${schemaName}Create`]: {
416
+ type: "object",
417
+ properties: createProperties,
418
+ required: createRequired
419
+ },
420
+ [`${schemaName}Patch`]: {
421
+ type: "object",
422
+ properties: patchProperties,
423
+ additionalProperties: false
424
+ }
425
+ };
426
+ }
427
+ function columnToSchema(column) {
428
+ const schema = { type: column.type || "string" };
429
+ if (column.type === "array") schema.items = buildItemsSchema(column.items);
430
+ if (column.format) schema.format = column.format;
431
+ if (Array.isArray(column.enum)) schema.enum = column.enum;
432
+ if (column.description) schema.description = column.description;
433
+ if (column.minimum != null) schema.minimum = column.minimum;
434
+ if (column.maximum != null) schema.maximum = column.maximum;
435
+ if (column.pattern) schema.pattern = column.pattern;
436
+ if (column.multipleOf != null) schema.multipleOf = column.multipleOf;
437
+ if (column.ref) schema["x-sheet-ref"] = {
438
+ table: column.ref.table,
439
+ field: column.ref.field,
440
+ expandKey: column.ref.expandKey
441
+ };
442
+ if (column.extensions && typeof column.extensions === "object") {
443
+ for (const [key, value] of Object.entries(column.extensions)) if (key.startsWith("x-") && value != null) schema[key] = value;
444
+ }
445
+ return schema;
446
+ }
447
+ function buildItemsSchema(items) {
448
+ if (!items) return { type: "string" };
449
+ const schema = { type: items.type };
450
+ if (items.format) schema.format = items.format;
451
+ if (Array.isArray(items.enum)) schema.enum = items.enum;
452
+ if (items.ref) schema["x-sheet-ref"] = {
453
+ table: items.ref.table,
454
+ field: items.ref.field,
455
+ expandKey: items.ref.expandKey
456
+ };
457
+ return schema;
458
+ }
459
+
460
+ //#endregion
461
+ //#region src/schema.ts
462
+ const READ_SCOPES = ["https://www.googleapis.com/auth/spreadsheets.readonly"];
463
+ const MAX_ENUM_VALUES = 200;
464
+ async function fetchSchema(options) {
465
+ const auth = new google.auth.GoogleAuth({ scopes: READ_SCOPES });
466
+ const sheets = google.sheets({
467
+ version: "v4",
468
+ auth
469
+ });
470
+ const debugEnabled = Boolean(options.debug || process.env.SHEET_SCHEMA_DEBUG);
471
+ const enumCache = /* @__PURE__ */ new Map();
472
+ const maxEnumValues = Number.isFinite(Number(options.enumLimit)) && Number(options.enumLimit) > 0 ? Number(options.enumLimit) : MAX_ENUM_VALUES;
473
+ const spreadsheet = (await sheets.spreadsheets.get({
474
+ spreadsheetId: options.spreadsheetId,
475
+ includeGridData: false,
476
+ fields: "sheets(properties(sheetId,title,hidden),developerMetadata),developerMetadata"
477
+ })).data;
478
+ const spreadsheetMetadata = spreadsheet.developerMetadata || [];
479
+ const sheetEntries = spreadsheet.sheets || [];
480
+ const tables = [];
481
+ for (const sheet of sheetEntries) {
482
+ const properties = sheet.properties || {};
483
+ const title = properties.title;
484
+ if (!title) continue;
485
+ if (!options.includeHidden && properties.hidden) continue;
486
+ if (options.excludePrefixes?.some((prefix) => prefix && title.startsWith(prefix))) continue;
487
+ const sheetId = properties.sheetId;
488
+ const metadataMap = buildColumnMetadataMap([...spreadsheetMetadata, ...sheet.developerMetadata || []], sheetId, options.metadataKey);
489
+ const range = `${escapeSheetName(title)}!1:${options.scanRows}`;
490
+ const rowData = (((await sheets.spreadsheets.get({
491
+ spreadsheetId: options.spreadsheetId,
492
+ includeGridData: true,
493
+ ranges: [range],
494
+ fields: "sheets(data(rowData(values(formattedValue,userEnteredValue,effectiveValue,userEnteredFormat,effectiveFormat,dataValidation))),properties(sheetId,title))"
495
+ })).data.sheets?.[0])?.data?.[0])?.rowData || [];
496
+ const headerRowIndex = (options.headerRow ?? 2) - 1;
497
+ const dataStartRowIndex = headerRowIndex + 1;
498
+ const headerValues = rowData[headerRowIndex]?.values || [];
499
+ const columnCells = collectColumnCells(rowData, dataStartRowIndex);
500
+ const columns = [];
501
+ const seenNames = /* @__PURE__ */ new Set();
502
+ for (let index = 0; index < headerValues.length; index += 1) {
503
+ const cell = headerValues[index];
504
+ const headerText = getCellText(cell);
505
+ const header = parseHeaderValue(headerText);
506
+ if (!header || !header.name) continue;
507
+ if (seenNames.has(header.name)) {
508
+ console.warn(`Duplicate column name skipped: ${header.name} (${title})`);
509
+ continue;
510
+ }
511
+ seenNames.add(header.name);
512
+ const meta = metadataMap.get(index) || null;
513
+ const cells = columnCells.get(index) || [];
514
+ const inferenceDebug = debugEnabled ? {} : null;
515
+ const inferred = await inferColumnType(cells, {
516
+ sheets,
517
+ spreadsheetId: options.spreadsheetId,
518
+ sheetTitle: title,
519
+ enumCache,
520
+ maxEnumValues,
521
+ debugInfo: inferenceDebug
522
+ });
523
+ const column = applyMetadataOverrides(applyHeaderOverrides({
524
+ name: header.name,
525
+ required: header.required,
526
+ index,
527
+ type: inferred.type,
528
+ format: inferred.format,
529
+ enum: inferred.enum,
530
+ minimum: inferred.minimum,
531
+ maximum: inferred.maximum,
532
+ pattern: inferred.pattern,
533
+ multipleOf: inferred.multipleOf,
534
+ validation: inferred.validation,
535
+ extensions: inferred.extensions,
536
+ source: inferred.source
537
+ }, header), meta);
538
+ if (header.typeHint?.format === "enum") {
539
+ const enumValues = collectUniqueStringValues(cells, maxEnumValues);
540
+ if (enumValues && enumValues.length > 0) {
541
+ column.enum = enumValues;
542
+ column.type = "string";
543
+ column.format = void 0;
544
+ column.source = "header-enum";
545
+ if (debugEnabled) console.log(` [enum] ${header.name}: ${enumValues.join(", ")}`);
546
+ }
547
+ }
548
+ const forcedId = column.name === "id";
549
+ if (forcedId) {
550
+ column.type = "string";
551
+ column.format = "uuid";
552
+ column.source = "fixed";
553
+ }
554
+ if (debugEnabled) logColumnDebug({
555
+ spreadsheetId: options.spreadsheetId,
556
+ sheet: title,
557
+ sheetId,
558
+ columnIndex: index,
559
+ rawHeader: headerText,
560
+ header: header.name,
561
+ required: header.required,
562
+ headerType: header.typeHint,
563
+ inferred,
564
+ inference: inferenceDebug,
565
+ metadata: meta,
566
+ final: column,
567
+ forcedId
568
+ });
569
+ columns.push(column);
570
+ }
571
+ const idColumn = columns.find((column) => column.name === "id");
572
+ if (!idColumn || !idColumn.required) {
573
+ if (options.debug) console.warn(`[sheet-schema] Skipping sheet "${title}": missing required id* column`);
574
+ continue;
575
+ }
576
+ tables.push({
577
+ name: title,
578
+ sheetId,
579
+ columns
580
+ });
581
+ }
582
+ applyAutoRefs(tables);
583
+ return {
584
+ version: 1,
585
+ spreadsheetId: options.spreadsheetId,
586
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
587
+ tables
588
+ };
589
+ }
590
+ function applyHeaderOverrides(column, header) {
591
+ if (!header?.typeHint) return column;
592
+ const hint = header.typeHint;
593
+ const merged = { ...column };
594
+ let changed = false;
595
+ if (hint.type) {
596
+ merged.type = hint.type;
597
+ changed = true;
598
+ }
599
+ if (hint.format) {
600
+ merged.format = hint.format;
601
+ changed = true;
602
+ }
603
+ if (hint.items) {
604
+ merged.type = "array";
605
+ merged.items = hint.items;
606
+ changed = true;
607
+ }
608
+ if (hint.ref) {
609
+ merged.ref = {
610
+ ...hint.ref,
611
+ source: "header"
612
+ };
613
+ if (!merged.type || merged.type === "string") {
614
+ merged.type = "string";
615
+ merged.format = merged.format || "uuid";
616
+ }
617
+ changed = true;
618
+ }
619
+ if (changed) merged.source = "header";
620
+ return merged;
621
+ }
622
+ function applyAutoRefs(tables) {
623
+ const nameMap = /* @__PURE__ */ new Map();
624
+ for (const table of tables) {
625
+ const normalized = normalizeKey(table.name);
626
+ nameMap.set(normalized, table.name);
627
+ if (normalized.endsWith("s") && normalized.length > 1) nameMap.set(normalized.slice(0, -1), table.name);
628
+ }
629
+ for (const table of tables) for (const column of table.columns) {
630
+ if (column.name === "id") continue;
631
+ if (column.ref) continue;
632
+ if (column.source === "header" || column.source === "metadata") continue;
633
+ const base = extractRefBase(column.name);
634
+ if (!base) continue;
635
+ const target = nameMap.get(normalizeKey(base));
636
+ if (!target) continue;
637
+ column.ref = {
638
+ table: target,
639
+ field: "id",
640
+ expandKey: base,
641
+ source: "auto"
642
+ };
643
+ if (!column.type || column.type === "string") {
644
+ column.type = "string";
645
+ column.format = column.format || "uuid";
646
+ }
647
+ }
648
+ }
649
+ function extractRefBase(columnName) {
650
+ if (!columnName) return null;
651
+ if (columnName.toLowerCase().endsWith("_id")) return columnName.slice(0, -3);
652
+ if (columnName.endsWith("Id") && columnName.length > 2) return columnName.slice(0, -2);
653
+ return null;
654
+ }
655
+ function normalizeKey(value) {
656
+ return String(value).toLowerCase().replace(/[^a-z0-9]+/g, "");
657
+ }
658
+ function collectColumnCells(rowData, dataStartRowIndex = 1) {
659
+ const columnCells = /* @__PURE__ */ new Map();
660
+ for (let rowIndex = dataStartRowIndex; rowIndex < rowData.length; rowIndex += 1) {
661
+ const values = rowData[rowIndex]?.values || [];
662
+ for (let colIndex = 0; colIndex < values.length; colIndex += 1) {
663
+ if (!columnCells.has(colIndex)) columnCells.set(colIndex, []);
664
+ columnCells.get(colIndex).push(values[colIndex]);
665
+ }
666
+ }
667
+ return columnCells;
668
+ }
669
+ function getCellText(cell) {
670
+ if (!cell) return "";
671
+ if (cell.formattedValue != null) return String(cell.formattedValue);
672
+ const entered = cell.userEnteredValue;
673
+ if (entered?.stringValue != null) return String(entered.stringValue);
674
+ if (entered?.numberValue != null) return String(entered.numberValue);
675
+ if (entered?.boolValue != null) return String(entered.boolValue);
676
+ const effective = cell.effectiveValue;
677
+ if (effective?.stringValue != null) return String(effective.stringValue);
678
+ if (effective?.numberValue != null) return String(effective.numberValue);
679
+ if (effective?.boolValue != null) return String(effective.boolValue);
680
+ return "";
681
+ }
682
+ function collectUniqueStringValues(cells, maxEnumValues) {
683
+ const uniqueValues = /* @__PURE__ */ new Set();
684
+ for (const cell of cells) {
685
+ const text = getCellText(cell).trim();
686
+ if (!text) continue;
687
+ uniqueValues.add(text);
688
+ if (uniqueValues.size > maxEnumValues) return null;
689
+ }
690
+ if (uniqueValues.size < 1) return null;
691
+ return Array.from(uniqueValues).sort();
692
+ }
693
+ async function inferColumnType(cells, context) {
694
+ const debugInfo = context?.debugInfo || null;
695
+ const validation = findFirstValidation(cells);
696
+ const validationType = validation?.condition?.type || null;
697
+ if (debugInfo) debugInfo.validationType = validationType;
698
+ const extensions = {};
699
+ const validationExtension = buildValidationExtension(validation, context);
700
+ if (validationExtension) extensions["x-sheet-validation"] = validationExtension;
701
+ const enumValues = await resolveValidationEnum(validation, context);
702
+ if (enumValues && enumValues.length > 0) {
703
+ if (debugInfo) {
704
+ debugInfo.validationValuesCount = enumValues.length;
705
+ debugInfo.validationValuesSample = enumValues.slice(0, 5);
706
+ }
707
+ return {
708
+ type: "string",
709
+ enum: enumValues,
710
+ source: "validation",
711
+ extensions: finalizeExtensions(extensions)
712
+ };
713
+ }
714
+ if (validationType && validationType.includes("BOOLEAN")) return {
715
+ type: "boolean",
716
+ source: "checkbox",
717
+ extensions: finalizeExtensions(extensions)
718
+ };
719
+ const validationHints = inferValidationHints(validation);
720
+ let result = {
721
+ type: validationHints?.type || "string",
722
+ format: validationHints?.format,
723
+ enum: validationHints?.enum,
724
+ minimum: validationHints?.minimum,
725
+ maximum: validationHints?.maximum,
726
+ pattern: validationHints?.pattern,
727
+ multipleOf: validationHints?.multipleOf,
728
+ source: validationHints ? "validation" : "fallback",
729
+ extensions: {
730
+ ...extensions,
731
+ ...validationHints?.extensions || {}
732
+ }
733
+ };
734
+ const numberFormatType = findNumberFormatType(cells);
735
+ if (debugInfo) debugInfo.numberFormatType = numberFormatType || null;
736
+ if (numberFormatType) {
737
+ const mapped = mapNumberFormat(numberFormatType);
738
+ if (mapped && result.source === "fallback") result = {
739
+ ...result,
740
+ ...mapped,
741
+ source: "format"
742
+ };
743
+ result.extensions = {
744
+ ...result.extensions,
745
+ "x-sheet-numberFormat": numberFormatType
746
+ };
747
+ }
748
+ result.extensions = finalizeExtensions(result.extensions);
749
+ return result;
750
+ }
751
+ function buildValidationExtension(validation, context) {
752
+ const type = validation?.condition?.type;
753
+ if (!type) return null;
754
+ const rawValues = extractConditionValues(validation.condition.values);
755
+ const extension = { type };
756
+ if (validation?.strict != null) extension.strict = validation.strict;
757
+ if (type === "ONE_OF_LIST") {
758
+ extension.valuesCount = rawValues.length;
759
+ if (rawValues.length > 0) extension.valuesSample = rawValues.slice(0, 5);
760
+ } else if (type === "ONE_OF_RANGE") {
761
+ const range = extractValidationRange(rawValues[0], context?.sheetTitle);
762
+ if (range) extension.range = range;
763
+ } else if (rawValues.length > 0) extension.values = rawValues.slice(0, 2);
764
+ return extension;
765
+ }
766
+ function inferValidationHints(validation) {
767
+ const type = validation?.condition?.type;
768
+ if (!type) return null;
769
+ const values = extractConditionValues(validation.condition.values);
770
+ const hints = {};
771
+ switch (type) {
772
+ case "NUMBER_BETWEEN": {
773
+ const min = parseNumberValue(values[0]);
774
+ const max = parseNumberValue(values[1]);
775
+ if (min != null) hints.minimum = min;
776
+ if (max != null) hints.maximum = max;
777
+ hints.type = "number";
778
+ break;
779
+ }
780
+ case "NUMBER_GREATER_THAN_EQ":
781
+ case "NUMBER_GREATER": {
782
+ const min = parseNumberValue(values[0]);
783
+ if (min != null) hints.minimum = min;
784
+ hints.type = "number";
785
+ break;
786
+ }
787
+ case "NUMBER_LESS_THAN_EQ":
788
+ case "NUMBER_LESS": {
789
+ const max = parseNumberValue(values[0]);
790
+ if (max != null) hints.maximum = max;
791
+ hints.type = "number";
792
+ break;
793
+ }
794
+ case "NUMBER_EQ": {
795
+ const exact = parseNumberValue(values[0]);
796
+ if (exact != null) {
797
+ hints.minimum = exact;
798
+ hints.maximum = exact;
799
+ }
800
+ hints.type = "number";
801
+ break;
802
+ }
803
+ case "TEXT_IS_EMAIL":
804
+ hints.type = "string";
805
+ hints.format = "email";
806
+ break;
807
+ case "TEXT_IS_URL":
808
+ hints.type = "string";
809
+ hints.format = "uri";
810
+ break;
811
+ case "TEXT_STARTS_WITH": {
812
+ const value = values[0];
813
+ if (value) hints.pattern = `^${escapeRegex(value)}`;
814
+ hints.type = "string";
815
+ break;
816
+ }
817
+ case "TEXT_ENDS_WITH": {
818
+ const value = values[0];
819
+ if (value) hints.pattern = `${escapeRegex(value)}$`;
820
+ hints.type = "string";
821
+ break;
822
+ }
823
+ case "TEXT_EQ": {
824
+ const value = values[0];
825
+ if (value) hints.enum = [value];
826
+ hints.type = "string";
827
+ break;
828
+ }
829
+ case "DATE_BETWEEN":
830
+ case "DATE_ON_OR_AFTER":
831
+ case "DATE_ON_OR_BEFORE":
832
+ case "DATE_AFTER":
833
+ case "DATE_BEFORE":
834
+ case "DATE_EQ":
835
+ hints.type = "string";
836
+ hints.format = "date";
837
+ break;
838
+ default: return null;
839
+ }
840
+ if (hints.enum) return {
841
+ ...hints,
842
+ enum: hints.enum.map(normalizeEnumValue).filter(Boolean)
843
+ };
844
+ return hints;
845
+ }
846
+ async function resolveValidationEnum(validation, context) {
847
+ const type = validation?.condition?.type;
848
+ if (!type) return null;
849
+ const rawValues = extractConditionValues(validation.condition.values);
850
+ if (type === "ONE_OF_LIST") return limitEnumValues(rawValues.map((value) => normalizeEnumValue(value)).filter(Boolean), context?.maxEnumValues);
851
+ if (type === "ONE_OF_RANGE") {
852
+ const range = extractValidationRange(rawValues[0], context?.sheetTitle);
853
+ if (!range) return null;
854
+ if (context?.debugInfo) context.debugInfo.validationRange = range;
855
+ try {
856
+ return limitEnumValues(await fetchEnumValuesFromRange({
857
+ sheets: context?.sheets,
858
+ spreadsheetId: context?.spreadsheetId,
859
+ range,
860
+ enumCache: context?.enumCache,
861
+ maxEnumValues: context?.maxEnumValues
862
+ }), context?.maxEnumValues);
863
+ } catch (error) {
864
+ if (context?.debugInfo) context.debugInfo.validationRangeError = error?.message || error?.stack || String(error);
865
+ }
866
+ }
867
+ return null;
868
+ }
869
+ function extractValidationRange(value, sheetTitle) {
870
+ if (!value) return null;
871
+ let range = String(value).trim();
872
+ if (!range) return null;
873
+ if (range.startsWith("=")) range = range.slice(1);
874
+ if (!range.includes("!") && sheetTitle) range = `${escapeSheetName(sheetTitle)}!${range}`;
875
+ return range;
876
+ }
877
+ async function fetchEnumValuesFromRange({ sheets, spreadsheetId, range, enumCache, maxEnumValues }) {
878
+ if (!sheets || !spreadsheetId || !range) return [];
879
+ const cacheKey = `${spreadsheetId}:${range}`;
880
+ if (enumCache?.has(cacheKey)) return enumCache.get(cacheKey);
881
+ const limited = limitEnumValues(((await sheets.spreadsheets.values.get({
882
+ spreadsheetId,
883
+ range
884
+ })).data.values || []).flat().map((value) => normalizeEnumValue(value)).filter(Boolean), maxEnumValues);
885
+ if (enumCache) enumCache.set(cacheKey, limited);
886
+ return limited;
887
+ }
888
+ function extractConditionValues(values) {
889
+ if (!Array.isArray(values)) return [];
890
+ return values.map((value) => value?.userEnteredValue ?? value?.stringValue ?? value).filter((value) => value != null && value !== "");
891
+ }
892
+ function parseNumberValue(value) {
893
+ if (value == null) return null;
894
+ const parsed = Number(value);
895
+ return Number.isFinite(parsed) ? parsed : null;
896
+ }
897
+ function escapeRegex(value) {
898
+ return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
899
+ }
900
+ function limitEnumValues(values, maxEnumValues) {
901
+ if (!Array.isArray(values) || values.length === 0) return [];
902
+ const unique = [];
903
+ const limit = Number.isFinite(maxEnumValues) ? maxEnumValues : MAX_ENUM_VALUES;
904
+ for (const value of values) {
905
+ if (unique.includes(value)) continue;
906
+ unique.push(value);
907
+ if (unique.length >= limit) break;
908
+ }
909
+ return unique;
910
+ }
911
+ function finalizeExtensions(extensions) {
912
+ if (!extensions || typeof extensions !== "object") return void 0;
913
+ const entries = Object.entries(extensions).filter(([key, value]) => key.startsWith("x-") && value != null);
914
+ if (entries.length === 0) return void 0;
915
+ return Object.fromEntries(entries);
916
+ }
917
+ function findFirstValidation(cells) {
918
+ for (const cell of cells) if (cell?.dataValidation) return cell.dataValidation;
919
+ return null;
920
+ }
921
+ function findNumberFormatType(cells) {
922
+ for (const cell of cells) {
923
+ const type = cell?.userEnteredFormat?.numberFormat?.type || cell?.effectiveFormat?.numberFormat?.type;
924
+ if (type) return type;
925
+ }
926
+ return null;
927
+ }
928
+ function mapNumberFormat(type) {
929
+ switch (type) {
930
+ case "NUMBER":
931
+ case "SCIENTIFIC": return { type: "number" };
932
+ case "CURRENCY": return {
933
+ type: "number",
934
+ format: "currency"
935
+ };
936
+ case "PERCENT": return {
937
+ type: "number",
938
+ format: "percent"
939
+ };
940
+ case "DATE": return {
941
+ type: "string",
942
+ format: "date"
943
+ };
944
+ case "DATE_TIME": return {
945
+ type: "string",
946
+ format: "date-time"
947
+ };
948
+ case "TIME": return {
949
+ type: "string",
950
+ format: "time"
951
+ };
952
+ case "TEXT": return { type: "string" };
953
+ default: return null;
954
+ }
955
+ }
956
+ function applyMetadataOverrides(column, metadata) {
957
+ if (!metadata) return column;
958
+ const merged = {
959
+ ...column,
960
+ extensions: { ...column.extensions || {} }
961
+ };
962
+ if (typeof metadata.description === "string") merged.description = metadata.description;
963
+ if (typeof metadata.type === "string") merged.type = metadata.type;
964
+ if (typeof metadata.format === "string") merged.format = metadata.format;
965
+ if (metadata.minimum != null) merged.minimum = Number(metadata.minimum);
966
+ if (metadata.maximum != null) merged.maximum = Number(metadata.maximum);
967
+ if (typeof metadata.pattern === "string") merged.pattern = metadata.pattern;
968
+ if (metadata.multipleOf != null) merged.multipleOf = Number(metadata.multipleOf);
969
+ if (Array.isArray(metadata.enum)) {
970
+ merged.enum = metadata.enum.map((value) => String(value));
971
+ if (!metadata.type) merged.type = "string";
972
+ }
973
+ if (metadata.ref && typeof metadata.ref === "object") {
974
+ merged.ref = {
975
+ ...metadata.ref,
976
+ source: "metadata"
977
+ };
978
+ if (!merged.type || merged.type === "string") {
979
+ merged.type = "string";
980
+ merged.format = merged.format || "uuid";
981
+ }
982
+ }
983
+ if (metadata.items && typeof metadata.items === "object") {
984
+ const items = metadata.items;
985
+ merged.type = "array";
986
+ merged.items = items;
987
+ }
988
+ if (metadata.validation) merged.extensions["x-sheet-validation"] = metadata.validation;
989
+ if (metadata.chip || metadata.sheetChip) merged.extensions["x-sheet-chip"] = metadata.chip || metadata.sheetChip;
990
+ if (metadata.numberFormat) merged.extensions["x-sheet-numberFormat"] = metadata.numberFormat;
991
+ if (metadata.extensions && typeof metadata.extensions === "object") Object.assign(merged.extensions, metadata.extensions);
992
+ for (const [key, value] of Object.entries(metadata)) if (key.startsWith("x-")) merged.extensions[key] = value;
993
+ merged.extensions = finalizeExtensions(merged.extensions);
994
+ merged.source = "metadata";
995
+ return merged;
996
+ }
997
+ function buildColumnMetadataMap(metadataList, sheetId, metadataKey) {
998
+ const map = /* @__PURE__ */ new Map();
999
+ for (const entry of metadataList) {
1000
+ if (metadataKey && entry.metadataKey !== metadataKey) continue;
1001
+ const location = entry.location || entry.metadataLocation;
1002
+ if (!location) continue;
1003
+ if (location.sheetId != null && sheetId != null && location.sheetId !== sheetId) continue;
1004
+ const columnIndices = extractColumnIndices(location);
1005
+ if (!columnIndices || columnIndices.length === 0) continue;
1006
+ const parsed = parseJsonMaybe(typeof entry.metadataValue === "string" ? entry.metadataValue : null);
1007
+ if (!parsed || typeof parsed !== "object") continue;
1008
+ const parsedRecord = parsed;
1009
+ for (const index of columnIndices) {
1010
+ if (!map.has(index)) map.set(index, {});
1011
+ map.set(index, {
1012
+ ...map.get(index),
1013
+ ...parsedRecord
1014
+ });
1015
+ }
1016
+ }
1017
+ return map;
1018
+ }
1019
+ function extractColumnIndices(location) {
1020
+ if (typeof location.columnIndex === "number") return [location.columnIndex];
1021
+ const range = location.dimensionRange;
1022
+ if (range && range.dimension === "COLUMNS") {
1023
+ const start = range.startIndex;
1024
+ const end = range.endIndex;
1025
+ if (Number.isFinite(start) && Number.isFinite(end)) {
1026
+ const indices = [];
1027
+ for (let idx = start; idx < end; idx += 1) indices.push(idx);
1028
+ return indices;
1029
+ }
1030
+ }
1031
+ return null;
1032
+ }
1033
+ function logColumnDebug(payload) {
1034
+ console.log(`[sheet-schema] column ${JSON.stringify(payload)}`);
1035
+ }
1036
+
1037
+ //#endregion
1038
+ export { buildOpenApi, fetchSchema };
1039
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/utils/helpers.ts","../src/utils/parser.ts","../src/openapi.ts","../src/schema.ts"],"sourcesContent":["export function toSchemaName(name: string): string {\n\tconst cleaned = String(name)\n\t\t.replace(/[^A-Za-z0-9]+/g, \" \")\n\t\t.trim();\n\tif (!cleaned) return \"Table\";\n\treturn cleaned\n\t\t.split(\" \")\n\t\t.filter(Boolean)\n\t\t.map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n\t\t.join(\"\");\n}\n\nexport function normalizePathPrefix(prefix: string): string {\n\tif (!prefix) return \"\";\n\tlet value = String(prefix).trim();\n\tif (!value || value === \"/\") return \"\";\n\tif (!value.startsWith(\"/\")) value = `/${value}`;\n\tif (value.endsWith(\"/\")) value = value.slice(0, -1);\n\treturn value;\n}\n\nexport function joinPath(prefix: string, segment: string): string {\n\tconst normalized = normalizePathPrefix(prefix);\n\tconst safeSegment = String(segment).replace(/^\\//, \"\");\n\treturn `${normalized}/${safeSegment}`.replace(\"//\", \"/\");\n}\n\nexport function escapeSheetName(name: string): string {\n\tconst safe = String(name).replace(/'/g, \"''\");\n\treturn `'${safe}'`;\n}\n","import type {\n\tColumnRef,\n\tHeaderSpec,\n\tHeaderTypeHint,\n\tOnDeleteAction,\n\tSchemaType,\n} from \"../types.js\";\n\nconst TYPE_ALIASES: Record<string, HeaderTypeHint> = {\n\tuuid: { type: \"string\", format: \"uuid\" },\n\temail: { type: \"string\", format: \"email\" },\n\turi: { type: \"string\", format: \"uri\" },\n\turl: { type: \"string\", format: \"uri\" },\n\tdate: { type: \"string\", format: \"date\" },\n\t\"date-time\": { type: \"string\", format: \"date-time\" },\n\ttime: { type: \"string\", format: \"time\" },\n\tcurrency: { type: \"number\", format: \"currency\" },\n\tpercent: { type: \"number\", format: \"percent\" },\n\tjson: { type: \"object\" },\n\tint32: { type: \"integer\", format: \"int32\" },\n\tint64: { type: \"integer\", format: \"int64\" },\n\tfloat: { type: \"number\", format: \"float\" },\n\tdouble: { type: \"number\", format: \"double\" },\n\tenum: { type: \"string\", format: \"enum\" },\n};\n\nconst PRIMITIVE_TYPES = new Set<SchemaType>([\n\t\"string\",\n\t\"number\",\n\t\"integer\",\n\t\"boolean\",\n\t\"object\",\n\t\"array\",\n]);\n\nexport function parseHeaderValue(\n\tvalue: string | null | undefined,\n): HeaderSpec | null {\n\tif (!value) return null;\n\tconst trimmed = String(value).trim();\n\tif (!trimmed) return null;\n\n\tconst colonIndex = trimmed.indexOf(\":\");\n\tlet namePart =\n\t\tcolonIndex >= 0 ? trimmed.slice(0, colonIndex).trim() : trimmed;\n\tlet typePart = colonIndex >= 0 ? trimmed.slice(colonIndex + 1).trim() : \"\";\n\n\tlet required = false;\n\tif (namePart.endsWith(\"*\")) {\n\t\trequired = true;\n\t\tnamePart = namePart.slice(0, -1).trim();\n\t}\n\tif (typePart.endsWith(\"*\")) {\n\t\trequired = true;\n\t\ttypePart = typePart.slice(0, -1).trim();\n\t}\n\n\tif (!namePart) return null;\n\n\tconst typeHint = typePart ? parseHeaderType(typePart) : undefined;\n\n\treturn {\n\t\tname: namePart,\n\t\trequired,\n\t\traw: trimmed,\n\t\ttypeHint,\n\t};\n}\n\nexport function normalizeEnumValue(\n\tvalue: string | number | null | undefined,\n): string | null {\n\tif (value == null) return null;\n\tconst text = String(value).trim();\n\tif (!text) return null;\n\tif (\n\t\t(text.startsWith('\"') && text.endsWith('\"')) ||\n\t\t(text.startsWith(\"'\") && text.endsWith(\"'\"))\n\t) {\n\t\treturn text.slice(1, -1);\n\t}\n\treturn text;\n}\n\nexport function parseJsonMaybe(\n\tvalue: string | null | undefined,\n): unknown | null {\n\tif (typeof value !== \"string\") return null;\n\ttry {\n\t\treturn JSON.parse(value);\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction parseHeaderType(typeValue: string): HeaderTypeHint | undefined {\n\tconst trimmed = String(typeValue).trim();\n\tif (!trimmed) return undefined;\n\n\tconst isArray = trimmed.endsWith(\"[]\");\n\tconst rawSpec = isArray ? trimmed.slice(0, -2).trim() : trimmed;\n\n\tconst ref = parseRefSpec(rawSpec);\n\tif (ref) {\n\t\tconst itemHint: HeaderTypeHint = {\n\t\t\ttype: \"string\",\n\t\t\tformat: \"uuid\",\n\t\t\tref,\n\t\t};\n\t\tif (isArray) {\n\t\t\treturn {\n\t\t\t\ttype: \"array\",\n\t\t\t\titems: {\n\t\t\t\t\ttype: itemHint.type,\n\t\t\t\t\tformat: itemHint.format,\n\t\t\t\t\tref: itemHint.ref,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\treturn itemHint;\n\t}\n\n\tconst tokenHint = parseTypeToken(rawSpec);\n\tif (!tokenHint) return undefined;\n\n\tif (!isArray) return tokenHint;\n\n\tconst itemType = tokenHint.type ?? \"string\";\n\treturn {\n\t\ttype: \"array\",\n\t\titems: { type: itemType, format: tokenHint.format },\n\t};\n}\n\nfunction parseTypeToken(value: string): HeaderTypeHint | undefined {\n\tconst token = String(value).trim().toLowerCase();\n\tif (!token) return undefined;\n\tconst alias = TYPE_ALIASES[token];\n\tif (alias) return { ...alias };\n\tif (PRIMITIVE_TYPES.has(token as SchemaType)) {\n\t\treturn { type: token as SchemaType };\n\t}\n\treturn undefined;\n}\n\nconst ON_DELETE_ACTIONS = new Set<OnDeleteAction>([\n\t\"cascade\",\n\t\"restrict\",\n\t\"set_null\",\n]);\n\nfunction parseRefSpec(value: string): ColumnRef | null {\n\tconst match = value.match(/^(ref|fk)\\((.+)\\)$/i);\n\tif (!match) return null;\n\n\tconst body = match[2].trim();\n\tif (!body) return null;\n\n\tconst parts = body.split(\",\").map((p) => p.trim());\n\tconst target = parts[0];\n\tif (!target) return null;\n\n\tconst [tableRaw, fieldRaw] = target.split(\".\", 2);\n\tconst table = tableRaw?.trim();\n\tif (!table) return null;\n\n\tconst field = fieldRaw?.trim() || \"id\";\n\n\tlet expandKey: string | undefined;\n\tlet onDelete: OnDeleteAction | undefined;\n\n\tfor (let i = 1; i < parts.length; i++) {\n\t\tconst part = parts[i];\n\t\tif (!part) continue;\n\n\t\tif (ON_DELETE_ACTIONS.has(part as OnDeleteAction)) {\n\t\t\tonDelete = part as OnDeleteAction;\n\t\t} else {\n\t\t\tif (!expandKey) {\n\t\t\t\texpandKey = part;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { table, field, expandKey, onDelete };\n}\n","import type {\n\tColumnItems,\n\tColumnSchema,\n\tGenerateOasOptions,\n\tOpenApiDocument,\n\tSchemaDocument,\n\tTableSchema,\n} from \"./types.js\";\nimport { joinPath, toSchemaName } from \"./utils/index.js\";\n\nexport function buildOpenApi(\n\tschema: SchemaDocument,\n\toptions: GenerateOasOptions,\n): OpenApiDocument {\n\tconst components: Record<string, any> = {\n\t\tschemas: {\n\t\t\tErrorResponse: {\n\t\t\t\ttype: \"object\",\n\t\t\t\tproperties: {\n\t\t\t\t\tcode: { type: \"string\" },\n\t\t\t\t\tmessage: { type: \"string\" },\n\t\t\t\t\tdetails: { type: \"object\" },\n\t\t\t\t},\n\t\t\t\trequired: [\"code\", \"message\"],\n\t\t\t},\n\t\t},\n\t};\n\n\tconst paths: Record<string, unknown> = {};\n\tconst tableNameToSchema = new Map<string, string>();\n\n\tfor (const table of schema.tables) {\n\t\tconst schemaName = toSchemaName(table.name);\n\t\ttableNameToSchema.set(table.name, schemaName);\n\t}\n\n\tfor (const table of schema.tables) {\n\t\tconst schemaName =\n\t\t\ttableNameToSchema.get(table.name) ?? toSchemaName(table.name);\n\t\tconst listPath = joinPath(options.pathPrefix, table.name);\n\t\tconst itemPath = `${listPath}/{id}`;\n\n\t\tconst schemas = buildTableSchemas(table, schemaName, tableNameToSchema);\n\t\tObject.assign(components.schemas, schemas);\n\n\t\tpaths[listPath] = {\n\t\t\tget: {\n\t\t\t\toperationId: `${table.name}_list`,\n\t\t\t\tdescription:\n\t\t\t\t\t\"Filters: exact field=value, contains field__contains=value, range field__gte / field__lte.\",\n\t\t\t\tparameters: [\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"sort\",\n\t\t\t\t\t\tin: \"query\",\n\t\t\t\t\t\tschema: { type: \"string\" },\n\t\t\t\t\t\tdescription: \"Sort by field. Use -field for desc.\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"expand\",\n\t\t\t\t\t\tin: \"query\",\n\t\t\t\t\t\tschema: { type: \"string\" },\n\t\t\t\t\t\tdescription: \"Comma-separated relations to expand.\",\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tresponses: {\n\t\t\t\t\t200: {\n\t\t\t\t\t\tdescription: \"OK\",\n\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\ttype: \"array\",\n\t\t\t\t\t\t\t\t\titems: { $ref: `#/components/schemas/${schemaName}` },\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tpost: {\n\t\t\t\toperationId: `${table.name}_create`,\n\t\t\t\trequestBody: {\n\t\t\t\t\trequired: true,\n\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\tschema: { $ref: `#/components/schemas/${schemaName}Create` },\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tresponses: {\n\t\t\t\t\t201: {\n\t\t\t\t\t\tdescription: \"Created\",\n\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\tschema: { $ref: `#/components/schemas/${schemaName}` },\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t400: {\n\t\t\t\t\t\tdescription: \"Validation error\",\n\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\tschema: { $ref: \"#/components/schemas/ErrorResponse\" },\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\n\t\tpaths[itemPath] = {\n\t\t\tget: {\n\t\t\t\toperationId: `${table.name}_get`,\n\t\t\t\tparameters: [\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"id\",\n\t\t\t\t\t\tin: \"path\",\n\t\t\t\t\t\trequired: true,\n\t\t\t\t\t\tschema: { type: \"string\", format: \"uuid\" },\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"expand\",\n\t\t\t\t\t\tin: \"query\",\n\t\t\t\t\t\tschema: { type: \"string\" },\n\t\t\t\t\t\tdescription: \"Comma-separated relations to expand.\",\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tresponses: {\n\t\t\t\t\t200: {\n\t\t\t\t\t\tdescription: \"OK\",\n\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\tschema: { $ref: `#/components/schemas/${schemaName}` },\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t404: {\n\t\t\t\t\t\tdescription: \"Not found\",\n\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\tschema: { $ref: \"#/components/schemas/ErrorResponse\" },\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tput: {\n\t\t\t\toperationId: `${table.name}_update`,\n\t\t\t\tparameters: [\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"id\",\n\t\t\t\t\t\tin: \"path\",\n\t\t\t\t\t\trequired: true,\n\t\t\t\t\t\tschema: { type: \"string\", format: \"uuid\" },\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\trequestBody: {\n\t\t\t\t\trequired: true,\n\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\tschema: { $ref: `#/components/schemas/${schemaName}Create` },\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tresponses: {\n\t\t\t\t\t200: {\n\t\t\t\t\t\tdescription: \"OK\",\n\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\tschema: { $ref: `#/components/schemas/${schemaName}` },\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t400: {\n\t\t\t\t\t\tdescription: \"Validation error\",\n\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\tschema: { $ref: \"#/components/schemas/ErrorResponse\" },\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tpatch: {\n\t\t\t\toperationId: `${table.name}_patch`,\n\t\t\t\tparameters: [\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"id\",\n\t\t\t\t\t\tin: \"path\",\n\t\t\t\t\t\trequired: true,\n\t\t\t\t\t\tschema: { type: \"string\", format: \"uuid\" },\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\trequestBody: {\n\t\t\t\t\trequired: true,\n\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\tschema: { $ref: `#/components/schemas/${schemaName}Patch` },\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tresponses: {\n\t\t\t\t\t200: {\n\t\t\t\t\t\tdescription: \"OK\",\n\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\tschema: { $ref: `#/components/schemas/${schemaName}` },\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t400: {\n\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\"Validation error (e.g. NOT NULL violated after patch)\",\n\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\tschema: { $ref: \"#/components/schemas/ErrorResponse\" },\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tdelete: {\n\t\t\t\toperationId: `${table.name}_delete`,\n\t\t\t\tparameters: [\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"id\",\n\t\t\t\t\t\tin: \"path\",\n\t\t\t\t\t\trequired: true,\n\t\t\t\t\t\tschema: { type: \"string\", format: \"uuid\" },\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tresponses: {\n\t\t\t\t\t204: { description: \"No Content\" },\n\t\t\t\t\t404: {\n\t\t\t\t\t\tdescription: \"Not found\",\n\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\tschema: { $ref: \"#/components/schemas/ErrorResponse\" },\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\t}\n\n\treturn {\n\t\topenapi: \"3.1.0\",\n\t\tinfo: {\n\t\t\ttitle: options.title,\n\t\t\tversion: options.version,\n\t\t},\n\t\tpaths,\n\t\tcomponents,\n\t};\n}\n\nfunction buildTableSchemas(\n\ttable: TableSchema,\n\tschemaName: string,\n\ttableNameToSchema: Map<string, string>,\n) {\n\tconst properties: Record<string, unknown> = {};\n\tconst required: string[] = [];\n\n\tfor (const column of table.columns) {\n\t\tproperties[column.name] = columnToSchema(column);\n\t\tif (column.required) required.push(column.name);\n\t}\n\n\tfor (const column of table.columns) {\n\t\tif (!column.ref) continue;\n\t\tconst expandKey = column.ref.expandKey || column.ref.table;\n\t\tif (!expandKey) continue;\n\t\tconst refSchemaName = tableNameToSchema.get(column.ref.table);\n\t\tif (!refSchemaName) continue;\n\t\tproperties[expandKey] = {\n\t\t\t$ref: `#/components/schemas/${refSchemaName}`,\n\t\t\tdescription: `Included when expand=${expandKey}.`,\n\t\t};\n\t}\n\n\tconst createProperties = { ...properties };\n\tconst patchProperties = { ...properties };\n\n\tdelete patchProperties.id;\n\tfor (const column of table.columns) {\n\t\tif (column.ref) {\n\t\t\tconst expandKey = column.ref.expandKey || column.ref.table;\n\t\t\tif (expandKey) delete createProperties[expandKey];\n\t\t\tif (expandKey) delete patchProperties[expandKey];\n\t\t}\n\t}\n\n\tconst createRequired = required.filter((name) => name !== \"id\");\n\n\tconst schemas = {\n\t\t[schemaName]: {\n\t\t\ttype: \"object\",\n\t\t\tproperties,\n\t\t\trequired,\n\t\t},\n\t\t[`${schemaName}Create`]: {\n\t\t\ttype: \"object\",\n\t\t\tproperties: createProperties,\n\t\t\trequired: createRequired,\n\t\t},\n\t\t[`${schemaName}Patch`]: {\n\t\t\ttype: \"object\",\n\t\t\tproperties: patchProperties,\n\t\t\tadditionalProperties: false,\n\t\t},\n\t};\n\n\treturn schemas;\n}\n\nfunction columnToSchema(column: ColumnSchema): Record<string, unknown> {\n\tconst schema: Record<string, unknown> = {\n\t\ttype: column.type || \"string\",\n\t};\n\n\tif (column.type === \"array\") {\n\t\tschema.items = buildItemsSchema(column.items);\n\t}\n\n\tif (column.format) schema.format = column.format;\n\tif (Array.isArray(column.enum)) schema.enum = column.enum;\n\tif (column.description) schema.description = column.description;\n\tif (column.minimum != null) schema.minimum = column.minimum;\n\tif (column.maximum != null) schema.maximum = column.maximum;\n\tif (column.pattern) schema.pattern = column.pattern;\n\tif (column.multipleOf != null) schema.multipleOf = column.multipleOf;\n\tif (column.ref) {\n\t\tschema[\"x-sheet-ref\"] = {\n\t\t\ttable: column.ref.table,\n\t\t\tfield: column.ref.field,\n\t\t\texpandKey: column.ref.expandKey,\n\t\t};\n\t}\n\tif (column.extensions && typeof column.extensions === \"object\") {\n\t\tfor (const [key, value] of Object.entries(column.extensions)) {\n\t\t\tif (key.startsWith(\"x-\") && value != null) schema[key] = value;\n\t\t}\n\t}\n\n\treturn schema;\n}\n\nfunction buildItemsSchema(\n\titems: ColumnItems | undefined,\n): Record<string, unknown> {\n\tif (!items) return { type: \"string\" };\n\tconst schema: Record<string, unknown> = { type: items.type };\n\tif (items.format) schema.format = items.format;\n\tif (Array.isArray(items.enum)) schema.enum = items.enum;\n\tif (items.ref) {\n\t\tschema[\"x-sheet-ref\"] = {\n\t\t\ttable: items.ref.table,\n\t\t\tfield: items.ref.field,\n\t\t\texpandKey: items.ref.expandKey,\n\t\t};\n\t}\n\treturn schema;\n}\n","import { google } from \"googleapis\";\nimport type {\n\tColumnItems,\n\tColumnRef,\n\tColumnSchema,\n\tFetchSchemaOptions,\n\tHeaderSpec,\n\tSchemaDocument,\n\tSchemaType,\n\tTableSchema,\n} from \"./types.js\";\nimport {\n\tescapeSheetName,\n\tnormalizeEnumValue,\n\tparseHeaderValue,\n\tparseJsonMaybe,\n} from \"./utils/index.js\";\n\nconst READ_SCOPES = [\"https://www.googleapis.com/auth/spreadsheets.readonly\"];\nconst MAX_ENUM_VALUES = 200;\n\ntype InferenceContext = {\n\tsheets: ReturnType<typeof google.sheets>;\n\tspreadsheetId: string;\n\tsheetTitle: string;\n\tenumCache: Map<string, string[]>;\n\tmaxEnumValues: number;\n\tdebugInfo?: Record<string, unknown> | null;\n};\n\ntype InferredColumn = {\n\ttype: string;\n\tformat?: string;\n\tenum?: string[];\n\tminimum?: number;\n\tmaximum?: number;\n\tpattern?: string;\n\tmultipleOf?: number;\n\tvalidation?: unknown;\n\textensions?: Record<string, unknown>;\n\tsource?: string;\n};\n\ntype ValidationHints = {\n\ttype?: string;\n\tformat?: string;\n\tenum?: string[];\n\tminimum?: number;\n\tmaximum?: number;\n\tpattern?: string;\n\tmultipleOf?: number;\n\textensions?: Record<string, unknown>;\n};\n\nexport async function fetchSchema(\n\toptions: FetchSchemaOptions,\n): Promise<SchemaDocument> {\n\tconst auth = new google.auth.GoogleAuth({ scopes: READ_SCOPES });\n\tconst sheets = google.sheets({ version: \"v4\", auth });\n\tconst debugEnabled = Boolean(options.debug || process.env.SHEET_SCHEMA_DEBUG);\n\tconst enumCache = new Map<string, string[]>();\n\tconst maxEnumValues =\n\t\tNumber.isFinite(Number(options.enumLimit)) && Number(options.enumLimit) > 0\n\t\t\t? Number(options.enumLimit)\n\t\t\t: MAX_ENUM_VALUES;\n\n\tconst metadataResponse = await sheets.spreadsheets.get({\n\t\tspreadsheetId: options.spreadsheetId,\n\t\tincludeGridData: false,\n\t\tfields:\n\t\t\t\"sheets(properties(sheetId,title,hidden),developerMetadata),developerMetadata\",\n\t});\n\tconst spreadsheet = metadataResponse.data;\n\tconst spreadsheetMetadata = spreadsheet.developerMetadata || [];\n\tconst sheetEntries = spreadsheet.sheets || [];\n\n\tconst tables: TableSchema[] = [];\n\n\tfor (const sheet of sheetEntries) {\n\t\tconst properties = sheet.properties || {};\n\t\tconst title = properties.title;\n\t\tif (!title) continue;\n\n\t\tif (!options.includeHidden && properties.hidden) continue;\n\t\tif (\n\t\t\toptions.excludePrefixes?.some(\n\t\t\t\t(prefix) => prefix && title.startsWith(prefix),\n\t\t\t)\n\t\t) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst sheetId = properties.sheetId;\n\t\tconst developerMetadata = [\n\t\t\t...spreadsheetMetadata,\n\t\t\t...(sheet.developerMetadata || []),\n\t\t];\n\n\t\tconst metadataMap = buildColumnMetadataMap(\n\t\t\tdeveloperMetadata as Array<Record<string, any>>,\n\t\t\tsheetId,\n\t\t\toptions.metadataKey,\n\t\t);\n\n\t\tconst range = `${escapeSheetName(title)}!1:${options.scanRows}`;\n\t\tconst gridResponse = await sheets.spreadsheets.get({\n\t\t\tspreadsheetId: options.spreadsheetId,\n\t\t\tincludeGridData: true,\n\t\t\tranges: [range],\n\t\t\tfields:\n\t\t\t\t\"sheets(data(rowData(values(formattedValue,userEnteredValue,effectiveValue,userEnteredFormat,effectiveFormat,dataValidation))),properties(sheetId,title))\",\n\t\t});\n\n\t\tconst gridSheet = gridResponse.data.sheets?.[0];\n\t\tconst gridData = gridSheet?.data?.[0];\n\t\tconst rowData = gridData?.rowData || [];\n\n\t\tconst headerRowIndex = (options.headerRow ?? 2) - 1;\n\t\tconst dataStartRowIndex = headerRowIndex + 1;\n\n\t\tconst headerValues = rowData[headerRowIndex]?.values || [];\n\t\tconst columnCells = collectColumnCells(rowData, dataStartRowIndex);\n\n\t\tconst columns: ColumnSchema[] = [];\n\t\tconst seenNames = new Set<string>();\n\n\t\tfor (let index = 0; index < headerValues.length; index += 1) {\n\t\t\tconst cell = headerValues[index];\n\t\t\tconst headerText = getCellText(cell);\n\t\t\tconst header = parseHeaderValue(headerText);\n\t\t\tif (!header || !header.name) continue;\n\n\t\t\tif (seenNames.has(header.name)) {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`Duplicate column name skipped: ${header.name} (${title})`,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tseenNames.add(header.name);\n\n\t\t\tconst meta = metadataMap.get(index) || null;\n\t\t\tconst cells = columnCells.get(index) || [];\n\t\t\tconst inferenceDebug = debugEnabled ? {} : null;\n\t\t\tconst inferred = await inferColumnType(cells, {\n\t\t\t\tsheets,\n\t\t\t\tspreadsheetId: options.spreadsheetId,\n\t\t\t\tsheetTitle: title,\n\t\t\t\tenumCache,\n\t\t\t\tmaxEnumValues,\n\t\t\t\tdebugInfo: inferenceDebug,\n\t\t\t});\n\t\t\tconst baseColumn: ColumnSchema = {\n\t\t\t\tname: header.name,\n\t\t\t\trequired: header.required,\n\t\t\t\tindex,\n\t\t\t\ttype: inferred.type as SchemaType,\n\t\t\t\tformat: inferred.format,\n\t\t\t\tenum: inferred.enum,\n\t\t\t\tminimum: inferred.minimum,\n\t\t\t\tmaximum: inferred.maximum,\n\t\t\t\tpattern: inferred.pattern,\n\t\t\t\tmultipleOf: inferred.multipleOf,\n\t\t\t\tvalidation: inferred.validation,\n\t\t\t\textensions: inferred.extensions,\n\t\t\t\tsource: inferred.source,\n\t\t\t};\n\t\t\tconst withHeader = applyHeaderOverrides(baseColumn, header);\n\t\t\tconst column = applyMetadataOverrides(withHeader, meta);\n\n\t\t\tif (header.typeHint?.format === \"enum\") {\n\t\t\t\tconst enumValues = collectUniqueStringValues(cells, maxEnumValues);\n\t\t\t\tif (enumValues && enumValues.length > 0) {\n\t\t\t\t\tcolumn.enum = enumValues;\n\t\t\t\t\tcolumn.type = \"string\";\n\t\t\t\t\tcolumn.format = undefined;\n\t\t\t\t\tcolumn.source = \"header-enum\";\n\t\t\t\t\tif (debugEnabled) {\n\t\t\t\t\t\tconsole.log(` [enum] ${header.name}: ${enumValues.join(\", \")}`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst forcedId = column.name === \"id\";\n\t\t\tif (forcedId) {\n\t\t\t\tcolumn.type = \"string\";\n\t\t\t\tcolumn.format = \"uuid\";\n\t\t\t\tcolumn.source = \"fixed\";\n\t\t\t}\n\n\t\t\tif (debugEnabled) {\n\t\t\t\tlogColumnDebug({\n\t\t\t\t\tspreadsheetId: options.spreadsheetId,\n\t\t\t\t\tsheet: title,\n\t\t\t\t\tsheetId,\n\t\t\t\t\tcolumnIndex: index,\n\t\t\t\t\trawHeader: headerText,\n\t\t\t\t\theader: header.name,\n\t\t\t\t\trequired: header.required,\n\t\t\t\t\theaderType: header.typeHint,\n\t\t\t\t\tinferred,\n\t\t\t\t\tinference: inferenceDebug,\n\t\t\t\t\tmetadata: meta,\n\t\t\t\t\tfinal: column,\n\t\t\t\t\tforcedId,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tcolumns.push(column);\n\t\t}\n\n\t\tconst idColumn = columns.find((column) => column.name === \"id\");\n\t\tif (!idColumn || !idColumn.required) {\n\t\t\tif (options.debug) {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`[sheet-schema] Skipping sheet \"${title}\": missing required id* column`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\ttables.push({\n\t\t\tname: title,\n\t\t\tsheetId,\n\t\t\tcolumns,\n\t\t});\n\t}\n\n\tapplyAutoRefs(tables);\n\n\treturn {\n\t\tversion: 1,\n\t\tspreadsheetId: options.spreadsheetId,\n\t\tgeneratedAt: new Date().toISOString(),\n\t\ttables,\n\t};\n}\n\nfunction applyHeaderOverrides(\n\tcolumn: ColumnSchema,\n\theader: HeaderSpec | null,\n): ColumnSchema {\n\tif (!header?.typeHint) return column;\n\n\tconst hint = header.typeHint;\n\tconst merged: ColumnSchema = { ...column };\n\tlet changed = false;\n\n\tif (hint.type) {\n\t\tmerged.type = hint.type;\n\t\tchanged = true;\n\t}\n\tif (hint.format) {\n\t\tmerged.format = hint.format;\n\t\tchanged = true;\n\t}\n\tif (hint.items) {\n\t\tmerged.type = \"array\";\n\t\tmerged.items = hint.items;\n\t\tchanged = true;\n\t}\n\tif (hint.ref) {\n\t\tmerged.ref = { ...hint.ref, source: \"header\" };\n\t\tif (!merged.type || merged.type === \"string\") {\n\t\t\tmerged.type = \"string\";\n\t\t\tmerged.format = merged.format || \"uuid\";\n\t\t}\n\t\tchanged = true;\n\t}\n\n\tif (changed) merged.source = \"header\";\n\treturn merged;\n}\n\nfunction applyAutoRefs(tables: TableSchema[]) {\n\tconst nameMap = new Map<string, string>();\n\tfor (const table of tables) {\n\t\tconst normalized = normalizeKey(table.name);\n\t\tnameMap.set(normalized, table.name);\n\t\tif (normalized.endsWith(\"s\") && normalized.length > 1) {\n\t\t\tnameMap.set(normalized.slice(0, -1), table.name);\n\t\t}\n\t}\n\n\tfor (const table of tables) {\n\t\tfor (const column of table.columns) {\n\t\t\tif (column.name === \"id\") continue;\n\t\t\tif (column.ref) continue;\n\t\t\tif (column.source === \"header\" || column.source === \"metadata\") continue;\n\n\t\t\tconst base = extractRefBase(column.name);\n\t\t\tif (!base) continue;\n\t\t\tconst target = nameMap.get(normalizeKey(base));\n\t\t\tif (!target) continue;\n\n\t\t\tcolumn.ref = {\n\t\t\t\ttable: target,\n\t\t\t\tfield: \"id\",\n\t\t\t\texpandKey: base,\n\t\t\t\tsource: \"auto\",\n\t\t\t};\n\t\t\tif (!column.type || column.type === \"string\") {\n\t\t\t\tcolumn.type = \"string\";\n\t\t\t\tcolumn.format = column.format || \"uuid\";\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction extractRefBase(columnName: string): string | null {\n\tif (!columnName) return null;\n\tconst lower = columnName.toLowerCase();\n\tif (lower.endsWith(\"_id\")) {\n\t\treturn columnName.slice(0, -3);\n\t}\n\tif (columnName.endsWith(\"Id\") && columnName.length > 2) {\n\t\treturn columnName.slice(0, -2);\n\t}\n\treturn null;\n}\n\nfunction normalizeKey(value: string): string {\n\treturn String(value)\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9]+/g, \"\");\n}\n\nfunction collectColumnCells(rowData: any[], dataStartRowIndex = 1) {\n\tconst columnCells = new Map();\n\tfor (\n\t\tlet rowIndex = dataStartRowIndex;\n\t\trowIndex < rowData.length;\n\t\trowIndex += 1\n\t) {\n\t\tconst row = rowData[rowIndex];\n\t\tconst values = row?.values || [];\n\t\tfor (let colIndex = 0; colIndex < values.length; colIndex += 1) {\n\t\t\tif (!columnCells.has(colIndex)) columnCells.set(colIndex, []);\n\t\t\tcolumnCells.get(colIndex).push(values[colIndex]);\n\t\t}\n\t}\n\treturn columnCells;\n}\n\nfunction getCellText(cell: any) {\n\tif (!cell) return \"\";\n\tif (cell.formattedValue != null) return String(cell.formattedValue);\n\tconst entered = cell.userEnteredValue;\n\tif (entered?.stringValue != null) return String(entered.stringValue);\n\tif (entered?.numberValue != null) return String(entered.numberValue);\n\tif (entered?.boolValue != null) return String(entered.boolValue);\n\tconst effective = cell.effectiveValue;\n\tif (effective?.stringValue != null) return String(effective.stringValue);\n\tif (effective?.numberValue != null) return String(effective.numberValue);\n\tif (effective?.boolValue != null) return String(effective.boolValue);\n\treturn \"\";\n}\n\nfunction collectUniqueStringValues(\n\tcells: Array<Record<string, any>>,\n\tmaxEnumValues: number,\n): string[] | null {\n\tconst uniqueValues = new Set<string>();\n\n\tfor (const cell of cells) {\n\t\tconst text = getCellText(cell).trim();\n\t\tif (!text) continue;\n\n\t\tuniqueValues.add(text);\n\n\t\tif (uniqueValues.size > maxEnumValues) {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tif (uniqueValues.size < 1) {\n\t\treturn null;\n\t}\n\n\treturn Array.from(uniqueValues).sort();\n}\n\nasync function inferColumnType(\n\tcells: Array<Record<string, any>>,\n\tcontext: InferenceContext,\n): Promise<InferredColumn> {\n\tconst debugInfo = context?.debugInfo || null;\n\tconst validation = findFirstValidation(cells);\n\tconst validationType = validation?.condition?.type || null;\n\tif (debugInfo) {\n\t\tdebugInfo.validationType = validationType;\n\t}\n\n\tconst extensions: Record<string, unknown> = {};\n\tconst validationExtension = buildValidationExtension(validation, context);\n\tif (validationExtension)\n\t\textensions[\"x-sheet-validation\"] = validationExtension;\n\n\tconst enumValues = await resolveValidationEnum(validation, context);\n\tif (enumValues && enumValues.length > 0) {\n\t\tif (debugInfo) {\n\t\t\tdebugInfo.validationValuesCount = enumValues.length;\n\t\t\tdebugInfo.validationValuesSample = enumValues.slice(0, 5);\n\t\t}\n\t\treturn {\n\t\t\ttype: \"string\",\n\t\t\tenum: enumValues,\n\t\t\tsource: \"validation\",\n\t\t\textensions: finalizeExtensions(extensions),\n\t\t};\n\t}\n\n\tif (validationType && validationType.includes(\"BOOLEAN\")) {\n\t\treturn {\n\t\t\ttype: \"boolean\",\n\t\t\tsource: \"checkbox\",\n\t\t\textensions: finalizeExtensions(extensions),\n\t\t};\n\t}\n\n\tconst validationHints = inferValidationHints(validation);\n\tlet result: InferredColumn = {\n\t\ttype: validationHints?.type || \"string\",\n\t\tformat: validationHints?.format,\n\t\tenum: validationHints?.enum,\n\t\tminimum: validationHints?.minimum,\n\t\tmaximum: validationHints?.maximum,\n\t\tpattern: validationHints?.pattern,\n\t\tmultipleOf: validationHints?.multipleOf,\n\t\tsource: validationHints ? \"validation\" : \"fallback\",\n\t\textensions: { ...extensions, ...(validationHints?.extensions || {}) },\n\t};\n\n\tconst numberFormatType = findNumberFormatType(cells);\n\tif (debugInfo) {\n\t\tdebugInfo.numberFormatType = numberFormatType || null;\n\t}\n\tif (numberFormatType) {\n\t\tconst mapped = mapNumberFormat(numberFormatType);\n\t\tif (mapped && result.source === \"fallback\") {\n\t\t\tresult = { ...result, ...mapped, source: \"format\" };\n\t\t}\n\t\tresult.extensions = {\n\t\t\t...result.extensions,\n\t\t\t\"x-sheet-numberFormat\": numberFormatType,\n\t\t};\n\t}\n\n\tresult.extensions = finalizeExtensions(result.extensions);\n\treturn result;\n}\n\nfunction buildValidationExtension(\n\tvalidation: Record<string, any> | null,\n\tcontext: InferenceContext,\n): Record<string, unknown> | null {\n\tconst type = validation?.condition?.type;\n\tif (!type) return null;\n\tconst rawValues = extractConditionValues(validation.condition.values);\n\tconst extension: Record<string, unknown> = { type };\n\tif (validation?.strict != null) extension.strict = validation.strict;\n\tif (type === \"ONE_OF_LIST\") {\n\t\textension.valuesCount = rawValues.length;\n\t\tif (rawValues.length > 0) extension.valuesSample = rawValues.slice(0, 5);\n\t} else if (type === \"ONE_OF_RANGE\") {\n\t\tconst range = extractValidationRange(rawValues[0], context?.sheetTitle);\n\t\tif (range) extension.range = range;\n\t} else if (rawValues.length > 0) {\n\t\textension.values = rawValues.slice(0, 2);\n\t}\n\treturn extension;\n}\n\nfunction inferValidationHints(\n\tvalidation: Record<string, any> | null,\n): ValidationHints | null {\n\tconst type = validation?.condition?.type;\n\tif (!type) return null;\n\tconst values = extractConditionValues(validation.condition.values);\n\tconst hints: ValidationHints = {};\n\n\tswitch (type) {\n\t\tcase \"NUMBER_BETWEEN\": {\n\t\t\tconst min = parseNumberValue(values[0]);\n\t\t\tconst max = parseNumberValue(values[1]);\n\t\t\tif (min != null) hints.minimum = min;\n\t\t\tif (max != null) hints.maximum = max;\n\t\t\thints.type = \"number\";\n\t\t\tbreak;\n\t\t}\n\t\tcase \"NUMBER_GREATER_THAN_EQ\":\n\t\tcase \"NUMBER_GREATER\": {\n\t\t\tconst min = parseNumberValue(values[0]);\n\t\t\tif (min != null) hints.minimum = min;\n\t\t\thints.type = \"number\";\n\t\t\tbreak;\n\t\t}\n\t\tcase \"NUMBER_LESS_THAN_EQ\":\n\t\tcase \"NUMBER_LESS\": {\n\t\t\tconst max = parseNumberValue(values[0]);\n\t\t\tif (max != null) hints.maximum = max;\n\t\t\thints.type = \"number\";\n\t\t\tbreak;\n\t\t}\n\t\tcase \"NUMBER_EQ\": {\n\t\t\tconst exact = parseNumberValue(values[0]);\n\t\t\tif (exact != null) {\n\t\t\t\thints.minimum = exact;\n\t\t\t\thints.maximum = exact;\n\t\t\t}\n\t\t\thints.type = \"number\";\n\t\t\tbreak;\n\t\t}\n\t\tcase \"TEXT_IS_EMAIL\":\n\t\t\thints.type = \"string\";\n\t\t\thints.format = \"email\";\n\t\t\tbreak;\n\t\tcase \"TEXT_IS_URL\":\n\t\t\thints.type = \"string\";\n\t\t\thints.format = \"uri\";\n\t\t\tbreak;\n\t\tcase \"TEXT_STARTS_WITH\": {\n\t\t\tconst value = values[0];\n\t\t\tif (value) hints.pattern = `^${escapeRegex(value)}`;\n\t\t\thints.type = \"string\";\n\t\t\tbreak;\n\t\t}\n\t\tcase \"TEXT_ENDS_WITH\": {\n\t\t\tconst value = values[0];\n\t\t\tif (value) hints.pattern = `${escapeRegex(value)}$`;\n\t\t\thints.type = \"string\";\n\t\t\tbreak;\n\t\t}\n\t\tcase \"TEXT_EQ\": {\n\t\t\tconst value = values[0];\n\t\t\tif (value) hints.enum = [value];\n\t\t\thints.type = \"string\";\n\t\t\tbreak;\n\t\t}\n\t\tcase \"DATE_BETWEEN\":\n\t\tcase \"DATE_ON_OR_AFTER\":\n\t\tcase \"DATE_ON_OR_BEFORE\":\n\t\tcase \"DATE_AFTER\":\n\t\tcase \"DATE_BEFORE\":\n\t\tcase \"DATE_EQ\":\n\t\t\thints.type = \"string\";\n\t\t\thints.format = \"date\";\n\t\t\tbreak;\n\t\tdefault:\n\t\t\treturn null;\n\t}\n\n\tif (hints.enum) {\n\t\treturn {\n\t\t\t...hints,\n\t\t\tenum: hints.enum.map(normalizeEnumValue).filter(Boolean) as string[],\n\t\t};\n\t}\n\treturn hints;\n}\n\nasync function resolveValidationEnum(\n\tvalidation: Record<string, any> | null,\n\tcontext: InferenceContext,\n): Promise<string[] | null> {\n\tconst type = validation?.condition?.type;\n\tif (!type) return null;\n\tconst rawValues = extractConditionValues(validation.condition.values);\n\n\tif (type === \"ONE_OF_LIST\") {\n\t\tconst enumValues = rawValues\n\t\t\t.map((value) => normalizeEnumValue(value))\n\t\t\t.filter(Boolean) as string[];\n\t\treturn limitEnumValues(enumValues, context?.maxEnumValues);\n\t}\n\n\tif (type === \"ONE_OF_RANGE\") {\n\t\tconst range = extractValidationRange(rawValues[0], context?.sheetTitle);\n\t\tif (!range) return null;\n\t\tif (context?.debugInfo) context.debugInfo.validationRange = range;\n\t\ttry {\n\t\t\tconst values = await fetchEnumValuesFromRange({\n\t\t\t\tsheets: context?.sheets,\n\t\t\t\tspreadsheetId: context?.spreadsheetId,\n\t\t\t\trange,\n\t\t\t\tenumCache: context?.enumCache,\n\t\t\t\tmaxEnumValues: context?.maxEnumValues,\n\t\t\t});\n\t\t\treturn limitEnumValues(values, context?.maxEnumValues);\n\t\t} catch (error: any) {\n\t\t\tif (context?.debugInfo) {\n\t\t\t\tcontext.debugInfo.validationRangeError =\n\t\t\t\t\terror?.message || error?.stack || String(error);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null;\n}\n\nfunction extractValidationRange(value: any, sheetTitle: string | undefined) {\n\tif (!value) return null;\n\tlet range = String(value).trim();\n\tif (!range) return null;\n\tif (range.startsWith(\"=\")) range = range.slice(1);\n\tif (!range.includes(\"!\") && sheetTitle) {\n\t\trange = `${escapeSheetName(sheetTitle)}!${range}`;\n\t}\n\treturn range;\n}\n\nasync function fetchEnumValuesFromRange({\n\tsheets,\n\tspreadsheetId,\n\trange,\n\tenumCache,\n\tmaxEnumValues,\n}: {\n\tsheets: any;\n\tspreadsheetId: string;\n\trange: string;\n\tenumCache: Map<string, string[]>;\n\tmaxEnumValues: number;\n}) {\n\tif (!sheets || !spreadsheetId || !range) return [];\n\tconst cacheKey = `${spreadsheetId}:${range}`;\n\tif (enumCache?.has(cacheKey)) return enumCache.get(cacheKey)!;\n\tconst response = await sheets.spreadsheets.values.get({\n\t\tspreadsheetId,\n\t\trange,\n\t});\n\tconst values = response.data.values || [];\n\tconst flattened = values\n\t\t.flat()\n\t\t.map((value: any) => normalizeEnumValue(value))\n\t\t.filter(Boolean) as string[];\n\tconst limited = limitEnumValues(flattened, maxEnumValues);\n\tif (enumCache) enumCache.set(cacheKey, limited);\n\treturn limited;\n}\n\nfunction extractConditionValues(values: any[]) {\n\tif (!Array.isArray(values)) return [];\n\treturn values\n\t\t.map((value) => value?.userEnteredValue ?? value?.stringValue ?? value)\n\t\t.filter((value) => value != null && value !== \"\");\n}\n\nfunction parseNumberValue(value: any) {\n\tif (value == null) return null;\n\tconst parsed = Number(value);\n\treturn Number.isFinite(parsed) ? parsed : null;\n}\n\nfunction escapeRegex(value: string) {\n\treturn String(value).replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction limitEnumValues(values: string[], maxEnumValues: number) {\n\tif (!Array.isArray(values) || values.length === 0) return [];\n\tconst unique: string[] = [];\n\tconst limit = Number.isFinite(maxEnumValues)\n\t\t? maxEnumValues\n\t\t: MAX_ENUM_VALUES;\n\tfor (const value of values) {\n\t\tif (unique.includes(value)) continue;\n\t\tunique.push(value);\n\t\tif (unique.length >= limit) break;\n\t}\n\treturn unique;\n}\n\nfunction finalizeExtensions(extensions: Record<string, unknown> | undefined) {\n\tif (!extensions || typeof extensions !== \"object\") return undefined;\n\tconst entries = Object.entries(extensions).filter(\n\t\t([key, value]) => key.startsWith(\"x-\") && value != null,\n\t);\n\tif (entries.length === 0) return undefined;\n\treturn Object.fromEntries(entries);\n}\n\nfunction findFirstValidation(cells: any[]) {\n\tfor (const cell of cells) {\n\t\tif (cell?.dataValidation) return cell.dataValidation;\n\t}\n\treturn null;\n}\n\nfunction findNumberFormatType(cells: any[]) {\n\tfor (const cell of cells) {\n\t\tconst type =\n\t\t\tcell?.userEnteredFormat?.numberFormat?.type ||\n\t\t\tcell?.effectiveFormat?.numberFormat?.type;\n\t\tif (type) return type;\n\t}\n\treturn null;\n}\n\nfunction mapNumberFormat(type: string) {\n\tswitch (type) {\n\t\tcase \"NUMBER\":\n\t\tcase \"SCIENTIFIC\":\n\t\t\treturn { type: \"number\" };\n\t\tcase \"CURRENCY\":\n\t\t\treturn { type: \"number\", format: \"currency\" };\n\t\tcase \"PERCENT\":\n\t\t\treturn { type: \"number\", format: \"percent\" };\n\t\tcase \"DATE\":\n\t\t\treturn { type: \"string\", format: \"date\" };\n\t\tcase \"DATE_TIME\":\n\t\t\treturn { type: \"string\", format: \"date-time\" };\n\t\tcase \"TIME\":\n\t\t\treturn { type: \"string\", format: \"time\" };\n\t\tcase \"TEXT\":\n\t\t\treturn { type: \"string\" };\n\t\tdefault:\n\t\t\treturn null;\n\t}\n}\n\nfunction applyMetadataOverrides(\n\tcolumn: ColumnSchema,\n\tmetadata: Record<string, unknown> | null,\n): ColumnSchema {\n\tif (!metadata) return column;\n\n\tconst merged: ColumnSchema = {\n\t\t...column,\n\t\textensions: { ...(column.extensions || {}) },\n\t};\n\n\tif (typeof metadata.description === \"string\")\n\t\tmerged.description = metadata.description;\n\tif (typeof metadata.type === \"string\")\n\t\tmerged.type = metadata.type as SchemaType;\n\tif (typeof metadata.format === \"string\") merged.format = metadata.format;\n\tif (metadata.minimum != null) merged.minimum = Number(metadata.minimum);\n\tif (metadata.maximum != null) merged.maximum = Number(metadata.maximum);\n\tif (typeof metadata.pattern === \"string\") merged.pattern = metadata.pattern;\n\tif (metadata.multipleOf != null)\n\t\tmerged.multipleOf = Number(metadata.multipleOf);\n\tif (Array.isArray(metadata.enum)) {\n\t\tmerged.enum = metadata.enum.map((value) => String(value));\n\t\tif (!metadata.type) merged.type = \"string\";\n\t}\n\tif (metadata.ref && typeof metadata.ref === \"object\") {\n\t\tconst ref = metadata.ref as ColumnRef;\n\t\tmerged.ref = { ...ref, source: \"metadata\" };\n\t\tif (!merged.type || merged.type === \"string\") {\n\t\t\tmerged.type = \"string\";\n\t\t\tmerged.format = merged.format || \"uuid\";\n\t\t}\n\t}\n\tif (metadata.items && typeof metadata.items === \"object\") {\n\t\tconst items = metadata.items as ColumnItems;\n\t\tmerged.type = \"array\";\n\t\tmerged.items = items;\n\t}\n\tif (metadata.validation) {\n\t\tmerged.extensions![\"x-sheet-validation\"] = metadata.validation;\n\t}\n\tif (metadata.chip || metadata.sheetChip) {\n\t\tmerged.extensions![\"x-sheet-chip\"] = metadata.chip || metadata.sheetChip;\n\t}\n\tif (metadata.numberFormat) {\n\t\tmerged.extensions![\"x-sheet-numberFormat\"] = metadata.numberFormat;\n\t}\n\tif (metadata.extensions && typeof metadata.extensions === \"object\") {\n\t\tObject.assign(merged.extensions!, metadata.extensions);\n\t}\n\tfor (const [key, value] of Object.entries(metadata)) {\n\t\tif (key.startsWith(\"x-\")) merged.extensions![key] = value;\n\t}\n\tmerged.extensions = finalizeExtensions(merged.extensions);\n\tmerged.source = \"metadata\";\n\treturn merged;\n}\n\nfunction buildColumnMetadataMap(\n\tmetadataList: Array<Record<string, unknown>>,\n\tsheetId: number | undefined,\n\tmetadataKey: string | null | undefined,\n) {\n\tconst map = new Map<number, Record<string, unknown>>();\n\tfor (const entry of metadataList) {\n\t\tif (metadataKey && entry.metadataKey !== metadataKey) continue;\n\t\tconst location =\n\t\t\t(entry as Record<string, any>).location || entry.metadataLocation;\n\t\tif (!location) continue;\n\t\tif (\n\t\t\tlocation.sheetId != null &&\n\t\t\tsheetId != null &&\n\t\t\tlocation.sheetId !== sheetId\n\t\t) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst columnIndices = extractColumnIndices(location as Record<string, any>);\n\t\tif (!columnIndices || columnIndices.length === 0) continue;\n\n\t\tconst rawValue =\n\t\t\ttypeof entry.metadataValue === \"string\" ? entry.metadataValue : null;\n\t\tconst parsed = parseJsonMaybe(rawValue);\n\t\tif (!parsed || typeof parsed !== \"object\") continue;\n\t\tconst parsedRecord = parsed as Record<string, unknown>;\n\n\t\tfor (const index of columnIndices) {\n\t\t\tif (!map.has(index)) map.set(index, {});\n\t\t\tmap.set(index, { ...map.get(index), ...parsedRecord });\n\t\t}\n\t}\n\treturn map;\n}\n\nfunction extractColumnIndices(location: Record<string, any>) {\n\tif (typeof location.columnIndex === \"number\") {\n\t\treturn [location.columnIndex];\n\t}\n\n\tconst range = location.dimensionRange;\n\tif (range && range.dimension === \"COLUMNS\") {\n\t\tconst start = range.startIndex;\n\t\tconst end = range.endIndex;\n\t\tif (Number.isFinite(start) && Number.isFinite(end)) {\n\t\t\tconst indices: number[] = [];\n\t\t\tfor (let idx = start; idx < end; idx += 1) indices.push(idx);\n\t\t\treturn indices;\n\t\t}\n\t}\n\n\treturn null;\n}\n\nfunction logColumnDebug(payload: any) {\n\tconsole.log(`[sheet-schema] column ${JSON.stringify(payload)}`);\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@schema2sheet/core",
3
+ "version": "1.0.0",
4
+ "description": "Core types and utilities for schema2sheet",
5
+ "type": "module",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "keywords": [
11
+ "schema",
12
+ "types",
13
+ "google-sheets"
14
+ ],
15
+ "license": "MIT",
16
+ "dependencies": {
17
+ "googleapis": "^140.0.0",
18
+ "dotenv": "^16.4.5"
19
+ },
20
+ "devDependencies": {
21
+ "@types/node": "^22.10.2",
22
+ "typescript": "^5.7.2"
23
+ },
24
+ "exports": {
25
+ ".": "./dist/index.mjs",
26
+ "./package.json": "./package.json"
27
+ },
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/froggy1014/schema2sheet.git"
31
+ },
32
+ "homepage": "https://github.com/froggy1014/schema2sheet#readme",
33
+ "bugs": {
34
+ "url": "https://github.com/froggy1014/schema2sheet/issues"
35
+ },
36
+ "author": "froggy1014",
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "scripts": {
41
+ "build": "tsdown",
42
+ "dev": "tsdown --watch",
43
+ "lint": "biome check --write .",
44
+ "clean": "rm -rf dist .turbo node_modules",
45
+ "typecheck": "tsc --noEmit"
46
+ }
47
+ }