Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .changeset/projection-aware-synchronous-mapping.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
"@cleverbrush/mapper": minor
"@cleverbrush/knex-schema": minor
"@cleverbrush/orm": minor
"@cleverbrush/schema": minor
"@cleverbrush/di": minor
---

Add opt-in immutable, detached PostgreSQL reads with projection-aware runtime
schemas, precise decimal/bigint decoding, nested relation graphs and explicit
STI/CTI branch schemas. Reuse result schemas directly in separately defined
application mappings without duplicating projection schemas.

Add `getSyncMapper()` with synchronous eligibility inferred through the existing
`configure()` API, completeness checks, nested mapping propagation and runtime
thenable guards. Existing queries and asynchronous mapping behavior are unchanged.

Add application-agnostic typed metadata extension methods that retain metadata
through immutable chains. Use these in database extensions without modifying
global schema prototypes.

Preserve typed function-schema compatibility in dependency injection when a
function declares its return schema.
Binary file added docs/assets/projection-read-schemas.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/projection-sync-mapper.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
44 changes: 44 additions & 0 deletions libs/benchmarks/src/mapper.bench.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { array, number, object, string } from '@cleverbrush/schema';
import { bench, describe } from 'vitest';
import { mapper } from '../../mapper/src/index.js';

const Source = object({ id: number(), name: string() });
const Target = object({ id: number(), label: string() });
const Parent = object({ id: number(), children: array(Source) });
const ParentDto = object({ id: number(), children: array(Target) });
const registry = mapper()
.configure(Source, Source, m => m)
.configure(Source, Target, m =>
m.for(t => t.label).compute(s => s.name.toUpperCase())
)
.configure(Parent, ParentDto, m => m);
const sourceRows = Array.from({ length: 1000 }, (_, id) => ({
id,
name: `user-${id}`
}));
const graphRows = Array.from({ length: 100 }, (_, id) => ({
id,
children: sourceRows.slice(id, id + 10)
}));

for (const [name, source, target, rows] of [
['flat copy / 1000 rows', Source, Source, sourceRows],
['computed / 1000 rows', Source, Target, sourceRows],
['nested arrays / 100 parents', Parent, ParentDto, graphRows]
] as const) {
// Set-up is intentionally outside the timed function. This is mapper overhead,
// not SQL/network latency or schema-construction performance.
const sync = (registry.getSyncMapper as any)(source, target);
const async = (registry.getMapper as any)(source, target);
const expected = JSON.stringify(rows.map(sync));
if (JSON.stringify(await Promise.all(rows.map(async))) !== expected)
throw new Error('Benchmark variants must produce identical values');
describe(`Prepared mapper: ${name}`, () => {
bench('synchronous map', () => {
rows.map(sync);
});
bench('async Promise.all', async () => {
await Promise.all(rows.map(async));
});
});
}
50 changes: 44 additions & 6 deletions libs/di/src/ServiceCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,16 @@ export class ServiceCollection {
*/
public addSingletonFromSchema<
TTargetSchema extends SchemaBuilder<any, any, any, any, any>,
TFuncSchema extends FunctionSchemaBuilder<any, any, any, any, any, any>
TFuncSchema extends FunctionSchemaBuilder<
any,
any,
any,
any,
any,
any,
any,
any
>
>(
targetSchema: TTargetSchema,
funcSchema: TFuncSchema,
Expand Down Expand Up @@ -295,7 +304,16 @@ export class ServiceCollection {
*/
public addScopedFromSchema<
TTargetSchema extends SchemaBuilder<any, any, any, any, any>,
TFuncSchema extends FunctionSchemaBuilder<any, any, any, any, any, any>
TFuncSchema extends FunctionSchemaBuilder<
any,
any,
any,
any,
any,
any,
any,
any
>
>(
targetSchema: TTargetSchema,
funcSchema: TFuncSchema,
Expand Down Expand Up @@ -330,7 +348,16 @@ export class ServiceCollection {
*/
public addTransientFromSchema<
TTargetSchema extends SchemaBuilder<any, any, any, any, any>,
TFuncSchema extends FunctionSchemaBuilder<any, any, any, any, any, any>
TFuncSchema extends FunctionSchemaBuilder<
any,
any,
any,
any,
any,
any,
any,
any
>
>(
targetSchema: TTargetSchema,
funcSchema: TFuncSchema,
Expand Down Expand Up @@ -416,7 +443,16 @@ export class ServiceCollection {

#addFromSchema<
TTargetSchema extends SchemaBuilder<any, any, any, any, any>,
TFuncSchema extends FunctionSchemaBuilder<any, any, any, any, any, any>
TFuncSchema extends FunctionSchemaBuilder<
any,
any,
any,
any,
any,
any,
any,
any
>
>(
targetSchema: TTargetSchema,
funcSchema: TFuncSchema,
Expand Down Expand Up @@ -451,15 +487,17 @@ export class ServiceCollection {
* Used internally to type-check `addSingletonFromSchema`-style registrations.
*/
type FuncSchemaParameters<
T extends FunctionSchemaBuilder<any, any, any, any, any, any>
T extends FunctionSchemaBuilder<any, any, any, any, any, any, any, any>
> =
T extends FunctionSchemaBuilder<
any,
any,
any,
any,
any,
infer TParams extends SchemaBuilder<any, any, any, any, any>[]
infer TParams extends SchemaBuilder<any, any, any, any, any>[],
any,
any
>
? { [K in keyof TParams]: InferType<TParams[K]> }
: never;
11 changes: 10 additions & 1 deletion libs/di/src/ServiceProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,16 @@ export class ServiceProvider implements IServiceProvider {
* @see {@link FunctionSchemaBuilder.hasReturnType}
*/
public invoke<
TFuncSchema extends FunctionSchemaBuilder<any, any, any, any, any, any>
TFuncSchema extends FunctionSchemaBuilder<
any,
any,
any,
any,
any,
any,
any,
any
>
>(
funcSchema: TFuncSchema,
implementation: InferType<TFuncSchema>
Expand Down
11 changes: 10 additions & 1 deletion libs/di/src/ServiceScope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,16 @@ export class ScopedServiceProvider implements IServiceProvider {
* @see {@link ServiceProvider.invoke}
*/
public invoke<
TFuncSchema extends FunctionSchemaBuilder<any, any, any, any, any, any>
TFuncSchema extends FunctionSchemaBuilder<
any,
any,
any,
any,
any,
any,
any,
any
>
>(
funcSchema: TFuncSchema,
implementation: InferType<TFuncSchema>
Expand Down
188 changes: 0 additions & 188 deletions libs/knex-schema/COMPOSABLE_QUERIES.md

This file was deleted.

Loading
Loading