@starci/skills 1.1.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/INDEX.md +74 -0
- package/INDEX.vi.md +75 -0
- package/README.md +44 -0
- package/README.vi.md +43 -0
- package/SKILL.md +135 -0
- package/SKILL.vi.md +128 -0
- package/alias/INDEX.md +104 -0
- package/alias/INDEX.vi.md +104 -0
- package/alias/alias.json +297 -0
- package/bin/starci-skills.mjs +219 -0
- package/knowledge/INDEX.md +22 -0
- package/knowledge/INDEX.vi.md +22 -0
- package/knowledge/grammars/starci/DNA.md +146 -0
- package/knowledge/grammars/starci/DNA.vi.md +146 -0
- package/knowledge/grammars/starci/INDEX.md +25 -0
- package/knowledge/grammars/starci/INDEX.vi.md +25 -0
- package/knowledge/grammars/starci/family.md +50 -0
- package/knowledge/grammars/starci/family.vi.md +50 -0
- package/knowledge/grammars/starci/idioms.md +120 -0
- package/knowledge/grammars/starci/idioms.vi.md +121 -0
- package/knowledge/grammars/starci/playbook.md +36 -0
- package/knowledge/grammars/starci/playbook.vi.md +35 -0
- package/knowledge/patterns/be/INDEX.md +29 -0
- package/knowledge/patterns/be/INDEX.vi.md +29 -0
- package/knowledge/patterns/be/comment.md +80 -0
- package/knowledge/patterns/be/comment.vi.md +80 -0
- package/knowledge/patterns/be/error.md +86 -0
- package/knowledge/patterns/be/error.vi.md +86 -0
- package/knowledge/patterns/be/folder.md +87 -0
- package/knowledge/patterns/be/folder.vi.md +86 -0
- package/knowledge/patterns/be/function.md +80 -0
- package/knowledge/patterns/be/function.vi.md +80 -0
- package/knowledge/patterns/be/imports.md +79 -0
- package/knowledge/patterns/be/imports.vi.md +79 -0
- package/knowledge/patterns/be/naming.md +87 -0
- package/knowledge/patterns/be/naming.vi.md +87 -0
- package/knowledge/patterns/be/test.md +79 -0
- package/knowledge/patterns/be/test.vi.md +79 -0
- package/knowledge/patterns/be/typing.md +73 -0
- package/knowledge/patterns/be/typing.vi.md +73 -0
- package/knowledge/patterns/fe/INDEX.md +29 -0
- package/knowledge/patterns/fe/INDEX.vi.md +29 -0
- package/knowledge/patterns/fe/comment.md +70 -0
- package/knowledge/patterns/fe/comment.vi.md +70 -0
- package/knowledge/patterns/fe/error.md +61 -0
- package/knowledge/patterns/fe/error.vi.md +61 -0
- package/knowledge/patterns/fe/folder.md +98 -0
- package/knowledge/patterns/fe/folder.vi.md +95 -0
- package/knowledge/patterns/fe/function.md +70 -0
- package/knowledge/patterns/fe/function.vi.md +70 -0
- package/knowledge/patterns/fe/imports.md +87 -0
- package/knowledge/patterns/fe/imports.vi.md +87 -0
- package/knowledge/patterns/fe/naming.md +77 -0
- package/knowledge/patterns/fe/naming.vi.md +77 -0
- package/knowledge/patterns/fe/test.md +73 -0
- package/knowledge/patterns/fe/test.vi.md +73 -0
- package/knowledge/patterns/fe/typing.md +67 -0
- package/knowledge/patterns/fe/typing.vi.md +67 -0
- package/knowledge/ui/INDEX.md +101 -0
- package/knowledge/ui/INDEX.vi.md +101 -0
- package/knowledge/ui/composition/INDEX.md +60 -0
- package/knowledge/ui/composition/INDEX.vi.md +63 -0
- package/knowledge/ui/composition/accent.md +73 -0
- package/knowledge/ui/composition/accent.vi.md +74 -0
- package/knowledge/ui/composition/action.md +55 -0
- package/knowledge/ui/composition/action.vi.md +56 -0
- package/knowledge/ui/composition/coverage.md +37 -0
- package/knowledge/ui/composition/coverage.vi.md +37 -0
- package/knowledge/ui/composition/cta.md +79 -0
- package/knowledge/ui/composition/cta.vi.md +79 -0
- package/knowledge/ui/composition/feedback.md +55 -0
- package/knowledge/ui/composition/feedback.vi.md +56 -0
- package/knowledge/ui/composition/hierarchy.md +83 -0
- package/knowledge/ui/composition/hierarchy.vi.md +82 -0
- package/knowledge/ui/composition/layout.md +91 -0
- package/knowledge/ui/composition/layout.vi.md +93 -0
- package/knowledge/ui/composition/responsive.md +67 -0
- package/knowledge/ui/composition/responsive.vi.md +67 -0
- package/knowledge/ui/composition/state.md +105 -0
- package/knowledge/ui/composition/state.vi.md +106 -0
- package/knowledge/ui/presentation/INDEX.md +94 -0
- package/knowledge/ui/presentation/INDEX.vi.md +69 -0
- package/knowledge/ui/presentation/boundary.md +170 -0
- package/knowledge/ui/presentation/boundary.vi.md +169 -0
- package/knowledge/ui/presentation/font.md +155 -0
- package/knowledge/ui/presentation/font.vi.md +156 -0
- package/knowledge/ui/presentation/gap.md +181 -0
- package/knowledge/ui/presentation/gap.vi.md +181 -0
- package/knowledge/ui/presentation/margin.md +168 -0
- package/knowledge/ui/presentation/margin.vi.md +171 -0
- package/knowledge/ui/presentation/measure.md +174 -0
- package/knowledge/ui/presentation/measure.vi.md +178 -0
- package/knowledge/ui/presentation/overflow.md +142 -0
- package/knowledge/ui/presentation/overflow.vi.md +142 -0
- package/knowledge/ui/presentation/padding.md +293 -0
- package/knowledge/ui/presentation/padding.vi.md +292 -0
- package/knowledge/ui/presentation/surface.md +166 -0
- package/knowledge/ui/presentation/surface.vi.md +166 -0
- package/knowledge/ui/presentation/text-flow.md +123 -0
- package/knowledge/ui/presentation/text-flow.vi.md +123 -0
- package/knowledge/ui/presentation/tone.md +114 -0
- package/knowledge/ui/presentation/tone.vi.md +113 -0
- package/knowledge/ui/proof/INDEX.md +55 -0
- package/knowledge/ui/proof/INDEX.vi.md +55 -0
- package/knowledge/ui/proof/accessibility.md +67 -0
- package/knowledge/ui/proof/accessibility.vi.md +68 -0
- package/knowledge/ui/proof/contrast.md +58 -0
- package/knowledge/ui/proof/contrast.vi.md +57 -0
- package/knowledge/ui/proof/focus.md +76 -0
- package/knowledge/ui/proof/focus.vi.md +76 -0
- package/knowledge/ui/proof/motion.md +63 -0
- package/knowledge/ui/proof/motion.vi.md +64 -0
- package/knowledge/ui/proof/render-truth.md +63 -0
- package/knowledge/ui/proof/render-truth.vi.md +63 -0
- package/operators/INDEX.md +199 -0
- package/operators/INDEX.vi.md +199 -0
- package/operators/architecture-decide/errors.json +115 -0
- package/operators/architecture-decide/operator.json +20 -0
- package/operators/architecture-decide/operator.md +133 -0
- package/operators/architecture-decide/operator.vi.md +130 -0
- package/operators/architecture-decide/self-test.mjs +221 -0
- package/operators/architecture-decide/validate.mjs +112 -0
- package/operators/backend-source-apply/errors.json +66 -0
- package/operators/backend-source-apply/operator.json +20 -0
- package/operators/backend-source-apply/operator.md +189 -0
- package/operators/backend-source-apply/operator.vi.md +185 -0
- package/operators/backend-source-apply/self-test.mjs +257 -0
- package/operators/backend-source-apply/validate.mjs +240 -0
- package/operators/business-decide/errors.json +78 -0
- package/operators/business-decide/operator.json +20 -0
- package/operators/business-decide/operator.md +176 -0
- package/operators/business-decide/operator.vi.md +174 -0
- package/operators/business-decide/self-test.mjs +225 -0
- package/operators/business-decide/validate.mjs +277 -0
- package/operators/content-generate/errors.json +106 -0
- package/operators/content-generate/operator.json +21 -0
- package/operators/content-generate/operator.md +155 -0
- package/operators/content-generate/operator.vi.md +155 -0
- package/operators/content-generate/self-test.mjs +288 -0
- package/operators/content-generate/validate.mjs +196 -0
- package/operators/errors.json +178 -0
- package/operators/frontend-direction-decide/errors.json +135 -0
- package/operators/frontend-direction-decide/operator.json +21 -0
- package/operators/frontend-direction-decide/operator.md +167 -0
- package/operators/frontend-direction-decide/operator.vi.md +167 -0
- package/operators/frontend-direction-decide/self-test.mjs +226 -0
- package/operators/frontend-direction-decide/validate.mjs +146 -0
- package/operators/frontend-presentation-resolve/errors.json +42 -0
- package/operators/frontend-presentation-resolve/operator.json +19 -0
- package/operators/frontend-presentation-resolve/operator.md +142 -0
- package/operators/frontend-presentation-resolve/operator.vi.md +140 -0
- package/operators/frontend-presentation-resolve/self-test.mjs +163 -0
- package/operators/frontend-presentation-resolve/validate.mjs +143 -0
- package/operators/frontend-source-apply/errors.json +30 -0
- package/operators/frontend-source-apply/operator.json +20 -0
- package/operators/frontend-source-apply/operator.md +127 -0
- package/operators/frontend-source-apply/operator.vi.md +126 -0
- package/operators/frontend-source-apply/self-test.mjs +214 -0
- package/operators/frontend-source-apply/validate.mjs +133 -0
- package/operators/frontend-surface-audit/errors.json +5 -0
- package/operators/frontend-surface-audit/operator.json +22 -0
- package/operators/frontend-surface-audit/operator.md +121 -0
- package/operators/frontend-surface-audit/operator.vi.md +121 -0
- package/operators/frontend-surface-audit/self-test.mjs +185 -0
- package/operators/frontend-surface-audit/validate.mjs +127 -0
- package/operators/git-publish/errors.json +54 -0
- package/operators/git-publish/operator.json +20 -0
- package/operators/git-publish/operator.md +180 -0
- package/operators/git-publish/operator.vi.md +178 -0
- package/operators/git-publish/self-test.mjs +177 -0
- package/operators/git-publish/validate.mjs +106 -0
- package/operators/platform-operate/errors.json +90 -0
- package/operators/platform-operate/operator.json +22 -0
- package/operators/platform-operate/operator.md +162 -0
- package/operators/platform-operate/operator.vi.md +160 -0
- package/operators/platform-operate/self-test.mjs +202 -0
- package/operators/platform-operate/validate.mjs +193 -0
- package/operators/quality-verify/errors.json +54 -0
- package/operators/quality-verify/operator.json +20 -0
- package/operators/quality-verify/operator.md +185 -0
- package/operators/quality-verify/operator.vi.md +177 -0
- package/operators/quality-verify/self-test.mjs +210 -0
- package/operators/quality-verify/validate.mjs +197 -0
- package/operators/release-deploy/errors.json +158 -0
- package/operators/release-deploy/operator.json +23 -0
- package/operators/release-deploy/operator.md +183 -0
- package/operators/release-deploy/operator.vi.md +181 -0
- package/operators/release-deploy/self-test.mjs +235 -0
- package/operators/release-deploy/validate.mjs +129 -0
- package/operators/uat-verify/errors.json +78 -0
- package/operators/uat-verify/operator.json +25 -0
- package/operators/uat-verify/operator.md +162 -0
- package/operators/uat-verify/operator.vi.md +161 -0
- package/operators/uat-verify/self-test.mjs +270 -0
- package/operators/uat-verify/validate.mjs +202 -0
- package/operators/workspace-bind/errors.json +90 -0
- package/operators/workspace-bind/operator.json +21 -0
- package/operators/workspace-bind/operator.md +148 -0
- package/operators/workspace-bind/operator.vi.md +148 -0
- package/operators/workspace-bind/self-test.mjs +205 -0
- package/operators/workspace-bind/validate.mjs +152 -0
- package/package.json +50 -0
- package/readiness/initialization/workspaces/commit-policy.json +91 -0
- package/readiness/initialization/workspaces/config.schema.json +35 -0
- package/readiness/initialization/workspaces/device-state.schema.json +54 -0
- package/readiness/initialization/workspaces/local-route.schema.json +206 -0
- package/readiness/initialization/workspaces/portable-route.schema.json +200 -0
- package/resources/INDEX.md +96 -0
- package/resources/INDEX.vi.md +99 -0
- package/resources/agents/profiles/claude.json +131 -0
- package/resources/agents/profiles/openai.json +131 -0
- package/resources/orchestrator.json +71 -0
- package/resources/tools.json +85 -0
- package/routing.json +272 -0
- package/scripts/alias-registry.mjs +31 -0
- package/scripts/device-state.mjs +497 -0
- package/scripts/device-state.spec.mjs +18 -0
- package/scripts/errors-registry.mjs +68 -0
- package/scripts/generate-alias-doc.mjs +66 -0
- package/scripts/generate-grammar-dna.mjs +387 -0
- package/scripts/generate-operators-index.mjs +95 -0
- package/scripts/generate-presentation-owned.mjs +681 -0
- package/scripts/install-cli.spec.mjs +74 -0
- package/scripts/json-schema.mjs +94 -0
- package/scripts/operator-md.mjs +96 -0
- package/scripts/run-operator-self-tests.mjs +36 -0
- package/scripts/validate-alias.mjs +165 -0
- package/scripts/validate-defaults.mjs +72 -0
- package/scripts/validate-knowledge-citations.mjs +90 -0
- package/scripts/validate-operator.mjs +125 -0
- package/scripts/validate-request.mjs +80 -0
- package/scripts/validate-resources.mjs +117 -0
- package/scripts/validate-response.mjs +141 -0
- package/scripts/validate-routing.mjs +91 -0
- package/scripts/validate-step.mjs +50 -0
- package/scripts/validate-templates.mjs +226 -0
- package/scripts/validate-templates.spec.mjs +144 -0
- package/scripts/validate-workflows.mjs +106 -0
- package/scripts/workspace-portable.mjs +389 -0
- package/scripts/workspace-portable.spec.mjs +246 -0
- package/templates/README.md +47 -0
- package/templates/README.vi.md +45 -0
- package/templates/changes.example.md +27 -0
- package/templates/grammars.template.md +34 -0
- package/templates/kinds/architecture-decision.contract.json +14 -0
- package/templates/kinds/architecture-decision.skeleton.md +52 -0
- package/templates/kinds/backend-source-application.contract.json +10 -0
- package/templates/kinds/backend-source-application.skeleton.md +34 -0
- package/templates/kinds/business-promise-authority.contract.json +13 -0
- package/templates/kinds/business-promise-authority.skeleton.md +67 -0
- package/templates/kinds/capture.schema.json +33 -0
- package/templates/kinds/changes.contract.json +10 -0
- package/templates/kinds/changes.skeleton.md +26 -0
- package/templates/kinds/checks.schema.json +44 -0
- package/templates/kinds/claims.schema.json +165 -0
- package/templates/kinds/conformance.schema.json +15 -0
- package/templates/kinds/content-brief.contract.json +12 -0
- package/templates/kinds/content-brief.skeleton.md +45 -0
- package/templates/kinds/content-generation-receipt.contract.json +11 -0
- package/templates/kinds/content-generation-receipt.skeleton.md +40 -0
- package/templates/kinds/content-review.contract.json +11 -0
- package/templates/kinds/content-review.skeleton.md +45 -0
- package/templates/kinds/contract.schema.json +28 -0
- package/templates/kinds/coverage-matrix.schema.json +86 -0
- package/templates/kinds/coverage.schema.json +62 -0
- package/templates/kinds/current-state.schema.json +40 -0
- package/templates/kinds/delta.schema.json +95 -0
- package/templates/kinds/e2e.schema.json +63 -0
- package/templates/kinds/frontend-direction-decision.contract.json +93 -0
- package/templates/kinds/frontend-direction-decision.skeleton.md +68 -0
- package/templates/kinds/frontend-presentation-resolution.contract.json +11 -0
- package/templates/kinds/frontend-presentation-resolution.skeleton.md +32 -0
- package/templates/kinds/frontend-source-application.contract.json +10 -0
- package/templates/kinds/frontend-source-application.skeleton.md +30 -0
- package/templates/kinds/frontend-surface-audit.contract.json +52 -0
- package/templates/kinds/frontend-surface-audit.skeleton.md +32 -0
- package/templates/kinds/gate-result.schema.json +64 -0
- package/templates/kinds/git-publication.contract.json +82 -0
- package/templates/kinds/git-publication.skeleton.md +61 -0
- package/templates/kinds/independent-critique.contract.json +9 -0
- package/templates/kinds/independent-critique.skeleton.md +28 -0
- package/templates/kinds/inventory.schema.json +35 -0
- package/templates/kinds/model.schema.json +99 -0
- package/templates/kinds/mutations.schema.json +65 -0
- package/templates/kinds/platform-operation-receipt.contract.json +76 -0
- package/templates/kinds/platform-operation-receipt.skeleton.md +54 -0
- package/templates/kinds/probes.schema.json +130 -0
- package/templates/kinds/proof.schema.json +17 -0
- package/templates/kinds/quality-verification.contract.json +87 -0
- package/templates/kinds/quality-verification.skeleton.md +58 -0
- package/templates/kinds/release-deployment.contract.json +85 -0
- package/templates/kinds/release-deployment.skeleton.md +67 -0
- package/templates/kinds/route.schema.json +293 -0
- package/templates/kinds/stack-model.schema.json +90 -0
- package/templates/kinds/uat-capture.schema.json +35 -0
- package/templates/kinds/uat-flow-verification.contract.json +11 -0
- package/templates/kinds/uat-flow-verification.skeleton.md +47 -0
- package/templates/kinds/uat-snapshot.schema.json +96 -0
- package/templates/kinds/uat-verdicts.schema.json +41 -0
- package/templates/kinds/ui-coverage.schema.json +77 -0
- package/templates/kinds/verdicts.schema.json +39 -0
- package/templates/kinds/workspace-route-binding.contract.json +12 -0
- package/templates/kinds/workspace-route-binding.skeleton.md +60 -0
- package/templates/kinds/writes.schema.json +37 -0
- package/templates/operator.template.md +73 -0
- package/templates/patterns.template.md +31 -0
- package/templates/step/request.schema.json +42 -0
- package/templates/step/response.schema.json +144 -0
- package/templates/ui-composition.template.md +36 -0
- package/templates/ui-presentation.template.md +57 -0
- package/templates/ui-proof.template.md +34 -0
- package/workflows/README.md +37 -0
- package/workflows/README.vi.md +37 -0
- package/workflows/backend-feature.json +59 -0
- package/workflows/content-unit.json +19 -0
- package/workflows/frontend-new-surface.json +81 -0
- package/workflows/frontend-reconstruct.json +67 -0
- package/workflows/frontend-refine.json +67 -0
- package/workflows/frontend-with-uat.json +78 -0
- package/workflows/full-feature.json +104 -0
- package/workflows/release.json +29 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Error
|
|
2
|
+
|
|
3
|
+
This file answers one question: given a failure in the backend, how is it declared, thrown,
|
|
4
|
+
wrapped, logged, and mapped onto HTTP and GraphQL?
|
|
5
|
+
|
|
6
|
+
Sources: `modules/platform/exceptions/errors/abstract.ts`, `errors/ai/ai-quota-exhausted.ts`,
|
|
7
|
+
`errors/courses/challenge-not-found.ts`, `errors/api/graphql.ts`,
|
|
8
|
+
`modules/platform/exceptions/filters/abstract-exception-http.filter.ts`,
|
|
9
|
+
`apps/core/src/app.module.ts`,
|
|
10
|
+
`modules/api/apollo/server/interceptors/graphql-transform.interceptor.ts`,
|
|
11
|
+
`modules/api/apollo/server/monolithic/monolithic-apollo-server.module.ts`,
|
|
12
|
+
`modules/api/apollo/server/types/graphql-response.ts`,
|
|
13
|
+
`features/api/core/graphql/mutations/courses/add-to-cart/add-to-cart.handler.ts`.
|
|
14
|
+
|
|
15
|
+
Verified: 294 files under `errors/**` declare a class that `extends AbstractException`; the only
|
|
16
|
+
class extending `Error` directly is `AbstractException` itself; lint `throw-abstract-exception`,
|
|
17
|
+
`exception-extends-abstract` and `exception-in-errors-folder` are at error for `src/**` and off
|
|
18
|
+
only under `src/tests/**` and `apps/*/test/**`.
|
|
19
|
+
|
|
20
|
+
## BE-ERROR-1 — The base
|
|
21
|
+
|
|
22
|
+
| Case | When | Write |
|
|
23
|
+
| --- | --- | --- |
|
|
24
|
+
| Case 1 | Shape | `export class AbstractException extends Error { readonly code: string; readonly metadata?: Record<string, unknown>; readonly httpStatus?: number; constructor(message: string, name: string, metadata?: Record<string, unknown>, httpStatus?: number) { super(message); this.code = name; this.name = name; this.metadata = metadata; this.httpStatus = httpStatus } … }` |
|
|
25
|
+
| Case 2 | Serialisation | `toJSON(): string` → `{ message, code, metadata }`; `static fromJSON<T extends AbstractException>(…)` |
|
|
26
|
+
| Case 3 | Cause | `getOriginalError(): Error { return this.metadata?.originalError as Error }`; `export interface AbstractExceptionMetadata { originalError?: Error }` |
|
|
27
|
+
| Case 4 | Status | `httpStatus` is optional; undefined means 500 at both transports |
|
|
28
|
+
|
|
29
|
+
## BE-ERROR-2 — Declaring one exception
|
|
30
|
+
|
|
31
|
+
| Case | When | Write |
|
|
32
|
+
| --- | --- | --- |
|
|
33
|
+
| Case 1 | Metadata interface first | `export interface ChallengeNotFoundExceptionMetadata extends AbstractExceptionMetadata { id?: string }` |
|
|
34
|
+
| Case 2 | Class | `export class ChallengeNotFoundException extends AbstractException { constructor({ id, originalError }: ChallengeNotFoundExceptionMetadata) { super("Challenge not found", "CHALLENGE_NOT_FOUND_EXCEPTION", { id, originalError }) } }` |
|
|
35
|
+
| Case 3 | Interpolated message | `` super(`AI quota exhausted (${window})`, "AI_QUOTA_EXHAUSTED_EXCEPTION", { window, originalError }) `` |
|
|
36
|
+
| Case 4 | With a status | fourth argument `HttpStatus.<X>` — set in 94 of 294 files, left undefined in the rest |
|
|
37
|
+
| Case 5 | Single object argument | always one metadata object, even when empty: `throw new UserNotFoundException({})` (lint `require-exception-object-arg`) |
|
|
38
|
+
|
|
39
|
+
## BE-ERROR-3 — Throwing
|
|
40
|
+
|
|
41
|
+
| Case | When | Write |
|
|
42
|
+
| --- | --- | --- |
|
|
43
|
+
| Case 1 | Guard in a handler | `if (!courseExists) { throw new CourseNotFoundException({ id: courseId }) }` |
|
|
44
|
+
| Case 2 | Conflict | `throw new CourseAlreadyEnrolledException({ courseId, userId: user.id })` |
|
|
45
|
+
| Case 3 | Never encode failure in the return | a handler resolves to the entity or throws; `success: false` appears in 1 of 154 handlers (lint `no-handler-encoded-failure`) |
|
|
46
|
+
| Case 4 | Never `new Error` | `NewExpression[callee.name="Error"]` is banned in `src/**` outside the test lanes |
|
|
47
|
+
|
|
48
|
+
## BE-ERROR-4 — Wrapping a foreign error
|
|
49
|
+
|
|
50
|
+
| Case | When | Write |
|
|
51
|
+
| --- | --- | --- |
|
|
52
|
+
| Case 1 | Crypto | `catch (error) { throw new DecryptionFailedException({ originalError: error instanceof Error ? error : new Error(String(error)) }) }` (25 non-exception files wrap this way) |
|
|
53
|
+
| Case 2 | Cursor parsing | `catch (error) { throw new CourseCommunityCursorException({ originalError: error as Error }) }` |
|
|
54
|
+
| Case 3 | At-least-once consumer | `catch (error) { const exception = new KafkaCdcMessageException({ topic, originalError: error instanceof Error ? error : undefined }); … log and continue }` |
|
|
55
|
+
| Case 4 | Upstream GraphQL | `GraphQLDataNotFoundException({ query, variables, url, originalError })` |
|
|
56
|
+
|
|
57
|
+
## BE-ERROR-5 — HTTP mapping
|
|
58
|
+
|
|
59
|
+
| Case | When | Write |
|
|
60
|
+
| --- | --- | --- |
|
|
61
|
+
| Case 1 | The filter | `@Catch(AbstractException) export class AbstractExceptionHttpFilter implements ExceptionFilter { catch(exception: AbstractException, host: ArgumentsHost): void { if (host.getType<string>() === "graphql") { throw exception } const status = exception.httpStatus ?? HttpStatus.INTERNAL_SERVER_ERROR; … response.status(status).json({ statusCode: status, code: exception.code, message: exception.message }) } }` |
|
|
62
|
+
| Case 2 | Registration | `apps/core/src/app.module.ts`: `{ provide: APP_FILTER, useClass: AbstractExceptionHttpFilter }` |
|
|
63
|
+
| Case 3 | Logged before responding | `this.winstonService.log(WinstonLog.HttpExceptionLogged, { op: "http.exception.logged", error: exception.message, meta: { code: exception.code, status } })` |
|
|
64
|
+
| Case 4 | GraphQL host | rethrown untouched so Apollo's pipeline owns it |
|
|
65
|
+
|
|
66
|
+
## BE-ERROR-6 — GraphQL mapping
|
|
67
|
+
|
|
68
|
+
Two layers. The interceptor shapes the resolver's body; `formatError` shapes the GraphQL `errors`
|
|
69
|
+
entry and the transport status.
|
|
70
|
+
|
|
71
|
+
| Case | When | Write |
|
|
72
|
+
| --- | --- | --- |
|
|
73
|
+
| Case 1 | Success envelope | `GraphQLTransformInterceptor` maps every resolver result to `{ data, message, success: true }` with `message` from `@GraphQLSuccessMessage({ [Locale.En]: "…", [Locale.Vi]: "…" })` resolved by locale |
|
|
74
|
+
| Case 2 | Failure envelope | `catchError((err) => … observer.next({ success: false, message: err?.message ?? "Internal server error", error: err?.name ?? "Error" }))` — `error` carries the exception `code` because `AbstractException` sets `this.name = name` |
|
|
75
|
+
| Case 3 | `formatError` | in `MonolithicApolloServerModule`: `if (original instanceof AbstractException) { return { ...formattedError, extensions: { ...formattedError.extensions, code: original.code, http: { status: original.httpStatus ?? 500 }, …retryAfterSeconds } } }` |
|
|
76
|
+
| Case 4 | Everything else | `return { ...formattedError, extensions: { ...formattedError.extensions, http: { status: 500, ...(formattedError.extensions?.http as ApolloHttpExtension \| undefined) } } }` |
|
|
77
|
+
| Case 5 | Transport status | `httpStatusFromExceptionsPlugin` reads `extensions.http.status` and sets the real HTTP status |
|
|
78
|
+
| Case 6 | Client contract | `interface GraphQLResponse<T = unknown> { data?: T; message: string; success: boolean; error?: string }`; the FE matches on `error` code, not on status |
|
|
79
|
+
|
|
80
|
+
## BE-ERROR-7 — Logging a failure
|
|
81
|
+
|
|
82
|
+
| Case | When | Write |
|
|
83
|
+
| --- | --- | --- |
|
|
84
|
+
| Case 1 | Identity | an enum member: `WinstonLog.HttpExceptionLogged` (lint `no-interpolated-log-message`, `no-error-wording-as-log-identity`) |
|
|
85
|
+
| Case 2 | Payload | `{ op: "http.exception.logged", error: exception.message, meta: { code, status } }` |
|
|
86
|
+
| Case 3 | Never | `console.error`, Nest `Logger` |
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Lỗi
|
|
2
|
+
|
|
3
|
+
Tệp này trả lời một câu hỏi: cho một thất bại trong backend, nó được khai báo, ném, bọc, ghi log,
|
|
4
|
+
và ánh xạ sang HTTP và GraphQL thế nào?
|
|
5
|
+
|
|
6
|
+
Nguồn: `modules/platform/exceptions/errors/abstract.ts`, `errors/ai/ai-quota-exhausted.ts`,
|
|
7
|
+
`errors/courses/challenge-not-found.ts`, `errors/api/graphql.ts`,
|
|
8
|
+
`modules/platform/exceptions/filters/abstract-exception-http.filter.ts`,
|
|
9
|
+
`apps/core/src/app.module.ts`,
|
|
10
|
+
`modules/api/apollo/server/interceptors/graphql-transform.interceptor.ts`,
|
|
11
|
+
`modules/api/apollo/server/monolithic/monolithic-apollo-server.module.ts`,
|
|
12
|
+
`modules/api/apollo/server/types/graphql-response.ts`,
|
|
13
|
+
`features/api/core/graphql/mutations/courses/add-to-cart/add-to-cart.handler.ts`.
|
|
14
|
+
|
|
15
|
+
Đã kiểm chứng: 294 tệp dưới `errors/**` khai báo một lớp `extends AbstractException`; lớp duy nhất
|
|
16
|
+
kế thừa thẳng `Error` là chính `AbstractException`; lint `throw-abstract-exception`,
|
|
17
|
+
`exception-extends-abstract` và `exception-in-errors-folder` ở mức error cho `src/**` và chỉ tắt
|
|
18
|
+
dưới `src/tests/**` và `apps/*/test/**`.
|
|
19
|
+
|
|
20
|
+
## BE-ERROR-1 — Lớp gốc
|
|
21
|
+
|
|
22
|
+
| Case | Dùng khi | Viết |
|
|
23
|
+
| --- | --- | --- |
|
|
24
|
+
| Case 1 | Hình dạng | `export class AbstractException extends Error { readonly code: string; readonly metadata?: Record<string, unknown>; readonly httpStatus?: number; constructor(message: string, name: string, metadata?: Record<string, unknown>, httpStatus?: number) { super(message); this.code = name; this.name = name; this.metadata = metadata; this.httpStatus = httpStatus } … }` |
|
|
25
|
+
| Case 2 | Tuần tự hóa | `toJSON(): string` → `{ message, code, metadata }`; `static fromJSON<T extends AbstractException>(…)` |
|
|
26
|
+
| Case 3 | Nguyên nhân | `getOriginalError(): Error { return this.metadata?.originalError as Error }`; `export interface AbstractExceptionMetadata { originalError?: Error }` |
|
|
27
|
+
| Case 4 | Mã trạng thái | `httpStatus` là tùy chọn; không định nghĩa nghĩa là 500 ở cả hai tầng vận chuyển |
|
|
28
|
+
|
|
29
|
+
## BE-ERROR-2 — Khai báo một exception
|
|
30
|
+
|
|
31
|
+
| Case | Dùng khi | Viết |
|
|
32
|
+
| --- | --- | --- |
|
|
33
|
+
| Case 1 | Interface metadata trước | `export interface ChallengeNotFoundExceptionMetadata extends AbstractExceptionMetadata { id?: string }` |
|
|
34
|
+
| Case 2 | Lớp | `export class ChallengeNotFoundException extends AbstractException { constructor({ id, originalError }: ChallengeNotFoundExceptionMetadata) { super("Challenge not found", "CHALLENGE_NOT_FOUND_EXCEPTION", { id, originalError }) } }` |
|
|
35
|
+
| Case 3 | Thông điệp nội suy | `` super(`AI quota exhausted (${window})`, "AI_QUOTA_EXHAUSTED_EXCEPTION", { window, originalError }) `` |
|
|
36
|
+
| Case 4 | Có mã trạng thái | đối số thứ tư `HttpStatus.<X>` — được đặt ở 94 trên 294 tệp, bỏ trống ở phần còn lại |
|
|
37
|
+
| Case 5 | Một đối số object duy nhất | luôn là một object metadata, kể cả khi rỗng: `throw new UserNotFoundException({})` (lint `require-exception-object-arg`) |
|
|
38
|
+
|
|
39
|
+
## BE-ERROR-3 — Ném
|
|
40
|
+
|
|
41
|
+
| Case | Dùng khi | Viết |
|
|
42
|
+
| --- | --- | --- |
|
|
43
|
+
| Case 1 | Chốt chặn trong handler | `if (!courseExists) { throw new CourseNotFoundException({ id: courseId }) }` |
|
|
44
|
+
| Case 2 | Xung đột | `throw new CourseAlreadyEnrolledException({ courseId, userId: user.id })` |
|
|
45
|
+
| Case 3 | Không bao giờ mã hóa thất bại vào giá trị trả về | handler trả entity hoặc ném; `success: false` xuất hiện ở 1 trên 154 handler (lint `no-handler-encoded-failure`) |
|
|
46
|
+
| Case 4 | Không bao giờ `new Error` | `NewExpression[callee.name="Error"]` bị cấm trong `src/**` ngoài các làn kiểm thử |
|
|
47
|
+
|
|
48
|
+
## BE-ERROR-4 — Bọc một lỗi bên ngoài
|
|
49
|
+
|
|
50
|
+
| Case | Dùng khi | Viết |
|
|
51
|
+
| --- | --- | --- |
|
|
52
|
+
| Case 1 | Mã hóa | `catch (error) { throw new DecryptionFailedException({ originalError: error instanceof Error ? error : new Error(String(error)) }) }` (25 tệp ngoài cây exception bọc theo cách này) |
|
|
53
|
+
| Case 2 | Phân tích con trỏ | `catch (error) { throw new CourseCommunityCursorException({ originalError: error as Error }) }` |
|
|
54
|
+
| Case 3 | Bên tiêu thụ ít-nhất-một-lần | `catch (error) { const exception = new KafkaCdcMessageException({ topic, originalError: error instanceof Error ? error : undefined }); … ghi log và tiếp tục }` |
|
|
55
|
+
| Case 4 | GraphQL thượng nguồn | `GraphQLDataNotFoundException({ query, variables, url, originalError })` |
|
|
56
|
+
|
|
57
|
+
## BE-ERROR-5 — Ánh xạ HTTP
|
|
58
|
+
|
|
59
|
+
| Case | Dùng khi | Viết |
|
|
60
|
+
| --- | --- | --- |
|
|
61
|
+
| Case 1 | Bộ lọc | `@Catch(AbstractException) export class AbstractExceptionHttpFilter implements ExceptionFilter { catch(exception: AbstractException, host: ArgumentsHost): void { if (host.getType<string>() === "graphql") { throw exception } const status = exception.httpStatus ?? HttpStatus.INTERNAL_SERVER_ERROR; … response.status(status).json({ statusCode: status, code: exception.code, message: exception.message }) } }` |
|
|
62
|
+
| Case 2 | Đăng ký | `apps/core/src/app.module.ts`: `{ provide: APP_FILTER, useClass: AbstractExceptionHttpFilter }` |
|
|
63
|
+
| Case 3 | Ghi log trước khi phản hồi | `this.winstonService.log(WinstonLog.HttpExceptionLogged, { op: "http.exception.logged", error: exception.message, meta: { code: exception.code, status } })` |
|
|
64
|
+
| Case 4 | Host GraphQL | ném lại nguyên vẹn để đường ống của Apollo sở hữu nó |
|
|
65
|
+
|
|
66
|
+
## BE-ERROR-6 — Ánh xạ GraphQL
|
|
67
|
+
|
|
68
|
+
Hai lớp. Interceptor định hình thân trả về của resolver; `formatError` định hình mục `errors` của
|
|
69
|
+
GraphQL và mã trạng thái vận chuyển.
|
|
70
|
+
|
|
71
|
+
| Case | Dùng khi | Viết |
|
|
72
|
+
| --- | --- | --- |
|
|
73
|
+
| Case 1 | Phong bì thành công | `GraphQLTransformInterceptor` ánh xạ mọi kết quả resolver thành `{ data, message, success: true }` với `message` lấy từ `@GraphQLSuccessMessage({ [Locale.En]: "…", [Locale.Vi]: "…" })` giải theo locale |
|
|
74
|
+
| Case 2 | Phong bì thất bại | `catchError((err) => … observer.next({ success: false, message: err?.message ?? "Internal server error", error: err?.name ?? "Error" }))` — `error` mang `code` của exception vì `AbstractException` đặt `this.name = name` |
|
|
75
|
+
| Case 3 | `formatError` | trong `MonolithicApolloServerModule`: `if (original instanceof AbstractException) { return { ...formattedError, extensions: { ...formattedError.extensions, code: original.code, http: { status: original.httpStatus ?? 500 }, …retryAfterSeconds } } }` |
|
|
76
|
+
| Case 4 | Mọi thứ khác | `return { ...formattedError, extensions: { ...formattedError.extensions, http: { status: 500, ...(formattedError.extensions?.http as ApolloHttpExtension \| undefined) } } }` |
|
|
77
|
+
| Case 5 | Mã trạng thái vận chuyển | `httpStatusFromExceptionsPlugin` đọc `extensions.http.status` và đặt mã HTTP thật |
|
|
78
|
+
| Case 6 | Hợp đồng với client | `interface GraphQLResponse<T = unknown> { data?: T; message: string; success: boolean; error?: string }`; FE so khớp theo mã trong `error`, không theo mã trạng thái |
|
|
79
|
+
|
|
80
|
+
## BE-ERROR-7 — Ghi log một thất bại
|
|
81
|
+
|
|
82
|
+
| Case | Dùng khi | Viết |
|
|
83
|
+
| --- | --- | --- |
|
|
84
|
+
| Case 1 | Danh tính | một thành viên enum: `WinstonLog.HttpExceptionLogged` (lint `no-interpolated-log-message`, `no-error-wording-as-log-identity`) |
|
|
85
|
+
| Case 2 | Payload | `{ op: "http.exception.logged", error: exception.message, meta: { code, status } }` |
|
|
86
|
+
| Case 3 | Không bao giờ | `console.error`, `Logger` của Nest |
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Folder
|
|
2
|
+
|
|
3
|
+
This file answers one question: given a piece of backend code, which directory and which file
|
|
4
|
+
name does it get?
|
|
5
|
+
|
|
6
|
+
Sources: `src/features/api/core/graphql/mutations/courses/add-to-cart/*`,
|
|
7
|
+
`src/features/api/core/graphql/queries/courses/course/*`,
|
|
8
|
+
`src/features/api/core/graphql/mutations/courses/courses.module.ts`,
|
|
9
|
+
`src/modules/platform/{winston,throttler,exceptions}/`, `src/modules/ai/`,
|
|
10
|
+
`src/modules/databases/postgresql/primary/{entities,enums}/`, `src/tests/`, `jest.config.ts`.
|
|
11
|
+
|
|
12
|
+
## BE-FOLDER-1 — Two roots
|
|
13
|
+
|
|
14
|
+
| Case | When | Write |
|
|
15
|
+
| --- | --- | --- |
|
|
16
|
+
| Case 1 | A door the outside world calls | `src/features/<door>/` — `api`, `socketio`, `cli`, `backup`, `mock`, `video-encoder` |
|
|
17
|
+
| Case 2 | A capability doors compose | `src/modules/<capability>/` — `ai`, `api`, `bussiness`, `crypto`, `databases`, `filesystem`, `init`, `integrations`, `lib`, `membership`, `platform`, `playground-agent-core` |
|
|
18
|
+
| Case 3 | Test infrastructure | `src/tests/{e2e,fixtures,harness,helpers,mocks}/` — e.g. `src/tests/mocks/entity-manager.mock.ts` |
|
|
19
|
+
| Case 4 | Composition root | `apps/core/src/app.module.ts` (registers `AbstractExceptionHttpFilter` as `APP_FILTER`) |
|
|
20
|
+
|
|
21
|
+
## BE-FOLDER-2 — One GraphQL unit
|
|
22
|
+
|
|
23
|
+
A mutation or query is one kebab-case folder under its domain. The file set is fixed.
|
|
24
|
+
|
|
25
|
+
| Case | When | Write |
|
|
26
|
+
| --- | --- | --- |
|
|
27
|
+
| Case 1 | Mutation unit `mutations/courses/add-to-cart/` | `add-to-cart.command.ts` · `add-to-cart.handler.ts` · `add-to-cart.handler.spec.ts` · `add-to-cart.service.ts` · `add-to-cart.resolver.ts` · `add-to-cart.module.ts` · `add-to-cart.module-definition.ts` · `graphql-types/request.ts` · `graphql-types/response.ts` |
|
|
28
|
+
| Case 2 | Query unit `queries/courses/course/` | `course.query.ts` · `course.handler.ts` · `course.handler.spec.ts` · `course.service.ts` · `course.resolver.ts` · `course.resolver.spec.ts` · `course.module.ts` · `course.module-definition.ts` · `graphql-types/request.ts` · `graphql-types/response.ts` |
|
|
29
|
+
| Case 3 | Shared GraphQL objects | `features/api/core/graphql/shared/…` (`*.object.ts`, 14) |
|
|
30
|
+
|
|
31
|
+
Counts under `features/api/core/graphql`: `.module.ts` 338, `.module-definition.ts` 336,
|
|
32
|
+
`.resolver.ts` 305, `.service.ts` 203, `.handler.ts` 129, `.handler.spec.ts` 104, `.query.ts` 64,
|
|
33
|
+
`.command.ts` 62, `.resolver.spec.ts` 59.
|
|
34
|
+
|
|
35
|
+
## BE-FOLDER-3 — Domain aggregator
|
|
36
|
+
|
|
37
|
+
| Case | When | Write |
|
|
38
|
+
| --- | --- | --- |
|
|
39
|
+
| Case 1 | The domain folder | `mutations/courses/courses.module.ts` + `courses.module-definition.ts` beside the unit folders `add-to-cart/`, `clear-cart/`, `course-enroll/`, … |
|
|
40
|
+
| Case 2 | Registration | `AddToCartSingleMutationModule.register({ … })` listed inside `courses.module.ts`; import alone is not registration |
|
|
41
|
+
| Case 3 | Top aggregators | `mutations/mutations.module.ts`, `queries/queries.module.ts`, `graphql/graphql.module.ts`, `core/core.module.ts`, `api/api.module.ts`, each with a `.module-definition.ts` |
|
|
42
|
+
|
|
43
|
+
## BE-FOLDER-4 — One capability module
|
|
44
|
+
|
|
45
|
+
| Case | When | Write |
|
|
46
|
+
| --- | --- | --- |
|
|
47
|
+
| Case 1 | `modules/platform/winston/` | `winston.module.ts` · `winston.module-definition.ts` · `winston.service.ts` · `winston.service.spec.ts` · `winston.providers.ts` · `winston.providers.spec.ts` · `winston.decorators.ts` · `config.ts` · `constants/` · `enums/` · `types/` · `utils/` |
|
|
48
|
+
| Case 2 | `modules/platform/throttler/` | `throttler.module.ts` · `throttler.module-definition.ts` · `throttler.decorators.ts` · `config.ts` · `types.ts` · `enums/` · `guards/` · `types/` · `utils/` |
|
|
49
|
+
| Case 3 | `modules/ai/` | `ai.module.ts` · `ai.module-definition.ts` · `ai-entitlement.service.ts` (+ `.spec.ts`) · `ai-invoke.service.ts` · `constants/ai-entitlement.constants.ts` · `balancer/` · `ping/` · `types/` · `utils/` |
|
|
50
|
+
| Case 4 | Helper | `modules/bussiness/projections/user-stats/kpi-current.util.ts` (`.util.ts` beside its `types.ts`) |
|
|
51
|
+
|
|
52
|
+
Counts under `src/modules`: `.service.ts` 396, `.service.spec.ts` 271, `.entity.ts` 197,
|
|
53
|
+
`.module.ts` 116, `.module-definition.ts` 115, `.listener.ts` 18, `.providers.ts` 16,
|
|
54
|
+
`.decorators.ts` 16, `.guard.ts` 11.
|
|
55
|
+
|
|
56
|
+
## BE-FOLDER-5 — Exceptions have one home
|
|
57
|
+
|
|
58
|
+
| Case | When | Write |
|
|
59
|
+
| --- | --- | --- |
|
|
60
|
+
| Case 1 | Any exception class | `src/modules/platform/exceptions/errors/<domain>/<name>.ts` — 294 files across `ai/ api/ backup/ bento4/ cache/ cli/ coding/ community/ courses/ …` (lint `exception-in-errors-folder`) |
|
|
61
|
+
| Case 2 | The base | `errors/abstract.ts` |
|
|
62
|
+
| Case 3 | Transport mapping | `exceptions/filters/abstract-exception-http.filter.ts` (+ `.spec.ts`) |
|
|
63
|
+
| Case 4 | Enums used by exceptions | `exceptions/enums/ensure.ts`, `exceptions/enums/job.ts` |
|
|
64
|
+
| Case 5 | Legacy suffix | five files still end in `.exception.ts` under `errors/mixin/`; the 289 others carry no suffix |
|
|
65
|
+
|
|
66
|
+
## BE-FOLDER-6 — Data
|
|
67
|
+
|
|
68
|
+
| Case | When | Write |
|
|
69
|
+
| --- | --- | --- |
|
|
70
|
+
| Case 1 | Entity | `modules/databases/postgresql/primary/entities/cart-item.entity.ts` (197 `.entity.ts`, 92 with `.entity.spec.ts`); base `entities/abstract.ts`, `abstract-projection.ts` |
|
|
71
|
+
| Case 2 | Enum | `modules/databases/postgresql/primary/enums/locale.ts`, `enums/action-type.ts` (76 files export an enum here) |
|
|
72
|
+
| Case 3 | Migration | `<timestamp>-<PascalCaseName>.ts`, e.g. `1719200000000-AddIsEnrolledToEnrollments.ts` — the only PascalCase file names in `src/` (95) |
|
|
73
|
+
|
|
74
|
+
## BE-FOLDER-7 — Tests
|
|
75
|
+
|
|
76
|
+
| Case | When | Write |
|
|
77
|
+
| --- | --- | --- |
|
|
78
|
+
| Case 1 | Unit | `<name>.spec.ts` beside `<name>.ts` (875) |
|
|
79
|
+
| Case 2 | Integration | `<name>.int-spec.ts` beside its subject (7), e.g. `graphql/schema-builds.int-spec.ts` |
|
|
80
|
+
| Case 3 | End-to-end and harness | `src/tests/e2e/`, `src/tests/harness/` (`*.e2e-spec.ts`, `*.harness-spec.ts`; `jest-harness.json`) |
|
|
81
|
+
| Case 4 | Shared mocks | `src/tests/mocks/entity-manager.mock.ts` imported as `@tests/mocks/entity-manager.mock` |
|
|
82
|
+
|
|
83
|
+
## Open question
|
|
84
|
+
|
|
85
|
+
One class per exception file holds in 275 of 294 files; 19 files hold two to eight classes
|
|
86
|
+
(`errors/api/graphql.ts`, `errors/community/*.ts`, `errors/users/user.ts`). The single-class form
|
|
87
|
+
dominates and is what new files follow; the multi-class files are not declared wrong here.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Thư mục
|
|
2
|
+
|
|
3
|
+
Tệp này trả lời một câu hỏi: cho một mẩu mã backend, nó nằm ở thư mục nào và mang tên tệp gì?
|
|
4
|
+
|
|
5
|
+
Nguồn: `src/features/api/core/graphql/mutations/courses/add-to-cart/*`,
|
|
6
|
+
`src/features/api/core/graphql/queries/courses/course/*`,
|
|
7
|
+
`src/features/api/core/graphql/mutations/courses/courses.module.ts`,
|
|
8
|
+
`src/modules/platform/{winston,throttler,exceptions}/`, `src/modules/ai/`,
|
|
9
|
+
`src/modules/databases/postgresql/primary/{entities,enums}/`, `src/tests/`, `jest.config.ts`.
|
|
10
|
+
|
|
11
|
+
## BE-FOLDER-1 — Hai gốc
|
|
12
|
+
|
|
13
|
+
| Case | Dùng khi | Viết |
|
|
14
|
+
| --- | --- | --- |
|
|
15
|
+
| Case 1 | Một cánh cửa thế giới bên ngoài gọi vào | `src/features/<door>/` — `api`, `socketio`, `cli`, `backup`, `mock`, `video-encoder` |
|
|
16
|
+
| Case 2 | Một năng lực mà các cánh cửa ghép lại | `src/modules/<capability>/` — `ai`, `api`, `bussiness`, `crypto`, `databases`, `filesystem`, `init`, `integrations`, `lib`, `membership`, `platform`, `playground-agent-core` |
|
|
17
|
+
| Case 3 | Hạ tầng kiểm thử | `src/tests/{e2e,fixtures,harness,helpers,mocks}/` — ví dụ `src/tests/mocks/entity-manager.mock.ts` |
|
|
18
|
+
| Case 4 | Gốc ghép nối | `apps/core/src/app.module.ts` (đăng ký `AbstractExceptionHttpFilter` làm `APP_FILTER`) |
|
|
19
|
+
|
|
20
|
+
## BE-FOLDER-2 — Một đơn vị GraphQL
|
|
21
|
+
|
|
22
|
+
Một mutation hay query là một thư mục kebab-case dưới miền của nó. Bộ tệp là cố định.
|
|
23
|
+
|
|
24
|
+
| Case | Dùng khi | Viết |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| Case 1 | Đơn vị mutation `mutations/courses/add-to-cart/` | `add-to-cart.command.ts` · `add-to-cart.handler.ts` · `add-to-cart.handler.spec.ts` · `add-to-cart.service.ts` · `add-to-cart.resolver.ts` · `add-to-cart.module.ts` · `add-to-cart.module-definition.ts` · `graphql-types/request.ts` · `graphql-types/response.ts` |
|
|
27
|
+
| Case 2 | Đơn vị query `queries/courses/course/` | `course.query.ts` · `course.handler.ts` · `course.handler.spec.ts` · `course.service.ts` · `course.resolver.ts` · `course.resolver.spec.ts` · `course.module.ts` · `course.module-definition.ts` · `graphql-types/request.ts` · `graphql-types/response.ts` |
|
|
28
|
+
| Case 3 | Đối tượng GraphQL dùng chung | `features/api/core/graphql/shared/…` (`*.object.ts`, 14) |
|
|
29
|
+
|
|
30
|
+
Số đếm dưới `features/api/core/graphql`: `.module.ts` 338, `.module-definition.ts` 336,
|
|
31
|
+
`.resolver.ts` 305, `.service.ts` 203, `.handler.ts` 129, `.handler.spec.ts` 104, `.query.ts` 64,
|
|
32
|
+
`.command.ts` 62, `.resolver.spec.ts` 59.
|
|
33
|
+
|
|
34
|
+
## BE-FOLDER-3 — Bộ gom theo miền
|
|
35
|
+
|
|
36
|
+
| Case | Dùng khi | Viết |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| Case 1 | Thư mục miền | `mutations/courses/courses.module.ts` + `courses.module-definition.ts` bên cạnh các thư mục đơn vị `add-to-cart/`, `clear-cart/`, `course-enroll/`, … |
|
|
39
|
+
| Case 2 | Đăng ký | `AddToCartSingleMutationModule.register({ … })` liệt kê trong `courses.module.ts`; import suông không phải đăng ký |
|
|
40
|
+
| Case 3 | Các bộ gom cấp trên | `mutations/mutations.module.ts`, `queries/queries.module.ts`, `graphql/graphql.module.ts`, `core/core.module.ts`, `api/api.module.ts`, mỗi cái có `.module-definition.ts` |
|
|
41
|
+
|
|
42
|
+
## BE-FOLDER-4 — Một module năng lực
|
|
43
|
+
|
|
44
|
+
| Case | Dùng khi | Viết |
|
|
45
|
+
| --- | --- | --- |
|
|
46
|
+
| Case 1 | `modules/platform/winston/` | `winston.module.ts` · `winston.module-definition.ts` · `winston.service.ts` · `winston.service.spec.ts` · `winston.providers.ts` · `winston.providers.spec.ts` · `winston.decorators.ts` · `config.ts` · `constants/` · `enums/` · `types/` · `utils/` |
|
|
47
|
+
| Case 2 | `modules/platform/throttler/` | `throttler.module.ts` · `throttler.module-definition.ts` · `throttler.decorators.ts` · `config.ts` · `types.ts` · `enums/` · `guards/` · `types/` · `utils/` |
|
|
48
|
+
| Case 3 | `modules/ai/` | `ai.module.ts` · `ai.module-definition.ts` · `ai-entitlement.service.ts` (+ `.spec.ts`) · `ai-invoke.service.ts` · `constants/ai-entitlement.constants.ts` · `balancer/` · `ping/` · `types/` · `utils/` |
|
|
49
|
+
| Case 4 | Helper | `modules/bussiness/projections/user-stats/kpi-current.util.ts` (`.util.ts` cạnh `types.ts` của nó) |
|
|
50
|
+
|
|
51
|
+
Số đếm dưới `src/modules`: `.service.ts` 396, `.service.spec.ts` 271, `.entity.ts` 197,
|
|
52
|
+
`.module.ts` 116, `.module-definition.ts` 115, `.listener.ts` 18, `.providers.ts` 16,
|
|
53
|
+
`.decorators.ts` 16, `.guard.ts` 11.
|
|
54
|
+
|
|
55
|
+
## BE-FOLDER-5 — Exception có một nhà duy nhất
|
|
56
|
+
|
|
57
|
+
| Case | Dùng khi | Viết |
|
|
58
|
+
| --- | --- | --- |
|
|
59
|
+
| Case 1 | Mọi lớp exception | `src/modules/platform/exceptions/errors/<domain>/<name>.ts` — 294 tệp trong `ai/ api/ backup/ bento4/ cache/ cli/ coding/ community/ courses/ …` (lint `exception-in-errors-folder`) |
|
|
60
|
+
| Case 2 | Lớp gốc | `errors/abstract.ts` |
|
|
61
|
+
| Case 3 | Ánh xạ vận chuyển | `exceptions/filters/abstract-exception-http.filter.ts` (+ `.spec.ts`) |
|
|
62
|
+
| Case 4 | Enum mà exception dùng | `exceptions/enums/ensure.ts`, `exceptions/enums/job.ts` |
|
|
63
|
+
| Case 5 | Hậu tố cũ | năm tệp vẫn kết thúc bằng `.exception.ts` dưới `errors/mixin/`; 289 tệp còn lại không có hậu tố |
|
|
64
|
+
|
|
65
|
+
## BE-FOLDER-6 — Dữ liệu
|
|
66
|
+
|
|
67
|
+
| Case | Dùng khi | Viết |
|
|
68
|
+
| --- | --- | --- |
|
|
69
|
+
| Case 1 | Entity | `modules/databases/postgresql/primary/entities/cart-item.entity.ts` (197 `.entity.ts`, 92 có `.entity.spec.ts`); gốc `entities/abstract.ts`, `abstract-projection.ts` |
|
|
70
|
+
| Case 2 | Enum | `modules/databases/postgresql/primary/enums/locale.ts`, `enums/action-type.ts` (76 tệp export enum ở đây) |
|
|
71
|
+
| Case 3 | Migration | `<timestamp>-<PascalCaseName>.ts`, ví dụ `1719200000000-AddIsEnrolledToEnrollments.ts` — tên tệp PascalCase duy nhất trong `src/` (95) |
|
|
72
|
+
|
|
73
|
+
## BE-FOLDER-7 — Kiểm thử
|
|
74
|
+
|
|
75
|
+
| Case | Dùng khi | Viết |
|
|
76
|
+
| --- | --- | --- |
|
|
77
|
+
| Case 1 | Đơn vị | `<name>.spec.ts` cạnh `<name>.ts` (875) |
|
|
78
|
+
| Case 2 | Tích hợp | `<name>.int-spec.ts` cạnh đối tượng của nó (7), ví dụ `graphql/schema-builds.int-spec.ts` |
|
|
79
|
+
| Case 3 | Đầu cuối và harness | `src/tests/e2e/`, `src/tests/harness/` (`*.e2e-spec.ts`, `*.harness-spec.ts`; `jest-harness.json`) |
|
|
80
|
+
| Case 4 | Mock dùng chung | `src/tests/mocks/entity-manager.mock.ts` import dưới tên `@tests/mocks/entity-manager.mock` |
|
|
81
|
+
|
|
82
|
+
## Câu hỏi để ngỏ
|
|
83
|
+
|
|
84
|
+
Một lớp cho một tệp exception đúng ở 275 trên 294 tệp; 19 tệp chứa từ hai đến tám lớp
|
|
85
|
+
(`errors/api/graphql.ts`, `errors/community/*.ts`, `errors/users/user.ts`). Dạng một lớp chiếm ưu
|
|
86
|
+
thế và là điều tệp mới đi theo; các tệp nhiều lớp không bị tuyên là sai ở đây.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Function
|
|
2
|
+
|
|
3
|
+
This file answers one question: given a backend handler, service, resolver, module or helper, what
|
|
4
|
+
shape does it take, what does it receive, and what does it return?
|
|
5
|
+
|
|
6
|
+
Sources: `features/api/core/graphql/mutations/courses/add-to-cart/*`,
|
|
7
|
+
`features/api/core/graphql/queries/courses/course/course.handler.ts`,
|
|
8
|
+
`modules/platform/cqrs/icqrs-handler.ts`, `features/api/core/types/execute.ts`,
|
|
9
|
+
`modules/bussiness/projections/user-stats/kpi-current.util.ts`,
|
|
10
|
+
`modules/api/apollo/server/interceptors/graphql-transform.interceptor.ts`.
|
|
11
|
+
|
|
12
|
+
## BE-FUNCTION-1 — Handler: extend the template, override `process`
|
|
13
|
+
|
|
14
|
+
| Case | When | Write |
|
|
15
|
+
| --- | --- | --- |
|
|
16
|
+
| Case 1 | Class head | `@CommandHandler(AddToCartCommand) @Injectable() export class AddToCartHandler extends ICQRSHandler<AddToCartCommand, CartItemEntity> implements ICommandHandler<AddToCartCommand, CartItemEntity>` (140 handlers extend `ICQRSHandler`; 138 also implement the Nest interface) |
|
|
17
|
+
| Case 2 | Query variant | `@QueryHandler(CourseQuery) @Injectable() export class CourseHandler extends ICQRSHandler<CourseQuery, CourseEntity> implements IQueryHandler<CourseQuery, CourseEntity>` |
|
|
18
|
+
| Case 3 | The one method | `protected override async process(command: AddToCartCommand): Promise<CartItemEntity> { … }` (140/140; lint `handler-overrides-process`) |
|
|
19
|
+
| Case 4 | The public door | inherited: `async execute(params: TParams): Promise<TResponse> { return await this.process(params) }` in `ICQRSHandler` |
|
|
20
|
+
| Case 5 | First statement | `const { request, user } = command.params` then guards |
|
|
21
|
+
|
|
22
|
+
## BE-FUNCTION-2 — Message: an inert envelope
|
|
23
|
+
|
|
24
|
+
| Case | When | Write |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| Case 1 | Command | `export class AddToCartCommand { constructor(readonly params: ExecuteParams<AddToCartRequest>) {} }` |
|
|
27
|
+
| Case 2 | Query | `export class CourseQuery { constructor(readonly params: ExecuteParams<CourseRequest>) {} }` |
|
|
28
|
+
| Case 3 | The envelope type | `interface ExecuteParams<T> { request: T; locale?: Locale; user?: UserEntity; enrollmentId?: string; keycloakToken?: KeycloakTokenIntrospectResponse }` |
|
|
29
|
+
| Case 4 | Nothing else | no methods, no derived fields (lint `message-carries-params-only`) |
|
|
30
|
+
|
|
31
|
+
## BE-FUNCTION-3 — Service: forward to the bus
|
|
32
|
+
|
|
33
|
+
| Case | When | Write |
|
|
34
|
+
| --- | --- | --- |
|
|
35
|
+
| Case 1 | Shape | `@Injectable() export class AddToCartService { constructor(private readonly commandBus: CommandBus) {} async execute(params: ExecuteParams<AddToCartRequest>): Promise<CartItemEntity> { return this.commandBus.execute(new AddToCartCommand(params)) } }` |
|
|
36
|
+
| Case 2 | Query variant | same with `QueryBus` and `new CourseQuery(params)` |
|
|
37
|
+
|
|
38
|
+
## BE-FUNCTION-4 — Resolver: decorate the door, build the envelope
|
|
39
|
+
|
|
40
|
+
| Case | When | Write |
|
|
41
|
+
| --- | --- | --- |
|
|
42
|
+
| Case 1 | Method decorators, in this order | `@UseThrottler(ThrottlerConfig.Medium)` · `@UseGuards(KeycloakAuthGraphQLGuard)` · `@GraphQLSuccessMessage({ [Locale.En]: "…", [Locale.Vi]: "…" })` · `@UseInterceptors(GraphQLTransformInterceptor)` · `@Mutation(() => AddToCartResponse, { name: "addToCart", description: "…" })` |
|
|
43
|
+
| Case 2 | Parameters | `@KeycloakGraphQLUser() user: UserEntity, @Args("request", { description: "…" }) request: AddToCartRequest, @GraphQLLocale() locale: Locale` |
|
|
44
|
+
| Case 3 | Body | `return this.addToCartService.execute({ request, user, locale })` — one call, no logic |
|
|
45
|
+
| Case 4 | Return type | the entity or object the `Response.data` field declares: `Promise<CartItemEntity>` |
|
|
46
|
+
|
|
47
|
+
## BE-FUNCTION-5 — Constructor injection
|
|
48
|
+
|
|
49
|
+
| Case | When | Write |
|
|
50
|
+
| --- | --- | --- |
|
|
51
|
+
| Case 1 | Required dependency | `constructor(private readonly s3ReadService: S3ReadService, private readonly s3NameResolverService: S3NameResolverService) { super() }` (2489 `private readonly` against 26 `private` without `readonly`) |
|
|
52
|
+
| Case 2 | Entity manager | `@InjectPrimaryPostgreSQLEntityManager() private readonly entityManager: EntityManager` — never a repository (lint `must-inject-entity-manager`, `no-injected-repository`) |
|
|
53
|
+
| Case 3 | Optional dependency | `@Optional() private readonly mountFilesystemService?: MountFilesystemService` |
|
|
54
|
+
| Case 4 | Handler | calls `super()` because `ICQRSHandler` is an abstract class |
|
|
55
|
+
|
|
56
|
+
## BE-FUNCTION-6 — Guards, then work, then return
|
|
57
|
+
|
|
58
|
+
| Case | When | Write |
|
|
59
|
+
| --- | --- | --- |
|
|
60
|
+
| Case 1 | Authentication | `if (!user) { throw new UserNotFoundException({}) }` before any query |
|
|
61
|
+
| Case 2 | Existence | `const courseExists = await this.entityManager.exists(CourseEntity, { where: { id: courseId } }); if (!courseExists) { throw new CourseNotFoundException({ id: courseId }) }` |
|
|
62
|
+
| Case 3 | Idempotent read-before-write | `const existing = await this.entityManager.findOne(CartItemEntity, { where: { course: { id: courseId }, user: { id: user.id } } }); if (existing) { return existing }` |
|
|
63
|
+
| Case 4 | Persist | `const cartItem = this.entityManager.create(CartItemEntity, { user: { id: user.id }, course: { id: courseId } }); return this.entityManager.save(cartItem)` |
|
|
64
|
+
|
|
65
|
+
## BE-FUNCTION-7 — Helpers
|
|
66
|
+
|
|
67
|
+
| Case | When | Write |
|
|
68
|
+
| --- | --- | --- |
|
|
69
|
+
| Case 1 | Pure mapping shared by two callers | `export const getKpiCurrentValues = (stats: UserStatsResult): Record<KpiKey, number> => ({ [KpiKey.Lessons]: stats.weeklyLessons, … })` in `kpi-current.util.ts` (245 exported arrow consts against 20 `export function` under `src/modules`) |
|
|
70
|
+
| Case 2 | Decorator factory | `export const GraphQLSuccessMessage = (message: GraphQLSuccessMessage) => SetMetadata(SUCCESS_MESSAGE_METADATA, message)` |
|
|
71
|
+
| Case 3 | Parameter list | a named interface, not an inline object (lint `no-inline-param-type`); single primitive params stay positional |
|
|
72
|
+
| Case 4 | Where it lives | beside its consumer as `<name>.util.ts`, or under the module's `utils/` |
|
|
73
|
+
|
|
74
|
+
## BE-FUNCTION-8 — Module
|
|
75
|
+
|
|
76
|
+
| Case | When | Write |
|
|
77
|
+
| --- | --- | --- |
|
|
78
|
+
| Case 1 | Definition file | `export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } = new ConfigurableModuleBuilder().setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal })).build()` |
|
|
79
|
+
| Case 2 | Module file | `@Module({ providers: [AddToCartService, AddToCartResolver, AddToCartHandler] }) export class AddToCartSingleMutationModule extends ConfigurableModuleClass {}` |
|
|
80
|
+
| Case 3 | Registration by the parent | `AddToCartSingleMutationModule.register({ … })` in `courses.module.ts` |
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Hàm
|
|
2
|
+
|
|
3
|
+
Tệp này trả lời một câu hỏi: cho một handler, service, resolver, module hay helper backend, nó có
|
|
4
|
+
hình dạng gì, nhận gì, và trả về gì?
|
|
5
|
+
|
|
6
|
+
Nguồn: `features/api/core/graphql/mutations/courses/add-to-cart/*`,
|
|
7
|
+
`features/api/core/graphql/queries/courses/course/course.handler.ts`,
|
|
8
|
+
`modules/platform/cqrs/icqrs-handler.ts`, `features/api/core/types/execute.ts`,
|
|
9
|
+
`modules/bussiness/projections/user-stats/kpi-current.util.ts`,
|
|
10
|
+
`modules/api/apollo/server/interceptors/graphql-transform.interceptor.ts`.
|
|
11
|
+
|
|
12
|
+
## BE-FUNCTION-1 — Handler: kế thừa khuôn, ghi đè `process`
|
|
13
|
+
|
|
14
|
+
| Case | Dùng khi | Viết |
|
|
15
|
+
| --- | --- | --- |
|
|
16
|
+
| Case 1 | Đầu lớp | `@CommandHandler(AddToCartCommand) @Injectable() export class AddToCartHandler extends ICQRSHandler<AddToCartCommand, CartItemEntity> implements ICommandHandler<AddToCartCommand, CartItemEntity>` (140 handler kế thừa `ICQRSHandler`; 138 đồng thời implement giao diện của Nest) |
|
|
17
|
+
| Case 2 | Biến thể query | `@QueryHandler(CourseQuery) @Injectable() export class CourseHandler extends ICQRSHandler<CourseQuery, CourseEntity> implements IQueryHandler<CourseQuery, CourseEntity>` |
|
|
18
|
+
| Case 3 | Phương thức duy nhất | `protected override async process(command: AddToCartCommand): Promise<CartItemEntity> { … }` (140/140; lint `handler-overrides-process`) |
|
|
19
|
+
| Case 4 | Cửa công khai | kế thừa: `async execute(params: TParams): Promise<TResponse> { return await this.process(params) }` trong `ICQRSHandler` |
|
|
20
|
+
| Case 5 | Câu lệnh đầu tiên | `const { request, user } = command.params` rồi các chốt chặn |
|
|
21
|
+
|
|
22
|
+
## BE-FUNCTION-2 — Thông điệp: một phong bì trơ
|
|
23
|
+
|
|
24
|
+
| Case | Dùng khi | Viết |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| Case 1 | Command | `export class AddToCartCommand { constructor(readonly params: ExecuteParams<AddToCartRequest>) {} }` |
|
|
27
|
+
| Case 2 | Query | `export class CourseQuery { constructor(readonly params: ExecuteParams<CourseRequest>) {} }` |
|
|
28
|
+
| Case 3 | Kiểu phong bì | `interface ExecuteParams<T> { request: T; locale?: Locale; user?: UserEntity; enrollmentId?: string; keycloakToken?: KeycloakTokenIntrospectResponse }` |
|
|
29
|
+
| Case 4 | Không gì khác | không phương thức, không trường suy diễn (lint `message-carries-params-only`) |
|
|
30
|
+
|
|
31
|
+
## BE-FUNCTION-3 — Service: chuyển tiếp lên bus
|
|
32
|
+
|
|
33
|
+
| Case | Dùng khi | Viết |
|
|
34
|
+
| --- | --- | --- |
|
|
35
|
+
| Case 1 | Hình dạng | `@Injectable() export class AddToCartService { constructor(private readonly commandBus: CommandBus) {} async execute(params: ExecuteParams<AddToCartRequest>): Promise<CartItemEntity> { return this.commandBus.execute(new AddToCartCommand(params)) } }` |
|
|
36
|
+
| Case 2 | Biến thể query | tương tự với `QueryBus` và `new CourseQuery(params)` |
|
|
37
|
+
|
|
38
|
+
## BE-FUNCTION-4 — Resolver: trang trí cánh cửa, dựng phong bì
|
|
39
|
+
|
|
40
|
+
| Case | Dùng khi | Viết |
|
|
41
|
+
| --- | --- | --- |
|
|
42
|
+
| Case 1 | Decorator phương thức, theo thứ tự này | `@UseThrottler(ThrottlerConfig.Medium)` · `@UseGuards(KeycloakAuthGraphQLGuard)` · `@GraphQLSuccessMessage({ [Locale.En]: "…", [Locale.Vi]: "…" })` · `@UseInterceptors(GraphQLTransformInterceptor)` · `@Mutation(() => AddToCartResponse, { name: "addToCart", description: "…" })` |
|
|
43
|
+
| Case 2 | Tham số | `@KeycloakGraphQLUser() user: UserEntity, @Args("request", { description: "…" }) request: AddToCartRequest, @GraphQLLocale() locale: Locale` |
|
|
44
|
+
| Case 3 | Thân | `return this.addToCartService.execute({ request, user, locale })` — một lời gọi, không logic |
|
|
45
|
+
| Case 4 | Kiểu trả về | entity hoặc object mà trường `Response.data` khai báo: `Promise<CartItemEntity>` |
|
|
46
|
+
|
|
47
|
+
## BE-FUNCTION-5 — Tiêm qua constructor
|
|
48
|
+
|
|
49
|
+
| Case | Dùng khi | Viết |
|
|
50
|
+
| --- | --- | --- |
|
|
51
|
+
| Case 1 | Phụ thuộc bắt buộc | `constructor(private readonly s3ReadService: S3ReadService, private readonly s3NameResolverService: S3NameResolverService) { super() }` (2489 `private readonly` so với 26 `private` không `readonly`) |
|
|
52
|
+
| Case 2 | Entity manager | `@InjectPrimaryPostgreSQLEntityManager() private readonly entityManager: EntityManager` — không bao giờ là repository (lint `must-inject-entity-manager`, `no-injected-repository`) |
|
|
53
|
+
| Case 3 | Phụ thuộc tùy chọn | `@Optional() private readonly mountFilesystemService?: MountFilesystemService` |
|
|
54
|
+
| Case 4 | Handler | gọi `super()` vì `ICQRSHandler` là lớp trừu tượng |
|
|
55
|
+
|
|
56
|
+
## BE-FUNCTION-6 — Chốt chặn, rồi việc, rồi trả về
|
|
57
|
+
|
|
58
|
+
| Case | Dùng khi | Viết |
|
|
59
|
+
| --- | --- | --- |
|
|
60
|
+
| Case 1 | Xác thực | `if (!user) { throw new UserNotFoundException({}) }` trước mọi truy vấn |
|
|
61
|
+
| Case 2 | Tồn tại | `const courseExists = await this.entityManager.exists(CourseEntity, { where: { id: courseId } }); if (!courseExists) { throw new CourseNotFoundException({ id: courseId }) }` |
|
|
62
|
+
| Case 3 | Đọc-trước-ghi lũy đẳng | `const existing = await this.entityManager.findOne(CartItemEntity, { where: { course: { id: courseId }, user: { id: user.id } } }); if (existing) { return existing }` |
|
|
63
|
+
| Case 4 | Lưu | `const cartItem = this.entityManager.create(CartItemEntity, { user: { id: user.id }, course: { id: courseId } }); return this.entityManager.save(cartItem)` |
|
|
64
|
+
|
|
65
|
+
## BE-FUNCTION-7 — Helper
|
|
66
|
+
|
|
67
|
+
| Case | Dùng khi | Viết |
|
|
68
|
+
| --- | --- | --- |
|
|
69
|
+
| Case 1 | Ánh xạ thuần dùng chung bởi hai bên gọi | `export const getKpiCurrentValues = (stats: UserStatsResult): Record<KpiKey, number> => ({ [KpiKey.Lessons]: stats.weeklyLessons, … })` trong `kpi-current.util.ts` (245 arrow const được export so với 20 `export function` dưới `src/modules`) |
|
|
70
|
+
| Case 2 | Nhà máy decorator | `export const GraphQLSuccessMessage = (message: GraphQLSuccessMessage) => SetMetadata(SUCCESS_MESSAGE_METADATA, message)` |
|
|
71
|
+
| Case 3 | Danh sách tham số | một interface có tên, không phải object nội tuyến (lint `no-inline-param-type`); tham số nguyên thủy đơn lẻ giữ dạng vị trí |
|
|
72
|
+
| Case 4 | Nơi sống | cạnh bên tiêu thụ dưới tên `<name>.util.ts`, hoặc dưới `utils/` của module |
|
|
73
|
+
|
|
74
|
+
## BE-FUNCTION-8 — Module
|
|
75
|
+
|
|
76
|
+
| Case | Dùng khi | Viết |
|
|
77
|
+
| --- | --- | --- |
|
|
78
|
+
| Case 1 | Tệp định nghĩa | `export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } = new ConfigurableModuleBuilder().setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal })).build()` |
|
|
79
|
+
| Case 2 | Tệp module | `@Module({ providers: [AddToCartService, AddToCartResolver, AddToCartHandler] }) export class AddToCartSingleMutationModule extends ConfigurableModuleClass {}` |
|
|
80
|
+
| Case 3 | Đăng ký bởi cha | `AddToCartSingleMutationModule.register({ … })` trong `courses.module.ts` |
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Imports
|
|
2
|
+
|
|
3
|
+
This file answers one question: given a backend file, what may it import, through which path, in
|
|
4
|
+
which style, and in which order?
|
|
5
|
+
|
|
6
|
+
Sources: `tsconfig.json` (`paths`), `jest.config.ts` (`moduleNameMapper`), `eslint.config.mjs`,
|
|
7
|
+
`features/api/core/graphql/mutations/courses/add-to-cart/*`,
|
|
8
|
+
`features/api/core/graphql/queries/courses/course/course.handler.ts`,
|
|
9
|
+
`modules/platform/exceptions/errors/ai/ai-quota-exhausted.ts`,
|
|
10
|
+
`modules/platform/exceptions/filters/abstract-exception-http.filter.ts`.
|
|
11
|
+
|
|
12
|
+
## BE-IMPORTS-1 — Aliases
|
|
13
|
+
|
|
14
|
+
| Case | When | Write |
|
|
15
|
+
| --- | --- | --- |
|
|
16
|
+
| Case 1 | A capability | `@modules/<capability>/<deep path to file>` — `import { ICQRSHandler } from "@modules/platform/cqrs/icqrs-handler"` |
|
|
17
|
+
| Case 2 | A door | `@features/<door>/…` (used from `apps/` and tests) |
|
|
18
|
+
| Case 3 | Test helpers | `import { makeEntityManagerMock } from "@tests/mocks/entity-manager.mock"` |
|
|
19
|
+
| Case 4 | Inside one unit | `./add-to-cart.command`, `./graphql-types/request`, `./add-to-cart.service` |
|
|
20
|
+
| Case 5 | Up the same door | `import { ExecuteParams } from "../../../../types/execute"` — 290 files under `features` use this relative path; 0 use `@features/api/core/types/execute` (lint `no-self-module-alias`) |
|
|
21
|
+
| Case 6 | Inside the exceptions tree | `import { AbstractException } from "../abstract"` |
|
|
22
|
+
|
|
23
|
+
## BE-IMPORTS-2 — Brace style
|
|
24
|
+
|
|
25
|
+
Every import is multi-line, one binding per line, trailing comma. Lint `object-curly-newline`
|
|
26
|
+
with `ImportDeclaration: "always"` enforces it.
|
|
27
|
+
|
|
28
|
+
| Case | When | Write |
|
|
29
|
+
| --- | --- | --- |
|
|
30
|
+
| Case 1 | One binding | `import {\n Injectable,\n} from "@nestjs/common"` |
|
|
31
|
+
| Case 2 | Several | `import {\n CommandHandler,\n ICommandHandler,\n} from "@nestjs/cqrs"` |
|
|
32
|
+
| Case 3 | Type only | `import type {\n EntityManager,\n} from "typeorm"`; `import type {\n AbstractExceptionMetadata,\n} from "../abstract"` (1388 of 4463 files use `import type`) |
|
|
33
|
+
|
|
34
|
+
## BE-IMPORTS-3 — Order
|
|
35
|
+
|
|
36
|
+
Not lint-enforced. The dominant first import in GraphQL unit files is the Nest framework.
|
|
37
|
+
|
|
38
|
+
| Case | When | Write |
|
|
39
|
+
| --- | --- | --- |
|
|
40
|
+
| Case 1 | Framework first | `@nestjs/graphql`, `@nestjs/common`, `@nestjs/cqrs` (first import in 303 of 398 sampled GraphQL files) |
|
|
41
|
+
| Case 2 | Then capabilities | `@modules/api/…`, `@modules/integrations/…`, `@modules/platform/…`, `@modules/databases/…` (46 files start here) |
|
|
42
|
+
| Case 3 | Then relative | `../../../../types/execute` (41 start here), then `./…` last (7) |
|
|
43
|
+
| Case 4 | Deviation | `add-to-cart.handler.ts` opens with `@modules/platform/cqrs/icqrs-handler` and interleaves `@nestjs/common` after several `@modules` imports; the order above is dominant, not universal |
|
|
44
|
+
|
|
45
|
+
## BE-IMPORTS-4 — Layering direction
|
|
46
|
+
|
|
47
|
+
| Case | When | Write |
|
|
48
|
+
| --- | --- | --- |
|
|
49
|
+
| Case 1 | `features` → `modules` | always allowed: a handler imports entities, exceptions, decorators from `@modules/…` |
|
|
50
|
+
| Case 2 | `modules` → `features` | not allowed (lint `no-capability-imports-features`); 6 non-spec files under `modules/bussiness/{daily-quest,flashcard,kpi-reward,streak,weekly-challenge}` still do — recorded debt, not a pattern |
|
|
51
|
+
| Case 3 | `modules/**/*.module.ts` → another in-repo module | not imported; capability modules are registered `isGlobal: true` at the app root (lint `no-non-global-module-import`, error in both `src/modules` and `src/features`) |
|
|
52
|
+
| Case 4 | Folder barrel | none exist to import; every import names a file (lint `must-deep-module-import`, `no-folder-reexport`) |
|
|
53
|
+
| Case 5 | Relative escape across capabilities | `from "../../modules/…"` occurs in 0 files (lint `no-relative-capability-escape`) |
|
|
54
|
+
|
|
55
|
+
## BE-IMPORTS-5 — What a GraphQL unit imports
|
|
56
|
+
|
|
57
|
+
| Case | When | Write |
|
|
58
|
+
| --- | --- | --- |
|
|
59
|
+
| Case 1 | Handler | `@modules/platform/cqrs/icqrs-handler`, entities from `@modules/databases/postgresql/primary/entities/*.entity`, exceptions from `@modules/platform/exceptions/errors/<domain>/<name>`, `@nestjs/common`, `@nestjs/cqrs`, `typeorm` (type), `./<name>.command` |
|
|
60
|
+
| Case 2 | Resolver | `@nestjs/graphql`, `@nestjs/common`, `@modules/api/apollo/server/decorators/locale.decorators`, `@modules/api/apollo/server/interceptors/graphql-transform.interceptor`, `@modules/integrations/keycloak/guards/keycloak-auth-graphql.guard`, `@modules/integrations/keycloak/keycloak.decorators`, `@modules/platform/throttler/*`, `./graphql-types/request`, `./graphql-types/response`, `./<name>.service` |
|
|
61
|
+
| Case 3 | Response | `@nestjs/graphql`, `@modules/api/apollo/server/graphql-types/object-types/graphql-response`, `@modules/api/apollo/server/types/graphql-response`, the entity |
|
|
62
|
+
| Case 4 | Module | `@nestjs/common` and the four sibling files |
|
|
63
|
+
|
|
64
|
+
## BE-IMPORTS-6 — Forbidden
|
|
65
|
+
|
|
66
|
+
| Case | When | Write |
|
|
67
|
+
| --- | --- | --- |
|
|
68
|
+
| Case 1 | Default export | none (lint `no-default-export`; Jest lifecycle entries carved out) |
|
|
69
|
+
| Case 2 | `process.env` | only `src/modules/platform/env/utils/parse-env.ts`; everyone else calls `envConfig()` |
|
|
70
|
+
| Case 3 | `console.*` | lint error in `src/**`; 188 files log through `WinstonService`, 12 residual `console.` sites |
|
|
71
|
+
| Case 4 | Nest `Logger` | not used (lint `no-nest-logger`, `no-framework-logger`) |
|
|
72
|
+
| Case 5 | Raw cache tokens outside the cache module | lint `must-use-cache-service` |
|
|
73
|
+
|
|
74
|
+
## BE-IMPORTS-7 — Jest sees the same aliases
|
|
75
|
+
|
|
76
|
+
| Case | When | Write |
|
|
77
|
+
| --- | --- | --- |
|
|
78
|
+
| Case 1 | `jest.config.ts` | `moduleNameMapper: { "^@modules/(.*)$": "<rootDir>/src/modules/$1", "^@features/(.*)$": "<rootDir>/src/features/$1", "^@tests/(.*)$": "<rootDir>/src/tests/$1" }` |
|
|
79
|
+
| Case 2 | Spec mocking a module path | `jest.mock("@modules/platform/env/config", () => ({ envConfig: () => ({ … }) }))` |
|