@synapsestudios/eslint-plugin-data-boundaries 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.
- package/LICENSE +21 -0
- package/README.md +219 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/parsers/prisma-parser.d.ts +20 -0
- package/dist/parsers/prisma-parser.d.ts.map +1 -0
- package/dist/parsers/prisma-parser.js +58 -0
- package/dist/parsers/prisma-parser.js.map +1 -0
- package/dist/rules/no-cross-domain-prisma-access.d.ts +8 -0
- package/dist/rules/no-cross-domain-prisma-access.d.ts.map +1 -0
- package/dist/rules/no-cross-domain-prisma-access.js +204 -0
- package/dist/rules/no-cross-domain-prisma-access.js.map +1 -0
- package/dist/rules/no-cross-file-model-references.d.ts +4 -0
- package/dist/rules/no-cross-file-model-references.d.ts.map +1 -0
- package/dist/rules/no-cross-file-model-references.js +108 -0
- package/dist/rules/no-cross-file-model-references.js.map +1 -0
- package/dist/utils/schema-parser.d.ts +20 -0
- package/dist/utils/schema-parser.d.ts.map +1 -0
- package/dist/utils/schema-parser.js +123 -0
- package/dist/utils/schema-parser.js.map +1 -0
- package/package.json +67 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Synapse Studios
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# @synapsestudios/eslint-plugin-data-boundaries
|
|
2
|
+
|
|
3
|
+
ESLint plugin to enforce data boundary policies in modular monoliths using Prisma ORM.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
When building modular monoliths, maintaining clear boundaries between domains is crucial for long-term maintainability. ORMs like Prisma make it easy to accidentally create tight coupling at the data layer by allowing modules to access models that belong to other domains.
|
|
8
|
+
|
|
9
|
+
This ESLint plugin provides two complementary rules to prevent such violations:
|
|
10
|
+
|
|
11
|
+
1. **Schema-level enforcement**: Prevents Prisma schema files from referencing models defined in other schema files
|
|
12
|
+
2. **Application-level enforcement**: Prevents TypeScript code from accessing Prisma models outside their domain boundaries
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install --save-dev @synapsestudios/eslint-plugin-data-boundaries
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Rules
|
|
21
|
+
|
|
22
|
+
### `no-cross-file-model-references`
|
|
23
|
+
|
|
24
|
+
Prevents Prisma models from referencing models defined in other schema files. This rule works with Prisma's multi-file schema feature to ensure each schema file is self-contained within its domain.
|
|
25
|
+
|
|
26
|
+
**Examples of violations:**
|
|
27
|
+
|
|
28
|
+
```prisma
|
|
29
|
+
// membership.prisma
|
|
30
|
+
model UserOrganization {
|
|
31
|
+
userId String
|
|
32
|
+
user User @relation(...) // ❌ Error: User not defined in this file
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Valid usage:**
|
|
37
|
+
|
|
38
|
+
```prisma
|
|
39
|
+
// auth.prisma
|
|
40
|
+
model User {
|
|
41
|
+
id String @id
|
|
42
|
+
sessions Session[]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
model Session {
|
|
46
|
+
id String @id
|
|
47
|
+
userId String
|
|
48
|
+
user User @relation(fields: [userId], references: [id]) // ✅ Valid: User is defined in same file
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### `no-cross-domain-prisma-access`
|
|
53
|
+
|
|
54
|
+
Prevents TypeScript/JavaScript modules from accessing Prisma models that belong to other domains. This rule analyzes your application code and maps file paths to domains, then ensures modules only access models from their own domain (plus optionally shared models).
|
|
55
|
+
|
|
56
|
+
**Examples of violations:**
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
// In /modules/auth/service.ts
|
|
60
|
+
class AuthService {
|
|
61
|
+
async getOrganizations() {
|
|
62
|
+
return this.prisma.organization.findMany();
|
|
63
|
+
// ❌ Error: Module 'auth' cannot access 'Organization' model (belongs to 'organization' domain)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Valid usage:**
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
// In /modules/auth/service.ts
|
|
72
|
+
class AuthService {
|
|
73
|
+
async getUser(id: string) {
|
|
74
|
+
return this.prisma.user.findUnique({ where: { id } }); // ✅ Valid: User belongs to auth domain
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async logAction(action: string) {
|
|
78
|
+
return this.prisma.auditLog.create({ data: { action } }); // ✅ Valid: AuditLog is a shared model
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Configuration
|
|
84
|
+
|
|
85
|
+
### Basic Setup
|
|
86
|
+
|
|
87
|
+
Add the plugin to your `.eslintrc.js`:
|
|
88
|
+
|
|
89
|
+
```javascript
|
|
90
|
+
module.exports = {
|
|
91
|
+
plugins: ['@synapsestudios/data-boundaries'],
|
|
92
|
+
overrides: [
|
|
93
|
+
// For Prisma schema files
|
|
94
|
+
{
|
|
95
|
+
files: ['**/*.prisma'],
|
|
96
|
+
parser: '@synapsestudios/data-boundaries/dist/parsers/prisma-parser',
|
|
97
|
+
rules: {
|
|
98
|
+
'@synapsestudios/data-boundaries/no-cross-file-model-references': 'error'
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
// For TypeScript application code
|
|
102
|
+
{
|
|
103
|
+
files: ['**/*.ts', '**/*.tsx'],
|
|
104
|
+
rules: {
|
|
105
|
+
'@synapsestudios/data-boundaries/no-cross-domain-prisma-access': ['error', {
|
|
106
|
+
schemaDir: 'prisma/schema',
|
|
107
|
+
allowSharedModels: true
|
|
108
|
+
}]
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
]
|
|
112
|
+
};
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Using the Recommended Configuration
|
|
116
|
+
|
|
117
|
+
```javascript
|
|
118
|
+
module.exports = {
|
|
119
|
+
extends: ['plugin:@synapsestudios/data-boundaries/recommended']
|
|
120
|
+
};
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Rule Options
|
|
124
|
+
|
|
125
|
+
#### `no-cross-domain-prisma-access`
|
|
126
|
+
|
|
127
|
+
- **`schemaDir`** (string): Directory containing Prisma schema files, relative to project root. Default: `'prisma/schema'`
|
|
128
|
+
- **`allowSharedModels`** (boolean): Whether to allow access to models in shared/main schema files. Default: `true`
|
|
129
|
+
|
|
130
|
+
```javascript
|
|
131
|
+
{
|
|
132
|
+
'@synapsestudios/data-boundaries/no-cross-domain-prisma-access': ['error', {
|
|
133
|
+
schemaDir: 'database/schemas',
|
|
134
|
+
allowSharedModels: false
|
|
135
|
+
}]
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Directory Structure
|
|
140
|
+
|
|
141
|
+
This plugin assumes your project follows these conventions:
|
|
142
|
+
|
|
143
|
+
### Module Structure
|
|
144
|
+
```
|
|
145
|
+
src/
|
|
146
|
+
modules/
|
|
147
|
+
auth/ # auth domain
|
|
148
|
+
service.ts
|
|
149
|
+
controller.ts
|
|
150
|
+
organization/ # organization domain
|
|
151
|
+
service.ts
|
|
152
|
+
controller.ts
|
|
153
|
+
user-profile/ # user-profile domain
|
|
154
|
+
service.ts
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Schema Structure
|
|
158
|
+
```
|
|
159
|
+
prisma/
|
|
160
|
+
schema/
|
|
161
|
+
auth.prisma # Contains User, Session models
|
|
162
|
+
organization.prisma # Contains Organization, Membership models
|
|
163
|
+
main.prisma # Contains shared models (AuditLog, Setting)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Domain Mapping
|
|
167
|
+
|
|
168
|
+
The plugin automatically maps:
|
|
169
|
+
|
|
170
|
+
- **File paths to domains**: `/modules/auth/` → `auth` domain
|
|
171
|
+
- **Schema files to domains**: `auth.prisma` → `auth` domain
|
|
172
|
+
- **Special cases**: `main.prisma` and `schema.prisma` → `shared` domain
|
|
173
|
+
|
|
174
|
+
## Use Cases
|
|
175
|
+
|
|
176
|
+
### Modular Monoliths
|
|
177
|
+
Perfect for applications transitioning from monolith to microservices, ensuring clean domain boundaries while maintaining a single codebase.
|
|
178
|
+
|
|
179
|
+
### Domain-Driven Design
|
|
180
|
+
Enforces DDD principles at the data layer, preventing cross-domain dependencies that can lead to tight coupling.
|
|
181
|
+
|
|
182
|
+
### Team Boundaries
|
|
183
|
+
Helps large teams maintain clear ownership of domains and prevents accidental coupling between team-owned modules.
|
|
184
|
+
|
|
185
|
+
### AI-Assisted Development
|
|
186
|
+
Particularly valuable when using AI coding tools, which can easily introduce unintended cross-domain dependencies.
|
|
187
|
+
|
|
188
|
+
## Error Messages
|
|
189
|
+
|
|
190
|
+
The plugin provides clear, actionable error messages:
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
Module 'auth' cannot access 'Organization' model (belongs to 'organization' domain).
|
|
194
|
+
Consider using a shared service or moving the logic to the appropriate domain.
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
```
|
|
198
|
+
Model field 'user' references 'User' which is not defined in this file.
|
|
199
|
+
Cross-file model references are not allowed.
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Migration Strategy
|
|
203
|
+
|
|
204
|
+
1. **Start with schema boundaries**: Add the `no-cross-file-model-references` rule to prevent new violations in schema files
|
|
205
|
+
2. **Split your schema**: Gradually move models to domain-specific schema files
|
|
206
|
+
3. **Add application boundaries**: Enable `no-cross-domain-prisma-access` to prevent cross-domain access in application code
|
|
207
|
+
4. **Refactor violations**: Create shared services or move logic to appropriate domains
|
|
208
|
+
|
|
209
|
+
## Contributing
|
|
210
|
+
|
|
211
|
+
Issues and pull requests are welcome! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
|
|
212
|
+
|
|
213
|
+
## License
|
|
214
|
+
|
|
215
|
+
MIT
|
|
216
|
+
|
|
217
|
+
## Credits
|
|
218
|
+
|
|
219
|
+
Originally developed for internal use at Synapse Studios and opensourced for the community.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +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,YAAY,KAAqC,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const noCrossFileModelReferences = require('./rules/no-cross-file-model-references');
|
|
3
|
+
const noCrossDomainPrismaAccess = require('./rules/no-cross-domain-prisma-access');
|
|
4
|
+
const prismaParser = require('./parsers/prisma-parser');
|
|
5
|
+
module.exports = {
|
|
6
|
+
rules: {
|
|
7
|
+
'no-cross-file-model-references': noCrossFileModelReferences,
|
|
8
|
+
'no-cross-domain-prisma-access': noCrossDomainPrismaAccess,
|
|
9
|
+
},
|
|
10
|
+
parsers: {
|
|
11
|
+
prisma: prismaParser,
|
|
12
|
+
},
|
|
13
|
+
configs: {
|
|
14
|
+
recommended: {
|
|
15
|
+
plugins: ['@synapsestudios/data-boundaries'],
|
|
16
|
+
overrides: [
|
|
17
|
+
{
|
|
18
|
+
files: ['**/*.prisma'],
|
|
19
|
+
parser: '@synapsestudios/data-boundaries/dist/parsers/prisma-parser',
|
|
20
|
+
rules: {
|
|
21
|
+
'@synapsestudios/data-boundaries/no-cross-file-model-references': 'error',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
files: ['**/*.ts', '**/*.tsx'],
|
|
26
|
+
rules: {
|
|
27
|
+
'@synapsestudios/data-boundaries/no-cross-domain-prisma-access': 'error',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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,YAAY,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAAC;AAExD,MAAM,CAAC,OAAO,GAAG;IACf,KAAK,EAAE;QACL,gCAAgC,EAAE,0BAA0B;QAC5D,+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,4DAA4D;oBACpE,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;qBACzE;iBACF;aACF;SACF;KACF;CACF,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Schema } from '@mrleebo/prisma-ast';
|
|
2
|
+
import { AST } from 'eslint';
|
|
3
|
+
interface PrismaEslintAST extends AST.Program {
|
|
4
|
+
prismaAst: Schema;
|
|
5
|
+
}
|
|
6
|
+
interface ParseResult {
|
|
7
|
+
ast: PrismaEslintAST | AST.Program;
|
|
8
|
+
services: {
|
|
9
|
+
getPrismaAst?: () => Schema;
|
|
10
|
+
};
|
|
11
|
+
scopeManager: null;
|
|
12
|
+
visitorKeys: Record<string, never>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Custom ESLint parser for Prisma schema files
|
|
16
|
+
* Converts Prisma AST to ESLint-compatible AST
|
|
17
|
+
*/
|
|
18
|
+
export declare function parseForESLint(code: string, _options?: Record<string, unknown>): ParseResult;
|
|
19
|
+
export {};
|
|
20
|
+
//# sourceMappingURL=prisma-parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prisma-parser.d.ts","sourceRoot":"","sources":["../../src/parsers/prisma-parser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,MAAM,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;AAE7B,UAAU,eAAgB,SAAQ,GAAG,CAAC,OAAO;IAC3C,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,WAAW;IACnB,GAAG,EAAE,eAAe,GAAG,GAAG,CAAC,OAAO,CAAC;IACnC,QAAQ,EAAE;QACR,YAAY,CAAC,EAAE,MAAM,MAAM,CAAC;KAC7B,CAAC;IACF,YAAY,EAAE,IAAI,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;CACpC;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,WAAW,CAiDhG"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseForESLint = parseForESLint;
|
|
4
|
+
const prisma_ast_1 = require("@mrleebo/prisma-ast");
|
|
5
|
+
/**
|
|
6
|
+
* Custom ESLint parser for Prisma schema files
|
|
7
|
+
* Converts Prisma AST to ESLint-compatible AST
|
|
8
|
+
*/
|
|
9
|
+
function parseForESLint(code, _options = {}) {
|
|
10
|
+
try {
|
|
11
|
+
// Parse Prisma schema
|
|
12
|
+
const prismaAst = (0, prisma_ast_1.getSchema)(code);
|
|
13
|
+
// Convert to ESLint AST format
|
|
14
|
+
const eslintAst = {
|
|
15
|
+
type: 'Program',
|
|
16
|
+
body: [],
|
|
17
|
+
sourceType: 'module',
|
|
18
|
+
range: [0, code.length],
|
|
19
|
+
loc: {
|
|
20
|
+
start: { line: 1, column: 0 },
|
|
21
|
+
end: { line: code.split('\n').length, column: 0 },
|
|
22
|
+
},
|
|
23
|
+
tokens: [],
|
|
24
|
+
comments: [],
|
|
25
|
+
// Store the Prisma AST for rule access
|
|
26
|
+
prismaAst: prismaAst,
|
|
27
|
+
};
|
|
28
|
+
return {
|
|
29
|
+
ast: eslintAst,
|
|
30
|
+
services: {
|
|
31
|
+
getPrismaAst: () => prismaAst,
|
|
32
|
+
},
|
|
33
|
+
scopeManager: null,
|
|
34
|
+
visitorKeys: {},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// Return a minimal AST on parse errors
|
|
39
|
+
return {
|
|
40
|
+
ast: {
|
|
41
|
+
type: 'Program',
|
|
42
|
+
body: [],
|
|
43
|
+
sourceType: 'module',
|
|
44
|
+
range: [0, code.length],
|
|
45
|
+
loc: {
|
|
46
|
+
start: { line: 1, column: 0 },
|
|
47
|
+
end: { line: 1, column: 0 },
|
|
48
|
+
},
|
|
49
|
+
tokens: [],
|
|
50
|
+
comments: [],
|
|
51
|
+
},
|
|
52
|
+
services: {},
|
|
53
|
+
scopeManager: null,
|
|
54
|
+
visitorKeys: {},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=prisma-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prisma-parser.js","sourceRoot":"","sources":["../../src/parsers/prisma-parser.ts"],"names":[],"mappings":";;AAoBA,wCAiDC;AArED,oDAAwD;AAgBxD;;;GAGG;AACH,SAAgB,cAAc,CAAC,IAAY,EAAE,WAAoC,EAAE;IACjF,IAAI,CAAC;QACH,sBAAsB;QACtB,MAAM,SAAS,GAAG,IAAA,sBAAS,EAAC,IAAI,CAAC,CAAC;QAElC,+BAA+B;QAC/B,MAAM,SAAS,GAAoB;YACjC,IAAI,EAAE,SAAS;YACf,IAAI,EAAE,EAAE;YACR,UAAU,EAAE,QAAQ;YACpB,KAAK,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC;YACvB,GAAG,EAAE;gBACH,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE;gBAC7B,GAAG,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE;aAClD;YACD,MAAM,EAAE,EAAE;YACV,QAAQ,EAAE,EAAE;YACZ,uCAAuC;YACvC,SAAS,EAAE,SAAS;SACrB,CAAC;QAEF,OAAO;YACL,GAAG,EAAE,SAAS;YACd,QAAQ,EAAE;gBACR,YAAY,EAAE,GAAG,EAAE,CAAC,SAAS;aAC9B;YACD,YAAY,EAAE,IAAI;YAClB,WAAW,EAAE,EAAE;SAChB,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,uCAAuC;QACvC,OAAO;YACL,GAAG,EAAE;gBACH,IAAI,EAAE,SAAS;gBACf,IAAI,EAAE,EAAE;gBACR,UAAU,EAAE,QAAQ;gBACpB,KAAK,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC;gBACvB,GAAG,EAAE;oBACH,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE;oBAC7B,GAAG,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE;iBAC5B;gBACD,MAAM,EAAE,EAAE;gBACV,QAAQ,EAAE,EAAE;aACb;YACD,QAAQ,EAAE,EAAE;YACZ,YAAY,EAAE,IAAI;YAClB,WAAW,EAAE,EAAE;SAChB,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
+
interface RuleOptions {
|
|
3
|
+
schemaDir: string;
|
|
4
|
+
allowSharedModels: boolean;
|
|
5
|
+
}
|
|
6
|
+
declare const rule: ESLintUtils.RuleModule<"crossDomainAccess" | "modelNotFound" | "configError", [RuleOptions], unknown, ESLintUtils.RuleListener>;
|
|
7
|
+
export = rule;
|
|
8
|
+
//# sourceMappingURL=no-cross-domain-prisma-access.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-cross-domain-prisma-access.d.ts","sourceRoot":"","sources":["../../src/rules/no-cross-domain-prisma-access.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAY,MAAM,0BAA0B,CAAC;AAWjE,UAAU,WAAW;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,OAAO,CAAC;CAC5B;AAyDD,QAAA,MAAM,IAAI,iIAmIR,CAAC;AAEH,SAAS,IAAI,CAAC"}
|
|
@@ -0,0 +1,204 @@
|
|
|
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 schema_parser_1 = require("../utils/schema-parser");
|
|
39
|
+
/**
|
|
40
|
+
* Check if a MemberExpression represents Prisma client access
|
|
41
|
+
*/
|
|
42
|
+
function isPrismaClientAccess(node) {
|
|
43
|
+
// Direct access: prisma.user
|
|
44
|
+
if (node.object && node.object.type === 'Identifier' && node.object.name === 'prisma') {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
// Property access: this.prisma.user, service.prisma.user
|
|
48
|
+
if (node.object && node.object.type === 'MemberExpression') {
|
|
49
|
+
return (node.object.property &&
|
|
50
|
+
node.object.property.type === 'Identifier' &&
|
|
51
|
+
node.object.property.name === 'prisma');
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Extract model name from MemberExpression
|
|
57
|
+
*/
|
|
58
|
+
function getModelNameFromMemberExpression(node) {
|
|
59
|
+
if (node.property && node.property.type === 'Identifier') {
|
|
60
|
+
return node.property.name;
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Find project root by looking for package.json
|
|
66
|
+
*/
|
|
67
|
+
function findProjectRoot(filePath) {
|
|
68
|
+
let dir = path.dirname(filePath);
|
|
69
|
+
while (dir !== path.dirname(dir)) {
|
|
70
|
+
if (fs.existsSync(path.join(dir, 'package.json'))) {
|
|
71
|
+
return dir;
|
|
72
|
+
}
|
|
73
|
+
dir = path.dirname(dir);
|
|
74
|
+
}
|
|
75
|
+
throw new Error('Could not find project root');
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* ESLint rule to prevent NestJS modules from accessing Prisma models
|
|
79
|
+
* that belong to other domains
|
|
80
|
+
*/
|
|
81
|
+
const createRule = utils_1.ESLintUtils.RuleCreator((name) => `https://github.com/synapsestudios/eslint-plugin-data-boundaries#${name}`);
|
|
82
|
+
const rule = createRule({
|
|
83
|
+
name: 'no-cross-domain-prisma-access',
|
|
84
|
+
meta: {
|
|
85
|
+
type: 'problem',
|
|
86
|
+
docs: {
|
|
87
|
+
description: 'Disallow modules from accessing Prisma models defined in other domain schema files',
|
|
88
|
+
},
|
|
89
|
+
fixable: undefined,
|
|
90
|
+
schema: [
|
|
91
|
+
{
|
|
92
|
+
type: 'object',
|
|
93
|
+
properties: {
|
|
94
|
+
schemaDir: {
|
|
95
|
+
type: 'string',
|
|
96
|
+
description: 'Directory containing Prisma schema files (relative to project root)',
|
|
97
|
+
},
|
|
98
|
+
allowSharedModels: {
|
|
99
|
+
type: 'boolean',
|
|
100
|
+
description: 'Allow access to models in shared/main schema files',
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
additionalProperties: false,
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
messages: {
|
|
107
|
+
crossDomainAccess: "Module '{{currentModule}}' cannot access '{{modelName}}' model (belongs to '{{modelDomain}}' domain). Consider using a shared service or moving the logic to the appropriate domain.",
|
|
108
|
+
modelNotFound: "Model '{{modelName}}' not found in any schema file. Ensure the model exists and schema files are properly configured.",
|
|
109
|
+
configError: "Could not determine schema directory. Please configure the 'schemaDir' option in your ESLint config.",
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
defaultOptions: [
|
|
113
|
+
{
|
|
114
|
+
schemaDir: 'prisma/schema',
|
|
115
|
+
allowSharedModels: true,
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
create(context, [options]) {
|
|
119
|
+
const filename = context.getFilename();
|
|
120
|
+
// Only process TypeScript files in modules
|
|
121
|
+
if (!filename.includes('/modules/') || !filename.match(/\.(ts|tsx)$/)) {
|
|
122
|
+
return {};
|
|
123
|
+
}
|
|
124
|
+
// Extract current module from file path
|
|
125
|
+
const currentModule = (0, schema_parser_1.extractModuleFromPath)(filename);
|
|
126
|
+
if (!currentModule) {
|
|
127
|
+
return {};
|
|
128
|
+
}
|
|
129
|
+
// Build model-to-domain mapping
|
|
130
|
+
let modelToDomain = {};
|
|
131
|
+
try {
|
|
132
|
+
let schemaDir;
|
|
133
|
+
if (path.isAbsolute(options.schemaDir)) {
|
|
134
|
+
// Use absolute path directly (for tests)
|
|
135
|
+
schemaDir = options.schemaDir;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
// Resolve relative to project root (for real usage)
|
|
139
|
+
const projectRoot = findProjectRoot(filename);
|
|
140
|
+
schemaDir = path.join(projectRoot, options.schemaDir);
|
|
141
|
+
}
|
|
142
|
+
modelToDomain = (0, schema_parser_1.buildModelToDomainMapping)(schemaDir);
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
// Report configuration error only once per file
|
|
146
|
+
let reportedConfigError = false;
|
|
147
|
+
return {
|
|
148
|
+
Program(node) {
|
|
149
|
+
if (!reportedConfigError) {
|
|
150
|
+
context.report({
|
|
151
|
+
node,
|
|
152
|
+
messageId: 'configError',
|
|
153
|
+
});
|
|
154
|
+
reportedConfigError = true;
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
// Detect property access: prisma.user, this.prisma.organization, etc.
|
|
161
|
+
MemberExpression(node) {
|
|
162
|
+
// Check if this looks like Prisma client access
|
|
163
|
+
if (!isPrismaClientAccess(node)) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
// Extract the model name from the property access
|
|
167
|
+
const modelName = getModelNameFromMemberExpression(node);
|
|
168
|
+
if (!modelName || !(0, schema_parser_1.isPrismaModelName)(modelName)) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
// Convert camelCase to PascalCase for schema lookup
|
|
172
|
+
const pascalModelName = (0, schema_parser_1.camelToPascalCase)(modelName);
|
|
173
|
+
// Check if model exists in schema files
|
|
174
|
+
const modelDomain = modelToDomain[pascalModelName];
|
|
175
|
+
if (!modelDomain) {
|
|
176
|
+
context.report({
|
|
177
|
+
node,
|
|
178
|
+
messageId: 'modelNotFound',
|
|
179
|
+
data: { modelName: pascalModelName },
|
|
180
|
+
});
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
// Allow access to shared models if configured
|
|
184
|
+
if (options.allowSharedModels && modelDomain === 'shared') {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
// Check if current module matches model domain
|
|
188
|
+
if (currentModule !== modelDomain) {
|
|
189
|
+
context.report({
|
|
190
|
+
node,
|
|
191
|
+
messageId: 'crossDomainAccess',
|
|
192
|
+
data: {
|
|
193
|
+
currentModule,
|
|
194
|
+
modelName: pascalModelName,
|
|
195
|
+
modelDomain,
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
module.exports = rule;
|
|
204
|
+
//# sourceMappingURL=no-cross-domain-prisma-access.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-cross-domain-prisma-access.js","sourceRoot":"","sources":["../../src/rules/no-cross-domain-prisma-access.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-cross-file-model-references.d.ts","sourceRoot":"","sources":["../../src/rules/no-cross-file-model-references.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAqB9B,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,UAuGhB,CAAC;AAEF,SAAS,IAAI,CAAC"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Helper function to find the line number of a field in the schema content
|
|
4
|
+
* This is a simplified implementation that finds the first occurrence
|
|
5
|
+
*/
|
|
6
|
+
function getLineNumber(content, fieldName) {
|
|
7
|
+
const lines = content.split('\n');
|
|
8
|
+
for (let i = 0; i < lines.length; i++) {
|
|
9
|
+
if (lines[i].trim().startsWith(fieldName)) {
|
|
10
|
+
return i + 1; // ESLint uses 1-based line numbers
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return 1; // Default to line 1 if not found
|
|
14
|
+
}
|
|
15
|
+
const rule = {
|
|
16
|
+
meta: {
|
|
17
|
+
type: 'problem',
|
|
18
|
+
docs: {
|
|
19
|
+
description: 'Disallow Prisma models from referencing models defined in other files',
|
|
20
|
+
category: 'Best Practices',
|
|
21
|
+
recommended: true,
|
|
22
|
+
},
|
|
23
|
+
fixable: undefined,
|
|
24
|
+
schema: [],
|
|
25
|
+
messages: {
|
|
26
|
+
crossFileReference: "Model field '{{field}}' references '{{model}}' which is not defined in this file. Cross-file model references are not allowed.",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
create(context) {
|
|
30
|
+
// Only process .prisma files
|
|
31
|
+
const filename = context.getFilename();
|
|
32
|
+
if (!filename.endsWith('.prisma')) {
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
Program(node) {
|
|
37
|
+
try {
|
|
38
|
+
// Get Prisma AST from parser services
|
|
39
|
+
const services = context.getSourceCode().parserServices;
|
|
40
|
+
if (!services || !services.getPrismaAst) {
|
|
41
|
+
// Parser doesn't support Prisma AST
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const ast = services.getPrismaAst();
|
|
45
|
+
// Extract all models defined in this file
|
|
46
|
+
const definedModels = new Set();
|
|
47
|
+
ast.list.forEach((item) => {
|
|
48
|
+
if (item.type === 'model' && item.name) {
|
|
49
|
+
definedModels.add(item.name);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
// Check each model for cross-file references
|
|
53
|
+
ast.list.forEach((item) => {
|
|
54
|
+
if (item.type === 'model' && item.properties) {
|
|
55
|
+
item.properties.forEach((property) => {
|
|
56
|
+
if (property && property.fieldType && property.name) {
|
|
57
|
+
// Check if this field references another model
|
|
58
|
+
const referencedModelName = property.fieldType;
|
|
59
|
+
// Skip primitive types (String, Int, DateTime, etc.)
|
|
60
|
+
const primitiveTypes = [
|
|
61
|
+
'String',
|
|
62
|
+
'Int',
|
|
63
|
+
'Float',
|
|
64
|
+
'Boolean',
|
|
65
|
+
'DateTime',
|
|
66
|
+
'Json',
|
|
67
|
+
'Bytes',
|
|
68
|
+
];
|
|
69
|
+
if (primitiveTypes.includes(referencedModelName)) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// Skip array types (remove [] suffix)
|
|
73
|
+
const cleanModelName = referencedModelName.replace(/\[\]$/, '');
|
|
74
|
+
// Check if the referenced model is defined in this file
|
|
75
|
+
if (!definedModels.has(cleanModelName)) {
|
|
76
|
+
// This is a cross-file model reference - report it
|
|
77
|
+
const sourceCode = context.getSourceCode();
|
|
78
|
+
const schemaContent = sourceCode.getText();
|
|
79
|
+
const fieldLine = getLineNumber(schemaContent, property.name);
|
|
80
|
+
context.report({
|
|
81
|
+
node,
|
|
82
|
+
loc: {
|
|
83
|
+
line: fieldLine,
|
|
84
|
+
column: 0,
|
|
85
|
+
},
|
|
86
|
+
messageId: 'crossFileReference',
|
|
87
|
+
data: {
|
|
88
|
+
field: property.name,
|
|
89
|
+
model: cleanModelName,
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
100
|
+
console.error(`Error parsing Prisma schema in ${filename}:`, errorMessage);
|
|
101
|
+
// Don't report ESLint errors for parsing issues
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
module.exports = rule;
|
|
108
|
+
//# sourceMappingURL=no-cross-file-model-references.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-cross-file-model-references.js","sourceRoot":"","sources":["../../src/rules/no-cross-file-model-references.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface ModelToDomainMapping {
|
|
2
|
+
[modelName: string]: string;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Parse all schema files in a directory and build model-to-domain mapping
|
|
6
|
+
*/
|
|
7
|
+
export declare function buildModelToDomainMapping(schemaDir: string): ModelToDomainMapping;
|
|
8
|
+
/**
|
|
9
|
+
* Extract module name from file path
|
|
10
|
+
*/
|
|
11
|
+
export declare function extractModuleFromPath(filePath: string): string | null;
|
|
12
|
+
/**
|
|
13
|
+
* Check if a string looks like a Prisma model name (either camelCase or PascalCase)
|
|
14
|
+
*/
|
|
15
|
+
export declare function isPrismaModelName(name: string): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Convert camelCase to PascalCase
|
|
18
|
+
*/
|
|
19
|
+
export declare function camelToPascalCase(camelCase: string): string;
|
|
20
|
+
//# sourceMappingURL=schema-parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-parser.d.ts","sourceRoot":"","sources":["../../src/utils/schema-parser.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,oBAAoB;IACnC,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;CAC7B;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,MAAM,GAAG,oBAAoB,CA6BjF;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOrE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAkCvD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE3D"}
|
|
@@ -0,0 +1,123 @@
|
|
|
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.buildModelToDomainMapping = buildModelToDomainMapping;
|
|
37
|
+
exports.extractModuleFromPath = extractModuleFromPath;
|
|
38
|
+
exports.isPrismaModelName = isPrismaModelName;
|
|
39
|
+
exports.camelToPascalCase = camelToPascalCase;
|
|
40
|
+
const prisma_ast_1 = require("@mrleebo/prisma-ast");
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const glob_1 = require("glob");
|
|
44
|
+
/**
|
|
45
|
+
* Parse all schema files in a directory and build model-to-domain mapping
|
|
46
|
+
*/
|
|
47
|
+
function buildModelToDomainMapping(schemaDir) {
|
|
48
|
+
const modelToDomain = {};
|
|
49
|
+
try {
|
|
50
|
+
// Find all .prisma files in the schema directory
|
|
51
|
+
const schemaFiles = glob_1.glob.sync(path.join(schemaDir, '**/*.prisma'));
|
|
52
|
+
for (const filePath of schemaFiles) {
|
|
53
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
54
|
+
const schema = (0, prisma_ast_1.getSchema)(content);
|
|
55
|
+
// Extract domain name from filename (e.g., auth.prisma -> auth)
|
|
56
|
+
const fileName = path.basename(filePath, '.prisma');
|
|
57
|
+
const domainName = fileName === 'schema' || fileName === 'main' ? 'shared' : fileName;
|
|
58
|
+
// Extract models from this schema file
|
|
59
|
+
schema.list.forEach((item) => {
|
|
60
|
+
if (item.type === 'model' && item.name) {
|
|
61
|
+
modelToDomain[item.name] = domainName;
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
// If we can't parse schemas, return empty mapping
|
|
68
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
69
|
+
console.warn(`Warning: Could not parse schema files in ${schemaDir}:`, errorMessage);
|
|
70
|
+
}
|
|
71
|
+
return modelToDomain;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Extract module name from file path
|
|
75
|
+
*/
|
|
76
|
+
function extractModuleFromPath(filePath) {
|
|
77
|
+
// Normalize path separators for cross-platform compatibility
|
|
78
|
+
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
79
|
+
// Match patterns like /modules/auth/... or /src/modules/organization/...
|
|
80
|
+
const moduleMatch = normalizedPath.match(/\/modules\/([^/]+)/);
|
|
81
|
+
return moduleMatch ? moduleMatch[1] : null;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Check if a string looks like a Prisma model name (either camelCase or PascalCase)
|
|
85
|
+
*/
|
|
86
|
+
function isPrismaModelName(name) {
|
|
87
|
+
// Should not be a common method/property name
|
|
88
|
+
const commonMethods = [
|
|
89
|
+
'findMany',
|
|
90
|
+
'findFirst',
|
|
91
|
+
'findUnique',
|
|
92
|
+
'create',
|
|
93
|
+
'update',
|
|
94
|
+
'delete',
|
|
95
|
+
'upsert',
|
|
96
|
+
'count',
|
|
97
|
+
'aggregate',
|
|
98
|
+
'find',
|
|
99
|
+
'get',
|
|
100
|
+
'connect',
|
|
101
|
+
'disconnect',
|
|
102
|
+
'executeRaw',
|
|
103
|
+
'queryRaw',
|
|
104
|
+
'transaction',
|
|
105
|
+
];
|
|
106
|
+
const commonProps = ['length', 'constructor', 'prototype', 'toString', 'valueOf'];
|
|
107
|
+
if (commonMethods.includes(name) || commonProps.includes(name)) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
// Must be non-empty, start with letter, contain only letters/numbers,
|
|
111
|
+
// be more than just a single letter, and not be all uppercase
|
|
112
|
+
return (name.length > 1 &&
|
|
113
|
+
/^[A-Za-z][A-Za-z0-9]*$/.test(name) &&
|
|
114
|
+
!/^\d/.test(name) &&
|
|
115
|
+
name !== name.toUpperCase()); // Not all uppercase
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Convert camelCase to PascalCase
|
|
119
|
+
*/
|
|
120
|
+
function camelToPascalCase(camelCase) {
|
|
121
|
+
return camelCase.charAt(0).toUpperCase() + camelCase.slice(1);
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=schema-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-parser.js","sourceRoot":"","sources":["../../src/utils/schema-parser.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@synapsestudios/eslint-plugin-data-boundaries",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "ESLint plugin to enforce data boundary policies in modular monoliths",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"README.md",
|
|
10
|
+
"LICENSE"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"build:watch": "tsc --watch",
|
|
15
|
+
"clean": "rm -rf dist",
|
|
16
|
+
"prebuild": "npm run clean",
|
|
17
|
+
"prepublishOnly": "npm run build && npm run lint && npm run format:check && npm test",
|
|
18
|
+
"prepack": "npm run build",
|
|
19
|
+
"test": "jest",
|
|
20
|
+
"test:watch": "jest --watch",
|
|
21
|
+
"lint": "eslint src --ext .ts",
|
|
22
|
+
"format": "prettier --write src/**/*.ts tests/**/*.ts",
|
|
23
|
+
"format:check": "prettier --check src/**/*.ts tests/**/*.ts"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"eslint",
|
|
27
|
+
"eslintplugin",
|
|
28
|
+
"eslint-plugin",
|
|
29
|
+
"prisma",
|
|
30
|
+
"data-boundaries",
|
|
31
|
+
"modular-monolith",
|
|
32
|
+
"domain-driven-design"
|
|
33
|
+
],
|
|
34
|
+
"author": "Synapse Studios",
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"eslint": ">=7.0.0"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@mrleebo/prisma-ast": "^0.12.0",
|
|
41
|
+
"@typescript-eslint/utils": "^8.0.0",
|
|
42
|
+
"glob": "^10.3.10"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/eslint": "^9.6.1",
|
|
46
|
+
"@types/jest": "^29.5.14",
|
|
47
|
+
"@types/node": "^24.1.0",
|
|
48
|
+
"@typescript-eslint/eslint-plugin": "^8.38.0",
|
|
49
|
+
"@typescript-eslint/parser": "^8.38.0",
|
|
50
|
+
"eslint": "^8.57.1",
|
|
51
|
+
"jest": "^29.7.0",
|
|
52
|
+
"prettier": "^3.6.2",
|
|
53
|
+
"ts-jest": "^29.4.0",
|
|
54
|
+
"typescript": "^5.8.3"
|
|
55
|
+
},
|
|
56
|
+
"engines": {
|
|
57
|
+
"node": ">=14.0.0"
|
|
58
|
+
},
|
|
59
|
+
"repository": {
|
|
60
|
+
"type": "git",
|
|
61
|
+
"url": "https://github.com/synapsestudios/eslint-plugin-data-boundaries.git"
|
|
62
|
+
},
|
|
63
|
+
"bugs": {
|
|
64
|
+
"url": "https://github.com/synapsestudios/eslint-plugin-data-boundaries/issues"
|
|
65
|
+
},
|
|
66
|
+
"homepage": "https://github.com/synapsestudios/eslint-plugin-data-boundaries#readme"
|
|
67
|
+
}
|