drizzle-multitenant 1.2.0 → 1.3.1

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,475 @@
1
+ /**
2
+ * Schema Linting Module Types
3
+ *
4
+ * Provides type definitions for schema validation and linting.
5
+ */
6
+ /**
7
+ * Severity level for lint rules
8
+ */
9
+ type LintSeverity = 'off' | 'warn' | 'error';
10
+ /**
11
+ * Naming style options
12
+ */
13
+ type NamingStyle = 'snake_case' | 'camelCase' | 'PascalCase' | 'kebab-case';
14
+ /**
15
+ * Rule configuration - can be severity only or [severity, options]
16
+ */
17
+ type RuleConfig<TOptions = Record<string, unknown>> = LintSeverity | [LintSeverity, TOptions];
18
+ /**
19
+ * Naming rule options
20
+ */
21
+ interface NamingRuleOptions {
22
+ style: NamingStyle;
23
+ /** Allow exceptions matching these patterns */
24
+ exceptions?: string[];
25
+ }
26
+ /**
27
+ * Lint configuration for defineConfig
28
+ */
29
+ interface LintConfig {
30
+ rules: LintRules;
31
+ /** Glob patterns to include */
32
+ include?: string[];
33
+ /** Glob patterns to exclude */
34
+ exclude?: string[];
35
+ /** Custom rules directory */
36
+ customRulesDir?: string;
37
+ }
38
+ /**
39
+ * All available lint rules
40
+ */
41
+ interface LintRules {
42
+ 'table-naming'?: RuleConfig<NamingRuleOptions>;
43
+ 'column-naming'?: RuleConfig<NamingRuleOptions>;
44
+ 'require-primary-key'?: RuleConfig;
45
+ 'prefer-uuid-pk'?: RuleConfig;
46
+ 'require-timestamps'?: RuleConfig<{
47
+ columns?: string[];
48
+ }>;
49
+ 'index-foreign-keys'?: RuleConfig;
50
+ 'no-cascade-delete'?: RuleConfig;
51
+ 'require-soft-delete'?: RuleConfig<{
52
+ column?: string;
53
+ }>;
54
+ }
55
+ /**
56
+ * Default lint rules configuration
57
+ */
58
+ declare const DEFAULT_LINT_RULES: LintRules;
59
+ /**
60
+ * Represents a column in a schema
61
+ */
62
+ interface SchemaColumn {
63
+ name: string;
64
+ dataType: string;
65
+ isPrimaryKey: boolean;
66
+ isNullable: boolean;
67
+ hasDefault: boolean;
68
+ defaultValue?: string | null | undefined;
69
+ references?: {
70
+ table: string;
71
+ column: string;
72
+ onDelete?: string | undefined;
73
+ onUpdate?: string | undefined;
74
+ } | undefined;
75
+ }
76
+ /**
77
+ * Represents an index in a schema
78
+ */
79
+ interface SchemaIndex {
80
+ name: string;
81
+ columns: string[];
82
+ isUnique: boolean;
83
+ }
84
+ /**
85
+ * Represents a table in a schema
86
+ */
87
+ interface SchemaTable {
88
+ name: string;
89
+ schemaType: 'tenant' | 'shared';
90
+ columns: SchemaColumn[];
91
+ indexes: SchemaIndex[];
92
+ filePath: string;
93
+ }
94
+ /**
95
+ * Parsed schema info for linting
96
+ */
97
+ interface ParsedSchema {
98
+ tables: SchemaTable[];
99
+ filePath: string;
100
+ }
101
+ /**
102
+ * A single lint issue
103
+ */
104
+ interface LintIssue {
105
+ /** Rule that triggered this issue */
106
+ rule: string;
107
+ /** Severity level */
108
+ severity: 'warn' | 'error';
109
+ /** Human-readable message */
110
+ message: string;
111
+ /** File path where issue was found */
112
+ filePath: string;
113
+ /** Table name (if applicable) */
114
+ table?: string;
115
+ /** Column name (if applicable) */
116
+ column?: string;
117
+ /** Line number (if available) */
118
+ line?: number;
119
+ /** Suggestion to fix the issue */
120
+ suggestion?: string;
121
+ }
122
+ /**
123
+ * Result of linting a single schema file
124
+ */
125
+ interface LintFileResult {
126
+ filePath: string;
127
+ issues: LintIssue[];
128
+ tables: number;
129
+ columns: number;
130
+ }
131
+ /**
132
+ * Aggregate result of linting all schemas
133
+ */
134
+ interface LintResult {
135
+ /** All file results */
136
+ files: LintFileResult[];
137
+ /** Total issues by severity */
138
+ summary: {
139
+ totalFiles: number;
140
+ totalTables: number;
141
+ totalColumns: number;
142
+ errors: number;
143
+ warnings: number;
144
+ };
145
+ /** Duration in milliseconds */
146
+ durationMs: number;
147
+ /** Whether linting passed (no errors) */
148
+ passed: boolean;
149
+ }
150
+ /**
151
+ * Output format for reporters
152
+ */
153
+ type ReporterFormat = 'console' | 'json' | 'github';
154
+ /**
155
+ * Reporter options
156
+ */
157
+ interface ReporterOptions {
158
+ format: ReporterFormat;
159
+ /** Show only issues (no summary) */
160
+ quiet?: boolean;
161
+ /** Show detailed output */
162
+ verbose?: boolean;
163
+ /** Use colors in output */
164
+ colors?: boolean;
165
+ }
166
+ /**
167
+ * Rule context passed to rule implementations
168
+ */
169
+ interface RuleContext {
170
+ /** Report an issue */
171
+ report: (issue: Omit<LintIssue, 'rule' | 'severity'>) => void;
172
+ /** Current severity for this rule */
173
+ severity: 'warn' | 'error';
174
+ /** Rule options (if any) */
175
+ options: Record<string, unknown>;
176
+ }
177
+ /**
178
+ * Rule implementation interface
179
+ */
180
+ interface LintRule {
181
+ /** Rule name */
182
+ name: string;
183
+ /** Rule description */
184
+ description: string;
185
+ /** Default severity */
186
+ defaultSeverity: LintSeverity;
187
+ /** Validate a table */
188
+ validateTable?: (table: SchemaTable, context: RuleContext) => void;
189
+ /** Validate a column */
190
+ validateColumn?: (column: SchemaColumn, table: SchemaTable, context: RuleContext) => void;
191
+ /** Validate the entire schema */
192
+ validateSchema?: (tables: SchemaTable[], context: RuleContext) => void;
193
+ }
194
+ /**
195
+ * Schema file detection result
196
+ */
197
+ interface SchemaFileInfo {
198
+ filePath: string;
199
+ type: 'tenant' | 'shared';
200
+ }
201
+ /**
202
+ * Linter options
203
+ */
204
+ interface LinterOptions {
205
+ /** Schema directories to lint */
206
+ schemaDirs?: {
207
+ tenant?: string;
208
+ shared?: string;
209
+ };
210
+ /** Glob patterns for schema files */
211
+ include?: string[];
212
+ /** Glob patterns to exclude */
213
+ exclude?: string[];
214
+ /** Rules configuration */
215
+ rules?: LintRules;
216
+ }
217
+
218
+ /**
219
+ * Schema Parser
220
+ *
221
+ * Parses Drizzle ORM schema files to extract table, column, and index information
222
+ * for linting purposes.
223
+ */
224
+
225
+ /**
226
+ * Parse a Drizzle schema module to extract table information
227
+ */
228
+ declare function parseSchemaModule(module: Record<string, unknown>, filePath: string, schemaType: 'tenant' | 'shared'): SchemaTable[];
229
+ /**
230
+ * Parse a raw table definition (for testing or direct input)
231
+ */
232
+ declare function parseRawTable(name: string, columns: Array<{
233
+ name: string;
234
+ dataType: string;
235
+ isPrimaryKey?: boolean;
236
+ isNullable?: boolean;
237
+ hasDefault?: boolean;
238
+ defaultValue?: string | null;
239
+ references?: SchemaColumn['references'];
240
+ }>, options?: {
241
+ schemaType?: 'tenant' | 'shared';
242
+ filePath?: string;
243
+ indexes?: Array<{
244
+ name: string;
245
+ columns: string[];
246
+ isUnique?: boolean;
247
+ }>;
248
+ }): SchemaTable;
249
+ /**
250
+ * Find schema files in a directory
251
+ */
252
+ declare function findSchemaFiles(dir: string, type: 'tenant' | 'shared'): Promise<SchemaFileInfo[]>;
253
+ /**
254
+ * Load and parse a schema file
255
+ */
256
+ declare function loadSchemaFile(filePath: string, type: 'tenant' | 'shared'): Promise<SchemaTable[]>;
257
+
258
+ /**
259
+ * Schema Linter
260
+ *
261
+ * Main linter class that orchestrates schema validation using configured rules.
262
+ */
263
+
264
+ /**
265
+ * Schema Linter
266
+ *
267
+ * Validates Drizzle ORM schemas against configurable rules.
268
+ *
269
+ * @example
270
+ * ```typescript
271
+ * const linter = new SchemaLinter({
272
+ * rules: {
273
+ * 'table-naming': ['error', { style: 'snake_case' }],
274
+ * 'require-primary-key': 'error',
275
+ * 'prefer-uuid-pk': 'warn',
276
+ * },
277
+ * });
278
+ *
279
+ * const result = await linter.lintDirectory('./src/db/schema');
280
+ * console.log(result.passed ? 'All good!' : 'Issues found');
281
+ * ```
282
+ */
283
+ declare class SchemaLinter {
284
+ private rules;
285
+ private config;
286
+ constructor(options?: Partial<LinterOptions>);
287
+ /**
288
+ * Get the effective severity for a rule
289
+ */
290
+ getEffectiveSeverity(ruleName: string): LintSeverity;
291
+ /**
292
+ * Get the options for a rule
293
+ */
294
+ getRuleOptions(ruleName: string): Record<string, unknown>;
295
+ /**
296
+ * Check if a rule is enabled
297
+ */
298
+ isRuleEnabled(ruleName: string): boolean;
299
+ /**
300
+ * Lint a single table
301
+ */
302
+ lintTable(table: SchemaTable): LintIssue[];
303
+ /**
304
+ * Lint multiple tables
305
+ */
306
+ lintTables(tables: SchemaTable[]): LintResult;
307
+ /**
308
+ * Lint a schema module (object with table exports)
309
+ */
310
+ lintModule(module: Record<string, unknown>, filePath: string, schemaType?: 'tenant' | 'shared'): LintResult;
311
+ /**
312
+ * Lint schema files in a directory
313
+ */
314
+ lintDirectory(dir: string, type?: 'tenant' | 'shared'): Promise<LintResult>;
315
+ /**
316
+ * Lint multiple directories (tenant and shared)
317
+ */
318
+ lintDirectories(options: {
319
+ tenant?: string;
320
+ shared?: string;
321
+ }): Promise<LintResult>;
322
+ /**
323
+ * Get all registered rules
324
+ */
325
+ getRules(): LintRule[];
326
+ /**
327
+ * Get a specific rule by name
328
+ */
329
+ getRule(name: string): LintRule | undefined;
330
+ /**
331
+ * Register a custom rule
332
+ */
333
+ registerRule(rule: LintRule): void;
334
+ /**
335
+ * Update rule configuration
336
+ */
337
+ setRuleConfig(ruleName: string, config: RuleConfig): void;
338
+ /**
339
+ * Get current configuration
340
+ */
341
+ getConfig(): LintRules;
342
+ }
343
+ /**
344
+ * Create a linter instance with configuration
345
+ */
346
+ declare function createLinter(options?: Partial<LinterOptions>): SchemaLinter;
347
+ /**
348
+ * Quick lint function for simple use cases
349
+ */
350
+ declare function lintSchemas(options: {
351
+ tenant?: string;
352
+ shared?: string;
353
+ rules?: LintRules;
354
+ }): Promise<LintResult>;
355
+
356
+ /**
357
+ * Lint Reporter
358
+ *
359
+ * Formats and outputs lint results in various formats.
360
+ */
361
+
362
+ /**
363
+ * Format lint results according to the specified format
364
+ */
365
+ declare function formatLintResult(result: LintResult, options: ReporterOptions): string;
366
+ /**
367
+ * Create a reporter function for the specified format
368
+ */
369
+ declare function createReporter(format: ReporterFormat, options?: Partial<ReporterOptions>): (result: LintResult) => string;
370
+ /**
371
+ * Print lint result to stdout
372
+ */
373
+ declare function printLintResult(result: LintResult, options: ReporterOptions): void;
374
+
375
+ /**
376
+ * Naming Convention Rules
377
+ *
378
+ * Rules for validating table and column naming conventions.
379
+ */
380
+
381
+ /**
382
+ * Table naming rule
383
+ *
384
+ * Validates that table names follow the configured naming convention.
385
+ */
386
+ declare const tableNamingRule: LintRule;
387
+ /**
388
+ * Column naming rule
389
+ *
390
+ * Validates that column names follow the configured naming convention.
391
+ */
392
+ declare const columnNamingRule: LintRule;
393
+ /**
394
+ * All naming rules
395
+ */
396
+ declare const namingRules: LintRule[];
397
+
398
+ /**
399
+ * Convention Rules
400
+ *
401
+ * Rules for validating schema best practices and conventions.
402
+ */
403
+
404
+ /**
405
+ * Require primary key rule
406
+ *
407
+ * Every table should have a primary key defined.
408
+ */
409
+ declare const requirePrimaryKeyRule: LintRule;
410
+ /**
411
+ * Prefer UUID primary key rule
412
+ *
413
+ * Recommends using UUID over serial/integer for primary keys.
414
+ */
415
+ declare const preferUuidPkRule: LintRule;
416
+ /**
417
+ * Require timestamps rule
418
+ *
419
+ * Tables should have created_at and updated_at columns.
420
+ */
421
+ declare const requireTimestampsRule: LintRule;
422
+ /**
423
+ * Index foreign keys rule
424
+ *
425
+ * Foreign key columns should have indexes for query performance.
426
+ */
427
+ declare const indexForeignKeysRule: LintRule;
428
+ /**
429
+ * All convention rules
430
+ */
431
+ declare const conventionRules: LintRule[];
432
+
433
+ /**
434
+ * Security Rules
435
+ *
436
+ * Rules for validating schema security best practices.
437
+ */
438
+
439
+ /**
440
+ * No cascade delete rule
441
+ *
442
+ * Warns about CASCADE DELETE which can accidentally remove data.
443
+ */
444
+ declare const noCascadeDeleteRule: LintRule;
445
+ /**
446
+ * Require soft delete rule
447
+ *
448
+ * Tables should have a soft delete column instead of hard deletes.
449
+ */
450
+ declare const requireSoftDeleteRule: LintRule;
451
+ /**
452
+ * All security rules
453
+ */
454
+ declare const securityRules: LintRule[];
455
+
456
+ /**
457
+ * Lint Rules Export
458
+ *
459
+ * Aggregates all available lint rules.
460
+ */
461
+
462
+ /**
463
+ * All built-in rules
464
+ */
465
+ declare const allRules: LintRule[];
466
+ /**
467
+ * Get rule by name
468
+ */
469
+ declare function getRuleByName(name: string): LintRule | undefined;
470
+ /**
471
+ * Get all rule names
472
+ */
473
+ declare function getAllRuleNames(): string[];
474
+
475
+ export { DEFAULT_LINT_RULES, type LintConfig, type LintFileResult, type LintIssue, type LintResult, type LintRule, type LintRules, type LintSeverity, type LinterOptions, type NamingRuleOptions, type NamingStyle, type ParsedSchema, type ReporterFormat, type ReporterOptions, type RuleConfig, type RuleContext, type SchemaColumn, type SchemaFileInfo, type SchemaIndex, SchemaLinter, type SchemaTable, allRules, columnNamingRule, conventionRules, createLinter, createReporter, findSchemaFiles, formatLintResult, getAllRuleNames, getRuleByName, indexForeignKeysRule, lintSchemas, loadSchemaFile, namingRules, noCascadeDeleteRule, parseRawTable, parseSchemaModule, preferUuidPkRule, printLintResult, requirePrimaryKeyRule, requireSoftDeleteRule, requireTimestampsRule, securityRules, tableNamingRule };
@@ -0,0 +1,5 @@
1
+ var L={"table-naming":["warn",{style:"snake_case"}],"column-naming":["warn",{style:"snake_case"}],"require-primary-key":"error","prefer-uuid-pk":"warn","require-timestamps":"off","index-foreign-keys":"warn","no-cascade-delete":"off","require-soft-delete":"off"};function A(e){return /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(e)}function j(e){return /^[a-z][a-zA-Z0-9]*$/.test(e)&&!e.includes("_")}function z(e){return /^[A-Z][a-zA-Z0-9]*$/.test(e)&&!e.includes("_")}function K(e){return /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(e)}function I(e,t){switch(t){case "snake_case":return A(e);case "camelCase":return j(e);case "PascalCase":return z(e);case "kebab-case":return K(e);default:return true}}function O(e,t){let n=e.replace(/([a-z])([A-Z])/g,"$1_$2").replace(/-/g,"_").toLowerCase().split("_").filter(Boolean);switch(t){case "snake_case":return n.join("_");case "camelCase":return n.map((s,i)=>i===0?s:s.charAt(0).toUpperCase()+s.slice(1)).join("");case "PascalCase":return n.map(s=>s.charAt(0).toUpperCase()+s.slice(1)).join("");case "kebab-case":return n.join("-");default:return e}}function q(e,t){return t?.length?t.some(n=>new RegExp("^"+n.replace(/\*/g,".*")+"$").test(e)):false}var w={name:"table-naming",description:"Enforce consistent table naming convention",defaultSeverity:"warn",validateTable(e,t){let n=t.options,s=n.style??"snake_case",i=n.exceptions??[];if(!q(e.name,i)&&!I(e.name,s)){let r=O(e.name,s);t.report({message:`Table "${e.name}" does not follow ${s} convention`,filePath:e.filePath,table:e.name,suggestion:`Consider renaming to "${r}"`});}}},v={name:"column-naming",description:"Enforce consistent column naming convention",defaultSeverity:"warn",validateColumn(e,t,n){let s=n.options,i=s.style??"snake_case",r=s.exceptions??[];if(!q(e.name,r)&&!I(e.name,i)){let a=O(e.name,i);n.report({message:`Column "${e.name}" in table "${t.name}" does not follow ${i} convention`,filePath:t.filePath,table:t.name,column:e.name,suggestion:`Consider renaming to "${a}"`});}}},R=[w,v];var T={name:"require-primary-key",description:"Require every table to have a primary key",defaultSeverity:"error",validateTable(e,t){e.columns.some(s=>s.isPrimaryKey)||t.report({message:`Table "${e.name}" does not have a primary key`,filePath:e.filePath,table:e.name,suggestion:"Add a primary key column (e.g., id: uuid().primaryKey())"});}},P={name:"prefer-uuid-pk",description:"Prefer UUID over serial/integer for primary keys",defaultSeverity:"warn",validateColumn(e,t,n){if(!e.isPrimaryKey)return;let s=["serial","bigserial","smallserial","integer","bigint","smallint"],i=e.dataType.toLowerCase();s.some(r=>i.includes(r))&&n.report({message:`Table "${t.name}" uses ${e.dataType} for primary key instead of UUID`,filePath:t.filePath,table:t.name,column:e.name,suggestion:"Consider using uuid() for better distribution and security"});}},k={name:"require-timestamps",description:"Require timestamp columns (created_at, updated_at) on tables",defaultSeverity:"warn",validateTable(e,t){let s=t.options.columns??["created_at","updated_at"],i=e.columns.map(r=>r.name.toLowerCase());for(let r of s){let a=r.toLowerCase(),o=r.replace(/_([a-z])/g,(l,u)=>u.toUpperCase()).toLowerCase();!i.includes(a)&&!i.includes(o)&&t.report({message:`Table "${e.name}" is missing "${r}" column`,filePath:e.filePath,table:e.name,suggestion:`Add ${a}: timestamp('${a}').defaultNow().notNull()`});}}},$={name:"index-foreign-keys",description:"Require indexes on foreign key columns",defaultSeverity:"warn",validateTable(e,t){let n=e.columns.filter(r=>r.references),s=new Set;for(let r of e.indexes){let a=r.columns[0];a&&s.add(a.toLowerCase());}let i=e.columns.filter(r=>r.isPrimaryKey);for(let r of i)s.add(r.name.toLowerCase());for(let r of n)s.has(r.name.toLowerCase())||t.report({message:`Foreign key column "${r.name}" in table "${e.name}" is not indexed`,filePath:e.filePath,table:e.name,column:r.name,suggestion:`Add index: index('${e.name}_${r.name}_idx').on(${e.name}.${r.name})`});}},b=[T,P,k,$];var N={name:"no-cascade-delete",description:"Warn about CASCADE DELETE on foreign keys",defaultSeverity:"warn",validateColumn(e,t,n){if(!e.references)return;e.references.onDelete?.toLowerCase()==="cascade"&&n.report({message:`Foreign key "${e.name}" in table "${t.name}" uses CASCADE DELETE`,filePath:t.filePath,table:t.name,column:e.name,suggestion:"Consider using SET NULL, RESTRICT, or implementing soft delete to prevent accidental data loss"});}},D={name:"require-soft-delete",description:"Require soft delete column on tables",defaultSeverity:"off",validateTable(e,t){let s=t.options.column??"deleted_at",i=s.toLowerCase(),r=s.replace(/_([a-z])/g,(o,l)=>l.toUpperCase()).toLowerCase(),a=e.columns.map(o=>o.name.toLowerCase());!a.includes(i)&&!a.includes(r)&&t.report({message:`Table "${e.name}" does not have a soft delete column ("${s}")`,filePath:e.filePath,table:e.name,suggestion:`Add ${i}: timestamp('${i}') for soft delete support`});}},x=[N,D];var g=[...R,...b,...x];function M(e){return g.find(t=>t.name===e)}function Z(){return g.map(e=>e.name)}var V=Symbol.for("drizzle:Table"),B=Symbol.for("drizzle:Column");function W(e){if(!e||typeof e!="object")return false;let t=e;if(t._&&typeof t._=="object"){let n=t._;return typeof n.name=="string"&&(n.schema===void 0||typeof n.schema=="string")}return !!Object.getOwnPropertySymbols(t).some(n=>n===V)}function J(e){if(!e||typeof e!="object")return false;let t=e;return typeof t.name=="string"||typeof t.columnType=="string"||typeof t.dataType=="string"||Object.getOwnPropertySymbols(t).some(n=>n===B)}function Q(e,t){let n=t.name??e,s="unknown";if(typeof t.dataType=="string")s=t.dataType;else if(typeof t.columnType=="string")s=t.columnType;else if(typeof t.getSQLType=="function")try{s=t.getSQLType();}catch{}let i=t.primary===true||t.isPrimaryKey===true||typeof t.primaryKey=="function"&&t._isPrimaryKey===true,r=t.notNull!==true&&t.isNotNull!==true,a=t.hasDefault===true||t.default!==void 0,o=t.default!==void 0?String(t.default):null,l;if(t.references&&typeof t.references=="object"){let u=t.references;l={table:u.table??"unknown",column:u.column??"unknown",onDelete:u.onDelete,onUpdate:u.onUpdate};}return {name:n,dataType:s,isPrimaryKey:i,isNullable:r,hasDefault:a,defaultValue:o,references:l}}function Y(e){let t=[],n=e._indexes??e.indexes;if(n&&typeof n=="object")for(let[s,i]of Object.entries(n)){if(!i||typeof i!="object")continue;let r=i,a=[];if(Array.isArray(r.columns))for(let o of r.columns)typeof o=="string"?a.push(o):o&&typeof o=="object"&&"name"in o&&a.push(String(o.name));t.push({name:s,columns:a,isUnique:r.isUnique===true||r.unique===true});}return t}function S(e,t,n){let s=[];for(let[i,r]of Object.entries(e)){if(!W(r))continue;let a=r,l=a._?.name??i,u=[];for(let[m,d]of Object.entries(a)){if(m==="_"||m.startsWith("_")||!J(d))continue;let f=Q(m,d);u.push(f);}let p=Y(a);s.push({name:l,schemaType:n,columns:u,indexes:p,filePath:t});}return s}function F(e,t,n){return {name:e,schemaType:n?.schemaType??"tenant",filePath:n?.filePath??"unknown",columns:t.map(s=>({name:s.name,dataType:s.dataType,isPrimaryKey:s.isPrimaryKey??false,isNullable:s.isNullable??true,hasDefault:s.hasDefault??false,defaultValue:s.defaultValue??null,references:s.references})),indexes:n?.indexes?.map(s=>({name:s.name,columns:s.columns,isUnique:s.isUnique??false}))??[]}}async function y(e,t){let{glob:n}=await import('glob'),{resolve:s}=await import('path'),i=s(e,"**/*.ts");return (await n(i,{ignore:["**/*.test.ts","**/*.spec.ts","**/node_modules/**"]})).map(a=>({filePath:a,type:t}))}async function h(e,t){try{let n=await import(e);return S(n,e,t)}catch(n){return console.warn(`Warning: Could not parse schema file ${e}:`,n.message),[]}}function G(e){return e?typeof e=="string"?e:e[0]:"off"}function H(e){return e?typeof e=="string"?{}:e[1]??{}:{}}var C=class{rules=new Map;config;constructor(t){this.config={...L,...t?.rules};for(let n of g)this.rules.set(n.name,n);}getEffectiveSeverity(t){let n=this.config[t];return G(n)}getRuleOptions(t){let n=this.config[t];return H(n)}isRuleEnabled(t){return this.getEffectiveSeverity(t)!=="off"}lintTable(t){let n=[];for(let[s,i]of this.rules){let r=this.getEffectiveSeverity(s);if(r==="off")continue;let a=this.getRuleOptions(s),o={severity:r,options:a,report:l=>{n.push({rule:s,severity:r,...l});}};if(i.validateTable&&i.validateTable(t,o),i.validateColumn)for(let l of t.columns)i.validateColumn(l,t,o);}return n}lintTables(t){let n=Date.now(),s=new Map;for(let o of t){s.has(o.filePath)||s.set(o.filePath,{filePath:o.filePath,issues:[],tables:0,columns:0});let l=s.get(o.filePath);l.tables++,l.columns+=o.columns.length;let u=this.lintTable(o);l.issues.push(...u);}for(let[o,l]of this.rules){if(!l.validateSchema)continue;let u=this.getEffectiveSeverity(o);if(u==="off")continue;let p=this.getRuleOptions(o),m={severity:u,options:p,report:d=>{let f=d.filePath||"schema";s.has(f)||s.set(f,{filePath:f,issues:[],tables:0,columns:0}),s.get(f).issues.push({rule:o,severity:u,...d});}};l.validateSchema(t,m);}let i=Array.from(s.values()),r=i.flatMap(o=>o.issues),a={totalFiles:i.length,totalTables:i.reduce((o,l)=>o+l.tables,0),totalColumns:i.reduce((o,l)=>o+l.columns,0),errors:r.filter(o=>o.severity==="error").length,warnings:r.filter(o=>o.severity==="warn").length};return {files:i,summary:a,durationMs:Date.now()-n,passed:a.errors===0}}lintModule(t,n,s="tenant"){let i=S(t,n,s);return this.lintTables(i)}async lintDirectory(t,n="tenant"){let s=Date.now(),i=await y(t,n),r=[];for(let o of i){let l=await h(o.filePath,o.type);r.push(...l);}let a=this.lintTables(r);return a.durationMs=Date.now()-s,a}async lintDirectories(t){let n=Date.now(),s=[];if(t.tenant){let r=await y(t.tenant,"tenant");for(let a of r){let o=await h(a.filePath,a.type);s.push(...o);}}if(t.shared){let r=await y(t.shared,"shared");for(let a of r){let o=await h(a.filePath,a.type);s.push(...o);}}let i=this.lintTables(s);return i.durationMs=Date.now()-n,i}getRules(){return Array.from(this.rules.values())}getRule(t){return this.rules.get(t)}registerRule(t){this.rules.set(t.name,t);}setRuleConfig(t,n){this.config[t]=n;}getConfig(){return {...this.config}}};function E(e){return new C(e)}async function X(e){let t=E(e.rules?{rules:e.rules}:void 0),n={};return e.tenant&&(n.tenant=e.tenant),e.shared&&(n.shared=e.shared),t.lintDirectories(n)}var U={reset:"\x1B[0m",red:"\x1B[31m",yellow:"\x1B[33m",green:"\x1B[32m",cyan:"\x1B[36m",dim:"\x1B[2m",bold:"\x1B[1m"};function c(e,t,n){return n?`${U[t]}${e}${U.reset}`:e}function ee(e,t){let n=e.severity==="error"?c("error","red",t):c("warn","yellow",t),s=e.column?`${e.table}.${e.column}`:e.table??e.filePath,i=c(`(${e.rule})`,"dim",t),r=` ${n} ${e.message} ${i}`;return r+=`
2
+ ${c(s,"dim",t)}`,e.suggestion&&(r+=`
3
+ ${c("\u2192 "+e.suggestion,"cyan",t)}`),r}function te(e,t){let n=t.colors!==false,s=[];for(let i of e.files)if(!(i.issues.length===0&&!t.verbose)){if(s.push(""),s.push(c(i.filePath,"bold",n)),i.issues.length===0){s.push(c(" No issues found","green",n));continue}for(let r of i.issues)s.push(ee(r,n));}if(!t.quiet){s.push("");let{errors:i,warnings:r,totalTables:a}=e.summary;if(i===0&&r===0)s.push(c(`\u2713 ${a} tables validated, no issues found`,"green",n));else {let o=[];o.push(`${a} tables validated`),r>0&&o.push(c(`${r} warning${r>1?"s":""}`,"yellow",n)),i>0&&o.push(c(`${i} error${i>1?"s":""}`,"red",n)),s.push(o.join(", "));}s.push(c(`Completed in ${e.durationMs}ms`,"dim",n));}return s.join(`
4
+ `)}function ne(e){return JSON.stringify({passed:e.passed,summary:e.summary,files:e.files.map(t=>({filePath:t.filePath,tables:t.tables,columns:t.columns,issues:t.issues})),durationMs:e.durationMs},null,2)}function se(e){let t=[];for(let r of e.files)for(let a of r.issues){let o=a.severity==="error"?"error":"warning",l=`[${a.rule}]`,u=a.filePath,p=a.line??1,m=`::${o} file=${u},line=${p},title=${l}::${a.message}`;a.suggestion&&(m+=` (${a.suggestion})`),t.push(m);}let{errors:n,warnings:s,totalTables:i}=e.summary;return n>0||s>0?t.push(`::notice::Schema lint: ${i} tables, ${n} errors, ${s} warnings`):t.push(`::notice::Schema lint: ${i} tables validated, no issues`),t.join(`
5
+ `)}function _(e,t){switch(t.format){case "json":return ne(e);case "github":return se(e);default:return te(e,t)}}function re(e,t){let n={format:e,quiet:t?.quiet??false,verbose:t?.verbose??false,colors:t?.colors??true};return s=>_(s,n)}function ie(e,t){let n=_(e,t);console.log(n);}export{L as DEFAULT_LINT_RULES,C as SchemaLinter,g as allRules,v as columnNamingRule,b as conventionRules,E as createLinter,re as createReporter,y as findSchemaFiles,_ as formatLintResult,Z as getAllRuleNames,M as getRuleByName,$ as indexForeignKeysRule,X as lintSchemas,h as loadSchemaFile,R as namingRules,N as noCascadeDeleteRule,F as parseRawTable,S as parseSchemaModule,P as preferUuidPkRule,ie as printLintResult,T as requirePrimaryKeyRule,D as requireSoftDeleteRule,k as requireTimestampsRule,x as securityRules,w as tableNamingRule};