@rangka/core 0.1.1 → 0.1.3
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/package.json +6 -2
- package/.claude/skills/extend-core/SKILL.md +0 -133
- package/.turbo/turbo-build.log +0 -4
- package/CHANGELOG.md +0 -25
- package/CLAUDE.md +0 -180
- package/src/__tests__/coerce.test.ts +0 -154
- package/src/__tests__/context.test.ts +0 -111
- package/src/__tests__/helpers.ts +0 -21
- package/src/__tests__/index.test.ts +0 -7
- package/src/__tests__/widgets.test.ts +0 -197
- package/src/api/__tests__/handlers.test.ts +0 -389
- package/src/api/__tests__/include-resolver.test.ts +0 -393
- package/src/api/__tests__/middleware.test.ts +0 -100
- package/src/api/__tests__/openapi-schema.test.ts +0 -210
- package/src/api/__tests__/query-parser.test.ts +0 -291
- package/src/api/__tests__/route-generator.test.ts +0 -137
- package/src/api/__tests__/server.test.ts +0 -73
- package/src/api/__tests__/swagger.test.ts +0 -166
- package/src/api/handlers.ts +0 -274
- package/src/api/include-resolver.ts +0 -27
- package/src/api/index.ts +0 -4
- package/src/api/meta-handler.ts +0 -254
- package/src/api/openapi-schema.ts +0 -99
- package/src/api/query-parser.ts +0 -315
- package/src/api/route-generator.ts +0 -448
- package/src/api/server.ts +0 -147
- package/src/api/types.ts +0 -16
- package/src/audit/__tests__/audit.test.ts +0 -144
- package/src/audit/index.ts +0 -3
- package/src/audit/record.ts +0 -69
- package/src/audit/tables.ts +0 -48
- package/src/audit/types.ts +0 -26
- package/src/auth/__tests__/core-module.test.ts +0 -54
- package/src/auth/__tests__/debug.test.ts +0 -47
- package/src/auth/__tests__/field-permissions.test.ts +0 -245
- package/src/auth/__tests__/integration.test.ts +0 -208
- package/src/auth/__tests__/meta-boot.test.ts +0 -538
- package/src/auth/__tests__/model-permissions.test.ts +0 -205
- package/src/auth/__tests__/password.test.ts +0 -29
- package/src/auth/__tests__/permission-registry.test.ts +0 -313
- package/src/auth/__tests__/scope-hook.test.ts +0 -509
- package/src/auth/__tests__/scope-registry.test.ts +0 -297
- package/src/auth/__tests__/scopes.test.ts +0 -66
- package/src/auth/__tests__/session.test.ts +0 -214
- package/src/auth/core-models.ts +0 -52
- package/src/auth/core-module.ts +0 -59
- package/src/auth/debug.ts +0 -157
- package/src/auth/field-permissions.ts +0 -116
- package/src/auth/index.ts +0 -37
- package/src/auth/model-permissions.ts +0 -59
- package/src/auth/password.ts +0 -22
- package/src/auth/permission-registry.ts +0 -171
- package/src/auth/scope-filters.ts +0 -11
- package/src/auth/scope-registry.ts +0 -121
- package/src/auth/scopes.ts +0 -146
- package/src/auth/seed.ts +0 -44
- package/src/auth/session.ts +0 -178
- package/src/auth/types.ts +0 -50
- package/src/boot/__tests__/page-scanning.test.ts +0 -170
- package/src/boot/__tests__/page-utils.test.ts +0 -225
- package/src/boot/__tests__/project-scanner.test.ts +0 -88
- package/src/boot/dependency-sort.ts +0 -82
- package/src/boot/discovery.ts +0 -85
- package/src/boot/index.ts +0 -457
- package/src/boot/page-utils.ts +0 -110
- package/src/boot/project-scanner.ts +0 -397
- package/src/boot/schema-loader.ts +0 -26
- package/src/boot/schema-merger.ts +0 -125
- package/src/boot/traits.ts +0 -25
- package/src/boot/types.ts +0 -73
- package/src/context.ts +0 -105
- package/src/db/__tests__/cascade-delete.test.ts +0 -182
- package/src/db/__tests__/desired-state.test.ts +0 -136
- package/src/db/__tests__/diff-engine.test.ts +0 -635
- package/src/db/__tests__/field-mapper.test.ts +0 -355
- package/src/db/__tests__/introspect.test.ts +0 -70
- package/src/db/__tests__/search-filter.test.ts +0 -45
- package/src/db/__tests__/sequence.test.ts +0 -221
- package/src/db/auto-sync.ts +0 -133
- package/src/db/client.ts +0 -147
- package/src/db/desired-state.ts +0 -98
- package/src/db/diff-engine.ts +0 -305
- package/src/db/field-mapper.ts +0 -504
- package/src/db/filter-applier.ts +0 -89
- package/src/db/include-resolver.ts +0 -40
- package/src/db/index.ts +0 -23
- package/src/db/introspect.ts +0 -265
- package/src/db/model-include-resolver.ts +0 -327
- package/src/db/model-ops.ts +0 -281
- package/src/db/scope-enforcer.ts +0 -37
- package/src/db/types.ts +0 -98
- package/src/errors.ts +0 -41
- package/src/events/__tests__/bus.test.ts +0 -105
- package/src/events/bus.ts +0 -89
- package/src/events/index.ts +0 -2
- package/src/events/types.ts +0 -9
- package/src/external-model/__tests__/computed-fields.test.ts +0 -106
- package/src/external-model/__tests__/field-mapper.test.ts +0 -160
- package/src/external-model/__tests__/in-memory-ops.test.ts +0 -247
- package/src/external-model/__tests__/mutation-executor.test.ts +0 -160
- package/src/external-model/__tests__/query-executor.test.ts +0 -284
- package/src/external-model/__tests__/schema-converter.test.ts +0 -174
- package/src/external-model/computed-fields.ts +0 -15
- package/src/external-model/define.ts +0 -5
- package/src/external-model/external-model-ops.ts +0 -108
- package/src/external-model/field-mapper.ts +0 -66
- package/src/external-model/in-memory-ops.ts +0 -107
- package/src/external-model/index.ts +0 -7
- package/src/external-model/mutation-executor.ts +0 -71
- package/src/external-model/query-executor.ts +0 -100
- package/src/external-model/schema-converter.ts +0 -53
- package/src/external-model/types.ts +0 -32
- package/src/fixtures/__tests__/fixtures.test.ts +0 -203
- package/src/fixtures/index.ts +0 -10
- package/src/fixtures/loader.ts +0 -196
- package/src/fixtures/registry.ts +0 -125
- package/src/fixtures/types.ts +0 -33
- package/src/helpers/assert-ownership.ts +0 -19
- package/src/helpers/coerce.ts +0 -28
- package/src/helpers/stamping.ts +0 -28
- package/src/helpers/validation.ts +0 -14
- package/src/hooks/__tests__/context.test.ts +0 -73
- package/src/hooks/__tests__/executor.test.ts +0 -433
- package/src/hooks/__tests__/middleware.test.ts +0 -224
- package/src/hooks/__tests__/registry.test.ts +0 -50
- package/src/hooks/context.ts +0 -89
- package/src/hooks/errors.ts +0 -11
- package/src/hooks/executor.ts +0 -115
- package/src/hooks/index.ts +0 -10
- package/src/hooks/middleware.ts +0 -220
- package/src/hooks/registry.ts +0 -20
- package/src/hooks/types.ts +0 -32
- package/src/index.ts +0 -172
- package/src/jobs/__tests__/enqueue.test.ts +0 -77
- package/src/jobs/__tests__/integration.test.ts +0 -71
- package/src/jobs/__tests__/registry.test.ts +0 -103
- package/src/jobs/__tests__/scheduler.test.ts +0 -92
- package/src/jobs/__tests__/worker-execution.test.ts +0 -202
- package/src/jobs/__tests__/worker.test.ts +0 -119
- package/src/jobs/enqueue.ts +0 -93
- package/src/jobs/index.ts +0 -14
- package/src/jobs/registry.ts +0 -92
- package/src/jobs/scheduler.ts +0 -205
- package/src/jobs/tables.ts +0 -132
- package/src/jobs/types.ts +0 -62
- package/src/jobs/worker.ts +0 -272
- package/src/model-api/__tests__/cross-boundary-includes.test.ts +0 -366
- package/src/model-api/__tests__/extended-api.test.ts +0 -244
- package/src/model-api/__tests__/filter-applier.test.ts +0 -177
- package/src/model-api/__tests__/filter-translator.test.ts +0 -186
- package/src/model-api/__tests__/include-resolver.test.ts +0 -226
- package/src/model-api/__tests__/model-access.test.ts +0 -284
- package/src/model-api/__tests__/query-builder.test.ts +0 -224
- package/src/model-api/__tests__/scope-enforcer.test.ts +0 -268
- package/src/model-api/field-access.ts +0 -28
- package/src/model-api/filter-applier.ts +0 -1
- package/src/model-api/filter-translator.ts +0 -67
- package/src/model-api/include-resolver.ts +0 -2
- package/src/model-api/index.ts +0 -86
- package/src/model-api/query-builder.ts +0 -155
- package/src/model-api/scope-enforcer.ts +0 -3
- package/src/model-api/types.ts +0 -139
- package/src/plugins/__tests__/adapter-registry.test.ts +0 -92
- package/src/plugins/__tests__/lifecycle.test.ts +0 -96
- package/src/plugins/__tests__/loader.test.ts +0 -273
- package/src/plugins/__tests__/validator.test.ts +0 -275
- package/src/plugins/adapter-registry.ts +0 -42
- package/src/plugins/define.ts +0 -5
- package/src/plugins/index.ts +0 -28
- package/src/plugins/lifecycle.ts +0 -27
- package/src/plugins/loader.ts +0 -126
- package/src/plugins/types.ts +0 -76
- package/src/plugins/validator.ts +0 -141
- package/src/schema/__tests__/registry-models-by-module.test.ts +0 -58
- package/src/schema/registry.ts +0 -93
- package/src/schema/relationships.ts +0 -93
- package/src/schema/types.ts +0 -43
- package/src/services/__tests__/integration.test.ts +0 -63
- package/src/services/__tests__/registry.test.ts +0 -175
- package/src/services/index.ts +0 -13
- package/src/services/registry.ts +0 -156
- package/src/services/types.ts +0 -27
- package/src/validation/__tests__/field-validator.test.ts +0 -195
- package/src/validation/field-validator.ts +0 -113
- package/src/validation/index.ts +0 -1
- package/src/widgets/index.ts +0 -3
- package/src/widgets/slot-validator.ts +0 -87
- package/src/widgets/widget-registry.ts +0 -32
- package/tests/boot.test.ts +0 -323
- package/tests/dependency-sort.test.ts +0 -99
- package/tests/discovery.test.ts +0 -126
- package/tests/registry.test.ts +0 -216
- package/tests/schema-loader.test.ts +0 -52
- package/tests/schema-merger.test.ts +0 -180
- package/tsconfig.json +0 -9
- package/tsconfig.tsbuildinfo +0 -1
- package/vitest.config.ts +0 -14
package/package.json
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rangka/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist",
|
|
8
|
+
"README.md"
|
|
9
|
+
],
|
|
6
10
|
"exports": {
|
|
7
11
|
".": {
|
|
8
12
|
"import": "./dist/index.js",
|
|
@@ -17,7 +21,7 @@
|
|
|
17
21
|
"kysely": "^0.29.2",
|
|
18
22
|
"pg": "^8.13.1",
|
|
19
23
|
"qs": "^6.15.2",
|
|
20
|
-
"@rangka/shared": "0.1.
|
|
24
|
+
"@rangka/shared": "0.1.3"
|
|
21
25
|
},
|
|
22
26
|
"devDependencies": {
|
|
23
27
|
"@types/node": "^20.0.0",
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: extend-core
|
|
3
|
-
description: Extending or modifying @rangka/core internals (registries, handlers, boot, context). Use when contributing to the framework itself.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
## Before you write any code
|
|
7
|
-
|
|
8
|
-
You are modifying framework internals. App developers depend on the public API staying stable. Every change must preserve existing behavior unless intentionally breaking (with migration path).
|
|
9
|
-
|
|
10
|
-
1. Identify what you are changing:
|
|
11
|
-
- A registry (SchemaRegistry, HookRegistry, PermissionRegistry, etc.)
|
|
12
|
-
- The boot sequence (`src/boot/index.ts`)
|
|
13
|
-
- The route generator (`src/api/route-generator.ts`)
|
|
14
|
-
- The hook pipeline (`src/hooks/executor.ts`, `src/hooks/middleware.ts`)
|
|
15
|
-
- FrameworkContext (`@rangka/shared` types/context.ts + `src/hooks/context.ts`)
|
|
16
|
-
- Model access layer (`src/model-api/`)
|
|
17
|
-
|
|
18
|
-
2. Read the public API surface that consumers use:
|
|
19
|
-
- `FrameworkContext` in `@rangka/shared/src/types/context.ts` — hooks, services, and jobs receive this
|
|
20
|
-
- `ModelAccessInterface` / `ModelQueryInterface` in the same file — the query API app devs call
|
|
21
|
-
- `defineHooks`, `defineService`, `defineJob` in `@rangka/shared/src/define.ts` — app definition API
|
|
22
|
-
- `BootPayload` in `@rangka/shared/src/types/boot.ts` — what the client receives at startup
|
|
23
|
-
|
|
24
|
-
3. Search for all consumers before modifying:
|
|
25
|
-
```bash
|
|
26
|
-
grep -r "ctx.models" packages/core/src/
|
|
27
|
-
grep -r "FrameworkContext" packages/
|
|
28
|
-
grep -r "SchemaRegistry" packages/core/src/
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
## Extending a registry
|
|
32
|
-
|
|
33
|
-
Registries are singleton objects created during boot. They hold runtime state.
|
|
34
|
-
|
|
35
|
-
Pattern for adding a method:
|
|
36
|
-
|
|
37
|
-
1. Add the method to the registry class (e.g., `src/schema/registry.ts`)
|
|
38
|
-
2. If the method is exposed to app devs via FrameworkContext, update the interface in `@rangka/shared`
|
|
39
|
-
3. Verify all existing tests still pass
|
|
40
|
-
4. Add a test for the new method
|
|
41
|
-
|
|
42
|
-
Pattern for adding a new registry:
|
|
43
|
-
|
|
44
|
-
1. Create in its own file under the relevant domain (e.g., `src/notifications/registry.ts`)
|
|
45
|
-
2. Instantiate in `src/boot/index.ts` during the boot sequence
|
|
46
|
-
3. Wire into `FrameworkContext` if app devs need access
|
|
47
|
-
4. Update `@rangka/shared` types if the interface is public
|
|
48
|
-
|
|
49
|
-
## Modifying FrameworkContext
|
|
50
|
-
|
|
51
|
-
This is the most sensitive change. Every hook, service, and job receives this object.
|
|
52
|
-
|
|
53
|
-
Adding a new field:
|
|
54
|
-
|
|
55
|
-
1. Add the field to `FrameworkContext` in `@rangka/shared/src/types/context.ts`
|
|
56
|
-
2. Make it optional if not all consumers need it (backward compatible)
|
|
57
|
-
3. Build it in `src/hooks/context.ts` (`createHookContext`) AND `src/context.ts` (`createFrameworkContext`)
|
|
58
|
-
4. Rebuild shared first, then core: `pnpm --filter @rangka/shared build && pnpm --filter @rangka/core build`
|
|
59
|
-
5. Verify no type errors across the monorepo: `pnpm build`
|
|
60
|
-
|
|
61
|
-
Modifying an existing field's type:
|
|
62
|
-
|
|
63
|
-
- Breaking change. Grep all usages across the monorepo. Update every consumer in the same PR.
|
|
64
|
-
|
|
65
|
-
## Modifying the hook pipeline
|
|
66
|
-
|
|
67
|
-
The hook pipeline is in `src/hooks/executor.ts` and `src/hooks/middleware.ts`.
|
|
68
|
-
|
|
69
|
-
- `executor.ts` — runs hook chains (validate → before → after) in sequence
|
|
70
|
-
- `middleware.ts` — Fastify request handlers that orchestrate the full CRUD + hooks flow
|
|
71
|
-
|
|
72
|
-
Rules:
|
|
73
|
-
|
|
74
|
-
- Hook phases are fixed: validate, beforeCreate, beforeUpdate, beforeSave, afterCreate, afterUpdate, afterSave, beforeDelete, afterDelete
|
|
75
|
-
- Do NOT add new phases without updating `HookLifecycle` type in `src/hooks/types.ts` AND the executor
|
|
76
|
-
- The middleware functions (`withHooksCreate`, `withHooksUpdate`, `withHooksDelete`) are the authoritative CRUD handlers. Never create a parallel CRUD path.
|
|
77
|
-
|
|
78
|
-
## Modifying the route generator
|
|
79
|
-
|
|
80
|
-
`src/api/route-generator.ts` auto-generates REST routes for all models.
|
|
81
|
-
|
|
82
|
-
- It uses `withHooksCreate/Update/Delete` from `src/hooks/middleware.ts` for mutations
|
|
83
|
-
- It uses `listHandler/getHandler` from `src/api/handlers.ts` for reads
|
|
84
|
-
- Auth/scope/permission guards are applied as Fastify preHandler hooks
|
|
85
|
-
|
|
86
|
-
To add a new capability to all model routes (e.g., new query parameter, new response field):
|
|
87
|
-
|
|
88
|
-
1. Modify the relevant handler in `src/api/handlers.ts`
|
|
89
|
-
2. Update OpenAPI schema generation if applicable (`src/api/openapi-schema.ts`)
|
|
90
|
-
3. Test with integration tests
|
|
91
|
-
|
|
92
|
-
To add a non-model route (e.g., `/api/meta/...`):
|
|
93
|
-
|
|
94
|
-
- Add it inside `generateRoutes()` alongside the existing meta/session routes
|
|
95
|
-
|
|
96
|
-
## Modifying model-api
|
|
97
|
-
|
|
98
|
-
The model access layer (`src/model-api/`) is the query engine.
|
|
99
|
-
|
|
100
|
-
- `query-builder.ts` — ModelQueryBuilder (fluent API for filtering/sorting/pagination)
|
|
101
|
-
- `filter-translator.ts` — converts API filter syntax to Kysely where clauses
|
|
102
|
-
- `filter-applier.ts` — applies filter conditions to a query
|
|
103
|
-
- `scope-enforcer.ts` — enforces tenant/scope/field-level permissions
|
|
104
|
-
- `include-resolver.ts` — resolves relationship includes
|
|
105
|
-
- `field-access.ts` — strips hidden fields, enforces readOnly
|
|
106
|
-
|
|
107
|
-
Rules:
|
|
108
|
-
|
|
109
|
-
- New filter operators go in `filter-translator.ts`
|
|
110
|
-
- New query capabilities go as methods on `ModelQueryBuilder`
|
|
111
|
-
- Never bypass scope enforcement — if you need unscoped access, use `.unscoped()` explicitly
|
|
112
|
-
- The `ModelAccessInterface` in `@rangka/shared` is the public contract. Adding a method there is fine (additive). Changing signatures is breaking.
|
|
113
|
-
|
|
114
|
-
## Anti-patterns
|
|
115
|
-
|
|
116
|
-
| Anti-pattern | Correct approach |
|
|
117
|
-
| ------------------------------------------------------------------ | -------------------------------------------------- |
|
|
118
|
-
| New CRUD handler that skips hooks | Use `withHooksCreate/Update/Delete` |
|
|
119
|
-
| Custom filter logic in a route handler | Add to `filter-translator.ts` |
|
|
120
|
-
| Direct Kysely queries for model CRUD in handlers | Use model-api through the existing handler pattern |
|
|
121
|
-
| New singleton that duplicates a registry's purpose | Extend the existing registry |
|
|
122
|
-
| Modifying `FrameworkContext` in core without updating shared types | Always update `@rangka/shared` first |
|
|
123
|
-
| Adding a field to `BootPayload` without updating client consumer | Check `packages/client/src/boot/` |
|
|
124
|
-
|
|
125
|
-
## Checklist
|
|
126
|
-
|
|
127
|
-
- [ ] Identified all consumers of the changed API (grep across monorepo)
|
|
128
|
-
- [ ] Public interfaces in `@rangka/shared` updated if needed
|
|
129
|
-
- [ ] `createHookContext()` and `createFrameworkContext()` both updated if FrameworkContext changed
|
|
130
|
-
- [ ] No parallel code paths introduced (no duplicate CRUD, no duplicate registries)
|
|
131
|
-
- [ ] Existing tests pass: `pnpm test`
|
|
132
|
-
- [ ] Integration tests pass: `pnpm test:integration`
|
|
133
|
-
- [ ] Full monorepo builds: `pnpm build`
|
package/.turbo/turbo-build.log
DELETED
package/CHANGELOG.md
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
# @rangka/core
|
|
2
|
-
|
|
3
|
-
## 0.1.1
|
|
4
|
-
|
|
5
|
-
### Patch Changes
|
|
6
|
-
|
|
7
|
-
- Updated dependencies []:
|
|
8
|
-
- @rangka/shared@0.1.1
|
|
9
|
-
|
|
10
|
-
## 0.1.0
|
|
11
|
-
|
|
12
|
-
### Minor Changes
|
|
13
|
-
|
|
14
|
-
- [`438bf4a`](https://github.com/rangka-dev/rangka-framework/commit/438bf4a385b99d1e497e937375dc03aac101c8ec) Thanks [@irfnmzk](https://github.com/irfnmzk)! - Initial open source release
|
|
15
|
-
|
|
16
|
-
- Modular ERP framework with declarative models, hooks, services, and widgets
|
|
17
|
-
- CLI with dev server, build, and studio commands
|
|
18
|
-
- Studio AI development environment with WebSocket-based UI
|
|
19
|
-
- create-rangka scaffolding tool
|
|
20
|
-
- MIT licensed framework, AGPL licensed studio
|
|
21
|
-
|
|
22
|
-
### Patch Changes
|
|
23
|
-
|
|
24
|
-
- Updated dependencies [[`438bf4a`](https://github.com/rangka-dev/rangka-framework/commit/438bf4a385b99d1e497e937375dc03aac101c8ec)]:
|
|
25
|
-
- @rangka/shared@0.1.0
|
package/CLAUDE.md
DELETED
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
# CLAUDE.md — @rangka/core
|
|
2
|
-
|
|
3
|
-
## Package overview
|
|
4
|
-
|
|
5
|
-
The Rangka server runtime. Handles boot, schema resolution, database sync, API routing, authentication, hooks, services, jobs, events, and plugins. This is the single source of truth for all server-side behavior.
|
|
6
|
-
|
|
7
|
-
## Tech stack
|
|
8
|
-
|
|
9
|
-
- Node.js >= 20, TypeScript 5
|
|
10
|
-
- Fastify (HTTP server, routing, plugins)
|
|
11
|
-
- Kysely (query builder, migrations, transactions)
|
|
12
|
-
- PostgreSQL (only supported database)
|
|
13
|
-
|
|
14
|
-
## Project structure
|
|
15
|
-
|
|
16
|
-
```
|
|
17
|
-
src/
|
|
18
|
-
├── api/ — Fastify server creation, route generation, request handling
|
|
19
|
-
├── audit/ — Audit trail recording
|
|
20
|
-
├── auth/ — JWT sessions, permissions, scopes, field-level access, seed
|
|
21
|
-
├── boot/ — Boot sequence: discovery, schema loading, merging, registry init
|
|
22
|
-
├── db/ — DatabaseClient, auto-sync (DiffEngine), model-ops (Kysely CRUD)
|
|
23
|
-
├── events/ — EventBus (in-process pub/sub with transaction support)
|
|
24
|
-
├── external-model/ — Adapter-based external data sources
|
|
25
|
-
├── fixtures/ — Seed data loading
|
|
26
|
-
├── helpers/ — Shared utilities
|
|
27
|
-
├── hooks/ — Hook registry, executor, middleware pipeline, context builder
|
|
28
|
-
├── jobs/ — Job registry, worker, scheduler, enqueue
|
|
29
|
-
├── model-api/ — Model access layer (query builder, filters, scopes, includes)
|
|
30
|
-
├── plugins/ — Plugin lifecycle, adapter registry, loader
|
|
31
|
-
├── schema/ — SchemaRegistry, relationship resolution, types
|
|
32
|
-
├── services/ — ServiceRegistry, service definitions
|
|
33
|
-
├── validation/ — Model-level validation rules
|
|
34
|
-
├── widgets/ — Server-side widget registry (for studio)
|
|
35
|
-
├── context.ts — FrameworkContext builder
|
|
36
|
-
├── errors.ts — AppError base class
|
|
37
|
-
└── index.ts — Public exports
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
## Key registries (MUST reuse, never recreate)
|
|
41
|
-
|
|
42
|
-
| Registry | Location | Purpose |
|
|
43
|
-
| -------------------- | ----------------------------- | ----------------------------------------------------------------------------------- |
|
|
44
|
-
| `SchemaRegistry` | `schema/registry.ts` | Holds all resolved models, relationships, field metadata. Single instance per boot. |
|
|
45
|
-
| `PermissionRegistry` | `auth/permission-registry.ts` | Maps roles to model/field permissions. |
|
|
46
|
-
| `ScopeRegistry` | `auth/scope-registry.ts` | Defines scope filters per role (tenant isolation, ownership). |
|
|
47
|
-
| `HookRegistry` | `hooks/registry.ts` | Stores validate/beforeSave/afterSave/beforeDelete/afterDelete hooks per model. |
|
|
48
|
-
| `ServiceRegistry` | `services/registry.ts` | Named service definitions, instantiated with context on call. |
|
|
49
|
-
| `JobRegistry` | `jobs/registry.ts` | Background job handlers. |
|
|
50
|
-
| `EventBus` | `events/bus.ts` | In-process event pub/sub with transaction-scoped emit. |
|
|
51
|
-
| `AdapterRegistry` | `plugins/adapter-registry.ts` | External data source adapters (REST, GraphQL, etc). |
|
|
52
|
-
| `WidgetRegistry` | `widgets/widget-registry.ts` | Server-side widget definitions for studio. |
|
|
53
|
-
|
|
54
|
-
## Model access layer
|
|
55
|
-
|
|
56
|
-
All CRUD operations go through `createModelAccess()` in `model-api/index.ts`. This provides:
|
|
57
|
-
|
|
58
|
-
- `models.get(model, id)` — fetch single record
|
|
59
|
-
- `models.query(model)` — returns a `ModelQueryBuilder` with filter/sort/paginate/include
|
|
60
|
-
- `models.create(model, data)` — insert with auth context
|
|
61
|
-
- `models.update(model, id, data)` — update with auth context
|
|
62
|
-
- `models.delete(model, id)` — delete with auth context
|
|
63
|
-
|
|
64
|
-
The query builder handles:
|
|
65
|
-
|
|
66
|
-
- Scope enforcement (`scope-enforcer.ts`) — auto-applies tenant/ownership filters
|
|
67
|
-
- Field-level access (`field-access.ts`) — strips hidden fields, enforces readOnly
|
|
68
|
-
- Filter translation (`filter-translator.ts`) — converts API filter syntax to Kysely where clauses
|
|
69
|
-
- Include resolution (`include-resolver.ts`) — resolves relationships and external model joins
|
|
70
|
-
|
|
71
|
-
Never write raw Kysely queries for model CRUD. Use `createModelAccess` or the existing `KyselyModelOps`.
|
|
72
|
-
|
|
73
|
-
## Hook pipeline
|
|
74
|
-
|
|
75
|
-
Hooks run inside a transaction via `hooks/executor.ts`:
|
|
76
|
-
|
|
77
|
-
1. `validate` — can throw to reject the operation
|
|
78
|
-
2. `beforeCreate` / `beforeUpdate` / `beforeSave` — can mutate the data before write
|
|
79
|
-
3. (database write happens here)
|
|
80
|
-
4. `afterCreate` / `afterUpdate` / `afterSave` — side effects after successful write
|
|
81
|
-
5. `beforeDelete` / `afterDelete` — same pattern for deletes
|
|
82
|
-
|
|
83
|
-
Hooks receive a `FrameworkContext` built by `hooks/context.ts`. This context provides: `db` (transaction), `schema`, `auth`, `models`, `events`, `config`, `service`, `enqueue`.
|
|
84
|
-
|
|
85
|
-
Never create a parallel context builder. Use `createHookContext()`.
|
|
86
|
-
|
|
87
|
-
## FrameworkContext (quick reference)
|
|
88
|
-
|
|
89
|
-
This is the single object passed to all hooks, services, and jobs. Defined in `@rangka/shared/src/types/context.ts`, built by `src/hooks/context.ts`.
|
|
90
|
-
|
|
91
|
-
```typescript
|
|
92
|
-
interface FrameworkContext {
|
|
93
|
-
db: Kysely<unknown>; // transaction-scoped connection
|
|
94
|
-
schema: SchemaRegistryInterface; // model/field/relationship lookups
|
|
95
|
-
scope: unknown; // current tenant/scope value
|
|
96
|
-
models: ModelAccessInterface; // CRUD with scopes + permissions
|
|
97
|
-
events: { emit; on }; // transaction-scoped pub/sub
|
|
98
|
-
auth: { user; roles }; // current user identity
|
|
99
|
-
config: Record<string, unknown>; // app configuration
|
|
100
|
-
service: (name) => ServiceInstance; // call other services
|
|
101
|
-
enqueue: (job, data, opts?) => Promise<void>; // background jobs
|
|
102
|
-
notify: (channel, message) => void;
|
|
103
|
-
email: { send: (template, options) => Promise<void> };
|
|
104
|
-
}
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
Rules:
|
|
108
|
-
|
|
109
|
-
- All data access goes through `ctx.models` (not raw Kysely for CRUD)
|
|
110
|
-
- All events go through `ctx.events.emit` (not custom pub/sub)
|
|
111
|
-
- All background work goes through `ctx.enqueue` (not custom dispatchers)
|
|
112
|
-
- All cross-service calls go through `ctx.service(name)` (not direct imports)
|
|
113
|
-
- Use `ctx.db` directly ONLY for queries ModelQueryInterface cannot express (aggregations, complex joins)
|
|
114
|
-
|
|
115
|
-
## Helper utilities (MUST reuse)
|
|
116
|
-
|
|
117
|
-
| Utility | Location | Purpose |
|
|
118
|
-
| ----------------------------------- | ------------------------------- | --------------------------------------------- |
|
|
119
|
-
| `stampCreate` | `helpers/stamping.ts` | Adds `created_at`, `created_by` fields |
|
|
120
|
-
| `stampUpdate` | `helpers/stamping.ts` | Adds `updated_at`, `updated_by` fields |
|
|
121
|
-
| `assertOwnership` | `helpers/assert-ownership.ts` | Verifies record belongs to current user/scope |
|
|
122
|
-
| `findMissingRequiredFields` | `helpers/validation.ts` | Checks required fields before write |
|
|
123
|
-
| `validateFields` | `validation/field-validator.ts` | Validates field values against schema |
|
|
124
|
-
| `BadRequestError` / `NotFoundError` | `errors.ts` | Standard error classes for API responses |
|
|
125
|
-
| `AppError` | `errors.ts` | Base error class with code + status |
|
|
126
|
-
|
|
127
|
-
Never recreate validation, stamping, or ownership logic. These helpers are used by the middleware layer and must be consistent.
|
|
128
|
-
|
|
129
|
-
## Boot sequence
|
|
130
|
-
|
|
131
|
-
`boot/index.ts` orchestrates startup:
|
|
132
|
-
|
|
133
|
-
1. Discover apps (file system or programmatic)
|
|
134
|
-
2. Load and merge schemas (YAML model definitions)
|
|
135
|
-
3. Build SchemaRegistry with resolved relationships
|
|
136
|
-
4. Create DatabaseClient, run autoSync (DDL diff)
|
|
137
|
-
5. Create Fastify server
|
|
138
|
-
6. Register all registries (hooks, services, jobs, permissions, scopes, events)
|
|
139
|
-
7. Generate API routes from resolved models
|
|
140
|
-
8. Load and initialize plugins
|
|
141
|
-
|
|
142
|
-
## API route generation
|
|
143
|
-
|
|
144
|
-
`api/route-generator.ts` auto-generates REST endpoints for every model:
|
|
145
|
-
|
|
146
|
-
- `GET /api/{module}/{model}` — list with filters, sort, pagination
|
|
147
|
-
- `GET /api/{module}/{model}/:id` — get by ID
|
|
148
|
-
- `POST /api/{module}/{model}` — create
|
|
149
|
-
- `PATCH /api/{module}/{model}/:id` — update
|
|
150
|
-
- `DELETE /api/{module}/{model}/:id` — delete
|
|
151
|
-
|
|
152
|
-
Custom routes are added via services or plugins. Never duplicate the auto-generated CRUD pattern.
|
|
153
|
-
|
|
154
|
-
## Commands
|
|
155
|
-
|
|
156
|
-
```bash
|
|
157
|
-
pnpm --filter @rangka/core build # Build → dist/
|
|
158
|
-
pnpm --filter @rangka/core test # Unit tests
|
|
159
|
-
pnpm test:integration # Integration tests (from repo root, needs PostgreSQL)
|
|
160
|
-
pnpm test:integration -- tests/integration/path/to/file.test.ts # Single integration test
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
## Skills available
|
|
164
|
-
|
|
165
|
-
- **`extend-core`** — step-by-step for modifying core internals (registries, hook pipeline, route generator, FrameworkContext)
|
|
166
|
-
|
|
167
|
-
Read this skill before modifying any core internal.
|
|
168
|
-
|
|
169
|
-
## Don'ts
|
|
170
|
-
|
|
171
|
-
- Don't write raw SQL — use Kysely query builder via `DatabaseClient` or `KyselyModelOps`
|
|
172
|
-
- Don't bypass `createModelAccess` for CRUD — it enforces scopes and permissions
|
|
173
|
-
- Don't create new registries — extend existing ones or add methods to them
|
|
174
|
-
- Don't build a new context object — use `createHookContext()` or `createFrameworkContext()`
|
|
175
|
-
- Don't add Fastify routes manually — use the route generator or service system
|
|
176
|
-
- Don't duplicate filter/sort/pagination logic — use `ModelQueryBuilder`
|
|
177
|
-
- Don't access `SchemaRegistry` fields directly — use its methods (`getModel`, `getRelationships`, etc.)
|
|
178
|
-
- Don't add new hook phases — use the existing validate/beforeSave/afterSave/beforeDelete/afterDelete
|
|
179
|
-
- Don't create new transaction boundaries inside hooks — hooks already run in a transaction
|
|
180
|
-
- Don't import from `@rangka/client` — this is a server package, client is browser-only
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { toBool, toInt, isNil, toCount } from '../helpers/coerce.js';
|
|
3
|
-
|
|
4
|
-
describe('toBool', () => {
|
|
5
|
-
it('returns true for boolean true', () => {
|
|
6
|
-
expect(toBool(true)).toBe(true);
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
it('returns true for string "true"', () => {
|
|
10
|
-
expect(toBool('true')).toBe(true);
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
it('returns true for number 1', () => {
|
|
14
|
-
expect(toBool(1)).toBe(true);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('returns true for string "1"', () => {
|
|
18
|
-
expect(toBool('1')).toBe(true);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('returns false for boolean false', () => {
|
|
22
|
-
expect(toBool(false)).toBe(false);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('returns false for string "false"', () => {
|
|
26
|
-
expect(toBool('false')).toBe(false);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('returns false for 0', () => {
|
|
30
|
-
expect(toBool(0)).toBe(false);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('returns false for null', () => {
|
|
34
|
-
expect(toBool(null)).toBe(false);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('returns false for undefined', () => {
|
|
38
|
-
expect(toBool(undefined)).toBe(false);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('returns false for empty string', () => {
|
|
42
|
-
expect(toBool('')).toBe(false);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('returns false for arbitrary string', () => {
|
|
46
|
-
expect(toBool('yes')).toBe(false);
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
describe('toInt', () => {
|
|
51
|
-
it('returns the integer for a number', () => {
|
|
52
|
-
expect(toInt(42)).toBe(42);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('truncates floating point numbers', () => {
|
|
56
|
-
expect(toInt(3.9)).toBe(3);
|
|
57
|
-
expect(toInt(-2.7)).toBe(-2);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('parses numeric strings', () => {
|
|
61
|
-
expect(toInt('10')).toBe(10);
|
|
62
|
-
expect(toInt(' 5 ')).toBe(5);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it('returns fallback for NaN values', () => {
|
|
66
|
-
expect(toInt('abc')).toBe(0);
|
|
67
|
-
expect(toInt('abc', -1)).toBe(-1);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('returns fallback for null', () => {
|
|
71
|
-
expect(toInt(null)).toBe(0);
|
|
72
|
-
expect(toInt(null, 99)).toBe(99);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('returns fallback for undefined', () => {
|
|
76
|
-
expect(toInt(undefined)).toBe(0);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('returns fallback for Infinity', () => {
|
|
80
|
-
expect(toInt(Infinity)).toBe(0);
|
|
81
|
-
expect(toInt(-Infinity, 5)).toBe(5);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('returns fallback for empty string', () => {
|
|
85
|
-
expect(toInt('')).toBe(0);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it('handles string with leading zeros', () => {
|
|
89
|
-
expect(toInt('007')).toBe(7);
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
describe('isNil', () => {
|
|
94
|
-
it('returns true for null', () => {
|
|
95
|
-
expect(isNil(null)).toBe(true);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('returns true for undefined', () => {
|
|
99
|
-
expect(isNil(undefined)).toBe(true);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it('returns false for 0', () => {
|
|
103
|
-
expect(isNil(0)).toBe(false);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it('returns false for empty string', () => {
|
|
107
|
-
expect(isNil('')).toBe(false);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it('returns false for false', () => {
|
|
111
|
-
expect(isNil(false)).toBe(false);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it('returns false for NaN', () => {
|
|
115
|
-
expect(isNil(NaN)).toBe(false);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it('returns false for objects', () => {
|
|
119
|
-
expect(isNil({})).toBe(false);
|
|
120
|
-
expect(isNil([])).toBe(false);
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
describe('toCount', () => {
|
|
125
|
-
it('extracts count from object', () => {
|
|
126
|
-
expect(toCount({ count: '5' })).toBe(5);
|
|
127
|
-
expect(toCount({ count: 10 })).toBe(10);
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
it('returns 0 for null', () => {
|
|
131
|
-
expect(toCount(null)).toBe(0);
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
it('returns 0 for undefined', () => {
|
|
135
|
-
expect(toCount(undefined)).toBe(0);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
it('returns 0 when count is missing', () => {
|
|
139
|
-
expect(toCount({})).toBe(0);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it('returns 0 when count is not a number', () => {
|
|
143
|
-
expect(toCount({ count: 'abc' })).toBe(0);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it('handles bigint-style string from database', () => {
|
|
147
|
-
expect(toCount({ count: '12345' })).toBe(12345);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it('handles raw numeric value without object wrapper', () => {
|
|
151
|
-
expect(toCount(7)).toBe(7);
|
|
152
|
-
expect(toCount('3')).toBe(3);
|
|
153
|
-
});
|
|
154
|
-
});
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { createFrameworkContext, createRequestContext } from '../context.js';
|
|
3
|
-
import { ServiceRegistry } from '../services/registry.js';
|
|
4
|
-
import { EventBus } from '../events/bus.js';
|
|
5
|
-
import { SchemaRegistry } from '../schema/registry.js';
|
|
6
|
-
|
|
7
|
-
describe('createFrameworkContext', () => {
|
|
8
|
-
function setup() {
|
|
9
|
-
const kysely = { fake: 'db' } as any;
|
|
10
|
-
const db = {
|
|
11
|
-
kysely,
|
|
12
|
-
selectFrom: () => {},
|
|
13
|
-
insertInto: () => {},
|
|
14
|
-
updateTable: () => {},
|
|
15
|
-
deleteFrom: () => {},
|
|
16
|
-
} as any;
|
|
17
|
-
const schema = new SchemaRegistry([]);
|
|
18
|
-
const eventBus = new EventBus();
|
|
19
|
-
const serviceRegistry = new ServiceRegistry();
|
|
20
|
-
return { db, kysely, schema, eventBus, serviceRegistry };
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
it('returns context with all fields populated', () => {
|
|
24
|
-
const { db, kysely, schema, eventBus, serviceRegistry } = setup();
|
|
25
|
-
const ctx = createFrameworkContext({
|
|
26
|
-
db,
|
|
27
|
-
schema,
|
|
28
|
-
eventBus,
|
|
29
|
-
serviceRegistry,
|
|
30
|
-
config: { env: 'test' },
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
expect(ctx.db).toBe(kysely);
|
|
34
|
-
expect(ctx.schema).toBe(schema);
|
|
35
|
-
expect(ctx.events.emit).toBeTypeOf('function');
|
|
36
|
-
expect(ctx.events.on).toBeTypeOf('function');
|
|
37
|
-
expect(ctx.auth).toEqual({ user: null, roles: [] });
|
|
38
|
-
expect(ctx.config).toEqual({ env: 'test' });
|
|
39
|
-
expect(ctx.service).toBeTypeOf('function');
|
|
40
|
-
expect(ctx.enqueue).toBeTypeOf('function');
|
|
41
|
-
expect(ctx.notify).toBeTypeOf('function');
|
|
42
|
-
expect(ctx.email.send).toBeTypeOf('function');
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('service() resolves registered services', () => {
|
|
46
|
-
const { db, schema, eventBus, serviceRegistry } = setup();
|
|
47
|
-
serviceRegistry.register({
|
|
48
|
-
name: 'pricing',
|
|
49
|
-
factory: () => ({ calculate: (x: unknown) => x }),
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
const ctx = createFrameworkContext({ db, schema, eventBus, serviceRegistry });
|
|
53
|
-
const pricing = ctx.service('pricing');
|
|
54
|
-
expect(pricing.calculate(100)).toBe(100);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('defaults config to empty object', () => {
|
|
58
|
-
const { db, schema, eventBus, serviceRegistry } = setup();
|
|
59
|
-
const ctx = createFrameworkContext({ db, schema, eventBus, serviceRegistry });
|
|
60
|
-
expect(ctx.config).toEqual({});
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
describe('createRequestContext', () => {
|
|
65
|
-
function setup() {
|
|
66
|
-
const kysely = { fake: 'db' } as any;
|
|
67
|
-
const db = {
|
|
68
|
-
kysely,
|
|
69
|
-
selectFrom: () => {},
|
|
70
|
-
insertInto: () => {},
|
|
71
|
-
updateTable: () => {},
|
|
72
|
-
deleteFrom: () => {},
|
|
73
|
-
} as any;
|
|
74
|
-
const schema = new SchemaRegistry([]);
|
|
75
|
-
const eventBus = new EventBus();
|
|
76
|
-
const serviceRegistry = new ServiceRegistry();
|
|
77
|
-
const base = createFrameworkContext({ db, schema, eventBus, serviceRegistry });
|
|
78
|
-
return { base, kysely };
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
it('inherits from base context', () => {
|
|
82
|
-
const { base } = setup();
|
|
83
|
-
const auth = { user: { id: '1', email: 'admin@test.com' } } as any;
|
|
84
|
-
const reqCtx = createRequestContext({ base, auth, roles: ['Administrator'] });
|
|
85
|
-
|
|
86
|
-
expect(reqCtx.schema).toBe(base.schema);
|
|
87
|
-
expect(reqCtx.service).toBe(base.service);
|
|
88
|
-
expect(reqCtx.auth).toEqual({
|
|
89
|
-
user: { id: '1', email: 'admin@test.com' },
|
|
90
|
-
roles: ['Administrator'],
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('uses transaction db when provided', () => {
|
|
95
|
-
const { base } = setup();
|
|
96
|
-
const trx = { fake: 'trx' } as any;
|
|
97
|
-
const auth = {} as any;
|
|
98
|
-
const reqCtx = createRequestContext({ base, auth, trx });
|
|
99
|
-
|
|
100
|
-
expect(reqCtx.db).toBe(trx);
|
|
101
|
-
expect(reqCtx.db).not.toBe(base.db);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it('uses base db when no transaction provided', () => {
|
|
105
|
-
const { base } = setup();
|
|
106
|
-
const auth = {} as any;
|
|
107
|
-
const reqCtx = createRequestContext({ base, auth });
|
|
108
|
-
|
|
109
|
-
expect(reqCtx.db).toBe(base.db);
|
|
110
|
-
});
|
|
111
|
-
});
|
package/src/__tests__/helpers.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import Fastify, { type FastifyInstance, type FastifyError } from 'fastify';
|
|
2
|
-
import { AppError } from '../errors.js';
|
|
3
|
-
|
|
4
|
-
export function createTestServer(): FastifyInstance {
|
|
5
|
-
const server = Fastify();
|
|
6
|
-
server.setErrorHandler((error: FastifyError | AppError, request, reply) => {
|
|
7
|
-
if (error instanceof AppError) {
|
|
8
|
-
return reply.status(error.statusCode).send({
|
|
9
|
-
error: {
|
|
10
|
-
code: error.code,
|
|
11
|
-
message: error.message,
|
|
12
|
-
...(error.details !== undefined && { details: error.details }),
|
|
13
|
-
},
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
reply.status(error.statusCode ?? 500).send({
|
|
17
|
-
error: { code: 'INTERNAL_ERROR', message: error.message },
|
|
18
|
-
});
|
|
19
|
-
});
|
|
20
|
-
return server;
|
|
21
|
-
}
|