@rangka/core 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. package/package.json +6 -2
  2. package/.claude/skills/extend-core/SKILL.md +0 -133
  3. package/.turbo/turbo-build.log +0 -4
  4. package/CHANGELOG.md +0 -18
  5. package/CLAUDE.md +0 -180
  6. package/src/__tests__/coerce.test.ts +0 -154
  7. package/src/__tests__/context.test.ts +0 -111
  8. package/src/__tests__/helpers.ts +0 -21
  9. package/src/__tests__/index.test.ts +0 -7
  10. package/src/__tests__/widgets.test.ts +0 -197
  11. package/src/api/__tests__/handlers.test.ts +0 -389
  12. package/src/api/__tests__/include-resolver.test.ts +0 -393
  13. package/src/api/__tests__/middleware.test.ts +0 -100
  14. package/src/api/__tests__/openapi-schema.test.ts +0 -210
  15. package/src/api/__tests__/query-parser.test.ts +0 -291
  16. package/src/api/__tests__/route-generator.test.ts +0 -137
  17. package/src/api/__tests__/server.test.ts +0 -73
  18. package/src/api/__tests__/swagger.test.ts +0 -166
  19. package/src/api/handlers.ts +0 -274
  20. package/src/api/include-resolver.ts +0 -27
  21. package/src/api/index.ts +0 -4
  22. package/src/api/meta-handler.ts +0 -254
  23. package/src/api/openapi-schema.ts +0 -99
  24. package/src/api/query-parser.ts +0 -315
  25. package/src/api/route-generator.ts +0 -448
  26. package/src/api/server.ts +0 -147
  27. package/src/api/types.ts +0 -16
  28. package/src/audit/__tests__/audit.test.ts +0 -144
  29. package/src/audit/index.ts +0 -3
  30. package/src/audit/record.ts +0 -69
  31. package/src/audit/tables.ts +0 -48
  32. package/src/audit/types.ts +0 -26
  33. package/src/auth/__tests__/core-module.test.ts +0 -54
  34. package/src/auth/__tests__/debug.test.ts +0 -47
  35. package/src/auth/__tests__/field-permissions.test.ts +0 -245
  36. package/src/auth/__tests__/integration.test.ts +0 -208
  37. package/src/auth/__tests__/meta-boot.test.ts +0 -538
  38. package/src/auth/__tests__/model-permissions.test.ts +0 -205
  39. package/src/auth/__tests__/password.test.ts +0 -29
  40. package/src/auth/__tests__/permission-registry.test.ts +0 -313
  41. package/src/auth/__tests__/scope-hook.test.ts +0 -509
  42. package/src/auth/__tests__/scope-registry.test.ts +0 -297
  43. package/src/auth/__tests__/scopes.test.ts +0 -66
  44. package/src/auth/__tests__/session.test.ts +0 -214
  45. package/src/auth/core-models.ts +0 -52
  46. package/src/auth/core-module.ts +0 -59
  47. package/src/auth/debug.ts +0 -157
  48. package/src/auth/field-permissions.ts +0 -116
  49. package/src/auth/index.ts +0 -37
  50. package/src/auth/model-permissions.ts +0 -59
  51. package/src/auth/password.ts +0 -22
  52. package/src/auth/permission-registry.ts +0 -171
  53. package/src/auth/scope-filters.ts +0 -11
  54. package/src/auth/scope-registry.ts +0 -121
  55. package/src/auth/scopes.ts +0 -146
  56. package/src/auth/seed.ts +0 -44
  57. package/src/auth/session.ts +0 -178
  58. package/src/auth/types.ts +0 -50
  59. package/src/boot/__tests__/page-scanning.test.ts +0 -170
  60. package/src/boot/__tests__/page-utils.test.ts +0 -225
  61. package/src/boot/__tests__/project-scanner.test.ts +0 -88
  62. package/src/boot/dependency-sort.ts +0 -82
  63. package/src/boot/discovery.ts +0 -85
  64. package/src/boot/index.ts +0 -457
  65. package/src/boot/page-utils.ts +0 -110
  66. package/src/boot/project-scanner.ts +0 -397
  67. package/src/boot/schema-loader.ts +0 -26
  68. package/src/boot/schema-merger.ts +0 -125
  69. package/src/boot/traits.ts +0 -25
  70. package/src/boot/types.ts +0 -73
  71. package/src/context.ts +0 -105
  72. package/src/db/__tests__/cascade-delete.test.ts +0 -182
  73. package/src/db/__tests__/desired-state.test.ts +0 -136
  74. package/src/db/__tests__/diff-engine.test.ts +0 -635
  75. package/src/db/__tests__/field-mapper.test.ts +0 -355
  76. package/src/db/__tests__/introspect.test.ts +0 -70
  77. package/src/db/__tests__/search-filter.test.ts +0 -45
  78. package/src/db/__tests__/sequence.test.ts +0 -221
  79. package/src/db/auto-sync.ts +0 -133
  80. package/src/db/client.ts +0 -147
  81. package/src/db/desired-state.ts +0 -98
  82. package/src/db/diff-engine.ts +0 -305
  83. package/src/db/field-mapper.ts +0 -504
  84. package/src/db/filter-applier.ts +0 -89
  85. package/src/db/include-resolver.ts +0 -40
  86. package/src/db/index.ts +0 -23
  87. package/src/db/introspect.ts +0 -265
  88. package/src/db/model-include-resolver.ts +0 -327
  89. package/src/db/model-ops.ts +0 -281
  90. package/src/db/scope-enforcer.ts +0 -37
  91. package/src/db/types.ts +0 -98
  92. package/src/errors.ts +0 -41
  93. package/src/events/__tests__/bus.test.ts +0 -105
  94. package/src/events/bus.ts +0 -89
  95. package/src/events/index.ts +0 -2
  96. package/src/events/types.ts +0 -9
  97. package/src/external-model/__tests__/computed-fields.test.ts +0 -106
  98. package/src/external-model/__tests__/field-mapper.test.ts +0 -160
  99. package/src/external-model/__tests__/in-memory-ops.test.ts +0 -247
  100. package/src/external-model/__tests__/mutation-executor.test.ts +0 -160
  101. package/src/external-model/__tests__/query-executor.test.ts +0 -284
  102. package/src/external-model/__tests__/schema-converter.test.ts +0 -174
  103. package/src/external-model/computed-fields.ts +0 -15
  104. package/src/external-model/define.ts +0 -5
  105. package/src/external-model/external-model-ops.ts +0 -108
  106. package/src/external-model/field-mapper.ts +0 -66
  107. package/src/external-model/in-memory-ops.ts +0 -107
  108. package/src/external-model/index.ts +0 -7
  109. package/src/external-model/mutation-executor.ts +0 -71
  110. package/src/external-model/query-executor.ts +0 -100
  111. package/src/external-model/schema-converter.ts +0 -53
  112. package/src/external-model/types.ts +0 -32
  113. package/src/fixtures/__tests__/fixtures.test.ts +0 -203
  114. package/src/fixtures/index.ts +0 -10
  115. package/src/fixtures/loader.ts +0 -196
  116. package/src/fixtures/registry.ts +0 -125
  117. package/src/fixtures/types.ts +0 -33
  118. package/src/helpers/assert-ownership.ts +0 -19
  119. package/src/helpers/coerce.ts +0 -28
  120. package/src/helpers/stamping.ts +0 -28
  121. package/src/helpers/validation.ts +0 -14
  122. package/src/hooks/__tests__/context.test.ts +0 -73
  123. package/src/hooks/__tests__/executor.test.ts +0 -433
  124. package/src/hooks/__tests__/middleware.test.ts +0 -224
  125. package/src/hooks/__tests__/registry.test.ts +0 -50
  126. package/src/hooks/context.ts +0 -89
  127. package/src/hooks/errors.ts +0 -11
  128. package/src/hooks/executor.ts +0 -115
  129. package/src/hooks/index.ts +0 -10
  130. package/src/hooks/middleware.ts +0 -220
  131. package/src/hooks/registry.ts +0 -20
  132. package/src/hooks/types.ts +0 -32
  133. package/src/index.ts +0 -172
  134. package/src/jobs/__tests__/enqueue.test.ts +0 -77
  135. package/src/jobs/__tests__/integration.test.ts +0 -71
  136. package/src/jobs/__tests__/registry.test.ts +0 -103
  137. package/src/jobs/__tests__/scheduler.test.ts +0 -92
  138. package/src/jobs/__tests__/worker-execution.test.ts +0 -202
  139. package/src/jobs/__tests__/worker.test.ts +0 -119
  140. package/src/jobs/enqueue.ts +0 -93
  141. package/src/jobs/index.ts +0 -14
  142. package/src/jobs/registry.ts +0 -92
  143. package/src/jobs/scheduler.ts +0 -205
  144. package/src/jobs/tables.ts +0 -132
  145. package/src/jobs/types.ts +0 -62
  146. package/src/jobs/worker.ts +0 -272
  147. package/src/model-api/__tests__/cross-boundary-includes.test.ts +0 -366
  148. package/src/model-api/__tests__/extended-api.test.ts +0 -244
  149. package/src/model-api/__tests__/filter-applier.test.ts +0 -177
  150. package/src/model-api/__tests__/filter-translator.test.ts +0 -186
  151. package/src/model-api/__tests__/include-resolver.test.ts +0 -226
  152. package/src/model-api/__tests__/model-access.test.ts +0 -284
  153. package/src/model-api/__tests__/query-builder.test.ts +0 -224
  154. package/src/model-api/__tests__/scope-enforcer.test.ts +0 -268
  155. package/src/model-api/field-access.ts +0 -28
  156. package/src/model-api/filter-applier.ts +0 -1
  157. package/src/model-api/filter-translator.ts +0 -67
  158. package/src/model-api/include-resolver.ts +0 -2
  159. package/src/model-api/index.ts +0 -86
  160. package/src/model-api/query-builder.ts +0 -155
  161. package/src/model-api/scope-enforcer.ts +0 -3
  162. package/src/model-api/types.ts +0 -139
  163. package/src/plugins/__tests__/adapter-registry.test.ts +0 -92
  164. package/src/plugins/__tests__/lifecycle.test.ts +0 -96
  165. package/src/plugins/__tests__/loader.test.ts +0 -273
  166. package/src/plugins/__tests__/validator.test.ts +0 -275
  167. package/src/plugins/adapter-registry.ts +0 -42
  168. package/src/plugins/define.ts +0 -5
  169. package/src/plugins/index.ts +0 -28
  170. package/src/plugins/lifecycle.ts +0 -27
  171. package/src/plugins/loader.ts +0 -126
  172. package/src/plugins/types.ts +0 -76
  173. package/src/plugins/validator.ts +0 -141
  174. package/src/schema/__tests__/registry-models-by-module.test.ts +0 -58
  175. package/src/schema/registry.ts +0 -93
  176. package/src/schema/relationships.ts +0 -93
  177. package/src/schema/types.ts +0 -43
  178. package/src/services/__tests__/integration.test.ts +0 -63
  179. package/src/services/__tests__/registry.test.ts +0 -175
  180. package/src/services/index.ts +0 -13
  181. package/src/services/registry.ts +0 -156
  182. package/src/services/types.ts +0 -27
  183. package/src/validation/__tests__/field-validator.test.ts +0 -195
  184. package/src/validation/field-validator.ts +0 -113
  185. package/src/validation/index.ts +0 -1
  186. package/src/widgets/index.ts +0 -3
  187. package/src/widgets/slot-validator.ts +0 -87
  188. package/src/widgets/widget-registry.ts +0 -32
  189. package/tests/boot.test.ts +0 -323
  190. package/tests/dependency-sort.test.ts +0 -99
  191. package/tests/discovery.test.ts +0 -126
  192. package/tests/registry.test.ts +0 -216
  193. package/tests/schema-loader.test.ts +0 -52
  194. package/tests/schema-merger.test.ts +0 -180
  195. package/tsconfig.json +0 -9
  196. package/tsconfig.tsbuildinfo +0 -1
  197. 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.0",
3
+ "version": "0.1.2",
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.0"
24
+ "@rangka/shared": "0.1.2"
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`
@@ -1,4 +0,0 @@
1
-
2
- > @rangka/core@0.1.0 build /home/runner/work/rangka-framework/rangka-framework/packages/core
3
- > tsc --build
4
-
package/CHANGELOG.md DELETED
@@ -1,18 +0,0 @@
1
- # @rangka/core
2
-
3
- ## 0.1.0
4
-
5
- ### Minor Changes
6
-
7
- - [`438bf4a`](https://github.com/rangka-dev/rangka-framework/commit/438bf4a385b99d1e497e937375dc03aac101c8ec) Thanks [@irfnmzk](https://github.com/irfnmzk)! - Initial open source release
8
-
9
- - Modular ERP framework with declarative models, hooks, services, and widgets
10
- - CLI with dev server, build, and studio commands
11
- - Studio AI development environment with WebSocket-based UI
12
- - create-rangka scaffolding tool
13
- - MIT licensed framework, AGPL licensed studio
14
-
15
- ### Patch Changes
16
-
17
- - Updated dependencies [[`438bf4a`](https://github.com/rangka-dev/rangka-framework/commit/438bf4a385b99d1e497e937375dc03aac101c8ec)]:
18
- - @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
- });
@@ -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
- }
@@ -1,7 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
-
3
- describe('@rangka/core', () => {
4
- it('should be importable', () => {
5
- expect(true).toBe(true);
6
- });
7
- });