@synapsestudios/eslint-plugin-data-boundaries 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @synapsestudios/eslint-plugin-data-boundaries
2
2
 
3
- ESLint plugin to enforce data boundary policies in modular monoliths using Prisma ORM and slonik.
3
+ ESLint plugin to enforce data boundary policies in modular monoliths using Prisma ORM, Drizzle ORM, and slonik.
4
4
 
5
5
  ## Rules
6
6
 
@@ -8,17 +8,21 @@ ESLint plugin to enforce data boundary policies in modular monoliths using Prism
8
8
  |------|-------------|-------|
9
9
  | [`no-cross-file-model-references`](#no-cross-file-model-references) | Prevents Prisma models from referencing models defined in other schema files | Prisma schema files |
10
10
  | [`no-cross-domain-prisma-access`](#no-cross-domain-prisma-access) | Prevents modules from accessing Prisma models outside their domain boundaries | TypeScript/JavaScript |
11
+ | [`no-cross-schema-drizzle-references`](#no-cross-schema-drizzle-references) | Prevents Drizzle table definitions from referencing tables in other schema files | Drizzle schema files |
12
+ | [`no-cross-domain-drizzle-access`](#no-cross-domain-drizzle-access) | Prevents modules from accessing Drizzle tables outside their domain boundaries | TypeScript/JavaScript |
11
13
  | [`no-cross-schema-slonik-access`](#no-cross-schema-slonik-access) | Prevents modules from accessing database tables outside their schema boundaries via slonik | TypeScript/JavaScript |
12
14
 
13
15
  ## Overview
14
16
 
15
- When building modular monoliths, maintaining clear boundaries between domains is crucial for long-term maintainability. ORMs like Prisma and query builders like slonik make it easy to accidentally create tight coupling at the data layer by allowing modules to access data that belongs to other domains.
17
+ When building modular monoliths, maintaining clear boundaries between domains is crucial for long-term maintainability. ORMs like Prisma and Drizzle and query builders like slonik make it easy to accidentally create tight coupling at the data layer by allowing modules to access data that belongs to other domains.
16
18
 
17
- This ESLint plugin provides three complementary rules to prevent such violations:
19
+ This ESLint plugin provides five complementary rules to prevent such violations:
18
20
 
19
- 1. **Schema-level enforcement**: Prevents Prisma schema files from referencing models defined in other schema files
20
- 2. **Application-level enforcement**: Prevents TypeScript code from accessing Prisma models outside their domain boundaries
21
- 3. **SQL-level enforcement**: Prevents slonik SQL queries from accessing tables outside the module's schema
21
+ 1. **Prisma schema-level enforcement**: Prevents Prisma schema files from referencing models defined in other schema files
22
+ 2. **Prisma application-level enforcement**: Prevents TypeScript code from accessing Prisma models outside their domain boundaries
23
+ 3. **Drizzle schema-level enforcement**: Prevents Drizzle table definitions from referencing tables in other schema files
24
+ 4. **Drizzle application-level enforcement**: Prevents TypeScript code from accessing Drizzle tables outside their domain boundaries
25
+ 5. **SQL-level enforcement**: Prevents slonik SQL queries from accessing tables outside the module's schema
22
26
 
23
27
  ## Installation
24
28
 
@@ -91,6 +95,79 @@ class AuthService {
91
95
  }
92
96
  ```
93
97
 
98
+ ### `no-cross-schema-drizzle-references`
99
+
100
+ Prevents Drizzle table definitions from referencing tables defined in other schema files. This rule ensures that each Drizzle schema file is self-contained within its domain by detecting foreign key references and relations that cross schema file boundaries.
101
+
102
+ **Examples of violations:**
103
+
104
+ ```typescript
105
+ // In execution.schema.ts
106
+ import { pgTable, text } from 'drizzle-orm/pg-core';
107
+ import { identity_user } from './auth.schema';
108
+
109
+ export const execution = pgTable('execution', {
110
+ id: text('id').primaryKey(),
111
+ userId: text('user_id')
112
+ .notNull()
113
+ .references(() => identity_user.id, { onDelete: 'cascade' }), // ❌ Error: identity_user not defined in this file
114
+ });
115
+ ```
116
+
117
+ **Valid usage:**
118
+
119
+ ```typescript
120
+ // In auth.schema.ts
121
+ import { pgTable, text } from 'drizzle-orm/pg-core';
122
+
123
+ export const identity_user = pgTable('identity_user', {
124
+ id: text('id').primaryKey(),
125
+ name: text('name').notNull(),
126
+ });
127
+
128
+ export const identity_session = pgTable('identity_session', {
129
+ id: text('id').primaryKey(),
130
+ userId: text('user_id')
131
+ .notNull()
132
+ .references(() => identity_user.id, { onDelete: 'cascade' }), // ✅ Valid: identity_user is defined in same file
133
+ });
134
+ ```
135
+
136
+ ### `no-cross-domain-drizzle-access`
137
+
138
+ Prevents TypeScript/JavaScript modules from accessing Drizzle tables that belong to other domains. This rule analyzes your application code and maps file paths to domains, then ensures modules only access tables from their own domain.
139
+
140
+ **Examples of violations:**
141
+
142
+ ```typescript
143
+ // In /modules/auth/service.ts
144
+ import { organization } from '@/db/schema';
145
+
146
+ class AuthService {
147
+ async getOrganizations() {
148
+ return db.select().from(organization);
149
+ // ❌ Error: Module 'auth' cannot access 'organization' table (belongs to 'organization' domain)
150
+ }
151
+ }
152
+ ```
153
+
154
+ **Valid usage:**
155
+
156
+ ```typescript
157
+ // In /modules/auth/service.ts
158
+ import { identity_user, identity_session } from '@/db/schema';
159
+
160
+ class AuthService {
161
+ async getUser(id: string) {
162
+ return db.select().from(identity_user).where(eq(identity_user.id, id)); // ✅ Valid: identity_user belongs to auth domain
163
+ }
164
+
165
+ async getSessions(userId: string) {
166
+ return db.select().from(identity_session).where(eq(identity_session.userId, userId)); // ✅ Valid: identity_session belongs to auth domain
167
+ }
168
+ }
169
+ ```
170
+
94
171
  ### `no-cross-schema-slonik-access`
95
172
 
96
173
  Prevents TypeScript/JavaScript modules from accessing database tables outside their schema boundaries when using slonik. This rule enforces that all table references must be explicitly qualified with the module's schema name and prevents cross-schema access.
@@ -185,10 +262,20 @@ module.exports = {
185
262
  {
186
263
  files: ['**/*.ts', '**/*.tsx'],
187
264
  rules: {
265
+ // Prisma rules
188
266
  '@synapsestudios/data-boundaries/no-cross-domain-prisma-access': ['error', {
189
267
  schemaDir: 'prisma/schema',
190
268
  modulePath: '/modules/' // Default - change to '/src/' for NestJS projects
191
269
  }],
270
+ // Drizzle rules
271
+ '@synapsestudios/data-boundaries/no-cross-schema-drizzle-references': ['error', {
272
+ schemaDir: 'src/db/schema', // Adjust to your Drizzle schema directory
273
+ }],
274
+ '@synapsestudios/data-boundaries/no-cross-domain-drizzle-access': ['error', {
275
+ schemaDir: 'src/db/schema', // Adjust to your Drizzle schema directory
276
+ modulePath: '/modules/' // Default - change to '/src/' for NestJS projects
277
+ }],
278
+ // Slonik rules
192
279
  '@synapsestudios/data-boundaries/no-cross-schema-slonik-access': ['error', {
193
280
  modulePath: '/modules/' // Default - change to '/src/' for NestJS projects
194
281
  }]
@@ -232,20 +319,36 @@ export default [
232
319
  // 4. TypeScript files rule config
233
320
  {
234
321
  files: ['**/*.ts', '**/*.tsx'],
235
- plugins: {
236
- '@synapsestudios/data-boundaries': eslintPluginDataBoundaries
322
+ plugins: {
323
+ '@synapsestudios/data-boundaries': eslintPluginDataBoundaries
237
324
  },
238
325
  rules: {
326
+ // Prisma rules
239
327
  '@synapsestudios/data-boundaries/no-cross-domain-prisma-access': [
240
328
  'error',
241
- {
242
- schemaDir: 'prisma/schema',
329
+ {
330
+ schemaDir: 'prisma/schema',
331
+ modulePath: '/src/' // Use '/src/' for NestJS, '/modules/' for other structures
332
+ }
333
+ ],
334
+ // Drizzle rules
335
+ '@synapsestudios/data-boundaries/no-cross-schema-drizzle-references': [
336
+ 'error',
337
+ {
338
+ schemaDir: 'src/db/schema', // Adjust to your Drizzle schema directory
339
+ }
340
+ ],
341
+ '@synapsestudios/data-boundaries/no-cross-domain-drizzle-access': [
342
+ 'error',
343
+ {
344
+ schemaDir: 'src/db/schema', // Adjust to your Drizzle schema directory
243
345
  modulePath: '/src/' // Use '/src/' for NestJS, '/modules/' for other structures
244
346
  }
245
347
  ],
348
+ // Slonik rules
246
349
  '@synapsestudios/data-boundaries/no-cross-schema-slonik-access': [
247
350
  'error',
248
- {
351
+ {
249
352
  modulePath: '/src/' // Use '/src/' for NestJS, '/modules/' for other structures
250
353
  }
251
354
  ],
@@ -289,6 +392,32 @@ module.exports = {
289
392
  }
290
393
  ```
291
394
 
395
+ #### `no-cross-schema-drizzle-references`
396
+
397
+ - **`schemaDir`** (string): Directory containing Drizzle schema files, relative to project root. Default: `'src/db/schema'`
398
+
399
+ ```javascript
400
+ {
401
+ '@synapsestudios/data-boundaries/no-cross-schema-drizzle-references': ['error', {
402
+ schemaDir: 'src/db/schema' // Adjust to your Drizzle schema directory
403
+ }]
404
+ }
405
+ ```
406
+
407
+ #### `no-cross-domain-drizzle-access`
408
+
409
+ - **`schemaDir`** (string): Directory containing Drizzle schema files, relative to project root. Default: `'src/db/schema'`
410
+ - **`modulePath`** (string): Path pattern to match module directories. Default: `'/modules/'`. Use `'/src/'` for NestJS projects or other domain-based structures.
411
+
412
+ ```javascript
413
+ {
414
+ '@synapsestudios/data-boundaries/no-cross-domain-drizzle-access': ['error', {
415
+ schemaDir: 'src/db/schema', // Adjust to your Drizzle schema directory
416
+ modulePath: '/src/' // For NestJS-style projects
417
+ }]
418
+ }
419
+ ```
420
+
292
421
  #### `no-cross-schema-slonik-access`
293
422
 
294
423
  - **`modulePath`** (string): Path pattern to match module directories. Default: `'/modules/'`. Use `'/src/'` for NestJS projects or other domain-based structures.
@@ -334,7 +463,7 @@ src/
334
463
 
335
464
  **Note**: For NestJS projects, set `modulePath: '/src/'` in your rule configuration.
336
465
 
337
- ### Schema Structure
466
+ ### Prisma Schema Structure
338
467
  ```
339
468
  prisma/
340
469
  schema/
@@ -343,14 +472,31 @@ prisma/
343
472
  main.prisma # Contains shared models (AuditLog, Setting)
344
473
  ```
345
474
 
475
+ ### Drizzle Schema Structure
476
+ ```
477
+ src/
478
+ db/
479
+ schema/
480
+ auth.schema.ts # Contains identity_user, identity_session tables
481
+ organization.schema.ts # Contains organization table
482
+ execution.schema.ts # Contains execution, message, llmCall tables
483
+ index.ts # Barrel export for all schemas
484
+ ```
485
+
346
486
  ## Domain Mapping
347
487
 
348
488
  The plugin automatically maps:
349
489
 
490
+ ### Prisma
350
491
  - **File paths to domains**: `/modules/auth/` → `auth` domain
351
492
  - **Schema files to domains**: `auth.prisma` → `auth` domain
352
493
  - **Special cases**: `main.prisma` and `schema.prisma` → `shared` domain
353
494
 
495
+ ### Drizzle
496
+ - **File paths to domains**: `/modules/auth/` → `auth` domain
497
+ - **Schema files to domains**: `auth.schema.ts` → `auth` domain
498
+ - **Table names to domains**: Extracted from schema file exports
499
+
354
500
  ## Use Cases
355
501
 
356
502
  ### Modular Monoliths
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  declare const noCrossFileModelReferences: any;
2
2
  declare const noCrossDomainPrismaAccess: any;
3
3
  declare const noCrossSchemaSlonikAccess: any;
4
+ declare const noCrossSchemaDrizzleReferences: any;
5
+ declare const noCrossDomainDrizzleAccess: any;
4
6
  declare const prismaParser: any;
5
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,QAAA,MAAM,0BAA0B,KAAoD,CAAC;AACrF,QAAA,MAAM,yBAAyB,KAAmD,CAAC;AACnF,QAAA,MAAM,yBAAyB,KAAmD,CAAC;AACnF,QAAA,MAAM,YAAY,KAAqC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,QAAA,MAAM,0BAA0B,KAAoD,CAAC;AACrF,QAAA,MAAM,yBAAyB,KAAmD,CAAC;AACnF,QAAA,MAAM,yBAAyB,KAAmD,CAAC;AACnF,QAAA,MAAM,8BAA8B,KAAwD,CAAC;AAC7F,QAAA,MAAM,0BAA0B,KAAoD,CAAC;AACrF,QAAA,MAAM,YAAY,KAAqC,CAAC"}
package/dist/index.js CHANGED
@@ -2,12 +2,16 @@
2
2
  const noCrossFileModelReferences = require('./rules/no-cross-file-model-references');
3
3
  const noCrossDomainPrismaAccess = require('./rules/no-cross-domain-prisma-access');
4
4
  const noCrossSchemaSlonikAccess = require('./rules/no-cross-schema-slonik-access');
5
+ const noCrossSchemaDrizzleReferences = require('./rules/no-cross-schema-drizzle-references');
6
+ const noCrossDomainDrizzleAccess = require('./rules/no-cross-domain-drizzle-access');
5
7
  const prismaParser = require('./parsers/prisma-parser');
6
8
  module.exports = {
7
9
  rules: {
8
10
  'no-cross-file-model-references': noCrossFileModelReferences,
9
11
  'no-cross-domain-prisma-access': noCrossDomainPrismaAccess,
10
12
  'no-cross-schema-slonik-access': noCrossSchemaSlonikAccess,
13
+ 'no-cross-schema-drizzle-references': noCrossSchemaDrizzleReferences,
14
+ 'no-cross-domain-drizzle-access': noCrossDomainDrizzleAccess,
11
15
  },
12
16
  parsers: {
13
17
  prisma: prismaParser,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,MAAM,0BAA0B,GAAG,OAAO,CAAC,wCAAwC,CAAC,CAAC;AACrF,MAAM,yBAAyB,GAAG,OAAO,CAAC,uCAAuC,CAAC,CAAC;AACnF,MAAM,yBAAyB,GAAG,OAAO,CAAC,uCAAuC,CAAC,CAAC;AACnF,MAAM,YAAY,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAAC;AAExD,MAAM,CAAC,OAAO,GAAG;IACf,KAAK,EAAE;QACL,gCAAgC,EAAE,0BAA0B;QAC5D,+BAA+B,EAAE,yBAAyB;QAC1D,+BAA+B,EAAE,yBAAyB;KAC3D;IACD,OAAO,EAAE;QACP,MAAM,EAAE,YAAY;KACrB;IACD,OAAO,EAAE;QACP,WAAW,EAAE;YACX,OAAO,EAAE,CAAC,iCAAiC,CAAC;YAC5C,SAAS,EAAE;gBACT;oBACE,KAAK,EAAE,CAAC,aAAa,CAAC;oBACtB,MAAM,EAAE,8DAA8D;oBACtE,KAAK,EAAE;wBACL,gEAAgE,EAAE,OAAO;qBAC1E;iBACF;gBACD;oBACE,KAAK,EAAE,CAAC,SAAS,EAAE,UAAU,CAAC;oBAC9B,KAAK,EAAE;wBACL,+DAA+D,EAAE,OAAO;wBACxE,+DAA+D,EAAE,OAAO;qBACzE;iBACF;aACF;SACF;KACF;CACF,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,MAAM,0BAA0B,GAAG,OAAO,CAAC,wCAAwC,CAAC,CAAC;AACrF,MAAM,yBAAyB,GAAG,OAAO,CAAC,uCAAuC,CAAC,CAAC;AACnF,MAAM,yBAAyB,GAAG,OAAO,CAAC,uCAAuC,CAAC,CAAC;AACnF,MAAM,8BAA8B,GAAG,OAAO,CAAC,4CAA4C,CAAC,CAAC;AAC7F,MAAM,0BAA0B,GAAG,OAAO,CAAC,wCAAwC,CAAC,CAAC;AACrF,MAAM,YAAY,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAAC;AAExD,MAAM,CAAC,OAAO,GAAG;IACf,KAAK,EAAE;QACL,gCAAgC,EAAE,0BAA0B;QAC5D,+BAA+B,EAAE,yBAAyB;QAC1D,+BAA+B,EAAE,yBAAyB;QAC1D,oCAAoC,EAAE,8BAA8B;QACpE,gCAAgC,EAAE,0BAA0B;KAC7D;IACD,OAAO,EAAE;QACP,MAAM,EAAE,YAAY;KACrB;IACD,OAAO,EAAE;QACP,WAAW,EAAE;YACX,OAAO,EAAE,CAAC,iCAAiC,CAAC;YAC5C,SAAS,EAAE;gBACT;oBACE,KAAK,EAAE,CAAC,aAAa,CAAC;oBACtB,MAAM,EAAE,8DAA8D;oBACtE,KAAK,EAAE;wBACL,gEAAgE,EAAE,OAAO;qBAC1E;iBACF;gBACD;oBACE,KAAK,EAAE,CAAC,SAAS,EAAE,UAAU,CAAC;oBAC9B,KAAK,EAAE;wBACL,+DAA+D,EAAE,OAAO;wBACxE,+DAA+D,EAAE,OAAO;qBACzE;iBACF;aACF;SACF;KACF;CACF,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ interface RuleOptions {
3
+ schemaDir: string;
4
+ modulePath: string;
5
+ }
6
+ declare const rule: ESLintUtils.RuleModule<"crossDomainAccess" | "tableNotFound" | "configError", [RuleOptions], unknown, ESLintUtils.RuleListener>;
7
+ export = rule;
8
+ //# sourceMappingURL=no-cross-domain-drizzle-access.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-cross-domain-drizzle-access.d.ts","sourceRoot":"","sources":["../../src/rules/no-cross-domain-drizzle-access.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAY,MAAM,0BAA0B,CAAC;AAMjE,UAAU,WAAW;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AA6CD,QAAA,MAAM,IAAI,iIAsIR,CAAC;AAEH,SAAS,IAAI,CAAC"}
@@ -0,0 +1,197 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ const utils_1 = require("@typescript-eslint/utils");
36
+ const path = __importStar(require("path"));
37
+ const fs = __importStar(require("fs"));
38
+ const drizzle_parser_1 = require("../utils/drizzle-parser");
39
+ const schema_parser_1 = require("../utils/schema-parser");
40
+ /**
41
+ * Find project root by looking for package.json
42
+ */
43
+ function findProjectRoot(filePath) {
44
+ let dir = path.dirname(filePath);
45
+ while (dir !== path.dirname(dir)) {
46
+ if (fs.existsSync(path.join(dir, 'package.json'))) {
47
+ return dir;
48
+ }
49
+ dir = path.dirname(dir);
50
+ }
51
+ throw new Error('Could not find project root');
52
+ }
53
+ /**
54
+ * Check if an import is from a Drizzle schema barrel export
55
+ * Looks for patterns like: import { users } from '@/db/schema'
56
+ */
57
+ function isSchemaImport(node) {
58
+ if (!node.source.value || typeof node.source.value !== 'string') {
59
+ return false;
60
+ }
61
+ const source = node.source.value;
62
+ // Check for various patterns:
63
+ // - '@/db/schema'
64
+ // - '../db/schema'
65
+ // - './schema'
66
+ // - etc.
67
+ return source.includes('/db/schema') || source.includes('/schema') || source.endsWith('/schema');
68
+ }
69
+ /**
70
+ * ESLint rule to prevent modules from accessing Drizzle tables
71
+ * that belong to other domains
72
+ */
73
+ const createRule = utils_1.ESLintUtils.RuleCreator((name) => `https://github.com/synapsestudios/eslint-plugin-data-boundaries#${name}`);
74
+ const rule = createRule({
75
+ name: 'no-cross-domain-drizzle-access',
76
+ meta: {
77
+ type: 'problem',
78
+ docs: {
79
+ description: 'Disallow modules from accessing Drizzle tables defined in other domain schema files',
80
+ },
81
+ fixable: undefined,
82
+ schema: [
83
+ {
84
+ type: 'object',
85
+ properties: {
86
+ schemaDir: {
87
+ type: 'string',
88
+ description: 'Directory containing Drizzle schema files (relative to project root)',
89
+ },
90
+ modulePath: {
91
+ type: 'string',
92
+ description: 'Path pattern to match module directories (e.g., "/modules/", "/src/")',
93
+ },
94
+ },
95
+ additionalProperties: false,
96
+ },
97
+ ],
98
+ messages: {
99
+ crossDomainAccess: "Module '{{currentModule}}' cannot access '{{tableName}}' table (belongs to '{{tableDomain}}' domain). Consider using a shared service or moving the logic to the appropriate domain.",
100
+ tableNotFound: "Table '{{tableName}}' not found in any schema file. Ensure the table exists and schema files are properly configured.",
101
+ configError: "Could not determine schema directory. Please configure the 'schemaDir' option in your ESLint config.",
102
+ },
103
+ },
104
+ defaultOptions: [
105
+ {
106
+ schemaDir: 'src/db/schema',
107
+ modulePath: '/modules/',
108
+ },
109
+ ],
110
+ create(context, [options]) {
111
+ const filename = context.getFilename();
112
+ // Only process TypeScript files in modules
113
+ if (!filename.includes(options.modulePath) || !filename.match(/\.(ts|tsx)$/)) {
114
+ return {};
115
+ }
116
+ // Extract current module from file path
117
+ const currentModule = (0, schema_parser_1.extractModuleFromPath)(filename, options.modulePath);
118
+ if (!currentModule) {
119
+ return {};
120
+ }
121
+ // Build table-to-domain mapping
122
+ let tableToDomain = {};
123
+ try {
124
+ let schemaDir;
125
+ if (path.isAbsolute(options.schemaDir)) {
126
+ // Use absolute path directly (for tests)
127
+ schemaDir = options.schemaDir;
128
+ }
129
+ else {
130
+ // Resolve relative to project root (for real usage)
131
+ const projectRoot = findProjectRoot(filename);
132
+ schemaDir = path.join(projectRoot, options.schemaDir);
133
+ }
134
+ tableToDomain = (0, drizzle_parser_1.buildTableToDomainMapping)(schemaDir);
135
+ }
136
+ catch {
137
+ // Report configuration error only once per file
138
+ let reportedConfigError = false;
139
+ return {
140
+ Program(node) {
141
+ if (!reportedConfigError) {
142
+ context.report({
143
+ node,
144
+ messageId: 'configError',
145
+ });
146
+ reportedConfigError = true;
147
+ }
148
+ },
149
+ };
150
+ }
151
+ // Track imported tables and their locations
152
+ const importedTables = new Map();
153
+ return {
154
+ // Detect imports: import { users, posts } from '@/db/schema'
155
+ ImportDeclaration(node) {
156
+ // Check if this is a schema import
157
+ if (!isSchemaImport(node)) {
158
+ return;
159
+ }
160
+ // Extract imported table names
161
+ for (const specifier of node.specifiers) {
162
+ if (specifier.type === 'ImportSpecifier' && specifier.imported.type === 'Identifier') {
163
+ const tableName = specifier.imported.name;
164
+ importedTables.set(tableName, specifier);
165
+ // Check if this table belongs to a different domain
166
+ const tableDomain = tableToDomain[tableName];
167
+ if (!tableDomain) {
168
+ // Table not found in any schema file
169
+ context.report({
170
+ node: specifier,
171
+ messageId: 'tableNotFound',
172
+ data: { tableName },
173
+ });
174
+ continue;
175
+ }
176
+ // Check if current module matches table domain
177
+ // Special case: 'index' files are treated as the domain name itself
178
+ const effectiveCurrentModule = currentModule === 'index' ? path.basename(path.dirname(filename)) : currentModule;
179
+ if (effectiveCurrentModule !== tableDomain) {
180
+ context.report({
181
+ node: specifier,
182
+ messageId: 'crossDomainAccess',
183
+ data: {
184
+ currentModule: effectiveCurrentModule,
185
+ tableName,
186
+ tableDomain,
187
+ },
188
+ });
189
+ }
190
+ }
191
+ }
192
+ },
193
+ };
194
+ },
195
+ });
196
+ module.exports = rule;
197
+ //# sourceMappingURL=no-cross-domain-drizzle-access.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-cross-domain-drizzle-access.js","sourceRoot":"","sources":["../../src/rules/no-cross-domain-drizzle-access.ts"],"names":[],"mappings":"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"}
@@ -3,6 +3,6 @@ interface RuleOptions {
3
3
  schemaDir: string;
4
4
  modulePath: string;
5
5
  }
6
- declare const rule: ESLintUtils.RuleModule<"crossDomainAccess" | "modelNotFound" | "configError", [RuleOptions], unknown, ESLintUtils.RuleListener>;
6
+ declare const rule: ESLintUtils.RuleModule<"crossDomainAccess" | "configError" | "modelNotFound", [RuleOptions], unknown, ESLintUtils.RuleListener>;
7
7
  export = rule;
8
8
  //# sourceMappingURL=no-cross-domain-prisma-access.d.ts.map
@@ -0,0 +1,7 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ interface RuleOptions {
3
+ schemaDir: string;
4
+ }
5
+ declare const rule: ESLintUtils.RuleModule<"configError" | "crossSchemaReference", [RuleOptions], unknown, ESLintUtils.RuleListener>;
6
+ export = rule;
7
+ //# sourceMappingURL=no-cross-schema-drizzle-references.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-cross-schema-drizzle-references.d.ts","sourceRoot":"","sources":["../../src/rules/no-cross-schema-drizzle-references.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAY,MAAM,0BAA0B,CAAC;AAOjE,UAAU,WAAW;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAsJD,QAAA,MAAM,IAAI,kHAwGR,CAAC;AAEH,SAAS,IAAI,CAAC"}
@@ -0,0 +1,261 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ const utils_1 = require("@typescript-eslint/utils");
36
+ const path = __importStar(require("path"));
37
+ const fs = __importStar(require("fs"));
38
+ const parser = __importStar(require("@typescript-eslint/typescript-estree"));
39
+ const utils_2 = require("@typescript-eslint/utils");
40
+ const drizzle_parser_1 = require("../utils/drizzle-parser");
41
+ /**
42
+ * Find project root by looking for package.json
43
+ */
44
+ function findProjectRoot(filePath) {
45
+ let dir = path.dirname(filePath);
46
+ while (dir !== path.dirname(dir)) {
47
+ if (fs.existsSync(path.join(dir, 'package.json'))) {
48
+ return dir;
49
+ }
50
+ dir = path.dirname(dir);
51
+ }
52
+ throw new Error('Could not find project root');
53
+ }
54
+ /**
55
+ * Extract table references from the AST
56
+ * Looks for patterns like:
57
+ * - .references(() => table_name.id)
58
+ * - relations(table_name, ...)
59
+ * - one(table_name, ...)
60
+ * - many(table_name, ...)
61
+ */
62
+ function extractTableReferences(ast) {
63
+ const references = [];
64
+ function visit(node) {
65
+ if (!node)
66
+ return;
67
+ // Look for .references() in column definitions
68
+ if (node.type === utils_2.AST_NODE_TYPES.CallExpression &&
69
+ node.callee.type === utils_2.AST_NODE_TYPES.MemberExpression &&
70
+ node.callee.property.type === utils_2.AST_NODE_TYPES.Identifier &&
71
+ node.callee.property.name === 'references') {
72
+ // The argument is usually an arrow function: () => users.id
73
+ const arg = node.arguments[0];
74
+ if (arg?.type === utils_2.AST_NODE_TYPES.ArrowFunctionExpression) {
75
+ const body = arg.body;
76
+ // Look for users.id pattern
77
+ if (body.type === utils_2.AST_NODE_TYPES.MemberExpression &&
78
+ body.object.type === utils_2.AST_NODE_TYPES.Identifier &&
79
+ body.object.loc) {
80
+ references.push({
81
+ name: body.object.name,
82
+ loc: body.object.loc,
83
+ });
84
+ }
85
+ }
86
+ }
87
+ // Look for relations() calls
88
+ if (node.type === utils_2.AST_NODE_TYPES.CallExpression &&
89
+ node.callee.type === utils_2.AST_NODE_TYPES.Identifier &&
90
+ node.callee.name === 'relations') {
91
+ // First argument should be the table reference
92
+ const firstArg = node.arguments[0];
93
+ if (firstArg?.type === utils_2.AST_NODE_TYPES.Identifier && firstArg.loc) {
94
+ references.push({
95
+ name: firstArg.name,
96
+ loc: firstArg.loc,
97
+ });
98
+ }
99
+ }
100
+ // Look for one() and many() calls inside relations
101
+ if (node.type === utils_2.AST_NODE_TYPES.CallExpression &&
102
+ node.callee.type === utils_2.AST_NODE_TYPES.Identifier &&
103
+ (node.callee.name === 'one' || node.callee.name === 'many')) {
104
+ // First argument should be the referenced table
105
+ const firstArg = node.arguments[0];
106
+ if (firstArg?.type === utils_2.AST_NODE_TYPES.Identifier && firstArg.loc) {
107
+ references.push({
108
+ name: firstArg.name,
109
+ loc: firstArg.loc,
110
+ });
111
+ }
112
+ }
113
+ // Recursively visit child nodes
114
+ for (const key in node) {
115
+ if (key === 'parent' || key === 'loc' || key === 'range')
116
+ continue;
117
+ const child = node[key];
118
+ if (Array.isArray(child)) {
119
+ child.forEach(visit);
120
+ }
121
+ else if (typeof child === 'object') {
122
+ visit(child);
123
+ }
124
+ }
125
+ }
126
+ visit(ast);
127
+ return references;
128
+ }
129
+ /**
130
+ * Build a mapping of all tables to their schema files
131
+ */
132
+ function buildTableToFileMapping(schemaDir) {
133
+ const tableToFile = new Map();
134
+ try {
135
+ const files = fs.readdirSync(schemaDir);
136
+ const schemaFiles = files.filter((f) => f.endsWith('.schema.ts') || f.endsWith('.ts'));
137
+ for (const file of schemaFiles) {
138
+ const filePath = path.join(schemaDir, file);
139
+ const content = fs.readFileSync(filePath, 'utf8');
140
+ // Parse TypeScript file
141
+ const ast = parser.parse(content, {
142
+ loc: true,
143
+ range: true,
144
+ jsx: false,
145
+ });
146
+ // Extract tables from this schema file
147
+ const tables = (0, drizzle_parser_1.extractTablesFromAst)(ast);
148
+ for (const table of tables) {
149
+ tableToFile.set(table, file);
150
+ }
151
+ }
152
+ }
153
+ catch (error) {
154
+ const errorMessage = error instanceof Error ? error.message : String(error);
155
+ console.warn(`Warning: Could not build table-to-file mapping in ${schemaDir}:`, errorMessage);
156
+ }
157
+ return tableToFile;
158
+ }
159
+ /**
160
+ * ESLint rule to prevent Drizzle table definitions in one schema file
161
+ * from referencing tables in other schema files
162
+ */
163
+ const createRule = utils_1.ESLintUtils.RuleCreator((name) => `https://github.com/synapsestudios/eslint-plugin-data-boundaries#${name}`);
164
+ const rule = createRule({
165
+ name: 'no-cross-schema-drizzle-references',
166
+ meta: {
167
+ type: 'problem',
168
+ docs: {
169
+ description: 'Disallow Drizzle table definitions from referencing tables defined in other schema files',
170
+ },
171
+ fixable: undefined,
172
+ schema: [
173
+ {
174
+ type: 'object',
175
+ properties: {
176
+ schemaDir: {
177
+ type: 'string',
178
+ description: 'Directory containing Drizzle schema files (relative to project root)',
179
+ },
180
+ },
181
+ additionalProperties: false,
182
+ },
183
+ ],
184
+ messages: {
185
+ crossSchemaReference: "Table '{{currentFile}}' references '{{tableName}}' which is defined in '{{referencedFile}}'. Cross-schema table references are not allowed. Consider moving related tables to the same schema file or restructuring your domain boundaries.",
186
+ configError: "Could not determine schema directory. Please configure the 'schemaDir' option in your ESLint config.",
187
+ },
188
+ },
189
+ defaultOptions: [
190
+ {
191
+ schemaDir: 'src/db/schema',
192
+ },
193
+ ],
194
+ create(context, [options]) {
195
+ const filename = context.getFilename();
196
+ // Only process .schema.ts or .ts files in the schema directory
197
+ if (!filename.includes('.schema.ts') && !filename.endsWith('.ts')) {
198
+ return {};
199
+ }
200
+ return {
201
+ Program(node) {
202
+ try {
203
+ // Determine schema directory
204
+ let schemaDir;
205
+ if (path.isAbsolute(options.schemaDir)) {
206
+ schemaDir = options.schemaDir;
207
+ }
208
+ else {
209
+ const projectRoot = findProjectRoot(filename);
210
+ schemaDir = path.join(projectRoot, options.schemaDir);
211
+ }
212
+ // Check if this file is actually in the schema directory
213
+ const normalizedFilename = path.normalize(filename);
214
+ const normalizedSchemaDir = path.normalize(schemaDir);
215
+ if (!normalizedFilename.startsWith(normalizedSchemaDir)) {
216
+ return;
217
+ }
218
+ // Build mapping of all tables to their files
219
+ const tableToFile = buildTableToFileMapping(schemaDir);
220
+ // Get tables defined in this file
221
+ const localTables = new Set((0, drizzle_parser_1.extractTablesFromAst)(node));
222
+ // Get all table references in this file
223
+ const references = extractTableReferences(node);
224
+ // Check each reference
225
+ for (const ref of references) {
226
+ // Skip if it's defined locally
227
+ if (localTables.has(ref.name)) {
228
+ continue;
229
+ }
230
+ // Check if it exists in another schema file
231
+ const referencedFile = tableToFile.get(ref.name);
232
+ if (referencedFile) {
233
+ const currentFile = path.basename(filename);
234
+ context.report({
235
+ node,
236
+ loc: ref.loc,
237
+ messageId: 'crossSchemaReference',
238
+ data: {
239
+ currentFile,
240
+ tableName: ref.name,
241
+ referencedFile,
242
+ },
243
+ });
244
+ }
245
+ }
246
+ }
247
+ catch (error) {
248
+ const errorMessage = error instanceof Error ? error.message : String(error);
249
+ if (errorMessage.includes('Could not find project root')) {
250
+ context.report({
251
+ node,
252
+ messageId: 'configError',
253
+ });
254
+ }
255
+ }
256
+ },
257
+ };
258
+ },
259
+ });
260
+ module.exports = rule;
261
+ //# sourceMappingURL=no-cross-schema-drizzle-references.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-cross-schema-drizzle-references.js","sourceRoot":"","sources":["../../src/rules/no-cross-schema-drizzle-references.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import { TSESTree } from '@typescript-eslint/utils';
2
+ export interface TableToDomainMapping {
3
+ [tableName: string]: string;
4
+ }
5
+ /**
6
+ * Extract table definitions from Drizzle schema file AST
7
+ * Looks for patterns like: export const users = pgTable('users', {...})
8
+ */
9
+ export declare function extractTablesFromAst(ast: TSESTree.Program): string[];
10
+ /**
11
+ * Parse all Drizzle schema files in a directory and build table-to-domain mapping
12
+ */
13
+ export declare function buildTableToDomainMapping(schemaDir: string): TableToDomainMapping;
14
+ /**
15
+ * Check if a table reference in a schema file references a table from another file
16
+ */
17
+ export declare function checkCrossSchemaTableReference(filePath: string, schemaDir: string): {
18
+ isValid: boolean;
19
+ violations: Array<{
20
+ table: string;
21
+ line: number;
22
+ column: number;
23
+ }>;
24
+ };
25
+ //# sourceMappingURL=drizzle-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drizzle-parser.d.ts","sourceRoot":"","sources":["../../src/utils/drizzle-parser.ts"],"names":[],"mappings":"AAIA,OAAO,EAAkB,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEpE,MAAM,WAAW,oBAAoB;IACnC,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;CAC7B;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,QAAQ,CAAC,OAAO,GAAG,MAAM,EAAE,CA+CpE;AAyED;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,MAAM,GAAG,oBAAoB,CAmCjF;AAED;;GAEG;AACH,wBAAgB,8BAA8B,CAC5C,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,CA+C1F"}
@@ -0,0 +1,263 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.extractTablesFromAst = extractTablesFromAst;
37
+ exports.buildTableToDomainMapping = buildTableToDomainMapping;
38
+ exports.checkCrossSchemaTableReference = checkCrossSchemaTableReference;
39
+ const fs = __importStar(require("fs"));
40
+ const path = __importStar(require("path"));
41
+ const glob_1 = require("glob");
42
+ const parser = __importStar(require("@typescript-eslint/typescript-estree"));
43
+ const utils_1 = require("@typescript-eslint/utils");
44
+ /**
45
+ * Extract table definitions from Drizzle schema file AST
46
+ * Looks for patterns like: export const users = pgTable('users', {...})
47
+ */
48
+ function extractTablesFromAst(ast) {
49
+ const tables = [];
50
+ function visit(node) {
51
+ if (!node)
52
+ return;
53
+ // Look for: export const tableName = pgTable/mysqlTable/sqliteTable(...)
54
+ if (node.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration &&
55
+ node.declaration?.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
56
+ const declarations = node.declaration.declarations;
57
+ for (const decl of declarations) {
58
+ if (decl.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
59
+ decl.id.type === utils_1.AST_NODE_TYPES.Identifier &&
60
+ decl.init?.type === utils_1.AST_NODE_TYPES.CallExpression) {
61
+ const callee = decl.init.callee;
62
+ // Check if calling pgTable, mysqlTable, or sqliteTable
63
+ if (callee.type === utils_1.AST_NODE_TYPES.Identifier &&
64
+ (callee.name === 'pgTable' ||
65
+ callee.name === 'mysqlTable' ||
66
+ callee.name === 'sqliteTable')) {
67
+ // Add the exported constant name (the table reference)
68
+ tables.push(decl.id.name);
69
+ }
70
+ }
71
+ }
72
+ }
73
+ // Recursively visit child nodes
74
+ for (const key in node) {
75
+ if (key === 'parent' || key === 'loc' || key === 'range')
76
+ continue;
77
+ const child = node[key];
78
+ if (Array.isArray(child)) {
79
+ child.forEach(visit);
80
+ }
81
+ else if (typeof child === 'object') {
82
+ visit(child);
83
+ }
84
+ }
85
+ }
86
+ visit(ast);
87
+ return tables;
88
+ }
89
+ /**
90
+ * Extract table references from relations() calls
91
+ * Looks for patterns like: relations(users, ...) or relations(posts, ...)
92
+ */
93
+ function extractTableReferencesFromRelations(ast) {
94
+ const references = [];
95
+ function visit(node) {
96
+ if (!node)
97
+ return;
98
+ // Look for: relations(tableName, ({ one, many }) => ({...}))
99
+ if (node.type === utils_1.AST_NODE_TYPES.CallExpression) {
100
+ const callee = node.callee;
101
+ if (callee.type === utils_1.AST_NODE_TYPES.Identifier && callee.name === 'relations') {
102
+ // First argument should be the table reference
103
+ const firstArg = node.arguments[0];
104
+ if (firstArg?.type === utils_1.AST_NODE_TYPES.Identifier) {
105
+ references.push(firstArg.name);
106
+ }
107
+ }
108
+ // Also look for one() and many() calls inside relations
109
+ if (callee.type === utils_1.AST_NODE_TYPES.Identifier &&
110
+ (callee.name === 'one' || callee.name === 'many')) {
111
+ // First argument should be the referenced table
112
+ const firstArg = node.arguments[0];
113
+ if (firstArg?.type === utils_1.AST_NODE_TYPES.Identifier) {
114
+ references.push(firstArg.name);
115
+ }
116
+ }
117
+ }
118
+ // Look for .references() in column definitions
119
+ if (node.type === utils_1.AST_NODE_TYPES.CallExpression &&
120
+ node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
121
+ node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
122
+ node.callee.property.name === 'references') {
123
+ // The argument is usually an arrow function: () => users.id
124
+ const arg = node.arguments[0];
125
+ if (arg?.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
126
+ const body = arg.body;
127
+ // Look for users.id pattern
128
+ if (body.type === utils_1.AST_NODE_TYPES.MemberExpression &&
129
+ body.object.type === utils_1.AST_NODE_TYPES.Identifier) {
130
+ references.push(body.object.name);
131
+ }
132
+ }
133
+ }
134
+ // Recursively visit child nodes
135
+ for (const key in node) {
136
+ if (key === 'parent' || key === 'loc' || key === 'range')
137
+ continue;
138
+ const child = node[key];
139
+ if (Array.isArray(child)) {
140
+ child.forEach(visit);
141
+ }
142
+ else if (typeof child === 'object') {
143
+ visit(child);
144
+ }
145
+ }
146
+ }
147
+ visit(ast);
148
+ return references;
149
+ }
150
+ /**
151
+ * Parse all Drizzle schema files in a directory and build table-to-domain mapping
152
+ */
153
+ function buildTableToDomainMapping(schemaDir) {
154
+ const tableToDomain = {};
155
+ try {
156
+ // Find all .ts files in the schema directory
157
+ const schemaFiles = glob_1.glob.sync(path.join(schemaDir, '**/*.ts'));
158
+ for (const filePath of schemaFiles) {
159
+ const content = fs.readFileSync(filePath, 'utf8');
160
+ // Parse TypeScript file
161
+ const ast = parser.parse(content, {
162
+ loc: true,
163
+ range: true,
164
+ jsx: false,
165
+ });
166
+ // Extract domain name from filename (e.g., auth.schema.ts -> auth)
167
+ const fileName = path.basename(filePath);
168
+ const match = fileName.match(/^(.+?)(?:\.schema)?\.ts$/);
169
+ const domainName = match ? match[1] : path.basename(filePath, '.ts');
170
+ // Extract tables from this schema file
171
+ const tables = extractTablesFromAst(ast);
172
+ for (const table of tables) {
173
+ tableToDomain[table] = domainName;
174
+ }
175
+ }
176
+ }
177
+ catch (error) {
178
+ // If we can't parse schemas, return empty mapping
179
+ const errorMessage = error instanceof Error ? error.message : String(error);
180
+ console.warn(`Warning: Could not parse Drizzle schema files in ${schemaDir}:`, errorMessage);
181
+ }
182
+ return tableToDomain;
183
+ }
184
+ /**
185
+ * Check if a table reference in a schema file references a table from another file
186
+ */
187
+ function checkCrossSchemaTableReference(filePath, schemaDir) {
188
+ const violations = [];
189
+ try {
190
+ const content = fs.readFileSync(filePath, 'utf8');
191
+ const ast = parser.parse(content, {
192
+ loc: true,
193
+ range: true,
194
+ jsx: false,
195
+ });
196
+ // Get tables defined in this file
197
+ const localTables = new Set(extractTablesFromAst(ast));
198
+ // Get all table references (from relations, foreign keys, etc.)
199
+ const referencedTables = extractTableReferencesFromRelations(ast);
200
+ // Build mapping of all tables in schema directory
201
+ const allTables = buildTableToDomainMapping(schemaDir);
202
+ // Check each reference
203
+ for (const refTable of referencedTables) {
204
+ // Skip if it's defined locally
205
+ if (localTables.has(refTable)) {
206
+ continue;
207
+ }
208
+ // Check if it exists in another schema file
209
+ if (allTables[refTable]) {
210
+ // Find the location of this reference
211
+ const location = findTableReferenceLocation(ast, refTable);
212
+ violations.push({
213
+ table: refTable,
214
+ line: location.line,
215
+ column: location.column,
216
+ });
217
+ }
218
+ }
219
+ }
220
+ catch (error) {
221
+ const errorMessage = error instanceof Error ? error.message : String(error);
222
+ console.warn(`Warning: Could not check cross-schema references in ${filePath}:`, errorMessage);
223
+ }
224
+ return {
225
+ isValid: violations.length === 0,
226
+ violations,
227
+ };
228
+ }
229
+ /**
230
+ * Find the location of a table reference in the AST
231
+ */
232
+ function findTableReferenceLocation(ast, tableName) {
233
+ let location = { line: 1, column: 0 };
234
+ function visit(node) {
235
+ if (!node)
236
+ return false;
237
+ // Look for identifier matching the table name
238
+ if (node.type === utils_1.AST_NODE_TYPES.Identifier && node.name === tableName && node.loc) {
239
+ location = { line: node.loc.start.line, column: node.loc.start.column };
240
+ return true; // Found it
241
+ }
242
+ // Recursively visit child nodes
243
+ for (const key in node) {
244
+ if (key === 'parent' || key === 'loc' || key === 'range')
245
+ continue;
246
+ const child = node[key];
247
+ if (Array.isArray(child)) {
248
+ for (const item of child) {
249
+ if (visit(item))
250
+ return true;
251
+ }
252
+ }
253
+ else if (typeof child === 'object') {
254
+ if (visit(child))
255
+ return true;
256
+ }
257
+ }
258
+ return false;
259
+ }
260
+ visit(ast);
261
+ return location;
262
+ }
263
+ //# sourceMappingURL=drizzle-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drizzle-parser.js","sourceRoot":"","sources":["../../src/utils/drizzle-parser.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synapsestudios/eslint-plugin-data-boundaries",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "ESLint plugin to enforce data boundary policies in modular monoliths",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -37,6 +37,8 @@
37
37
  "eslintplugin",
38
38
  "eslint-plugin",
39
39
  "prisma",
40
+ "drizzle",
41
+ "drizzle-orm",
40
42
  "data-boundaries",
41
43
  "modular-monolith",
42
44
  "domain-driven-design"