Skip to content
Closed
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
1 change: 1 addition & 0 deletions packages/2-sql/2-authoring/contract-ts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ This is the current SQL TypeScript authoring implementation. Shared descriptor t
- **Composed helper namespaces**: `defineContract(config, (helpers) => ...)` synthesizes `helpers.field.*` and `helpers.type.*` from the selected family, target, and extension packs
- **SQL resolution and contract generation**: internal resolution normalizes names, relations, indexes, and FK materialization before producing the canonical SQL contract artifacts
- **Shared descriptor layer**: `@internal/contract-authoring` provides the target-neutral descriptor types used by the DSL and by authoring-adjacent packs
- **Native query inference**: `SqlContractResult` preserves scalar/list channels, relation cardinality, and literal model namespaces for clients consuming the contract directly. Packs can carry literal aggregate descriptors through `__aggregateDescriptors`, alongside `__codecTypes`; aggregate type maps resolve exact-codec overloads before trait fallbacks without duplicating the runtime result matrix.

Contributor-facing lowering notes and detailed warning semantics live in [DEVELOPING.md](./DEVELOPING.md).

Expand Down
81 changes: 81 additions & 0 deletions packages/2-sql/2-authoring/contract-ts/src/aggregate-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import type { AggregateDescriptor } from '@internal/framework-components/components';

type PackDescriptors<Pack> = Pack extends {
readonly __aggregateDescriptors?: ReadonlyArray<infer Descriptor extends AggregateDescriptor>;
}
? Descriptor
: never;

type Descriptors<Packs> = {
[Key in keyof Packs]: PackDescriptors<Packs[Key]>;
}[keyof Packs];

type CodecTraits<Codec> = Codec extends { readonly traits: infer Traits } ? Traits : never;

type ExactMatch<Descriptor, Id> = Descriptor extends {
readonly input: { readonly kind: 'codec'; readonly codecId: infer CodecId };
}
? Id extends CodecId
? Descriptor
: never
: never;

type TraitMatch<Descriptor, Traits> = Descriptor extends {
readonly input: { readonly kind: 'trait'; readonly trait: infer Trait };
}
? Trait extends Traits
? Descriptor
: never
: never;

type Fallback<First, Second> = [First] extends [never] ? Second : First;

type Match<Descriptor, Id, Codec> = Fallback<
ExactMatch<Descriptor, Id>,
TraitMatch<Descriptor, CodecTraits<Codec>>
>;

type Result<Descriptor, Id> = Descriptor extends {
readonly output: infer Output;
readonly nullable: infer Nullable extends boolean;
}
? {
readonly output: Output extends {
readonly kind: 'codec';
readonly codecId: infer CodecId extends string;
}
? CodecId
: Id & string;
readonly nullable: Nullable;
}
: never;

type Operation<Descriptor, Codecs> = {
readonly byCodec: {
readonly [Id in keyof Codecs & string as [Match<Descriptor, Id, Codecs[Id]>] extends [never]
? never
: Id]: Result<Match<Descriptor, Id, Codecs[Id]>, Id>;
};
} & ([Extract<Descriptor, { readonly input: { readonly kind: 'any' | 'none' } }>] extends [never]
? Record<never, never>
: {
readonly withoutInput: Result<
Extract<Descriptor, { readonly input: { readonly kind: 'any' | 'none' } }>,
never
>;
}) &
([Extract<Descriptor, { readonly input: { readonly kind: 'any' } }>] extends [never]
? Record<never, never>
: {
readonly anyInput: Result<
Extract<Descriptor, { readonly input: { readonly kind: 'any' } }>,
never
>;
});

export type AggregateTypesFromPacks<Packs, Codecs> = {
readonly [Name in Descriptors<Packs>['operation']]: Operation<
Extract<Descriptors<Packs>, { readonly operation: Name }>,
Codecs
>;
};
Original file line number Diff line number Diff line change
Expand Up @@ -115,18 +115,42 @@ type PackAwareModel<IndexTypes extends IndexTypeMap> = {
const ModelName extends string,
Fields extends Record<string, ScalarFieldBuilder>,
Relations extends Record<string, AnyRelationBuilder> = Record<never, never>,
const TNamespace extends string | undefined = undefined,
>(
modelName: ModelName,
input: { readonly fields: Fields; readonly relations?: Relations; readonly namespace?: string },
): ContractModelBuilder<ModelName, Fields, Relations, undefined, undefined, IndexTypes>;
input: {
readonly fields: Fields;
readonly relations?: Relations;
readonly namespace?: TNamespace;
},
): ContractModelBuilder<
ModelName,
Fields,
Relations,
undefined,
undefined,
IndexTypes,
'<self>',
NoInfer<TNamespace>
>;
<
Fields extends Record<string, ScalarFieldBuilder>,
Relations extends Record<string, AnyRelationBuilder> = Record<never, never>,
const TNamespace extends string | undefined = undefined,
>(input: {
readonly fields: Fields;
readonly relations?: Relations;
readonly namespace?: string;
}): ContractModelBuilder<undefined, Fields, Relations, undefined, undefined, IndexTypes>;
readonly namespace?: TNamespace;
}): ContractModelBuilder<
undefined,
Fields,
Relations,
undefined,
undefined,
IndexTypes,
'<self>',
NoInfer<TNamespace>
>;
};

export type ComposedAuthoringHelpers<
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ControlPolicy } from '@internal/contract/types';
import type { Contract, ControlPolicy } from '@internal/contract/types';
import type { ForeignKeyDefaultsState } from '@internal/contract-authoring';
import type { CodecLookup } from '@internal/framework-components/codec';
import type {
Expand All @@ -10,6 +10,7 @@ import type { PackEntityHandle } from '@internal/sql-contract/entity-handle-lowe
import type {
SqlNamespaceBase,
SqlNamespaceInput,
SqlStorage,
StorageTypeInstance,
} from '@internal/sql-contract/types';
import { blindCast } from '@internal/utils/casts';
Expand Down Expand Up @@ -590,7 +591,7 @@ export function defineContract(
Record<string, ModelLike>,
Record<string, ExtensionPackRef<'sql', string>> | undefined
>,
): SqlContractResult<ContractInput> {
): Contract<SqlStorage> {
if (!isContractInput(definition)) {
throw contractError(
'CONTRACT.ARGUMENT_INVALID',
Expand Down
43 changes: 34 additions & 9 deletions packages/2-sql/2-authoring/contract-ts/src/contract-dsl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1448,6 +1448,7 @@ export class ContractModelBuilder<
SqlSpec extends SqlStageSpec | undefined = undefined,
IndexTypes extends IndexTypeMap = Record<never, never>,
TSpaceId extends string = '<self>',
TNamespace extends string | undefined = string | undefined,
> {
declare readonly __name: ModelName;
declare readonly __fields: Fields;
Expand All @@ -1461,7 +1462,7 @@ export class ContractModelBuilder<
constructor(
readonly stageOne: {
readonly modelName?: ModelName;
readonly namespace?: string;
readonly namespace?: Exclude<TNamespace, undefined>;
readonly fields: Fields;
readonly relations: Relations;
},
Expand Down Expand Up @@ -1519,7 +1520,8 @@ export class ContractModelBuilder<
AttributesSpec,
SqlSpec,
IndexTypes,
TSpaceId
TSpaceId,
TNamespace
> {
const duplicateRelationName = findDuplicateRelationName(this.stageOne.relations, relations);
if (duplicateRelationName) {
Expand Down Expand Up @@ -1563,7 +1565,8 @@ export class ContractModelBuilder<
NextAttributesSpec,
SqlSpec,
IndexTypes,
TSpaceId
TSpaceId,
TNamespace
> {
return new ContractModelBuilder(
this.stageOne,
Expand All @@ -1584,7 +1587,8 @@ export class ContractModelBuilder<
AttributesSpec,
never,
IndexTypes,
TSpaceId
TSpaceId,
TNamespace
>
: ContractModelBuilder<
ModelName,
Expand All @@ -1593,7 +1597,8 @@ export class ContractModelBuilder<
AttributesSpec,
NextSqlSpec,
IndexTypes,
TSpaceId
TSpaceId,
TNamespace
> {
// Conditional return type cannot be verified by the implementation; the runtime value is always a valid ContractModelBuilder regardless of the validation outcome (validation is type-level only).
// When specOrFactory is a static object (not a function), extract tableName for the cross-space coordinate.
Expand Down Expand Up @@ -1815,23 +1820,43 @@ export function model<
const ModelName extends string,
Fields extends Record<string, ScalarFieldBuilder>,
Relations extends Record<string, AnyRelationBuilder> = Record<never, never>,
const TNamespace extends string | undefined = undefined,
>(
modelName: ModelName,
input: {
readonly fields: Fields;
readonly relations?: Relations;
readonly namespace?: string;
readonly namespace?: TNamespace;
},
): ContractModelBuilder<ModelName, Fields, Relations>;
): ContractModelBuilder<
ModelName,
Fields,
Relations,
undefined,
undefined,
Record<never, never>,
'<self>',
NoInfer<TNamespace>
>;

export function model<
Fields extends Record<string, ScalarFieldBuilder>,
Relations extends Record<string, AnyRelationBuilder> = Record<never, never>,
const TNamespace extends string | undefined = undefined,
>(input: {
readonly fields: Fields;
readonly relations?: Relations;
readonly namespace?: string;
}): ContractModelBuilder<undefined, Fields, Relations>;
readonly namespace?: TNamespace;
}): ContractModelBuilder<
undefined,
Fields,
Relations,
undefined,
undefined,
Record<never, never>,
'<self>',
NoInfer<TNamespace>
>;

export function model<
const ModelName extends string,
Expand Down
Loading