@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/src/boot/index.ts DELETED
@@ -1,457 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import type { FrameworkContext } from '@rangka/shared';
3
- import type { FastifyInstance } from 'fastify';
4
- import type { DiscoverySource, DiscoveredApp } from './types.js';
5
- import { dependencySort } from './dependency-sort.js';
6
- import { loadSchemas } from './schema-loader.js';
7
- import { mergeSchemas } from './schema-merger.js';
8
- import { SchemaRegistry } from '../schema/registry.js';
9
- import { DatabaseClient } from '../db/client.js';
10
- import type { DatabaseClientConfig } from '../db/client.js';
11
- import { autoSync } from '../db/auto-sync.js';
12
- import { createServer } from '../api/server.js';
13
- import { generateRoutes } from '../api/route-generator.js';
14
- import type { ServerConfig } from '../api/types.js';
15
- import { PermissionRegistry } from '../auth/permission-registry.js';
16
- import { HookRegistry } from '../hooks/registry.js';
17
- import { JobRegistry } from '../jobs/registry.js';
18
- import { JobWorker } from '../jobs/worker.js';
19
- import { ScheduleManager } from '../jobs/scheduler.js';
20
- import { EventBus } from '../events/bus.js';
21
- import { ServiceRegistry } from '../services/registry.js';
22
- import { FixtureRegistry } from '../fixtures/registry.js';
23
- import { createFrameworkContext } from '../context.js';
24
- import { getCoreApp, getCoreModels } from '../auth/core-module.js';
25
- import { seedCoreData } from '../auth/seed.js';
26
- import { ScopeRegistry } from '../auth/scope-registry.js';
27
- import { WidgetRegistry } from '../widgets/widget-registry.js';
28
- import { AdapterRegistry } from '../plugins/adapter-registry.js';
29
- import { PluginLifecycleManager } from '../plugins/lifecycle.js';
30
- import { loadPlugins } from '../plugins/loader.js';
31
- import type { PluginDefinition } from '../plugins/types.js';
32
- import type { RolesConfig, JobConfig } from '@rangka/shared';
33
- import type { JobWorkerConfig } from '../jobs/types.js';
34
- import type { ServiceDefinition } from '../services/types.js';
35
- import type { FixtureDefinition } from '../fixtures/types.js';
36
-
37
- export interface BootOptions {
38
- discoverySource: DiscoverySource;
39
- apps: DiscoveredApp[];
40
- database?: DatabaseClientConfig;
41
- server?: ServerConfig;
42
- skipAutoSync?: boolean;
43
- roles?: Array<{ config: RolesConfig; app: string }>;
44
- jobs?: Array<{ name: string; config: JobConfig }>;
45
- services?: ServiceDefinition[];
46
- fixtures?: FixtureDefinition[];
47
- worker?: JobWorkerConfig;
48
- config?: Record<string, unknown>;
49
- plugins?: PluginDefinition[];
50
- pluginConfig?: Record<string, Record<string, unknown>>;
51
- }
52
-
53
- export interface BootResult {
54
- registry: SchemaRegistry;
55
- permissionRegistry: PermissionRegistry;
56
- hookRegistry: HookRegistry;
57
- jobRegistry: JobRegistry;
58
- eventBus: EventBus;
59
- serviceRegistry: ServiceRegistry;
60
- fixtureRegistry: FixtureRegistry;
61
- widgetRegistry: WidgetRegistry;
62
- adapterRegistry: AdapterRegistry;
63
- pluginLifecycle: PluginLifecycleManager;
64
- frameworkContext?: FrameworkContext;
65
- jobWorker?: JobWorker;
66
- scheduleManager?: ScheduleManager;
67
- db?: DatabaseClient;
68
- server?: FastifyInstance;
69
- pages?: Array<{ module: string; page: import('@rangka/shared').PageDefinition }>;
70
- modules?: import('@rangka/shared').ModuleConfig[];
71
- }
72
-
73
- // Boot sequence:
74
- // 1. Discover and sort apps by dependency order
75
- // 2. Load and merge schemas from all apps
76
- // 3. Build registries (hooks, permissions, jobs, services, fixtures)
77
- // 4. If database configured: connect, sync schema, seed, start workers
78
- // 5. If server configured: create HTTP server and mount routes
79
- export async function boot(options: BootOptions): Promise<BootResult> {
80
- const sortedApps = await discoverAndSortApps(options);
81
-
82
- // Load plugins and build adapter registry
83
- const { adapterRegistry, lifecycleManager } = await loadPluginSystem(options);
84
- await lifecycleManager.emit('beforeBoot');
85
-
86
- const registry = buildSchemaRegistry(sortedApps);
87
- const registries = buildRegistries(sortedApps, registry, options);
88
-
89
- if (!options.database) {
90
- await lifecycleManager.emit('afterBoot');
91
- return { registry, ...registries, adapterRegistry, pluginLifecycle: lifecycleManager };
92
- }
93
-
94
- const { db, frameworkContext, jobWorker, scheduleManager } = await initDatabase(
95
- options,
96
- registry,
97
- registries.eventBus,
98
- registries.jobRegistry,
99
- registries.serviceRegistry,
100
- );
101
-
102
- if (!options.server) {
103
- await lifecycleManager.emit('afterBoot');
104
- return {
105
- registry,
106
- ...registries,
107
- adapterRegistry,
108
- pluginLifecycle: lifecycleManager,
109
- frameworkContext,
110
- jobWorker,
111
- scheduleManager,
112
- db,
113
- };
114
- }
115
-
116
- const { server, pages, modules } = await initServer(
117
- options,
118
- sortedApps,
119
- registry,
120
- db,
121
- registries,
122
- adapterRegistry,
123
- );
124
-
125
- await lifecycleManager.emit('afterBoot');
126
-
127
- return {
128
- registry,
129
- ...registries,
130
- adapterRegistry,
131
- pluginLifecycle: lifecycleManager,
132
- frameworkContext,
133
- jobWorker,
134
- scheduleManager,
135
- db,
136
- server,
137
- pages,
138
- modules,
139
- };
140
- }
141
-
142
- // --- Phase 1: Discovery ---
143
-
144
- async function loadPluginSystem(options: BootOptions) {
145
- if (!options.plugins || options.plugins.length === 0) {
146
- return {
147
- adapterRegistry: new AdapterRegistry(),
148
- lifecycleManager: new PluginLifecycleManager(),
149
- };
150
- }
151
-
152
- return loadPlugins({
153
- plugins: options.plugins,
154
- config: options.pluginConfig,
155
- });
156
- }
157
-
158
- async function discoverAndSortApps(options: BootOptions): Promise<DiscoveredApp[]> {
159
- await options.discoverySource.findRangkaPackages();
160
-
161
- const coreApp = getCoreApp();
162
- const userHasCoreApp = options.apps.some((a) => a.config.name === 'core');
163
- const allApps = userHasCoreApp ? options.apps : [coreApp, ...options.apps];
164
-
165
- const sorted = dependencySort(allApps.map((a) => a.config));
166
-
167
- return sorted.map((config) => {
168
- const app = allApps.find((a) => a.config.name === config.name);
169
- if (!app) throw new Error(`App "${config.name}" not found in provided apps`);
170
- return app;
171
- });
172
- }
173
-
174
- // --- Phase 2: Schema ---
175
-
176
- function buildSchemaRegistry(sortedApps: DiscoveredApp[]): SchemaRegistry {
177
- const loadResult = loadSchemas(sortedApps);
178
- const mergeResult = mergeSchemas(loadResult);
179
-
180
- const coreModels = getCoreModels();
181
- const existingNames = new Set(mergeResult.models.map((m) => m.qualifiedName));
182
- const missingCoreModels = coreModels.filter((m) => !existingNames.has(m.qualifiedName));
183
-
184
- return new SchemaRegistry([...missingCoreModels, ...mergeResult.models]);
185
- }
186
-
187
- // --- Phase 3: Registries ---
188
-
189
- interface Registries {
190
- permissionRegistry: PermissionRegistry;
191
- hookRegistry: HookRegistry;
192
- jobRegistry: JobRegistry;
193
- eventBus: EventBus;
194
- serviceRegistry: ServiceRegistry;
195
- fixtureRegistry: FixtureRegistry;
196
- scopeRegistry: ScopeRegistry;
197
- widgetRegistry: WidgetRegistry;
198
- }
199
-
200
- function buildRegistries(
201
- sortedApps: DiscoveredApp[],
202
- registry: SchemaRegistry,
203
- options: BootOptions,
204
- ): Registries {
205
- const allModules: import('@rangka/shared').ModuleConfig[] = [];
206
- for (const app of sortedApps) {
207
- if (app.modules) allModules.push(...app.modules);
208
- }
209
-
210
- return {
211
- hookRegistry: buildHookRegistry(sortedApps),
212
- permissionRegistry: buildPermissionRegistry(sortedApps, registry, options),
213
- jobRegistry: buildJobRegistry(sortedApps, options),
214
- eventBus: buildEventBus(sortedApps, options),
215
- serviceRegistry: buildServiceRegistry(sortedApps, options),
216
- fixtureRegistry: buildFixtureRegistry(sortedApps, options),
217
- scopeRegistry: new ScopeRegistry(allModules, registry),
218
- widgetRegistry: buildWidgetRegistry(sortedApps),
219
- };
220
- }
221
-
222
- function buildHookRegistry(sortedApps: DiscoveredApp[]): HookRegistry {
223
- const hookRegistry = new HookRegistry();
224
- for (const app of sortedApps) {
225
- if (!app.hooks) continue;
226
- for (const { model, hooks } of app.hooks) {
227
- hookRegistry.register(model, hooks, app.config.name);
228
- }
229
- }
230
- return hookRegistry;
231
- }
232
-
233
- function buildPermissionRegistry(
234
- sortedApps: DiscoveredApp[],
235
- registry: SchemaRegistry,
236
- options: BootOptions,
237
- ): PermissionRegistry {
238
- const permissionRegistry = new PermissionRegistry();
239
-
240
- // Administrator role gets full access to all models
241
- const adminModels: Record<string, Record<string, boolean>> = {};
242
- for (const model of registry.getAllModels()) {
243
- adminModels[model.qualifiedName] = {
244
- create: true,
245
- read: true,
246
- write: true,
247
- delete: true,
248
- };
249
- }
250
- permissionRegistry.registerRoles(
251
- { Administrator: { label: 'Administrator', models: adminModels } },
252
- 'core',
253
- );
254
-
255
- // Register roles discovered from each app's modules/<name>/roles.ts
256
- for (const app of sortedApps) {
257
- if (!app.roles) continue;
258
- for (const { config, app: appName } of app.roles) {
259
- permissionRegistry.registerRoles(config, appName);
260
- }
261
- }
262
-
263
- // Register roles passed explicitly via boot options (override/legacy)
264
- if (options.roles) {
265
- for (const { config, app } of options.roles) {
266
- permissionRegistry.registerRoles(config, app);
267
- }
268
- }
269
-
270
- return permissionRegistry;
271
- }
272
-
273
- function buildJobRegistry(sortedApps: DiscoveredApp[], options: BootOptions): JobRegistry {
274
- const jobRegistry = new JobRegistry();
275
-
276
- for (const app of sortedApps) {
277
- if (!app.jobs) continue;
278
- for (const { name, config } of app.jobs) {
279
- jobRegistry.register(name, config);
280
- }
281
- }
282
-
283
- if (options.jobs) {
284
- for (const { name, config } of options.jobs) {
285
- if (!jobRegistry.has(name)) {
286
- jobRegistry.register(name, config);
287
- }
288
- }
289
- }
290
-
291
- return jobRegistry;
292
- }
293
-
294
- function buildEventBus(sortedApps: DiscoveredApp[], options: BootOptions): EventBus {
295
- const eventBus = new EventBus();
296
- const jobRegistry = buildJobRegistry(sortedApps, options);
297
-
298
- for (const job of jobRegistry.getAll()) {
299
- if (job.name.startsWith('__event:')) {
300
- const eventName = job.name.slice('__event:'.length);
301
- eventBus.on(eventName, async (payload: unknown) => {
302
- await job.config.handler(payload, {} as any);
303
- });
304
- }
305
- }
306
-
307
- return eventBus;
308
- }
309
-
310
- function buildServiceRegistry(sortedApps: DiscoveredApp[], options: BootOptions): ServiceRegistry {
311
- const serviceRegistry = new ServiceRegistry();
312
-
313
- for (const app of sortedApps) {
314
- if (!app.services) continue;
315
- for (const def of app.services) {
316
- if (!serviceRegistry.has(def.name)) {
317
- serviceRegistry.register(def);
318
- }
319
- }
320
- }
321
-
322
- if (options.services) {
323
- for (const def of options.services) {
324
- if (!serviceRegistry.has(def.name)) {
325
- serviceRegistry.register(def);
326
- }
327
- }
328
- }
329
-
330
- serviceRegistry.detectCircularDependencies();
331
- return serviceRegistry;
332
- }
333
-
334
- function buildFixtureRegistry(sortedApps: DiscoveredApp[], options: BootOptions): FixtureRegistry {
335
- const fixtureRegistry = new FixtureRegistry();
336
-
337
- for (const app of sortedApps) {
338
- if (!app.fixtures) continue;
339
- for (const def of app.fixtures) {
340
- fixtureRegistry.register(def);
341
- }
342
- }
343
-
344
- if (options.fixtures) {
345
- for (const def of options.fixtures) {
346
- fixtureRegistry.register(def);
347
- }
348
- }
349
-
350
- return fixtureRegistry;
351
- }
352
-
353
- // --- Phase 4: Database ---
354
-
355
- async function initDatabase(
356
- options: BootOptions,
357
- registry: SchemaRegistry,
358
- eventBus: EventBus,
359
- jobRegistry: JobRegistry,
360
- serviceRegistry: ServiceRegistry,
361
- ) {
362
- const db = new DatabaseClient(options.database!, registry);
363
-
364
- try {
365
- await db.verifyConnection();
366
- } catch (err) {
367
- const message = err instanceof Error ? err.message : String(err);
368
- throw new Error(`Boot failed: ${message}`, { cause: err });
369
- }
370
-
371
- eventBus.setDb(db.kysely);
372
-
373
- if (!options.skipAutoSync) {
374
- await autoSync(registry, db.kysely);
375
- await seedCoreData(db);
376
- }
377
-
378
- const frameworkContext = createFrameworkContext({
379
- db,
380
- schema: registry,
381
- eventBus,
382
- serviceRegistry,
383
- config: options.config ?? {},
384
- });
385
-
386
- let jobWorker: JobWorker | undefined;
387
- let scheduleManager: ScheduleManager | undefined;
388
-
389
- if (options.worker?.enabled !== false) {
390
- jobWorker = new JobWorker(db.kysely, jobRegistry, frameworkContext, options.worker);
391
- scheduleManager = new ScheduleManager(db.kysely, jobRegistry);
392
- await scheduleManager.syncSchedules();
393
- jobWorker.start();
394
- scheduleManager.start();
395
- }
396
-
397
- return { db, frameworkContext, jobWorker, scheduleManager };
398
- }
399
-
400
- // --- Phase 5: Server ---
401
-
402
- async function initServer(
403
- options: BootOptions,
404
- sortedApps: DiscoveredApp[],
405
- registry: SchemaRegistry,
406
- db: DatabaseClient,
407
- registries: Registries,
408
- adapterRegistry?: AdapterRegistry,
409
- ): Promise<{
410
- server: FastifyInstance;
411
- pages: Array<{ module: string; page: import('@rangka/shared').PageDefinition }>;
412
- modules: import('@rangka/shared').ModuleConfig[];
413
- }> {
414
- const moduleTags = [...registry.getModelsByModule().keys()].map((name) => ({ name }));
415
- const server = await createServer({ ...options.server!, tags: moduleTags });
416
-
417
- const allPages: Array<{ module: string; page: import('@rangka/shared').PageDefinition }> = [];
418
- const allModules: import('@rangka/shared').ModuleConfig[] = [];
419
-
420
- for (const app of sortedApps) {
421
- if (app.pages) allPages.push(...app.pages);
422
- if (app.modules) allModules.push(...app.modules);
423
- }
424
-
425
- generateRoutes(server, registry, db, {
426
- permissionRegistry: registries.permissionRegistry,
427
- hookRegistry: registries.hookRegistry,
428
- serviceRegistry: registries.serviceRegistry,
429
- eventBus: registries.eventBus,
430
- scopeRegistry: registries.scopeRegistry,
431
- config: options.config ?? {},
432
- pages: allPages.length > 0 ? allPages : undefined,
433
- modules: allModules.length > 0 ? allModules : undefined,
434
- widgets: registries.widgetRegistry.getAll(),
435
- adapterRegistry,
436
- });
437
-
438
- for (const app of sortedApps) {
439
- if (!app.apiDefinitions) continue;
440
- for (const def of app.apiDefinitions) {
441
- server.route({ method: def.method, url: def.path, handler: def.handler });
442
- }
443
- }
444
-
445
- return { server, pages: allPages, modules: allModules };
446
- }
447
-
448
- function buildWidgetRegistry(sortedApps: DiscoveredApp[]): WidgetRegistry {
449
- const widgetRegistry = new WidgetRegistry();
450
- for (const app of sortedApps) {
451
- if (!app.widgets) continue;
452
- for (const widget of app.widgets) {
453
- widgetRegistry.register(widget);
454
- }
455
- }
456
- return widgetRegistry;
457
- }
@@ -1,110 +0,0 @@
1
- import type { PageDefinition, WidgetNode } from '@rangka/shared';
2
-
3
- export interface PageValidationWarning {
4
- pageKey: string;
5
- location: string;
6
- message: string;
7
- }
8
-
9
- /**
10
- * Collect all distinct model names referenced as data sources within a page body.
11
- */
12
- export function extractSourceModels(page: PageDefinition): string[] {
13
- const models = new Set<string>();
14
-
15
- for (const node of page.body) {
16
- collectModelsFromWidgetNode(node, models);
17
- }
18
-
19
- return [...models];
20
- }
21
-
22
- /**
23
- * Validate that every source model referenced by pages actually exists
24
- * in the known model set. Returns warnings for unresolved references.
25
- */
26
- export function validatePageSources(
27
- pages: Array<{ module: string; page: PageDefinition }>,
28
- knownModels: Set<string>,
29
- ): PageValidationWarning[] {
30
- const warnings: PageValidationWarning[] = [];
31
-
32
- for (const { page } of pages) {
33
- for (let i = 0; i < page.body.length; i++) {
34
- checkWidgetNodeSources(page.body[i], `body[${i}]`, page.key, knownModels, warnings);
35
- }
36
- }
37
-
38
- return warnings;
39
- }
40
-
41
- /** Detect pages that share the same key across different modules. */
42
- export function detectDuplicatePageKeys(
43
- pages: Array<{ module: string; page: PageDefinition }>,
44
- ): PageValidationWarning[] {
45
- const warnings: PageValidationWarning[] = [];
46
- const seenKeyInModule = new Map<string, string>();
47
-
48
- for (const { module, page } of pages) {
49
- const previousModule = seenKeyInModule.get(page.key);
50
- if (previousModule) {
51
- warnings.push({
52
- pageKey: page.key,
53
- location: `modules/${module}/pages`,
54
- message: `Duplicate page key "${page.key}" (also defined in ${previousModule})`,
55
- });
56
- } else {
57
- seenKeyInModule.set(page.key, `modules/${module}/pages`);
58
- }
59
- }
60
-
61
- return warnings;
62
- }
63
-
64
- function collectModelsFromWidgetNode(node: WidgetNode, models: Set<string>): void {
65
- if (node.source?.model) {
66
- models.add(node.source.model);
67
- }
68
- if (node.bind?.model?.name) {
69
- models.add(node.bind.model.name);
70
- }
71
- if (node.children) {
72
- for (const child of node.children) {
73
- collectModelsFromWidgetNode(child, models);
74
- }
75
- }
76
- }
77
-
78
- function checkWidgetNodeSources(
79
- node: WidgetNode,
80
- path: string,
81
- pageKey: string,
82
- knownModels: Set<string>,
83
- warnings: PageValidationWarning[],
84
- ): void {
85
- if (node.source?.model && !knownModels.has(node.source.model)) {
86
- warnings.push({
87
- pageKey,
88
- location: path,
89
- message: `Unresolved source model "${node.source.model}"`,
90
- });
91
- }
92
- if (node.bind?.model?.name && !knownModels.has(node.bind.model.name)) {
93
- warnings.push({
94
- pageKey,
95
- location: path,
96
- message: `Unresolved source model "${node.bind.model.name}"`,
97
- });
98
- }
99
- if (node.children) {
100
- for (let i = 0; i < node.children.length; i++) {
101
- checkWidgetNodeSources(
102
- node.children[i],
103
- `${path}.children[${i}]`,
104
- pageKey,
105
- knownModels,
106
- warnings,
107
- );
108
- }
109
- }
110
- }