@pjts/eslint-style-rules 0.0.0-stage → 0.0.2
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 +138 -2
- package/dist/index.d.ts +38 -0
- package/dist/index.js +230 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/ast.d.ts +27 -0
- package/dist/lib/ast.js +60 -0
- package/dist/lib/ast.js.map +1 -0
- package/dist/lib/db.d.ts +13 -0
- package/dist/lib/db.js +50 -0
- package/dist/lib/db.js.map +1 -0
- package/dist/lib/messages.d.ts +13 -0
- package/dist/lib/messages.js +76 -0
- package/dist/lib/messages.js.map +1 -0
- package/dist/lib/rule.d.ts +9 -0
- package/dist/lib/rule.js +10 -0
- package/dist/lib/rule.js.map +1 -0
- package/dist/rules/app-class-suffix.d.ts +3 -0
- package/dist/rules/app-class-suffix.js +28 -0
- package/dist/rules/app-class-suffix.js.map +1 -0
- package/dist/rules/app-service-endpoints.d.ts +12 -0
- package/dist/rules/app-service-endpoints.js +79 -0
- package/dist/rules/app-service-endpoints.js.map +1 -0
- package/dist/rules/auth-access.d.ts +14 -0
- package/dist/rules/auth-access.js +122 -0
- package/dist/rules/auth-access.js.map +1 -0
- package/dist/rules/db-client-dependency.d.ts +11 -0
- package/dist/rules/db-client-dependency.js +74 -0
- package/dist/rules/db-client-dependency.js.map +1 -0
- package/dist/rules/db-writes-in-repositories.d.ts +12 -0
- package/dist/rules/db-writes-in-repositories.js +105 -0
- package/dist/rules/db-writes-in-repositories.js.map +1 -0
- package/dist/rules/decorated-class-contract.d.ts +6 -0
- package/dist/rules/decorated-class-contract.js +37 -0
- package/dist/rules/decorated-class-contract.js.map +1 -0
- package/dist/rules/entity-constructor-data.d.ts +6 -0
- package/dist/rules/entity-constructor-data.js +35 -0
- package/dist/rules/entity-constructor-data.js.map +1 -0
- package/dist/rules/event-sourced-aggregate.d.ts +15 -0
- package/dist/rules/event-sourced-aggregate.js +134 -0
- package/dist/rules/event-sourced-aggregate.js.map +1 -0
- package/dist/rules/find-one-naming.d.ts +7 -0
- package/dist/rules/find-one-naming.js +38 -0
- package/dist/rules/find-one-naming.js.map +1 -0
- package/dist/rules/import-only-in.d.ts +7 -0
- package/dist/rules/import-only-in.js +52 -0
- package/dist/rules/import-only-in.js.map +1 -0
- package/dist/rules/knex-first.d.ts +3 -0
- package/dist/rules/knex-first.js +28 -0
- package/dist/rules/knex-first.js.map +1 -0
- package/dist/rules/message-file.d.ts +18 -0
- package/dist/rules/message-file.js +118 -0
- package/dist/rules/message-file.js.map +1 -0
- package/dist/rules/message-handler.d.ts +19 -0
- package/dist/rules/message-handler.js +114 -0
- package/dist/rules/message-handler.js.map +1 -0
- package/dist/rules/message-name-reference.d.ts +7 -0
- package/dist/rules/message-name-reference.js +52 -0
- package/dist/rules/message-name-reference.js.map +1 -0
- package/dist/rules/message-names.d.ts +7 -0
- package/dist/rules/message-names.js +80 -0
- package/dist/rules/message-names.js.map +1 -0
- package/dist/rules/module-layout.d.ts +9 -0
- package/dist/rules/module-layout.js +71 -0
- package/dist/rules/module-layout.js.map +1 -0
- package/dist/rules/module-service-getters.d.ts +19 -0
- package/dist/rules/module-service-getters.js +132 -0
- package/dist/rules/module-service-getters.js.map +1 -0
- package/dist/rules/no-async-in-domain.d.ts +2 -0
- package/dist/rules/no-async-in-domain.js +116 -0
- package/dist/rules/no-async-in-domain.js.map +1 -0
- package/dist/rules/no-duplicate-sdk-types.d.ts +6 -0
- package/dist/rules/no-duplicate-sdk-types.js +40 -0
- package/dist/rules/no-duplicate-sdk-types.js.map +1 -0
- package/dist/rules/no-entity-assembly-in-app.d.ts +7 -0
- package/dist/rules/no-entity-assembly-in-app.js +37 -0
- package/dist/rules/no-entity-assembly-in-app.js.map +1 -0
- package/dist/rules/no-entity-mutation-outside-domain.d.ts +11 -0
- package/dist/rules/no-entity-mutation-outside-domain.js +68 -0
- package/dist/rules/no-entity-mutation-outside-domain.js.map +1 -0
- package/dist/rules/no-message-construction-outside-domain.d.ts +15 -0
- package/dist/rules/no-message-construction-outside-domain.js +45 -0
- package/dist/rules/no-message-construction-outside-domain.js.map +1 -0
- package/dist/rules/no-reconstitute.d.ts +3 -0
- package/dist/rules/no-reconstitute.js +22 -0
- package/dist/rules/no-reconstitute.js.map +1 -0
- package/dist/rules/port-file.d.ts +9 -0
- package/dist/rules/port-file.js +94 -0
- package/dist/rules/port-file.js.map +1 -0
- package/dist/rules/port-has-adapters.d.ts +9 -0
- package/dist/rules/port-has-adapters.js +62 -0
- package/dist/rules/port-has-adapters.js.map +1 -0
- package/dist/rules/repository-naming.d.ts +19 -0
- package/dist/rules/repository-naming.js +122 -0
- package/dist/rules/repository-naming.js.map +1 -0
- package/dist/rules/repository-serialization.d.ts +6 -0
- package/dist/rules/repository-serialization.js +50 -0
- package/dist/rules/repository-serialization.js.map +1 -0
- package/dist/rules/return-entities.d.ts +6 -0
- package/dist/rules/return-entities.js +38 -0
- package/dist/rules/return-entities.js.map +1 -0
- package/dist/rules/save-contract.d.ts +7 -0
- package/dist/rules/save-contract.js +41 -0
- package/dist/rules/save-contract.js.map +1 -0
- package/dist/rules/test-location.d.ts +14 -0
- package/dist/rules/test-location.js +94 -0
- package/dist/rules/test-location.js.map +1 -0
- package/dist/rules/throw-app-error.d.ts +12 -0
- package/dist/rules/throw-app-error.js +68 -0
- package/dist/rules/throw-app-error.js.map +1 -0
- package/dist/rules/trackable-repository.d.ts +10 -0
- package/dist/rules/trackable-repository.js +97 -0
- package/dist/rules/trackable-repository.js.map +1 -0
- package/package.json +46 -4
package/README.md
CHANGED
|
@@ -1,3 +1,139 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @pjts/eslint-style-rules
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
ESLint rules and a flat-config preset that enforce the pjts DDD layout and conventions: layer
|
|
4
|
+
boundaries, module structure, repositories, ports, endpoints, errors and tests.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
npm i -D @pjts/eslint-style-rules eslint typescript-eslint
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
The preset assumes the pjts layout — `src/modules/<module>/{app,domain,infrastructure}`, one
|
|
13
|
+
`<Module>Module.ts` per module, tests next to the file they test.
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
// eslint.config.mjs
|
|
17
|
+
import pjts from '@pjts/eslint-style-rules';
|
|
18
|
+
|
|
19
|
+
export default [
|
|
20
|
+
{ ignores: ['dist/**'] },
|
|
21
|
+
...pjts.configs.ddd,
|
|
22
|
+
];
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Different paths:
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
...pjts.createDddConfig({
|
|
29
|
+
src: 'src',
|
|
30
|
+
modules: 'src/modules',
|
|
31
|
+
exclude: ['src/infrastructure/database/**', 'src/deployments/**'], // migrations, composition root
|
|
32
|
+
uuidPaths: ['/domain/', '/src/common/values/'],
|
|
33
|
+
messages: 'src/common/messages', // one <Name>.ts per message + index.ts (MessageNames)
|
|
34
|
+
authAccess: { allow: ['NewsletterService.findMany'] }, // opt-in: @AuthAccess('scope:level') on every AuthToken param
|
|
35
|
+
storages: {
|
|
36
|
+
pg: { prefix: 'Pg', imports: ['@pjts/infra#PostgresDbClient', 'knex', 'pg'] },
|
|
37
|
+
nats: { prefix: 'Nats', imports: ['nats', '@nats-io/*'] },
|
|
38
|
+
memory: { prefix: 'InMemory' },
|
|
39
|
+
},
|
|
40
|
+
}),
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Rules can also be used one by one: `plugins: { pjts }`, then `'pjts/<rule>': 'error'`.
|
|
44
|
+
|
|
45
|
+
### Adopting it in an existing codebase
|
|
46
|
+
|
|
47
|
+
Freeze existing violations with ESLint's bulk suppressions (ESLint ≥ 9.24) and burn them down:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npx eslint src/ --suppress-all # writes eslint-suppressions.json
|
|
51
|
+
npx eslint src/ --prune-suppressions # after fixing old violations
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
New code must comply; a file may not gain violations of a rule it already has. A justified
|
|
55
|
+
exception is an inline disable with a reason:
|
|
56
|
+
`// eslint-disable-next-line pjts/save-contract -- RETURNING sequence_number`.
|
|
57
|
+
|
|
58
|
+
## Rules
|
|
59
|
+
|
|
60
|
+
### Domain
|
|
61
|
+
|
|
62
|
+
| Rule | What it checks |
|
|
63
|
+
|---|---|
|
|
64
|
+
| `event-sourced-aggregate` | `extends EventSourceable<XEvent>`: `@trackable`, typed event union, `protected apply` with a `case` for every emitted `MessageNames.X`, `apply` replay-safe (no emit / throw / clock / id minting / randomness). Option `stateChangesInApply`: `this.x = …` only in the constructor and `apply` |
|
|
65
|
+
| `no-async-in-domain` | no `async`, `await` or `Promise<…>` — except awaiting/returning a call from `allowedModules` (`bcrypt`) |
|
|
66
|
+
| `entity-constructor-data` | entity constructors take one `<Entity>Data` object (decorated primitives, `…Error`s skipped) |
|
|
67
|
+
| `decorated-class-contract` | classes with a configured decorator implement the listed methods (`@StringDomainPrimitive` → `toJSON`) |
|
|
68
|
+
| `no-reconstitute` | no `reconstitute` definition or call — repositories deserialize, services call `existing.update()` |
|
|
69
|
+
|
|
70
|
+
### Application
|
|
71
|
+
|
|
72
|
+
| Rule | What it checks |
|
|
73
|
+
|---|---|
|
|
74
|
+
| `app-class-suffix` | app-layer classes end with `Service` \| `Handler` \| `View` |
|
|
75
|
+
| `app-service-endpoints` | every public method of an `@AppService` has exactly one `@Get`/`@Post`/`@Put`/`@Patch`/`@Delete` and every parameter a request-binding decorator (`@Auth`, `@Body`, `@UrlParam`, `@QueryParam(s)`, `@Header`, `@Ip`, `@File`); non-public methods carry none |
|
|
76
|
+
| `return-entities` | no `.toJSON()` and no `Promise<…ResponseDto/Response/JSON>` — return entities, the framework serializes |
|
|
77
|
+
| `no-entity-assembly-in-app` | no `new Entity({...})` / `Entity.create({...})` from domain imports — use `Entity.fromDTO(...)` |
|
|
78
|
+
| `find-one-naming` | the private owner-scoped fetch helper (`AuthToken` first, throws `AppError.notFound`) is `findOne` |
|
|
79
|
+
| `save-contract` | `save()`'s result is never used; `save` returns `Promise<void>` |
|
|
80
|
+
| `no-entity-mutation-outside-domain` | a loaded entity (`await this.findOne(…)` / `await this.<repo>.find…(…)`) changes through its methods, not `entity.field = …` |
|
|
81
|
+
| `auth-access` | (opt-in) every `AuthToken` parameter of an `@AppService` uses `@AuthAccess('scope:read', …)` with literal rights; plain `@Auth` only for `allow`ed `Service.method` / `File.method` (stale entries reported); `authToken`, or `_authToken` when unread |
|
|
82
|
+
|
|
83
|
+
### Messages
|
|
84
|
+
|
|
85
|
+
| Rule | What it checks |
|
|
86
|
+
|---|---|
|
|
87
|
+
| `message-file` | one message per file: an exported interface named like the file, extending `Message` (or a `…Base`), `name: MessageNames.<FileName>`; helper payload types may stay |
|
|
88
|
+
| `message-names` | `MessageNames` in the messages `index.ts`: every key has a file, every message file has a key and a re-export, values are the key in SCREAMING_SNAKE_CASE |
|
|
89
|
+
| `no-message-construction-outside-domain` | an object literal pinning `name: MessageNames.X` exists only under `domain/` — aggregates return or emit message bodies, the app layer passes them on |
|
|
90
|
+
| `message-name-reference` | message names through the enum — no `'CALENDAR_BOOKED'` in `name:`, `case` of a `switch (x.name)`, or a handler's `super([...])` |
|
|
91
|
+
| `message-handler` | `MessageHandlerBase` subclasses: `…Handler` in `app/`, `super([MessageNames.X, …])`, subscribed names = handled `case`s, protected typed `handleMessage`, depends only on ports or app classes (`allowedTypes` for e.g. `MessageInbox`) |
|
|
92
|
+
|
|
93
|
+
### Infrastructure & database
|
|
94
|
+
|
|
95
|
+
| Rule | What it checks |
|
|
96
|
+
|---|---|
|
|
97
|
+
| `repository-naming` | repositories live in `infrastructure/repositories/<storage>/` (several: `pg+nats/`), prefixed after it (`Pg`, `PgNats`, `InMemory`); the folder matches the file's imports; one repository per file, named after it |
|
|
98
|
+
| `repository-serialization` | `Pg*Repository` has `serialize` + `deserialize` (no `toDomain`/`toRow`/…); `deserialize` returns `new Entity(...)` |
|
|
99
|
+
| `knex-first` | single-record reads end with `.first()`, not `const [row] = await this.db(…)` |
|
|
100
|
+
| `trackable-repository` | repositories on pjx `RepositoryBase` / `OutboxRepository`: every `deserialize…` is `@reconstitute` (else `save()` inserts a duplicate); an event-sourced aggregate's repository extends `…OutboxRepository` |
|
|
101
|
+
| `db-client-dependency` | only `…Pg…Repository` classes (Pg anywhere in the prefix) take `PostgresDbClient` / knex / pg as a dependency |
|
|
102
|
+
| `db-writes-in-repositories` | insert/update/delete (+ truncate, upsert, increment, decrement, onConflict, transactions, writing raw SQL) happen only in a `…Pg…Repository` under `infrastructure/` |
|
|
103
|
+
|
|
104
|
+
### Structure
|
|
105
|
+
|
|
106
|
+
| Rule | What it checks |
|
|
107
|
+
|---|---|
|
|
108
|
+
| `module-layout` | only `rootFiles` at a module root (`<Module>Module.ts`, `<Module>MessageHandlers.ts`, `index.ts`); allowed top-level and `app/` subfolders; required entries exist |
|
|
109
|
+
| `module-service-getters` | a `…Module`'s public getters return only `new X(...)` of a class from its own `./app/`, named `x` (camelCase) |
|
|
110
|
+
| `port-file` | ports live in `app/ports/<Name>Port.ts`, one exported interface per file named like it; only types; no `…Port` elsewhere |
|
|
111
|
+
| `port-has-adapters` | every `XRepositoryPort` has at least one implementation in the module's infrastructure |
|
|
112
|
+
| `test-location` | `<TestedFile>.<optional-suffix>.test.ts` sits next to `<TestedFile>.ts`; no `__tests__/` |
|
|
113
|
+
|
|
114
|
+
### Cross-cutting
|
|
115
|
+
|
|
116
|
+
| Rule | What it checks |
|
|
117
|
+
|---|---|
|
|
118
|
+
| `throw-app-error` | only `throw AppError.x(...)` / `throw new AppError(...)` from `@pjts/core`; rethrowing the caught error is allowed |
|
|
119
|
+
| `import-only-in` | a package is imported only under given path fragments (`uuid` → `/domain/`) |
|
|
120
|
+
| `no-duplicate-sdk-types` | frontend: no local type named like one in the generated client's `types.d.ts` |
|
|
121
|
+
|
|
122
|
+
## Limits
|
|
123
|
+
|
|
124
|
+
- Rules see one file at a time. `module-layout`, `port-has-adapters`, `test-location`,
|
|
125
|
+
`message-names` and `trackable-repository` read
|
|
126
|
+
sibling files from disk, so in an editor they refresh only when the reporting file is re-linted;
|
|
127
|
+
the CLI is always accurate. Don't use `eslint --cache` with them.
|
|
128
|
+
- `no-entity-assembly-in-app` and `no-duplicate-sdk-types` are name-based heuristics.
|
|
129
|
+
|
|
130
|
+
## Development
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
npm run test # RuleTester suites, one <rule>.test.ts next to each rule
|
|
134
|
+
npm run lint
|
|
135
|
+
npm run build # tsc → dist/
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Adding a rule: `src/rules/<name>.ts` (`defineRule({ meta, create })`, policy in options), its
|
|
139
|
+
`<name>.test.ts`, register it in `src/index.ts`, add it to the preset if it is part of the policy.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ESLint, Linter } from 'eslint';
|
|
2
|
+
interface DddConfigOptions {
|
|
3
|
+
/** Source root, relative to the config file. Default `src`. */
|
|
4
|
+
src?: string;
|
|
5
|
+
/** Folder holding one directory per module, relative to the config file. Default `src/modules`. */
|
|
6
|
+
modules?: string;
|
|
7
|
+
/** Excluded from the cross-cutting rules (DB, errors, uuid): migrations, composition root. */
|
|
8
|
+
exclude?: string[];
|
|
9
|
+
/** Path fragments where `uuid` may be imported. */
|
|
10
|
+
uuidPaths?: string[];
|
|
11
|
+
/** Messages folder (one `<Name>.ts` per message + index.ts with `MessageNames`). Default `<src>/common/messages`. */
|
|
12
|
+
messages?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Require `@AuthAccess('scope:level')` on every AuthToken parameter of @AppService endpoints.
|
|
15
|
+
* Off by default; `allow` lists `Service.method` (or `File.method`) entries still on plain `@Auth`.
|
|
16
|
+
*/
|
|
17
|
+
authAccess?: false | {
|
|
18
|
+
allow?: string[];
|
|
19
|
+
rightPattern?: string;
|
|
20
|
+
decorator?: string;
|
|
21
|
+
checkParamName?: boolean;
|
|
22
|
+
};
|
|
23
|
+
/** Storage folders of `infrastructure/repositories/<storage>/`, their prefixes and import signatures. */
|
|
24
|
+
storages?: Record<string, {
|
|
25
|
+
prefix: string;
|
|
26
|
+
imports?: string[];
|
|
27
|
+
}>;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The pjts DDD layer policy as a flat config. Every module in `<modules>/<name>/` has `app/`,
|
|
31
|
+
* `domain/` and `infrastructure/`; tests sit next to the file they test.
|
|
32
|
+
*/
|
|
33
|
+
declare const createDddConfig: (options?: DddConfigOptions) => Linter.Config[];
|
|
34
|
+
declare const _default: ESLint.Plugin & {
|
|
35
|
+
createDddConfig: typeof createDddConfig;
|
|
36
|
+
configs: Record<string, Linter.Config[]>;
|
|
37
|
+
};
|
|
38
|
+
export = _default;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const tslib_1 = require("tslib");
|
|
3
|
+
const app_class_suffix_1 = tslib_1.__importDefault(require("./rules/app-class-suffix"));
|
|
4
|
+
const auth_access_1 = tslib_1.__importDefault(require("./rules/auth-access"));
|
|
5
|
+
const event_sourced_aggregate_1 = tslib_1.__importDefault(require("./rules/event-sourced-aggregate"));
|
|
6
|
+
const message_file_1 = tslib_1.__importDefault(require("./rules/message-file"));
|
|
7
|
+
const message_handler_1 = tslib_1.__importDefault(require("./rules/message-handler"));
|
|
8
|
+
const message_name_reference_1 = tslib_1.__importDefault(require("./rules/message-name-reference"));
|
|
9
|
+
const message_names_1 = tslib_1.__importDefault(require("./rules/message-names"));
|
|
10
|
+
const no_entity_mutation_outside_domain_1 = tslib_1.__importDefault(require("./rules/no-entity-mutation-outside-domain"));
|
|
11
|
+
const no_message_construction_outside_domain_1 = tslib_1.__importDefault(require("./rules/no-message-construction-outside-domain"));
|
|
12
|
+
const trackable_repository_1 = tslib_1.__importDefault(require("./rules/trackable-repository"));
|
|
13
|
+
const app_service_endpoints_1 = tslib_1.__importDefault(require("./rules/app-service-endpoints"));
|
|
14
|
+
const db_client_dependency_1 = tslib_1.__importDefault(require("./rules/db-client-dependency"));
|
|
15
|
+
const db_writes_in_repositories_1 = tslib_1.__importDefault(require("./rules/db-writes-in-repositories"));
|
|
16
|
+
const decorated_class_contract_1 = tslib_1.__importDefault(require("./rules/decorated-class-contract"));
|
|
17
|
+
const entity_constructor_data_1 = tslib_1.__importDefault(require("./rules/entity-constructor-data"));
|
|
18
|
+
const find_one_naming_1 = tslib_1.__importDefault(require("./rules/find-one-naming"));
|
|
19
|
+
const import_only_in_1 = tslib_1.__importDefault(require("./rules/import-only-in"));
|
|
20
|
+
const knex_first_1 = tslib_1.__importDefault(require("./rules/knex-first"));
|
|
21
|
+
const module_layout_1 = tslib_1.__importDefault(require("./rules/module-layout"));
|
|
22
|
+
const module_service_getters_1 = tslib_1.__importDefault(require("./rules/module-service-getters"));
|
|
23
|
+
const no_async_in_domain_1 = tslib_1.__importDefault(require("./rules/no-async-in-domain"));
|
|
24
|
+
const no_duplicate_sdk_types_1 = tslib_1.__importDefault(require("./rules/no-duplicate-sdk-types"));
|
|
25
|
+
const no_entity_assembly_in_app_1 = tslib_1.__importDefault(require("./rules/no-entity-assembly-in-app"));
|
|
26
|
+
const no_reconstitute_1 = tslib_1.__importDefault(require("./rules/no-reconstitute"));
|
|
27
|
+
const port_file_1 = tslib_1.__importDefault(require("./rules/port-file"));
|
|
28
|
+
const port_has_adapters_1 = tslib_1.__importDefault(require("./rules/port-has-adapters"));
|
|
29
|
+
const repository_naming_1 = tslib_1.__importDefault(require("./rules/repository-naming"));
|
|
30
|
+
const repository_serialization_1 = tslib_1.__importDefault(require("./rules/repository-serialization"));
|
|
31
|
+
const return_entities_1 = tslib_1.__importDefault(require("./rules/return-entities"));
|
|
32
|
+
const save_contract_1 = tslib_1.__importDefault(require("./rules/save-contract"));
|
|
33
|
+
const test_location_1 = tslib_1.__importDefault(require("./rules/test-location"));
|
|
34
|
+
const throw_app_error_1 = tslib_1.__importDefault(require("./rules/throw-app-error"));
|
|
35
|
+
const rules = {
|
|
36
|
+
'auth-access': auth_access_1.default,
|
|
37
|
+
'event-sourced-aggregate': event_sourced_aggregate_1.default,
|
|
38
|
+
'message-file': message_file_1.default,
|
|
39
|
+
'message-handler': message_handler_1.default,
|
|
40
|
+
'message-name-reference': message_name_reference_1.default,
|
|
41
|
+
'message-names': message_names_1.default,
|
|
42
|
+
'no-entity-mutation-outside-domain': no_entity_mutation_outside_domain_1.default,
|
|
43
|
+
'no-message-construction-outside-domain': no_message_construction_outside_domain_1.default,
|
|
44
|
+
'trackable-repository': trackable_repository_1.default,
|
|
45
|
+
'app-class-suffix': app_class_suffix_1.default,
|
|
46
|
+
'app-service-endpoints': app_service_endpoints_1.default,
|
|
47
|
+
'db-client-dependency': db_client_dependency_1.default,
|
|
48
|
+
'db-writes-in-repositories': db_writes_in_repositories_1.default,
|
|
49
|
+
'decorated-class-contract': decorated_class_contract_1.default,
|
|
50
|
+
'entity-constructor-data': entity_constructor_data_1.default,
|
|
51
|
+
'find-one-naming': find_one_naming_1.default,
|
|
52
|
+
'import-only-in': import_only_in_1.default,
|
|
53
|
+
'knex-first': knex_first_1.default,
|
|
54
|
+
'module-layout': module_layout_1.default,
|
|
55
|
+
'module-service-getters': module_service_getters_1.default,
|
|
56
|
+
'no-async-in-domain': no_async_in_domain_1.default,
|
|
57
|
+
'no-duplicate-sdk-types': no_duplicate_sdk_types_1.default,
|
|
58
|
+
'no-entity-assembly-in-app': no_entity_assembly_in_app_1.default,
|
|
59
|
+
'no-reconstitute': no_reconstitute_1.default,
|
|
60
|
+
'port-file': port_file_1.default,
|
|
61
|
+
'port-has-adapters': port_has_adapters_1.default,
|
|
62
|
+
'repository-naming': repository_naming_1.default,
|
|
63
|
+
'repository-serialization': repository_serialization_1.default,
|
|
64
|
+
'return-entities': return_entities_1.default,
|
|
65
|
+
'save-contract': save_contract_1.default,
|
|
66
|
+
'test-location': test_location_1.default,
|
|
67
|
+
'throw-app-error': throw_app_error_1.default,
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* The pjts DDD layer policy as a flat config. Every module in `<modules>/<name>/` has `app/`,
|
|
71
|
+
* `domain/` and `infrastructure/`; tests sit next to the file they test.
|
|
72
|
+
*/
|
|
73
|
+
const createDddConfig = (options = {}) => {
|
|
74
|
+
// typescript-eslint is a peer dependency: only needed when the preset is used.
|
|
75
|
+
const tseslint = require('typescript-eslint');
|
|
76
|
+
const { src = 'src', modules = 'src/modules', exclude = [`${src}/infrastructure/database/**`, `${src}/deployments/**`], uuidPaths = ['/domain/', `/${src}/common/values/`], messages = `${src}/common/messages`, authAccess = false, storages = {
|
|
77
|
+
pg: { prefix: 'Pg', imports: ['@pjts/infra#PostgresDbClient', 'knex', 'pg'] },
|
|
78
|
+
nats: { prefix: 'Nats', imports: ['nats', '@nats-io/*'] },
|
|
79
|
+
memory: { prefix: 'InMemory' },
|
|
80
|
+
}, } = options;
|
|
81
|
+
const module = `${modules}/*`;
|
|
82
|
+
const infrastructureImport = { group: ['**/infrastructure', '**/infrastructure/**'], message: 'Depend on a port in app/ports, not on infrastructure.' };
|
|
83
|
+
return [
|
|
84
|
+
{
|
|
85
|
+
files: [`${src}/**/*.ts`],
|
|
86
|
+
languageOptions: { parser: tseslint.parser },
|
|
87
|
+
plugins: { pjts: plugin, '@typescript-eslint': tseslint.plugin },
|
|
88
|
+
rules: {
|
|
89
|
+
'pjts/no-reconstitute': 'error',
|
|
90
|
+
// @AppService public methods are endpoints: one HTTP decorator, every parameter request-bound.
|
|
91
|
+
'pjts/app-service-endpoints': 'error',
|
|
92
|
+
// Fields, constructor, static factories (`fromDTO`, `create`), then instance methods.
|
|
93
|
+
'@typescript-eslint/member-ordering': ['error', { default: ['field', 'constructor', 'static-method', 'instance-method'] }],
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
// pjts DTOs: constructor(data) first, then the validated (decorated) fields, then toJSON.
|
|
98
|
+
files: [`${module}/app/dto/**/*.ts`],
|
|
99
|
+
rules: { '@typescript-eslint/member-ordering': ['error', { default: ['constructor', 'field', 'static-method', 'instance-method'] }] },
|
|
100
|
+
},
|
|
101
|
+
// ── Cross-cutting: database, errors, identifiers ──────────────────────────
|
|
102
|
+
{
|
|
103
|
+
files: [`${src}/**/*.ts`],
|
|
104
|
+
ignores: exclude,
|
|
105
|
+
rules: {
|
|
106
|
+
// Only …Pg…Repository classes depend on the client, and only they write through it.
|
|
107
|
+
'pjts/db-client-dependency': 'error',
|
|
108
|
+
'pjts/db-writes-in-repositories': 'error',
|
|
109
|
+
// Every thrown error is an AppError (rethrowing a caught error unchanged is fine).
|
|
110
|
+
'pjts/throw-app-error': 'error',
|
|
111
|
+
// Identities are minted by the domain.
|
|
112
|
+
'pjts/import-only-in': ['error', {
|
|
113
|
+
modules: { uuid: { paths: uuidPaths, reason: 'ids are created by domain factories (`TaskId.create()`), not by services or repositories.' } },
|
|
114
|
+
}],
|
|
115
|
+
// Message names only through the MessageNames enum.
|
|
116
|
+
'pjts/message-name-reference': 'error',
|
|
117
|
+
// Message bodies are built by domain methods (returned or emitted), never in app/infrastructure.
|
|
118
|
+
'pjts/no-message-construction-outside-domain': 'error',
|
|
119
|
+
...(authAccess ? { 'pjts/auth-access': ['error', authAccess] } : {}),
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
// ── Messages: one per file, MessageNames in step ──────────────────────────
|
|
123
|
+
{
|
|
124
|
+
files: [`${messages}/*.ts`],
|
|
125
|
+
rules: { 'pjts/message-file': 'error', 'pjts/message-names': 'error' },
|
|
126
|
+
},
|
|
127
|
+
// ── Module structure ──────────────────────────────────────────────────────
|
|
128
|
+
{
|
|
129
|
+
files: [`${module}/**/*.ts`],
|
|
130
|
+
rules: {
|
|
131
|
+
'pjts/module-layout': ['error', {
|
|
132
|
+
folders: ['app', 'domain', 'infrastructure', 'testing'],
|
|
133
|
+
subfolders: { app: ['dto', 'ports'] },
|
|
134
|
+
required: ['app', 'index.ts'],
|
|
135
|
+
// The module's MessageHandlerBase composition root sits next to its HTTP module.
|
|
136
|
+
rootFiles: ['{Module}Module.ts', '{Module}MessageHandlers.ts', 'index.ts'],
|
|
137
|
+
}],
|
|
138
|
+
// Ports: app/ports/<Name>Port.ts, one exported interface named like the file; `…Port` nowhere else.
|
|
139
|
+
'pjts/port-file': 'error',
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
// Modules expose their own app-layer classes — and nothing else — as camelCase getters.
|
|
144
|
+
files: [`${module}/*Module.ts`],
|
|
145
|
+
rules: { 'pjts/module-service-getters': ['error', { suffixes: ['Service', 'View', 'Handler'] }] },
|
|
146
|
+
},
|
|
147
|
+
// ── Domain layer: pure, synchronous, no I/O ───────────────────────────────
|
|
148
|
+
{
|
|
149
|
+
files: [`${module}/domain/**/*.ts`],
|
|
150
|
+
rules: {
|
|
151
|
+
'pjts/no-async-in-domain': ['error', { allowedModules: ['bcrypt'] }],
|
|
152
|
+
'pjts/decorated-class-contract': ['error', {
|
|
153
|
+
contracts: { StringDomainPrimitive: ['toJSON'], NumberDomainPrimitive: ['toJSON'], ObjectDomainPrimitive: ['toJSON'] },
|
|
154
|
+
}],
|
|
155
|
+
'pjts/entity-constructor-data': 'error',
|
|
156
|
+
// EventSourceable: @trackable, typed events, replay-safe apply() handling every emitted name.
|
|
157
|
+
'pjts/event-sourced-aggregate': 'error',
|
|
158
|
+
'no-restricted-imports': ['error', {
|
|
159
|
+
patterns: [
|
|
160
|
+
{ group: ['**/infrastructure', '**/infrastructure/**'], message: 'The domain must not depend on infrastructure.' },
|
|
161
|
+
{ group: ['knex', 'pg', '@pjts/infra', 'axios', '@aws-sdk/*', '@anthropic-ai/*', '@elevenlabs/*'], message: 'The domain must stay free of I/O libraries.' },
|
|
162
|
+
// Importing app-layer DTOs into the domain is fine (Entity.fromDTO) — nothing else from app/.
|
|
163
|
+
{ group: ['**/app', '**/app/**', '!**/app/dto', '!**/app/dto/**'], message: 'The domain may import app-layer DTOs only.' },
|
|
164
|
+
],
|
|
165
|
+
}],
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
// Value objects, domain services and events aren't entities.
|
|
170
|
+
files: [`${module}/domain/values/**/*.ts`, `${module}/domain/services/**/*.ts`, `${module}/domain/events/**/*.ts`],
|
|
171
|
+
rules: { 'pjts/entity-constructor-data': 'off' },
|
|
172
|
+
},
|
|
173
|
+
// ── Application layer ─────────────────────────────────────────────────────
|
|
174
|
+
{
|
|
175
|
+
files: [`${module}/app/*.ts`],
|
|
176
|
+
ignores: ['**/index.ts'],
|
|
177
|
+
rules: {
|
|
178
|
+
'pjts/app-class-suffix': ['error', { allowed: ['Service', 'Handler', 'View'] }],
|
|
179
|
+
'pjts/return-entities': 'error',
|
|
180
|
+
'pjts/no-entity-assembly-in-app': 'error',
|
|
181
|
+
'pjts/find-one-naming': 'error',
|
|
182
|
+
// MessageHandlerBase consumers: …Handler, subscribed = handled, ports/app classes only.
|
|
183
|
+
'pjts/message-handler': 'error',
|
|
184
|
+
// Loaded entities change through their own methods (events, change tracking, invariants).
|
|
185
|
+
'pjts/no-entity-mutation-outside-domain': 'error',
|
|
186
|
+
'no-restricted-imports': ['error', { patterns: [infrastructureImport] }],
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
files: [`${module}/app/ports/*.ts`],
|
|
191
|
+
rules: { 'pjts/port-has-adapters': 'error' },
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
files: [`${module}/app/**/*.ts`, `${module}/infrastructure/**/*.ts`],
|
|
195
|
+
rules: { 'pjts/save-contract': 'error' },
|
|
196
|
+
},
|
|
197
|
+
// ── Infrastructure layer ──────────────────────────────────────────────────
|
|
198
|
+
{
|
|
199
|
+
files: [`${module}/infrastructure/**/*.ts`],
|
|
200
|
+
rules: {
|
|
201
|
+
// One folder per infrastructural dependency set: repositories/pg/, repositories/pg+nats/ → PgNats…Repository.
|
|
202
|
+
'pjts/repository-naming': ['error', { storages, requireImplementationFolder: true, fileNameMatchesClass: true }],
|
|
203
|
+
'pjts/repository-serialization': ['error', { classPattern: '^Pg\\w*Repository$' }],
|
|
204
|
+
'pjts/knex-first': 'error',
|
|
205
|
+
// RepositoryBase/OutboxRepository: @reconstitute on deserialize, event-sourced → OutboxRepository.
|
|
206
|
+
'pjts/trackable-repository': 'error',
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
// ── Tests: next to the tested file; that is the only rule applied to them ──
|
|
210
|
+
{
|
|
211
|
+
files: [`${src}/**/*.test.ts`],
|
|
212
|
+
rules: {
|
|
213
|
+
...Object.fromEntries(Object.keys(rules).map((rule) => [`pjts/${rule}`, 'off'])),
|
|
214
|
+
'@typescript-eslint/member-ordering': 'off',
|
|
215
|
+
'no-restricted-imports': 'off',
|
|
216
|
+
'pjts/test-location': 'error',
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
];
|
|
220
|
+
};
|
|
221
|
+
const plugin = {
|
|
222
|
+
meta: { name: '@pjts/eslint-style-rules' },
|
|
223
|
+
rules,
|
|
224
|
+
configs: {},
|
|
225
|
+
createDddConfig,
|
|
226
|
+
};
|
|
227
|
+
// `configs.ddd` is built on first access so the plugin itself loads without typescript-eslint.
|
|
228
|
+
Object.defineProperty(plugin.configs, 'ddd', { enumerable: true, get: () => createDddConfig() });
|
|
229
|
+
module.exports = plugin;
|
|
230
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Forward-slash path of the file being linted. */
|
|
2
|
+
export declare const filePath: (context: any) => any;
|
|
3
|
+
/** `apps/api/src/modules/<module>/<rest>` → `{ module, rest: ['app', 'TaskService.ts'] }`, or null. */
|
|
4
|
+
export declare const moduleLocation: (context: any) => {
|
|
5
|
+
module: any;
|
|
6
|
+
rest: any;
|
|
7
|
+
root: string;
|
|
8
|
+
} | null;
|
|
9
|
+
export declare const methods: (classNode: any) => any;
|
|
10
|
+
export declare const memberName: (member: any) => any;
|
|
11
|
+
export declare const findMethod: (classNode: any, name: any, isStatic?: boolean) => any;
|
|
12
|
+
/** Decorator names on a class: `@Foo` and `@Foo()` both give `Foo`. */
|
|
13
|
+
export declare const decoratorNames: (classNode: any) => any;
|
|
14
|
+
/** Type name of an annotation such as `data: TaskData` → `TaskData`. */
|
|
15
|
+
export declare const annotationTypeName: (annotation: any) => any;
|
|
16
|
+
/**
|
|
17
|
+
* `Promise<X>` return annotation → the X node; `undefined` when the annotation is not a Promise
|
|
18
|
+
* (or missing), so callers can tell "not async-typed" from "Promise of nothing".
|
|
19
|
+
*/
|
|
20
|
+
export declare const promiseTypeArgument: (returnType: any) => any;
|
|
21
|
+
/** `foo.bar(...)` → 'bar'. */
|
|
22
|
+
export declare const calledMethodName: (call: any) => any;
|
|
23
|
+
export declare const unwrapAwait: (node: any) => any;
|
|
24
|
+
/** `kebab-case` → `PascalCase`. */
|
|
25
|
+
export declare const pascalCase: (kebab: any) => any;
|
|
26
|
+
/** `Array.prototype.findLast` (ES2023) for the ES2021 target. */
|
|
27
|
+
export declare const findLast: <T>(items: readonly T[], predicate: (item: T) => boolean) => T | undefined;
|
package/dist/lib/ast.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findLast = exports.pascalCase = exports.unwrapAwait = exports.calledMethodName = exports.promiseTypeArgument = exports.annotationTypeName = exports.decoratorNames = exports.findMethod = exports.memberName = exports.methods = exports.moduleLocation = exports.filePath = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const node_path_1 = tslib_1.__importDefault(require("node:path"));
|
|
6
|
+
/** Forward-slash path of the file being linted. */
|
|
7
|
+
const filePath = (context) => context.filename.split(node_path_1.default.sep).join('/');
|
|
8
|
+
exports.filePath = filePath;
|
|
9
|
+
/** `apps/api/src/modules/<module>/<rest>` → `{ module, rest: ['app', 'TaskService.ts'] }`, or null. */
|
|
10
|
+
const moduleLocation = (context) => {
|
|
11
|
+
const match = (0, exports.filePath)(context).match(/\/src\/modules\/([^/]+)\/(.+)$/);
|
|
12
|
+
return match ? { module: match[1], rest: match[2].split('/'), root: (0, exports.filePath)(context).slice(0, match.index) + `/src/modules/${match[1]}` } : null;
|
|
13
|
+
};
|
|
14
|
+
exports.moduleLocation = moduleLocation;
|
|
15
|
+
const methods = (classNode) => classNode.body.body.filter((member) => member.type === 'MethodDefinition');
|
|
16
|
+
exports.methods = methods;
|
|
17
|
+
const memberName = (member) => member.key?.name ?? member.key?.value ?? null;
|
|
18
|
+
exports.memberName = memberName;
|
|
19
|
+
const findMethod = (classNode, name, isStatic) => (0, exports.methods)(classNode).find((m) => (0, exports.memberName)(m) === name && (isStatic === undefined || m.static === isStatic));
|
|
20
|
+
exports.findMethod = findMethod;
|
|
21
|
+
/** Decorator names on a class: `@Foo` and `@Foo()` both give `Foo`. */
|
|
22
|
+
const decoratorNames = (classNode) => (classNode.decorators ?? []).map((d) => (d.expression.type === 'CallExpression' ? d.expression.callee.name : d.expression.name));
|
|
23
|
+
exports.decoratorNames = decoratorNames;
|
|
24
|
+
/** Type name of an annotation such as `data: TaskData` → `TaskData`. */
|
|
25
|
+
const annotationTypeName = (annotation) => {
|
|
26
|
+
const type = annotation?.typeAnnotation ?? annotation;
|
|
27
|
+
return type?.type === 'TSTypeReference' && type.typeName.type === 'Identifier' ? type.typeName.name : null;
|
|
28
|
+
};
|
|
29
|
+
exports.annotationTypeName = annotationTypeName;
|
|
30
|
+
/**
|
|
31
|
+
* `Promise<X>` return annotation → the X node; `undefined` when the annotation is not a Promise
|
|
32
|
+
* (or missing), so callers can tell "not async-typed" from "Promise of nothing".
|
|
33
|
+
*/
|
|
34
|
+
const promiseTypeArgument = (returnType) => {
|
|
35
|
+
const type = returnType?.typeAnnotation;
|
|
36
|
+
if (type?.type !== 'TSTypeReference' || type.typeName.name !== 'Promise')
|
|
37
|
+
return undefined;
|
|
38
|
+
return type.typeArguments?.params?.[0] ?? null;
|
|
39
|
+
};
|
|
40
|
+
exports.promiseTypeArgument = promiseTypeArgument;
|
|
41
|
+
/** `foo.bar(...)` → 'bar'. */
|
|
42
|
+
const calledMethodName = (call) => call?.type === 'CallExpression' && call.callee.type === 'MemberExpression' && !call.callee.computed
|
|
43
|
+
? call.callee.property.name
|
|
44
|
+
: null;
|
|
45
|
+
exports.calledMethodName = calledMethodName;
|
|
46
|
+
const unwrapAwait = (node) => (node?.type === 'AwaitExpression' ? node.argument : node);
|
|
47
|
+
exports.unwrapAwait = unwrapAwait;
|
|
48
|
+
/** `kebab-case` → `PascalCase`. */
|
|
49
|
+
const pascalCase = (kebab) => kebab.split('-').map((part) => part[0].toUpperCase() + part.slice(1)).join('');
|
|
50
|
+
exports.pascalCase = pascalCase;
|
|
51
|
+
/** `Array.prototype.findLast` (ES2023) for the ES2021 target. */
|
|
52
|
+
const findLast = (items, predicate) => {
|
|
53
|
+
for (let index = items.length - 1; index >= 0; index--) {
|
|
54
|
+
if (predicate(items[index]))
|
|
55
|
+
return items[index];
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
};
|
|
59
|
+
exports.findLast = findLast;
|
|
60
|
+
//# sourceMappingURL=ast.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ast.js","sourceRoot":"","sources":["../../src/lib/ast.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/db.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Default DB client types: `module#ImportedName` specs (see `clientTypeTracker`). */
|
|
2
|
+
export declare const DEFAULT_CLIENTS: string[];
|
|
3
|
+
/**
|
|
4
|
+
* Tracks which local names refer to a DB client type, from the file's imports, and tells whether a
|
|
5
|
+
* type annotation uses one (`PostgresDbClient`, `Knex`, `Knex.Transaction`, `PostgresDbClient | null`).
|
|
6
|
+
*/
|
|
7
|
+
export declare const clientTypeTracker: (clients?: string[]) => {
|
|
8
|
+
names: Set<unknown>;
|
|
9
|
+
onImport(node: any): void;
|
|
10
|
+
usesClient: (type: any) => any;
|
|
11
|
+
};
|
|
12
|
+
/** `param`, `private readonly param`, `param = x` → the binding node carrying the annotation. */
|
|
13
|
+
export declare const paramBinding: (param: any) => any;
|
package/dist/lib/db.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.paramBinding = exports.clientTypeTracker = exports.DEFAULT_CLIENTS = void 0;
|
|
4
|
+
/** Default DB client types: `module#ImportedName` specs (see `clientTypeTracker`). */
|
|
5
|
+
exports.DEFAULT_CLIENTS = ['@pjts/infra#PostgresDbClient', 'knex#Knex', 'pg#Pool', 'pg#Client', 'pg#PoolClient'];
|
|
6
|
+
const matchesSpec = (spec, source, importedName) => {
|
|
7
|
+
const [module, name] = spec.split('#');
|
|
8
|
+
return source === module && (!name || importedName === name);
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Tracks which local names refer to a DB client type, from the file's imports, and tells whether a
|
|
12
|
+
* type annotation uses one (`PostgresDbClient`, `Knex`, `Knex.Transaction`, `PostgresDbClient | null`).
|
|
13
|
+
*/
|
|
14
|
+
const clientTypeTracker = (clients = exports.DEFAULT_CLIENTS) => {
|
|
15
|
+
const names = new Set();
|
|
16
|
+
const usesClient = (type) => {
|
|
17
|
+
if (!type)
|
|
18
|
+
return false;
|
|
19
|
+
if (type.type === 'TSTypeAnnotation')
|
|
20
|
+
return usesClient(type.typeAnnotation);
|
|
21
|
+
if (type.type === 'TSTypeReference') {
|
|
22
|
+
let name = type.typeName;
|
|
23
|
+
while (name.type === 'TSQualifiedName')
|
|
24
|
+
name = name.left;
|
|
25
|
+
return names.has(name.name);
|
|
26
|
+
}
|
|
27
|
+
if (type.type === 'TSUnionType' || type.type === 'TSIntersectionType')
|
|
28
|
+
return type.types.some(usesClient);
|
|
29
|
+
return false;
|
|
30
|
+
};
|
|
31
|
+
return {
|
|
32
|
+
names,
|
|
33
|
+
onImport(node) {
|
|
34
|
+
for (const specifier of node.specifiers) {
|
|
35
|
+
const imported = specifier.imported?.name ?? specifier.local.name;
|
|
36
|
+
if (clients.some((spec) => matchesSpec(spec, node.source.value, imported)))
|
|
37
|
+
names.add(specifier.local.name);
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
usesClient,
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
exports.clientTypeTracker = clientTypeTracker;
|
|
44
|
+
/** `param`, `private readonly param`, `param = x` → the binding node carrying the annotation. */
|
|
45
|
+
const paramBinding = (param) => {
|
|
46
|
+
const node = param.type === 'TSParameterProperty' ? param.parameter : param;
|
|
47
|
+
return node.type === 'AssignmentPattern' ? node.left : node;
|
|
48
|
+
};
|
|
49
|
+
exports.paramBinding = paramBinding;
|
|
50
|
+
//# sourceMappingURL=db.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.js","sourceRoot":"","sources":["../../src/lib/db.ts"],"names":[],"mappings":";;;AAAA,sFAAsF;AACzE,QAAA,eAAe,GAAG,CAAC,8BAA8B,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,eAAe,CAAC,CAAC;AAEtH,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE;IACjD,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACvC,OAAO,MAAM,KAAK,MAAM,IAAI,CAAC,CAAC,IAAI,IAAI,YAAY,KAAK,IAAI,CAAC,CAAC;AAC/D,CAAC,CAAC;AAEF;;;GAGG;AACI,MAAM,iBAAiB,GAAG,CAAC,OAAO,GAAG,uBAAe,EAAE,EAAE;IAC7D,MAAM,KAAK,GAAG,IAAI,GAAG,EAAE,CAAC;IACxB,MAAM,UAAU,GAAG,CAAC,IAAI,EAAE,EAAE;QAC1B,IAAI,CAAC,IAAI;YAAE,OAAO,KAAK,CAAC;QACxB,IAAI,IAAI,CAAC,IAAI,KAAK,kBAAkB;YAAE,OAAO,UAAU,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC7E,IAAI,IAAI,CAAC,IAAI,KAAK,iBAAiB,EAAE,CAAC;YACpC,IAAI,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC;YACzB,OAAO,IAAI,CAAC,IAAI,KAAK,iBAAiB;gBAAE,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;YACzD,OAAO,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9B,CAAC;QACD,IAAI,IAAI,CAAC,IAAI,KAAK,aAAa,IAAI,IAAI,CAAC,IAAI,KAAK,oBAAoB;YAAE,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC1G,OAAO,KAAK,CAAC;IACf,CAAC,CAAC;IACF,OAAO;QACL,KAAK;QACL,QAAQ,CAAC,IAAI;YACX,KAAK,MAAM,SAAS,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;gBACxC,MAAM,QAAQ,GAAG,SAAS,CAAC,QAAQ,EAAE,IAAI,IAAI,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC;gBAClE,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;oBAAE,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC9G,CAAC;QACH,CAAC;QACD,UAAU;KACX,CAAC;AACJ,CAAC,CAAC;AAvBW,QAAA,iBAAiB,qBAuB5B;AAEF,iGAAiG;AAC1F,MAAM,YAAY,GAAG,CAAC,KAAK,EAAE,EAAE;IACpC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,KAAK,qBAAqB,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC;IAC5E,OAAO,IAAI,CAAC,IAAI,KAAK,mBAAmB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;AAC9D,CAAC,CAAC;AAHW,QAAA,YAAY,gBAGvB"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** `CatalogProfilePublished` → `CATALOG_PROFILE_PUBLISHED`. */
|
|
2
|
+
export declare const screamingSnake: (name: string) => string;
|
|
3
|
+
/** `MessageNames.X` (expression or type position) → 'X'; anything else → null. */
|
|
4
|
+
export declare const messageNameOf: (node: any, enumName?: string) => string | null;
|
|
5
|
+
/** Names set in `name: MessageNames.X` of an object literal (spreads ignored). */
|
|
6
|
+
export declare const nameProperty: (objectExpression: any, enumName?: string) => {
|
|
7
|
+
name: string | null;
|
|
8
|
+
node: any;
|
|
9
|
+
} | null;
|
|
10
|
+
/** Every `case MessageNames.X:` inside `node` (any switch). */
|
|
11
|
+
export declare const switchCaseNames: (node: any, enumName?: string) => Set<string>;
|
|
12
|
+
/** Depth-first walk that does not enter nested functions or classes. */
|
|
13
|
+
export declare const walkOwnBody: (node: any, visit: (node: any) => void) => void;
|