aws-amplify / aws-amplify/amplify-data
SQLSchema.setRelationships is LogN - Order NMOdels X NModels Type Operation tripping ts recursive limit of 50 - 97% solution, trying to find final client extract issue.
- Dominant language
- TypeScript
- Stars
- 18
- Forks
- 23
- Avg merge
- 26m
- Merged PRs (30d)
- 1
Description
Hi,
Basically follow the instruction and create a really large model generate from a database.
Then attempt to set the Relationships and basically the whole frontend client types generation falls over, because
they can't complete the evaluation of anything, everything just keeps falling over as keep shitting the TS 50 recursive limit.
"@aws-amplify/data-schema": "^1.20.3",
I would like to propose that you fix this. I was looking at making a pull request with the fix, I was about 97% of the way there. I wanted to avoid using your SetSubArg, which results in type re-writes, instead just merging two dataset to gather of the final results, because should be faster, but you see there some issues with that, because typescript then seem to not work well with that, which I put down possible to things not being allowed to happen across files different levels of files and libraries, as to remain or be done in the same Library from my un wrapping of things.
Proposed fix, make sure you merge dataset and then use dictionary looks instead rather capture things using keys, that immediate look up the results instead that horrible ghastly array iteration that gets slow and slow with each new model added.
Case ID:176768433200003, sent you some private zip models examples of everything include node_modules to files I changed about a year ago, but I didn't have time to look at this again, where was left as found new way to fix things, just don't use setRelationships, I just put directly into the models, like do for dynamo db, which avoid the Nmodel X Nmodel computation time and ts limit being hit.
Additional to learn about typing's and previous attempts at doing things like this, which work out far better and faster and better typing control of modifiers extra, please look to what I did for mongo to infer everything. Done in 2019
[https://github.com/wesleyolis/mongooseRelationalTypes](https://github.com/wesleyolis/mongooseRelationalTypes) -refactored again, to not use extends as it was slow... use 2.6 hack
[https://github.com/wesleyolis/mongooseRelationTypesPoorPerformance](https://github.com/wesleyolis/mongooseRelationTypesPoorPerformance)
[https://github.com/wesleyolis/tsTypesFromASchemaImplemention](https://github.com/wesleyolis/tsTypesFromASchemaImplemention )
```.ts
const schemaSQL2 = setRelationshipsObject(schemaSQL1,(models) => {
return {
Catalogue: models.Catalogue.relationships({
Categorys: a.hasMany("CatalogueCategory", "CatalogueID"),
}),
CatalogueCategory: models.CatalogueCategory.relationships({
Catalogues: a.belongsTo("Catalogue", "CatalogueID"),
Categorys: a.belongsTo("Category", "CategoryID")
}),
ProductCharacteristics: models.ProductCharacteristics.relationships({
ProductSpesification: a.belongsTo("ProductSpesification","ProductSpesificationID"),
ProductCharacteristicsValues: a.hasMany("ProductCharacteristicsValue","ProductCharacteristicsID")
}),
ProductCharacteristic: models.ProductCharacteristicsValue.relationships({
ProductCharacteristic: a.belongsTo("ProductCharacteristics","ProductCharacteristicsID")
}),
};
})
type SchemaSQLUnitTest33 = ClientSchema
type genClientSchemaSQL33 = V6Client;
const test3: genClientSchemaSQL33
test3.models.ProductCharacteristics.list().then(r => {
})
```
Attempts to post fix this, but because alot of your types are not export that is problem and ment I had to edit your types directly.
```.ts
type ModelRelationshipFieldParamShape = {
type: 'model';
relationshipType: string;
relatedModel: string;
array: boolean;
valueRequired: boolean;
references: string[];
arrayRequired: boolean;
};
type TModelWithRelationShips = {
[TypeKey in keyof T]: {
relationships>>>(relationships: Param): Record
//relationships>>>(relationships: Param): Record
};
}
export interface PrimaryIndexIrShape {
pk: {
[key: string]: string | number;
};
sk: {
[key: string]: string | number;
} | never;
compositeSk: never | string;
}
export interface SecondaryIndexIrShape extends PrimaryIndexIrShape {
defaultQueryFieldSuffix: string;
queryField: string;
}
export type DisableOperationsOptions = 'queries' | 'mutations' | 'subscriptions' | 'list' | 'get' | 'create' | 'update' | 'delete' | 'onCreate' | 'onUpdate' | 'onDelete';
export type ModelTypeParamShapeCus = {
fields: ModelFields;
identifier: PrimaryIndexIrShape;
secondaryIndexes: ReadonlyArray;
authorization: Authorization[];
disabledOperations: ReadonlyArray;
};
type ModelTypeParamNew<
TModelFields extends ModelFields,
TPrimaryIndexIrShape extends PrimaryIndexIrShape,
TSecondaryIndexIrShape extends ReadonlyArray,
TAuthorization extends Authorization[],
TDisableOperationsOptions extends ReadonlyArray
> = {
fields: TModelFields;
identifier: TPrimaryIndexIrShape;
secondaryIndexes: TSecondaryIndexIrShape;
authorization: TAuthorization;
disabledOperations: TDisableOperationsOptions;
};
type ModelSchemaContents = Record;
type ModelSchemaParamShapeV2<
Types extends ModelSchemaContents = ModelSchemaContents,
TSchemaAuthorization = unknown, //extends any, SchemaAuthorization[] = SchemaAuthorization[],
TSchemaConfiguration = unknown //extends any, SchemaConfiguration = SchemaConfiguration
>= {
types: Types;
authorization: TSchemaAuthorization;
configuration: TSchemaConfiguration;
};
/*
export type AddRelationshipFieldsToModelTypeFields>> = Model extends
ModelType
? ModelType, HiddenKeys> : never;
*/
type ModelWithRelationships, RelationshipsMap extends Record,
InterectKeys extends keyof Types = keyof RelationshipsMap & keyof Types,
//RemaningKeys extends keyof Types = Exclude
> =
{
[k in InterectKeys]: Types[k] extends ModelType ?
//ModelParams extends ModelTypeParamNew
//?
//ModelType
//{sdfs:'sdfsdf'}
ModelType, HiddenKeys>
//:{sdfs:'sdfsdfsdfsdf'}
//extends ModelTypeParamShape
//ModelParams['fields']
/*
ModelType<
ModelTypeParamNew, HiddenKeys>
:'sdfsdf'
*/
/*
ModelType<
ModelTypeParamNew<
{
fields: ModelParams['fields'] & RelationshipsMap[k],
identifier: ModelParams['identifier'],
secondaryIndexes: ModelParams['secondaryIndexes'],
authorization: ModelParams['authorization'],
disabledOperations: ModelParams['disabledOperations']
} , HiddenKeys>
*/
:{sdfs:'sdfsdfsdfsdf'}
}
&
{
[k in Exclude] : Types[k]
};
type RelationshipTemplate = Record>;
```
ModelSchema.d.ts
```.ts
export type RDSModelSchemaParamShape = ModelSchemaParamShape;
export type InternalSchema = {
data: {
types: InternalSchemaModels;
authorization: SchemaAuthorization[];
configuration: SchemaConfiguration;
};
context?: {
schemas: InternalSchema[];
};
};
export type BaseSchema = {
data: T;
models:
{
[TypeKey in keyof T['types']]: T['types'][TypeKey] extends BaseModelType ? SchemaModelType : never;
};
transform: () => DerivedApiDefinition;
context?: {
schemas: GenericModelSchema[];
};
};
export type GenericModelSchema = BaseSchema & Brand;
/**
* Model schema definition interface
*
* @param T - The shape of the model schema
* @param UsedMethods - The method keys already defined
*/
export type ModelSchema = Omit<{
authorization: >(callback: (allow: AllowModifier) => AuthRules | AuthRules[]) => ModelSchema, UsedMethods | 'authorization'>;
}, UsedMethods> & BaseSchema & DDBSchemaBrand;
type RDSModelSchemaFunctions = 'addToSchema' | 'addQueries' | 'addMutations' | 'addSubscriptions' | 'authorization' | 'setRelationships' | 'setAuthorization' | 'renameModelFields' | 'renameModels';
type OmitFromEach = {
[ModelName in keyof Models]: Omit;
};
type RelationshipTemplate = Record>;
/**
* RDSModel schema definition interface
*
* @param T - The shape of the RDS model schema
* @param UsedMethods - The method keys already defined
*/
export type RDSModelSchema = Omit<{
addToSchema: (types: AddedTypes) => RDSModelSchema, UsedMethods | 'addToSchema'>;
/**
* @deprecated use `addToSchema()` to add operations to a SQL schema
*/
addQueries: >(types: Queries) => RDSModelSchema, UsedMethods | 'addQueries'>;
/**
* @deprecated use `addToSchema()` to add operations to a SQL schema
*/
addMutations: >(types: Mutations) => RDSModelSchema, UsedMethods | 'addMutations'>;
/**
* @deprecated use `addToSchema()` to add operations to a SQL schema
*/
addSubscriptions: >(types: Subscriptions) => RDSModelSchema, UsedMethods | 'addSubscriptions'>;
authorization: >(callback: (allow: AllowModifier) => AuthRules | AuthRules[]) => RDSModelSchema, UsedMethods | 'authorization'>;
setAuthorization: (callback: (models: OmitFromEach['models'], 'secondaryIndexes'>, schema: RDSModelSchema) => void) => RDSModelSchema;
setRelationships: >>>(
callback: (models: OmitFromEach['models'], 'authorization' | 'fields' | 'secondaryIndexes'>) => Relationships) =>
RDSModelSchema;
}>, UsedMethods | 'setRelationships'>;
renameModels: ['models'] & string, const ChangeLog extends readonly [CurName, NewName][] = []>(callback: () => ChangeLog) => RDSModelSchema>, UsedMethods | 'renameModels'>;
}, UsedMethods> & BaseSchema & RDSSchemaBrand;
/**
* Amplify API Next Model Schema shape
*/
export type ModelSchemaType = ModelSchema;
type ModelSchemaParamShapeV2<
Types extends ModelSchemaContents = ModelSchemaContents,
TSchemaAuthorization = unknown, //extends any, SchemaAuthorization[] = SchemaAuthorization[],
TSchemaConfiguration = unknown //extends any, SchemaConfiguration = SchemaConfiguration
>= {
types: Types;
authorization: TSchemaAuthorization;
configuration: TSchemaConfiguration;
};
type TModelWithRelationShips = {
[TypeKey in keyof T]: {
relationships>>>(relationships: Param): Record
//relationships>>>(relationships: Param): Record
};
}
type TModelWithRelationShips2 = {
[TypeKey in keyof T]: {
relationships['models'], 'authorization' | 'fields' | 'secondaryIndexes'>
//ModelRelationshipField
>>(relationships: Param): Record
//relationships>>>(relationships: Param): Record
};
}
export function setRelationshipsObject2['models'], 'authorization' | 'fields' | 'secondaryIndexes'>>
> (model: TModel, relationships: (models: OmitFromEach['models'], 'authorization' | 'fields' | 'secondaryIndexes'>) => Relationships)
//ModelWithRelationshipsV3
//RDSModelSchema, TModel['data']['authorization'], TModel['data']['configuration']>>
RDSModelSchema;}>, UsedMethods | 'setRelationships'>
//RDSModelSchema
{
return (model as any).setRelationships(Object.values(obj));
}
export function setRelationshipsObject>,
TTypes extends Record = TModel['data']['types'],
TModelTrans = TModelWithRelationShips,
//TResult extends ModelSchemaParamShapeV2 =
//ModelSchemaParamShapeV2, TModel['data']['authorization'], TModel['data']['configuration']>
>(model: TModel, obj:(model:TModelTrans) => TRelationships) :
//ModelWithRelationshipsV3
//RDSModelSchema, TModel['data']['authorization'], TModel['data']['configuration']>>
RDSModelSchema>, UsedMethods | 'setRelationships'>
//RDSModelSchema
{
return (model as any).setRelationships(Object.values(obj));
}
/*
callback: (models: OmitFromEach['models'], 'authorization' | 'fields' | 'secondaryIndexes'>) => Relationships) => RDSModelSchema;
*/
export type ModelWithRelationshipsV4, RelationshipsMap extends Record,
InterectKeys extends keyof Types = keyof RelationshipsMap & keyof Types,
//RemaningKeys extends keyof Types = Exclude
> =
{
[k in keyof Types]: Types[k] extends ModelType ?
AddRelationshipFieldsToModelTypeFieldsV2
: Types[k]
}
/* &
{
[k in Exclude] : Types[k]
};
*/
/*
export type AddRelationshipFieldsToModelTypeFields>> = Model extends
ModelType
? ModelType, HiddenKeys> : never;
*/
export type ModelWithRelationshipsV3, RelationshipsMap extends Record,
InterectKeys extends keyof Types = keyof RelationshipsMap & keyof Types,
//RemaningKeys extends keyof Types = Exclude
> =
{
[k in InterectKeys]: Types[k] extends ModelType ?
//ModelParams extends ModelTypeParamNew
//?
//ModelType
//{sdfs:'sdfsdf'}
//AddRelationshipFieldsToModelTypeFields
AddRelationshipFieldsToModelTypeFieldsV2
//ModelType, HiddenKeys>
//:{sdfs:'sdfsdfsdfsdf'}
//extends ModelTypeParamShape
//ModelParams['fields']
/*
ModelType<
ModelTypeParamNew, HiddenKeys>
:'sdfsdf'
*/
/*
ModelType<
ModelTypeParamNew<
{
fields: ModelParams['fields'] & RelationshipsMap[k],
identifier: ModelParams['identifier'],
secondaryIndexes: ModelParams['secondaryIndexes'],
authorization: ModelParams['authorization'],
disabledOperations: ModelParams['disabledOperations']
} , HiddenKeys>
*/
:Types[k]
}
&
{
[k in Exclude] : Types[k]
};
type ModelWithRelationshipsV2, Relationships extends ReadonlyArray>,
ModelName extends keyof Types, RelationshipMap extends UnionToIntersection =
UnionToIntersection> = ModelName extends keyof RelationshipMap ?
RelationshipMap[ModelName] extends Record> ?
AddRelationshipFieldsToModelTypeFields : Types[ModelName] : Types[ModelName];
type ModelWithRelationships, Relationships extends ReadonlyArray>,
ModelName extends keyof Types, RelationshipMap extends UnionToIntersection =
UnionToIntersection> = ModelName extends keyof RelationshipMap ?
RelationshipMap[ModelName] extends Record> ?
AddRelationshipFieldsToModelTypeFields : Types[ModelName] : Types[ModelName];
```
ModelTypes.d.ts
```.ts
import type { SetTypeSubArg } from '@aws-amplify/data-schema-types';
import { type PrimaryIndexIrShape, type SecondaryIndexIrShape } from './util';
import type { InternalField, BaseModelField } from './ModelField';
import type { ModelRelationshipField, InternalRelationshipField, ModelRelationshipFieldParamShape } from './ModelRelationshipField';
import { type Authorization, type BaseAllowModifier, type AnyAuthorization } from './Authorization';
import type { RefType, RefTypeParamShape } from './RefType';
import type { EnumType } from './EnumType';
import type { CustomType, CustomTypeParamShape } from './CustomType';
import { type ModelIndexType, type InternalModelIndexType } from './ModelIndex';
import type { PrimaryIndexFieldsToIR, SecondaryIndexToIR } from './MappedTypes/MapIndexes';
import type { brandSymbol } from './util/Brand.js';
import type { methodKeyOf } from './util/usedMethods.js';
declare const brandName = "modelType";
export type deferredRefResolvingPrefix = 'deferredRefResolving:';
type ModelFields = Record | RefType | EnumType | CustomType>;
type InternalModelFields = Record;
export type DisableOperationsOptions = 'queries' | 'mutations' | 'subscriptions' | 'list' | 'get' | 'create' | 'update' | 'delete' | 'onCreate' | 'onUpdate' | 'onDelete';
type ModelData = {
fields: ModelFields;
identifier: ReadonlyArray;
secondaryIndexes: ReadonlyArray>;
authorization: Authorization[];
disabledOperations: ReadonlyArray;
};
type InternalModelData = ModelData & {
fields: InternalModelFields;
identifier: ReadonlyArray;
secondaryIndexes: ReadonlyArray;
authorization: Authorization[];
disabledOperations: ReadonlyArray;
originalName?: string;
};
export type ModelTypeParamShape = {
fields: ModelFields;
identifier: PrimaryIndexIrShape;
secondaryIndexes: ReadonlyArray;
authorization: Authorization[];
disabledOperations: ReadonlyArray;
};
/**
* Extract fields that are eligible to be PK or SK fields with their resolved type.
*
* Eligible fields include:
* 1. ModelField that contains string or number
* 2. inline EnumType
* 3. RefType that refers to a top level defined EnumType (this is enforced by
* validation that happens in the Schema Processor)
*
* NOTE: at this point, there is no way to resolve the type from a RefType as
* we don't have access to the NonModelType at this location. So we generate am
* indicator string, and resolve its corresponding type later in
* packages/data-schema/src/runtime/client/index.ts
*/
export type ExtractSecondaryIndexIRFields = {
[FieldProp in keyof T['fields'] as T['fields'][FieldProp] extends BaseModelField ? NonNullable extends string | number ? FieldProp : never : T['fields'][FieldProp] extends EnumType | RefType ? FieldProp : never]: T['fields'][FieldProp] extends BaseModelField ? R : T['fields'][FieldProp] extends EnumType ? values[number] : T['fields'][FieldProp] extends RefType ? `${deferredRefResolvingPrefix}${R['link']}` : never;
};
export type AddRelationshipFieldsToModelTypeFields>>
= Model extends ModelType ?
ModelType, HiddenKeys> : never;
type ModelTypeParamNew<
TModelFields,// extends ModelFields,
TPrimaryIndexIrShape,// extends PrimaryIndexIrShape,
TSecondaryIndexIrShape,// extends ReadonlyArray,
TAuthorization,// extends Authorization[],
TDisableOperationsOptions,// extends ReadonlyArray
> = {
fields: TModelFields;
identifier: TPrimaryIndexIrShape;
secondaryIndexes: TSecondaryIndexIrShape;
authorization: TAuthorization;
disabledOperations: TDisableOperationsOptions;
};
export type AddRelationshipFieldsToModelTypeFieldsV2>>
= Model extends ModelType ?
/*
ModelType, HiddenKeys>
*/
/*
ModelType<
{
fields: ModelParams['fields'] & RelationshipsMap[k],
identifier: ModelParams['identifier'],
secondaryIndexes: ModelParams['secondaryIndexes'],
authorization: ModelParams['authorization'],
disabledOperations: ModelParams['disabledOperations']
} , HiddenKeys>
*/
ModelType, HiddenKeys>
: never;
export type BaseModelType = ModelType;
export type UsableModelTypeKey = methodKeyOf;
/**
* Model type definition interface
*
* @param T - The shape of the model type
* @param UsedMethod - The method keys already defined
*/
export type ModelType = Omit<{
[brandSymbol]: typeof brandName;
/**
* Defines single-field or composite identifiers, the fields must be marked required
*
* @param identifier A list of field names used as identifiers for the data model
* @returns A ModelType instance with updated identifiers
*
* @example
* a.model({
* name: a.string().required(),
* email: a.string().required(),
* age: a.integer(),
* }).identifier(['name', 'email'])
*/
identifier, PrimaryIndexPool extends string = keyof PrimaryIndexFields & string, const ID extends ReadonlyArray = readonly [], const PrimaryIndexIR extends PrimaryIndexIrShape = PrimaryIndexFieldsToIR>(identifier: ID): ModelType, UsedMethod | 'identifier'>;
/**
* Adds secondary index for a model, secondary index consists of a "hash key" and optionally, a "sort key"
*
* @param callback A function that specifies "hash key" and "sort key"
* @returns A ModelType instance with updated secondary index
*
* @example
* a.model().secondaryIndexes((index) => [index('type').sortKeys(['sort'])])
*
* @see [Amplify documentation for secondary indexes](https://docs.amplify.aws/react/build-a-backend/data/data-modeling/secondary-index/)
* @see [Amazon DynamoDB documentation for secondary indexes](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SecondaryIndexes.html)
*/
secondaryIndexes, const SecondaryIndexPKPool extends string = keyof SecondaryIndexFields & string, const Indexes extends readonly ModelIndexType[] = readonly [], const IndexesIR extends readonly any[] = SecondaryIndexToIR>(callback: (index: (pk: PK) => ModelIndexType>>) => Indexes): ModelType, UsedMethod | 'secondaryIndexes'>;
/**
* Disables the specified operations for the model
*
* @param ops A list of operations to be disabled
* @returns A ModelType instance with updated disabled operations
*
* @example
* a.model().disableOperations(['delete', 'update', 'queries', 'subscriptions'])
*
* @see [Amplify Data documentation for supported operations](https://docs.amplify.aws/react/build-a-backend/data/)
*/
disableOperations>(ops: Ops): ModelType, UsedMethod | 'disableOperations'>;
/**
* Configures authorization rules for public, signed-in user, per user, and per user group data access
*
* @param callback A function that receives an allow modifier to define authorization rules
* @returns A ModelType instance with updated authorization rules
*
* @example
* a.model().authorization((allow) => [
* allow.guest(),
* allow.publicApiKey(),
* allow.authenticated(),
* ])
*/
authorization(callback: (allow: BaseAllowModifier) => AuthRuleType | AuthRuleType[]): ModelType, UsedMethod | 'authorization'>;
}, UsedMethod>;
/**
* External representation of Model Type that exposes the `relationships` modifier.
* Used on the complete schema object.
*/
export type SchemaModelType, ModelName extends string = string, IsRDS extends boolean = false> = IsRDS extends true ? T & {
relationships> = Record>(relationships: Param): Record;
fields: T extends ModelType ? R['fields'] : never;
} : T;
/**
* Internal representation of Model Type that exposes the `data` property.
* Used at buildtime.
*/
export type InternalModel = SchemaModelType, string, true> & {
data: InternalModelData;
};
/**
* Model Type type guard
* @param modelType - api-next ModelType
* @returns true if the given value is a ModelSchema
*/
export declare const isSchemaModelType: (modelType: any | SchemaModelType) => modelType is SchemaModelType;
/**
* Model default identifier
*
* @param pk - primary key
* @param sk - secondary key
* @param compositeSk - composite secondary key
*/
export type ModelDefaultIdentifier = {
pk: {
readonly id: string;
};
sk: never;
compositeSk: never;
};
/**
* A data model that creates a matching Amazon DynamoDB table and provides create, read (list and get), update,
* delete, and subscription APIs.
*
* @param fields database table fields. Supports scalar types and relationship types.
* @returns a data model definition
*/
export declare function model(fields: T): ModelType<{
fields: T;
identifier: ModelDefaultIdentifier;
secondaryIndexes: [];
authorization: [];
disabledOperations: [];
}>;
export {};
``
Contributor guide
Research direction
Start with the setRelationships type in ModelSchema.d.ts and reproduce the failure using a large database-generated SQL model with @aws-amplify/data-schema. Compare the reported relationship type experiments and the mongoose examples before determining the final approach. Done means client type generation and operations such as test3.models.ProductCharacteristics.list() compile without TypeScript's recursive limit being exceeded.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- aws, typescript
- Domain
- backend-api-design, databases, developer-experience
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100