@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.
- 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 -18
- 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/src/hooks/context.ts
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import type { Kysely } from 'kysely';
|
|
2
|
-
import type { FrameworkContext } from '@rangka/shared';
|
|
3
|
-
import type { SchemaRegistry } from '../schema/registry.js';
|
|
4
|
-
import type { RequestContext } from '../auth/types.js';
|
|
5
|
-
import type { EventBus } from '../events/bus.js';
|
|
6
|
-
import type { ServiceRegistry } from '../services/registry.js';
|
|
7
|
-
import type { AdapterRegistry } from '../plugins/adapter-registry.js';
|
|
8
|
-
import { enqueue } from '../jobs/enqueue.js';
|
|
9
|
-
import { createModelAccess } from '../model-api/index.js';
|
|
10
|
-
|
|
11
|
-
export interface HookContextOptions {
|
|
12
|
-
trx: unknown;
|
|
13
|
-
schema: SchemaRegistry;
|
|
14
|
-
auth: RequestContext;
|
|
15
|
-
eventBus?: EventBus;
|
|
16
|
-
serviceRegistry?: ServiceRegistry;
|
|
17
|
-
config?: Record<string, unknown>;
|
|
18
|
-
adapterRegistry?: AdapterRegistry;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Build a full FrameworkContext for hooks, scoped to the current transaction and auth.
|
|
23
|
-
*/
|
|
24
|
-
export function createHookContext(opts: HookContextOptions): FrameworkContext {
|
|
25
|
-
const { trx, schema, auth, eventBus, serviceRegistry, config = {}, adapterRegistry } = opts;
|
|
26
|
-
|
|
27
|
-
const transaction = trx as Kysely<unknown>;
|
|
28
|
-
|
|
29
|
-
const events = {
|
|
30
|
-
emit: async (event: string, payload: unknown) => {
|
|
31
|
-
if (eventBus) {
|
|
32
|
-
await eventBus.emitWithTrx(event, payload, transaction);
|
|
33
|
-
}
|
|
34
|
-
},
|
|
35
|
-
on: (event: string, handler: (payload: unknown) => Promise<void>) => {
|
|
36
|
-
eventBus?.on(event, handler);
|
|
37
|
-
},
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
const enqueueJob = async (
|
|
41
|
-
job: string,
|
|
42
|
-
data: unknown,
|
|
43
|
-
opts?: { delay?: number; unique?: boolean; uniqueKey?: string },
|
|
44
|
-
) => {
|
|
45
|
-
await enqueue(transaction, job, data, opts);
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
49
|
-
const models = createModelAccess({
|
|
50
|
-
db: transaction as any,
|
|
51
|
-
registry: schema,
|
|
52
|
-
auth,
|
|
53
|
-
adapterRegistry,
|
|
54
|
-
});
|
|
55
|
-
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
56
|
-
|
|
57
|
-
const service = (name: string) => {
|
|
58
|
-
if (!serviceRegistry) {
|
|
59
|
-
throw new Error('ServiceRegistry not available in this context');
|
|
60
|
-
}
|
|
61
|
-
return serviceRegistry.get(name, {
|
|
62
|
-
db: transaction,
|
|
63
|
-
schema,
|
|
64
|
-
enqueue: enqueueJob,
|
|
65
|
-
events,
|
|
66
|
-
config,
|
|
67
|
-
models,
|
|
68
|
-
auth: { user: auth.user ?? null, roles: auth.roles ?? [] },
|
|
69
|
-
scope: auth.scopeFilters?.[0]?.value ?? null,
|
|
70
|
-
});
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
return {
|
|
74
|
-
db: transaction,
|
|
75
|
-
schema,
|
|
76
|
-
auth: {
|
|
77
|
-
user: auth.user ?? null,
|
|
78
|
-
roles: auth.roles ?? [],
|
|
79
|
-
},
|
|
80
|
-
scope: auth.scopeFilters?.[0]?.value ?? null,
|
|
81
|
-
models,
|
|
82
|
-
events,
|
|
83
|
-
config,
|
|
84
|
-
service,
|
|
85
|
-
enqueue: enqueueJob,
|
|
86
|
-
notify: () => {},
|
|
87
|
-
email: { send: async () => {} },
|
|
88
|
-
};
|
|
89
|
-
}
|
package/src/hooks/errors.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export class ValidationError extends Error {
|
|
2
|
-
public readonly field?: string;
|
|
3
|
-
public readonly code: string;
|
|
4
|
-
|
|
5
|
-
constructor(field: string, message: string) {
|
|
6
|
-
super(message);
|
|
7
|
-
this.name = 'ValidationError';
|
|
8
|
-
this.field = field;
|
|
9
|
-
this.code = 'VALIDATION_ERROR';
|
|
10
|
-
}
|
|
11
|
-
}
|
package/src/hooks/executor.ts
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import type { Kysely } from 'kysely';
|
|
3
|
-
import type { HooksConfig } from '@rangka/shared';
|
|
4
|
-
import type { HookChain, HookContext, HookDocument } from './types.js';
|
|
5
|
-
import type { SchemaRegistry } from '../schema/registry.js';
|
|
6
|
-
import type { RequestContext } from '../auth/types.js';
|
|
7
|
-
import type { EventBus } from '../events/bus.js';
|
|
8
|
-
import type { ServiceRegistry } from '../services/registry.js';
|
|
9
|
-
import { createHookContext } from './context.js';
|
|
10
|
-
|
|
11
|
-
export type HookOperation = 'create' | 'update' | 'delete' | 'submit' | 'cancel';
|
|
12
|
-
|
|
13
|
-
export interface ExecutePipelineOptions {
|
|
14
|
-
model: string;
|
|
15
|
-
operation: HookOperation;
|
|
16
|
-
chain: HookChain;
|
|
17
|
-
doc: HookDocument;
|
|
18
|
-
db: Kysely<any>;
|
|
19
|
-
schema: SchemaRegistry;
|
|
20
|
-
auth: RequestContext;
|
|
21
|
-
eventBus?: EventBus;
|
|
22
|
-
serviceRegistry?: ServiceRegistry;
|
|
23
|
-
config?: Record<string, unknown>;
|
|
24
|
-
execute: (doc: HookDocument, trx: Kysely<any>) => Promise<HookDocument>;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Run the full hook pipeline for a mutation operation.
|
|
29
|
-
* Transaction covers: validate -> beforeSave -> before{Op} -> execute -> after{Op}
|
|
30
|
-
* afterSave runs AFTER the transaction commits (cannot roll back the write).
|
|
31
|
-
*/
|
|
32
|
-
export async function executeHookPipeline(opts: ExecutePipelineOptions): Promise<HookDocument> {
|
|
33
|
-
const { chain, operation, db, schema, auth, eventBus, serviceRegistry, config, execute } = opts;
|
|
34
|
-
const doc = { ...opts.doc };
|
|
35
|
-
|
|
36
|
-
const beforeHookKey = `before${capitalize(operation)}` as keyof HooksConfig;
|
|
37
|
-
const afterHookKey = `after${capitalize(operation)}` as keyof HooksConfig;
|
|
38
|
-
|
|
39
|
-
const result = await db.transaction().execute(async (trx) => {
|
|
40
|
-
const ctx: HookContext = createHookContext({
|
|
41
|
-
trx,
|
|
42
|
-
schema,
|
|
43
|
-
auth,
|
|
44
|
-
eventBus,
|
|
45
|
-
serviceRegistry,
|
|
46
|
-
config,
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
// Step 1: Run synchronous validators (skip for delete)
|
|
50
|
-
if (operation !== 'delete') {
|
|
51
|
-
runValidators(chain, doc);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Step 2: Run beforeSave hooks (only for create/update)
|
|
55
|
-
if (operation === 'create' || operation === 'update') {
|
|
56
|
-
await runHooksForKey(chain, 'beforeSave', doc, ctx);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Step 3: Run operation-specific before hooks (e.g. beforeCreate)
|
|
60
|
-
await runHooksForKey(chain, beforeHookKey, doc, ctx);
|
|
61
|
-
|
|
62
|
-
// Step 4: Execute the actual database operation
|
|
63
|
-
const txResult = await execute(doc, trx);
|
|
64
|
-
|
|
65
|
-
// Step 5: Run operation-specific after hooks (e.g. afterCreate)
|
|
66
|
-
await runHooksForKey(chain, afterHookKey, txResult, ctx);
|
|
67
|
-
|
|
68
|
-
return txResult;
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
// Step 6: Run afterSave hooks OUTSIDE the transaction (data already committed)
|
|
72
|
-
if (operation === 'create' || operation === 'update') {
|
|
73
|
-
const ctx: HookContext = createHookContext({
|
|
74
|
-
trx: db,
|
|
75
|
-
schema,
|
|
76
|
-
auth,
|
|
77
|
-
eventBus,
|
|
78
|
-
serviceRegistry,
|
|
79
|
-
config,
|
|
80
|
-
});
|
|
81
|
-
await runHooksForKey(chain, 'afterSave', result, ctx);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return result;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// --- Helpers ---
|
|
88
|
-
|
|
89
|
-
function capitalize(s: string): string {
|
|
90
|
-
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/** Run all synchronous validate hooks in the chain. */
|
|
94
|
-
function runValidators(chain: HookChain, doc: HookDocument): void {
|
|
95
|
-
for (const entry of chain.entries) {
|
|
96
|
-
if (entry.hooks.validate) {
|
|
97
|
-
entry.hooks.validate(doc);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/** Run all hooks for a given key across the chain entries. */
|
|
103
|
-
async function runHooksForKey(
|
|
104
|
-
chain: HookChain,
|
|
105
|
-
key: keyof HooksConfig,
|
|
106
|
-
doc: HookDocument,
|
|
107
|
-
ctx: HookContext,
|
|
108
|
-
): Promise<void> {
|
|
109
|
-
for (const entry of chain.entries) {
|
|
110
|
-
const hook = entry.hooks[key] as ((doc: HookDocument, ctx: any) => Promise<void>) | undefined;
|
|
111
|
-
if (hook) {
|
|
112
|
-
await hook(doc, ctx);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
package/src/hooks/index.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export { HookRegistry } from './registry.js';
|
|
2
|
-
export { ValidationError } from './errors.js';
|
|
3
|
-
export { executeHookPipeline } from './executor.js';
|
|
4
|
-
export { createHookContext } from './context.js';
|
|
5
|
-
export {
|
|
6
|
-
withHooksCreate,
|
|
7
|
-
withHooksUpdate,
|
|
8
|
-
withHooksDelete,
|
|
9
|
-
} from './middleware.js';
|
|
10
|
-
export type { HookChain, HookEntry, HookLifecycle, HookContext, HookDocument } from './types.js';
|
package/src/hooks/middleware.ts
DELETED
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import type { FastifyRequest, FastifyReply } from 'fastify';
|
|
3
|
-
import type { Kysely } from 'kysely';
|
|
4
|
-
import type { ResolvedModel } from '../schema/types.js';
|
|
5
|
-
import type { SchemaRegistry } from '../schema/registry.js';
|
|
6
|
-
import type { HookRegistry } from './registry.js';
|
|
7
|
-
import type { HookDocument } from './types.js';
|
|
8
|
-
import type { ServiceRegistry } from '../services/registry.js';
|
|
9
|
-
import type { EventBus } from '../events/bus.js';
|
|
10
|
-
import { executeHookPipeline, type HookOperation } from './executor.js';
|
|
11
|
-
import { ValidationError } from './errors.js';
|
|
12
|
-
import { getAuthContext } from '../auth/session.js';
|
|
13
|
-
import { validateFields } from '../validation/field-validator.js';
|
|
14
|
-
import { assertOwnership } from '../helpers/assert-ownership.js';
|
|
15
|
-
import { stampCreate, stampUpdate } from '../helpers/stamping.js';
|
|
16
|
-
import { findMissingRequiredFields } from '../helpers/validation.js';
|
|
17
|
-
import { BadRequestError, NotFoundError } from '../errors.js';
|
|
18
|
-
import type { ModelOps } from '../model-api/types.js';
|
|
19
|
-
import { ModelQueryBuilder } from '../model-api/query-builder.js';
|
|
20
|
-
|
|
21
|
-
// --- Types ---
|
|
22
|
-
|
|
23
|
-
interface WithHooksContext {
|
|
24
|
-
model: ResolvedModel;
|
|
25
|
-
registry: SchemaRegistry;
|
|
26
|
-
db: Kysely<any>;
|
|
27
|
-
ops: ModelOps;
|
|
28
|
-
hookRegistry: HookRegistry;
|
|
29
|
-
serviceRegistry?: ServiceRegistry;
|
|
30
|
-
eventBus?: EventBus;
|
|
31
|
-
config?: Record<string, unknown>;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// --- Shared helpers (internal) ---
|
|
35
|
-
|
|
36
|
-
function parseRequestBody(request: FastifyRequest): Record<string, unknown> {
|
|
37
|
-
const body = request.body as Record<string, unknown> | undefined;
|
|
38
|
-
if (!body || typeof body !== 'object') {
|
|
39
|
-
throw new BadRequestError('VALIDATION_ERROR', 'Request body is required');
|
|
40
|
-
}
|
|
41
|
-
return body;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async function findRecordOrNotFound(
|
|
45
|
-
ctx: WithHooksContext,
|
|
46
|
-
id: string,
|
|
47
|
-
): Promise<Record<string, unknown>> {
|
|
48
|
-
const record = await new ModelQueryBuilder(ctx.ops, ctx.model, ctx.registry)
|
|
49
|
-
.filter({ id })
|
|
50
|
-
.first();
|
|
51
|
-
|
|
52
|
-
if (!record) {
|
|
53
|
-
throw new NotFoundError(`Record not found: ${id}`);
|
|
54
|
-
}
|
|
55
|
-
return record;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function handleHookError(err: unknown): never {
|
|
59
|
-
if (err instanceof ValidationError) {
|
|
60
|
-
throw new BadRequestError(err.code, err.message, err.field ? { field: err.field } : undefined);
|
|
61
|
-
}
|
|
62
|
-
if (err instanceof Error) {
|
|
63
|
-
throw new BadRequestError('VALIDATION_ERROR', err.message);
|
|
64
|
-
}
|
|
65
|
-
throw err;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function buildPipelineOptions(
|
|
69
|
-
ctx: WithHooksContext,
|
|
70
|
-
operation: HookOperation,
|
|
71
|
-
doc: HookDocument,
|
|
72
|
-
auth: any,
|
|
73
|
-
execute: (doc: HookDocument, trx: Kysely<unknown>) => Promise<HookDocument>,
|
|
74
|
-
) {
|
|
75
|
-
return {
|
|
76
|
-
model: ctx.model.qualifiedName,
|
|
77
|
-
operation,
|
|
78
|
-
chain: ctx.hookRegistry.getChain(ctx.model.qualifiedName)!,
|
|
79
|
-
doc,
|
|
80
|
-
db: ctx.db,
|
|
81
|
-
schema: ctx.registry,
|
|
82
|
-
auth,
|
|
83
|
-
eventBus: ctx.eventBus,
|
|
84
|
-
serviceRegistry: ctx.serviceRegistry,
|
|
85
|
-
config: ctx.config,
|
|
86
|
-
execute,
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// --- Exported middleware factories ---
|
|
91
|
-
|
|
92
|
-
export function withHooksCreate(ctx: WithHooksContext) {
|
|
93
|
-
return async (request: FastifyRequest, reply: FastifyReply) => {
|
|
94
|
-
const body = parseRequestBody(request);
|
|
95
|
-
|
|
96
|
-
const missingFields = findMissingRequiredFields(ctx.model, body);
|
|
97
|
-
if (missingFields.length > 0) {
|
|
98
|
-
throw new BadRequestError(
|
|
99
|
-
'VALIDATION_ERROR',
|
|
100
|
-
`Missing required fields: ${missingFields.join(', ')}`,
|
|
101
|
-
missingFields,
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const fieldViolations = validateFields(ctx.model, body, 'create');
|
|
106
|
-
if (fieldViolations.length > 0) {
|
|
107
|
-
throw new BadRequestError(
|
|
108
|
-
'VALIDATION_ERROR',
|
|
109
|
-
fieldViolations.map((v) => v.message).join('; '),
|
|
110
|
-
fieldViolations,
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const auth = getAuthContext(request);
|
|
115
|
-
stampCreate(body, ctx.model, auth);
|
|
116
|
-
|
|
117
|
-
const chain = ctx.hookRegistry.getChain(ctx.model.qualifiedName);
|
|
118
|
-
if (!chain) {
|
|
119
|
-
const record = await ctx.ops.create(body, auth);
|
|
120
|
-
return reply.status(201).send({ data: record });
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
try {
|
|
124
|
-
const result = await executeHookPipeline(
|
|
125
|
-
buildPipelineOptions(ctx, 'create', body as HookDocument, auth, async (doc, trx) => {
|
|
126
|
-
const txOps = ctx.ops.withTransaction!(trx);
|
|
127
|
-
return txOps.create(doc) as Promise<HookDocument>;
|
|
128
|
-
}),
|
|
129
|
-
);
|
|
130
|
-
return reply.status(201).send({ data: result });
|
|
131
|
-
} catch (err: unknown) {
|
|
132
|
-
return handleHookError(err);
|
|
133
|
-
}
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export function withHooksUpdate(ctx: WithHooksContext) {
|
|
138
|
-
return async (request: FastifyRequest, reply: FastifyReply) => {
|
|
139
|
-
const { id } = request.params as { id: string };
|
|
140
|
-
const body = parseRequestBody(request);
|
|
141
|
-
|
|
142
|
-
await findRecordOrNotFound(ctx, id);
|
|
143
|
-
|
|
144
|
-
const fieldViolations = validateFields(ctx.model, body, 'update');
|
|
145
|
-
if (fieldViolations.length > 0) {
|
|
146
|
-
throw new BadRequestError(
|
|
147
|
-
'VALIDATION_ERROR',
|
|
148
|
-
fieldViolations.map((v) => v.message).join('; '),
|
|
149
|
-
fieldViolations,
|
|
150
|
-
);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const authContext = getAuthContext(request);
|
|
154
|
-
const existing = await findRecordOrNotFound(ctx, id);
|
|
155
|
-
assertOwnership(authContext.permissions, ctx.model, existing, authContext.user?.id, 'write');
|
|
156
|
-
|
|
157
|
-
stampUpdate(body, ctx.model, authContext);
|
|
158
|
-
|
|
159
|
-
const chain = ctx.hookRegistry.getChain(ctx.model.qualifiedName);
|
|
160
|
-
if (!chain) {
|
|
161
|
-
const record = await ctx.ops.update(id, body, authContext);
|
|
162
|
-
return reply.send({ data: record });
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
try {
|
|
166
|
-
const result = await executeHookPipeline(
|
|
167
|
-
buildPipelineOptions(
|
|
168
|
-
ctx,
|
|
169
|
-
'update',
|
|
170
|
-
{ ...body, id } as HookDocument,
|
|
171
|
-
authContext,
|
|
172
|
-
async (doc, trx) => {
|
|
173
|
-
const { id: docId, ...data } = doc;
|
|
174
|
-
const txOps = ctx.ops.withTransaction!(trx);
|
|
175
|
-
return txOps.update(docId as string, data) as Promise<HookDocument>;
|
|
176
|
-
},
|
|
177
|
-
),
|
|
178
|
-
);
|
|
179
|
-
return reply.send({ data: result });
|
|
180
|
-
} catch (err: unknown) {
|
|
181
|
-
return handleHookError(err);
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
export function withHooksDelete(ctx: WithHooksContext) {
|
|
187
|
-
return async (request: FastifyRequest, reply: FastifyReply) => {
|
|
188
|
-
const { id } = request.params as { id: string };
|
|
189
|
-
|
|
190
|
-
const existing = await findRecordOrNotFound(ctx, id);
|
|
191
|
-
|
|
192
|
-
const authContext = getAuthContext(request);
|
|
193
|
-
assertOwnership(authContext.permissions, ctx.model, existing, authContext.user?.id, 'delete');
|
|
194
|
-
|
|
195
|
-
const chain = ctx.hookRegistry.getChain(ctx.model.qualifiedName);
|
|
196
|
-
if (!chain) {
|
|
197
|
-
await ctx.ops.delete(id, authContext);
|
|
198
|
-
return reply.status(204).send();
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
try {
|
|
202
|
-
await executeHookPipeline(
|
|
203
|
-
buildPipelineOptions(
|
|
204
|
-
ctx,
|
|
205
|
-
'delete',
|
|
206
|
-
existing as HookDocument,
|
|
207
|
-
authContext,
|
|
208
|
-
async (doc, trx) => {
|
|
209
|
-
const txOps = ctx.ops.withTransaction!(trx);
|
|
210
|
-
await txOps.delete(doc.id as string);
|
|
211
|
-
return doc;
|
|
212
|
-
},
|
|
213
|
-
),
|
|
214
|
-
);
|
|
215
|
-
return reply.status(204).send();
|
|
216
|
-
} catch (err: unknown) {
|
|
217
|
-
return handleHookError(err);
|
|
218
|
-
}
|
|
219
|
-
};
|
|
220
|
-
}
|
package/src/hooks/registry.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { HooksConfig } from '@rangka/shared';
|
|
2
|
-
import type { HookChain } from './types.js';
|
|
3
|
-
|
|
4
|
-
export class HookRegistry {
|
|
5
|
-
private chains: Map<string, HookChain> = new Map();
|
|
6
|
-
|
|
7
|
-
register(model: string, hooks: HooksConfig, source: string): void {
|
|
8
|
-
const chain = this.chains.get(model) ?? { entries: [] };
|
|
9
|
-
chain.entries.push({ hooks, source });
|
|
10
|
-
this.chains.set(model, chain);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
getChain(model: string): HookChain | undefined {
|
|
14
|
-
return this.chains.get(model);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
hasHooks(model: string): boolean {
|
|
18
|
-
return this.chains.has(model);
|
|
19
|
-
}
|
|
20
|
-
}
|
package/src/hooks/types.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
HooksConfig,
|
|
3
|
-
HookDocument,
|
|
4
|
-
ValidateHook,
|
|
5
|
-
BeforeHook,
|
|
6
|
-
AfterHook,
|
|
7
|
-
FrameworkContext,
|
|
8
|
-
} from '@rangka/shared';
|
|
9
|
-
|
|
10
|
-
export type HookLifecycle =
|
|
11
|
-
| 'validate'
|
|
12
|
-
| 'beforeSave'
|
|
13
|
-
| 'afterSave'
|
|
14
|
-
| 'beforeCreate'
|
|
15
|
-
| 'afterCreate'
|
|
16
|
-
| 'beforeUpdate'
|
|
17
|
-
| 'afterUpdate'
|
|
18
|
-
| 'beforeDelete'
|
|
19
|
-
| 'afterDelete';
|
|
20
|
-
|
|
21
|
-
export interface HookEntry {
|
|
22
|
-
hooks: HooksConfig;
|
|
23
|
-
source: string;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface HookChain {
|
|
27
|
-
entries: HookEntry[];
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export type HookContext = FrameworkContext;
|
|
31
|
-
|
|
32
|
-
export type { HooksConfig, HookDocument, ValidateHook, BeforeHook, AfterHook };
|
package/src/index.ts
DELETED
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
export { boot } from './boot/index.js';
|
|
2
|
-
export type { BootOptions, BootResult } from './boot/index.js';
|
|
3
|
-
export { SchemaRegistry } from './schema/registry.js';
|
|
4
|
-
export { loadSchemas } from './boot/schema-loader.js';
|
|
5
|
-
export type { SchemaLoadResult } from './boot/schema-loader.js';
|
|
6
|
-
export { mergeSchemas } from './boot/schema-merger.js';
|
|
7
|
-
export type { MergeResult } from './boot/schema-merger.js';
|
|
8
|
-
export type {
|
|
9
|
-
ResolvedModel,
|
|
10
|
-
ResolvedField,
|
|
11
|
-
FieldProvenance,
|
|
12
|
-
ModelRelationship,
|
|
13
|
-
ExtensionSource,
|
|
14
|
-
} from './schema/types.js';
|
|
15
|
-
export type { RangkaPackageInfo, DiscoverySource, DiscoveredApp } from './boot/types.js';
|
|
16
|
-
export {
|
|
17
|
-
SchemaConflictError,
|
|
18
|
-
MissingDependencyError,
|
|
19
|
-
CircularDependencyError,
|
|
20
|
-
} from './boot/types.js';
|
|
21
|
-
export {
|
|
22
|
-
AppError,
|
|
23
|
-
BadRequestError,
|
|
24
|
-
UnauthorizedError,
|
|
25
|
-
ForbiddenError,
|
|
26
|
-
NotFoundError,
|
|
27
|
-
} from './errors.js';
|
|
28
|
-
export { NodeModulesDiscoverySource, MemoryDiscoverySource } from './boot/discovery.js';
|
|
29
|
-
export { ProjectScanner } from './boot/project-scanner.js';
|
|
30
|
-
export type { ProjectScanResult } from './boot/project-scanner.js';
|
|
31
|
-
export { DatabaseClient } from './db/client.js';
|
|
32
|
-
export type { DatabaseClientConfig } from './db/client.js';
|
|
33
|
-
export type {
|
|
34
|
-
ColumnDefinition,
|
|
35
|
-
TableDefinition,
|
|
36
|
-
IndexDefinition,
|
|
37
|
-
ForeignKeyDefinition,
|
|
38
|
-
DesiredState,
|
|
39
|
-
ActualState,
|
|
40
|
-
DdlOperation,
|
|
41
|
-
} from './db/types.js';
|
|
42
|
-
export { mapFieldsToColumns, modelToTableName } from './db/field-mapper.js';
|
|
43
|
-
export { SchemaToDesired } from './db/desired-state.js';
|
|
44
|
-
export { DiffEngine } from './db/diff-engine.js';
|
|
45
|
-
export { introspect } from './db/introspect.js';
|
|
46
|
-
export { autoSync } from './db/auto-sync.js';
|
|
47
|
-
export type { DdlOperation as CoreDdlOperation } from './db/types.js';
|
|
48
|
-
export { createServer } from './api/server.js';
|
|
49
|
-
export { QueryParser, QueryValidationError } from './api/query-parser.js';
|
|
50
|
-
export { generateRoutes } from './api/route-generator.js';
|
|
51
|
-
export type { ServerConfig, ApiDefinition } from './api/types.js';
|
|
52
|
-
export { defineApi, defineRoles, defineConfig } from '@rangka/shared';
|
|
53
|
-
export type { RangkaConfig } from '@rangka/shared';
|
|
54
|
-
|
|
55
|
-
// Auth & Permissions
|
|
56
|
-
export {
|
|
57
|
-
PermissionRegistry,
|
|
58
|
-
DuplicateRoleError,
|
|
59
|
-
RoleInheritanceCycleError,
|
|
60
|
-
} from './auth/permission-registry.js';
|
|
61
|
-
export {
|
|
62
|
-
createAuthHook,
|
|
63
|
-
getAuthContext,
|
|
64
|
-
createSessionHandler,
|
|
65
|
-
deleteSessionHandler,
|
|
66
|
-
regenerateSessionToken,
|
|
67
|
-
generateToken,
|
|
68
|
-
} from './auth/session.js';
|
|
69
|
-
export { createModelPermissionGuard } from './auth/model-permissions.js';
|
|
70
|
-
export { createScopeHook, createScopeWriteGuard, applyScopeFiltersToQuery } from './auth/scopes.js';
|
|
71
|
-
export { ScopeRegistry, ScopeResolutionError } from './auth/scope-registry.js';
|
|
72
|
-
export type { ResolvedScope, ModelScopeBinding } from './auth/scope-registry.js';
|
|
73
|
-
export {
|
|
74
|
-
createFieldWriteGuard,
|
|
75
|
-
createFieldStripHook,
|
|
76
|
-
resolveFieldPermissions,
|
|
77
|
-
} from './auth/field-permissions.js';
|
|
78
|
-
export { debugPermissions, formatDebugResult } from './auth/debug.js';
|
|
79
|
-
export { getCoreModels, getCoreApp } from './auth/core-module.js';
|
|
80
|
-
export { coreSchemas } from './auth/core-models.js';
|
|
81
|
-
export { seedCoreData } from './auth/seed.js';
|
|
82
|
-
export { hashPassword, verifyPassword } from './auth/password.js';
|
|
83
|
-
export type {
|
|
84
|
-
AuthUser,
|
|
85
|
-
AuthSession,
|
|
86
|
-
RequestContext,
|
|
87
|
-
ScopeFilter,
|
|
88
|
-
ResolvedPermissions,
|
|
89
|
-
RegisteredRole,
|
|
90
|
-
} from './auth/types.js';
|
|
91
|
-
|
|
92
|
-
// Hooks
|
|
93
|
-
export { HookRegistry } from './hooks/registry.js';
|
|
94
|
-
export { ValidationError } from './hooks/errors.js';
|
|
95
|
-
export { executeHookPipeline } from './hooks/executor.js';
|
|
96
|
-
export { createHookContext } from './hooks/context.js';
|
|
97
|
-
export type {
|
|
98
|
-
HookChain,
|
|
99
|
-
HookEntry,
|
|
100
|
-
HookLifecycle,
|
|
101
|
-
HookContext,
|
|
102
|
-
HookDocument,
|
|
103
|
-
} from './hooks/types.js';
|
|
104
|
-
|
|
105
|
-
// Validation
|
|
106
|
-
export { validateFields } from './validation/field-validator.js';
|
|
107
|
-
export type { FieldViolation } from './validation/field-validator.js';
|
|
108
|
-
|
|
109
|
-
// Jobs & Queue
|
|
110
|
-
export { JobRegistry } from './jobs/registry.js';
|
|
111
|
-
export { enqueue } from './jobs/enqueue.js';
|
|
112
|
-
export { JobWorker } from './jobs/worker.js';
|
|
113
|
-
export { ScheduleManager } from './jobs/scheduler.js';
|
|
114
|
-
export { getJobTables } from './jobs/tables.js';
|
|
115
|
-
export type {
|
|
116
|
-
JobRecord,
|
|
117
|
-
DeadLetterRecord,
|
|
118
|
-
ScheduledJobRecord,
|
|
119
|
-
JobState,
|
|
120
|
-
BackoffStrategy,
|
|
121
|
-
JobWorkerConfig,
|
|
122
|
-
RegisteredJob,
|
|
123
|
-
EnqueueOptions,
|
|
124
|
-
} from './jobs/types.js';
|
|
125
|
-
|
|
126
|
-
// Events
|
|
127
|
-
export { EventBus } from './events/bus.js';
|
|
128
|
-
export type { EventListener, EventBusConfig } from './events/types.js';
|
|
129
|
-
|
|
130
|
-
// Services
|
|
131
|
-
export {
|
|
132
|
-
ServiceRegistry,
|
|
133
|
-
ServiceCircularDependencyError,
|
|
134
|
-
ServiceNotFoundError,
|
|
135
|
-
DuplicateServiceError,
|
|
136
|
-
} from './services/registry.js';
|
|
137
|
-
export type {
|
|
138
|
-
ServiceDefinition,
|
|
139
|
-
ServiceFactory,
|
|
140
|
-
ServiceInstance,
|
|
141
|
-
ServiceDependency,
|
|
142
|
-
ServiceContext,
|
|
143
|
-
} from './services/types.js';
|
|
144
|
-
|
|
145
|
-
// Context
|
|
146
|
-
export { createFrameworkContext, createRequestContext } from './context.js';
|
|
147
|
-
export type { FrameworkContextOptions, RequestScopedContextOptions } from './context.js';
|
|
148
|
-
|
|
149
|
-
// Fixtures
|
|
150
|
-
export { FixtureRegistry } from './fixtures/registry.js';
|
|
151
|
-
export { loadFixtures } from './fixtures/loader.js';
|
|
152
|
-
export type {
|
|
153
|
-
FixtureDefinition,
|
|
154
|
-
FixtureRef,
|
|
155
|
-
FixtureRecord,
|
|
156
|
-
FixtureStatus,
|
|
157
|
-
FixtureLoadResult,
|
|
158
|
-
RegisteredFixture,
|
|
159
|
-
} from './fixtures/types.js';
|
|
160
|
-
|
|
161
|
-
// Audit Log
|
|
162
|
-
export { getAuditTables } from './audit/tables.js';
|
|
163
|
-
export { recordAudit, getAuditHistory } from './audit/record.js';
|
|
164
|
-
export type { AuditAction, AuditChange, AuditLogRecord, AuditOptions } from './audit/types.js';
|
|
165
|
-
|
|
166
|
-
// Widgets
|
|
167
|
-
export { WidgetRegistry } from './widgets/widget-registry.js';
|
|
168
|
-
export { validatePageBody } from './widgets/slot-validator.js';
|
|
169
|
-
export type { SlotValidationError } from './widgets/slot-validator.js';
|
|
170
|
-
|
|
171
|
-
// Coercion helpers
|
|
172
|
-
export { toBool, toInt, isNil, toCount } from './helpers/coerce.js';
|