@aws-amplify/data-schema 0.14.1 → 0.14.3

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.
@@ -5,7 +5,9 @@ import type { CreateImplicitModelsFromRelations, ResolveFieldProperties, Resolve
5
5
  import type { ModelIdentifier, ModelSecondaryIndexes, RelationalMetadata } from './MappedTypes/ModelMetadata';
6
6
  import type { ExtractNonModelTypes, NonModelTypesShape } from './MappedTypes/ExtractNonModelTypes';
7
7
  import { ResolveCustomOperations, CustomOperationHandlerTypes } from './MappedTypes/CustomOperations';
8
- export type ClientSchema<Schema extends GenericModelSchema<any>> = InternalClientSchema<Schema>;
8
+ import { CombinedModelSchema, CombinedSchemaIndexesUnion } from './CombineSchema';
9
+ import { SpreadTuple } from './util';
10
+ export type ClientSchema<Schema extends GenericModelSchema<any> | CombinedModelSchema<any>> = Schema extends GenericModelSchema<any> ? InternalClientSchema<Schema> : Schema extends CombinedModelSchema<any> ? InternalCombinedSchema<Schema> : never;
9
11
  /**
10
12
  * Types for unwrapping generic type args into client-consumable types
11
13
  *
@@ -15,8 +17,11 @@ export type ClientSchema<Schema extends GenericModelSchema<any>> = InternalClien
15
17
  * They should not receive external type args.
16
18
  *
17
19
  * @internal @typeParam NonModelTypes - Custom Types, Enums, and Custom Operations
20
+ * @internal @typeParam ResolvedSchema - Resolve the schema types used by other generics
18
21
  * @internal @typeParam ImplicitModels - The implicit models created to represent relationships
22
+ * @internal @typeParam ImplicitModelsIdentifierMeta - The implicite model identifiers derived from ImplicitModels
19
23
  * @internal @typeParam ResolvedFields - Resolved client-facing types used for CRUDL response shapes
24
+ * @internal @typeParam IdentifierMeta - Resolve the identifier fields for all models
20
25
  * @internal @typeParam SecondaryIndexes - Map of model secondary index metadata
21
26
  */
22
27
  type InternalClientSchema<Schema extends GenericModelSchema<any>, NonModelTypes extends NonModelTypesShape = ExtractNonModelTypes<Schema>, ResolvedSchema = ResolveSchema<Schema>, ImplicitModels = Schema extends RDSModelSchema<any, any> ? object : CreateImplicitModelsFromRelations<ResolvedSchema>, ImplicitModelsIdentifierMeta = {
@@ -26,4 +31,23 @@ type InternalClientSchema<Schema extends GenericModelSchema<any>, NonModelTypes
26
31
  }, ResolvedFields extends Record<string, unknown> = Schema extends RDSModelSchema<any, any> ? ResolveStaticFieldProperties<Schema, NonModelTypes, object> : ResolveFieldProperties<Schema, NonModelTypes, ImplicitModels>, IdentifierMeta extends Record<string, any> = ModelIdentifier<SchemaTypes<Schema>>, SecondaryIndexes extends Record<string, any> = Schema extends RDSModelSchema<any, any> ? object : ModelSecondaryIndexes<SchemaTypes<Schema>>> = CustomOperationHandlerTypes<ResolveCustomOperations<Schema, ResolvedFields, NonModelTypes>['customOperations']> & ResolvedFields & {
27
32
  [__modelMeta__]: IdentifierMeta & ImplicitModelsIdentifierMeta & SecondaryIndexes & RelationalMetadata<ResolvedSchema, ResolvedFields, IdentifierMeta> & NonModelTypes & ResolveCustomOperations<Schema, ResolvedFields, NonModelTypes>;
28
33
  };
34
+ type GetInternalClientSchema<Schema> = Schema extends GenericModelSchema<any> ? InternalClientSchema<Schema> : never;
35
+ type CombinedClientSchemas<Schemas extends CombinedModelSchema<any>['schemas']> = {
36
+ [Index in keyof Schemas]: Index extends CombinedSchemaIndexesUnion ? GetInternalClientSchema<Schemas[Index]> : never;
37
+ };
38
+ /**
39
+ * Types for unwrapping and combining generic type args into client-consumable types
40
+ * for multiple schemas
41
+ *
42
+ * @typeParam Combined - A container of multiple schemas
43
+ *
44
+ * @internal @typeParam ClientSchemas - The tuple of client schemas to combine
45
+ */
46
+ type InternalCombinedSchema<Combined extends CombinedModelSchema<any>, ClientSchemas extends [...any] = CombinedClientSchemas<Combined['schemas']>> = SpreadTuple<{
47
+ [I in keyof ClientSchemas]: I extends CombinedSchemaIndexesUnion ? Exclude<ClientSchemas[I], typeof __modelMeta__> : never;
48
+ }> & {
49
+ [__modelMeta__]: SpreadTuple<{
50
+ [I in keyof ClientSchemas]: I extends CombinedSchemaIndexesUnion ? ClientSchemas[I][typeof __modelMeta__] : never;
51
+ }>;
52
+ };
29
53
  export {};
@@ -0,0 +1,18 @@
1
+ import { BaseSchema, GenericModelSchema } from './ModelSchema';
2
+ import { Brand, IndexLimitUnion } from './util';
3
+ declare const COMBINED_SCHEMA_LIMIT = 50;
4
+ export type CombinedSchemaIndexesUnion = IndexLimitUnion<typeof COMBINED_SCHEMA_LIMIT>[number];
5
+ declare const CombinedSchemaBrandName = "CombinedSchema";
6
+ export declare const combinedSchemaBrand: Brand<"CombinedSchema">;
7
+ export type CombinedSchemaBrand = Brand<typeof CombinedSchemaBrandName>;
8
+ export type CombinedModelSchema<Schemas extends GenericModelSchema<any>[]> = CombinedSchemaBrand & {
9
+ schemas: [...Schemas];
10
+ } & BaseSchema<any>;
11
+ /**
12
+ * The interface for merging up to 50 schemas into a single API.
13
+ * @param schemas The schemas to combine into a single API
14
+ * @returns An API and data model definition to be deployed with Amplify (Gen 2) experience (`processSchema(...)`)
15
+ * or with the Amplify Data CDK construct (`@aws-amplify/data-construct`)
16
+ */
17
+ export declare function combine<Schema extends GenericModelSchema<any>[]>(schemas: [...Schema]): CombinedModelSchema<Schema>;
18
+ export {};
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.combine = exports.combinedSchemaBrand = void 0;
4
+ const util_1 = require("./util");
5
+ const COMBINED_SCHEMA_LIMIT = 50;
6
+ const CombinedSchemaBrandName = 'CombinedSchema';
7
+ exports.combinedSchemaBrand = (0, util_1.brand)(CombinedSchemaBrandName);
8
+ /**
9
+ * The interface for merging up to 50 schemas into a single API.
10
+ * @param schemas The schemas to combine into a single API
11
+ * @returns An API and data model definition to be deployed with Amplify (Gen 2) experience (`processSchema(...)`)
12
+ * or with the Amplify Data CDK construct (`@aws-amplify/data-construct`)
13
+ */
14
+ function combine(schemas) {
15
+ return internalCombine(schemas);
16
+ }
17
+ exports.combine = combine;
18
+ function internalCombine(schemas) {
19
+ return {
20
+ schemas: schemas,
21
+ data: {
22
+ types: schemas.reduce((prev, schema) => ({ ...prev, ...schema.data.types }), {}),
23
+ },
24
+ models: schemas.reduce((prev, schema) => ({ ...prev, ...schema.models }), {}),
25
+ transform() {
26
+ const baseDefinition = {
27
+ functionSlots: [],
28
+ jsFunctions: [],
29
+ schema: '',
30
+ functionSchemaAccess: [],
31
+ lambdaFunctions: {},
32
+ };
33
+ return schemas.reduce((prev, schema) => {
34
+ const transformedSchema = schema.transform();
35
+ return {
36
+ functionSlots: [
37
+ ...prev.functionSlots,
38
+ ...transformedSchema.functionSlots,
39
+ ],
40
+ jsFunctions: [...prev.jsFunctions, ...transformedSchema.jsFunctions],
41
+ schema: [prev.schema, transformedSchema.schema].join('\n'),
42
+ functionSchemaAccess: [
43
+ ...prev.functionSchemaAccess,
44
+ ...transformedSchema.functionSchemaAccess,
45
+ ],
46
+ lambdaFunctions: {
47
+ ...prev.lambdaFunctions,
48
+ ...transformedSchema.lambdaFunctions,
49
+ },
50
+ };
51
+ }, baseDefinition);
52
+ },
53
+ ...exports.combinedSchemaBrand,
54
+ };
55
+ }
@@ -54,14 +54,14 @@ export type InternalSchema = {
54
54
  configuration: SchemaConfig<any, any>;
55
55
  };
56
56
  };
57
- type BaseSchema<T extends ModelSchemaParamShape> = {
57
+ export type BaseSchema<T extends ModelSchemaParamShape> = {
58
58
  data: T;
59
59
  models: {
60
60
  [TypeKey in keyof T['types']]: T['types'][TypeKey] extends ModelType<ModelTypeParamShape> ? SchemaModelType<T['types'][TypeKey]> : never;
61
61
  };
62
62
  transform: () => DerivedApiDefinition;
63
63
  };
64
- export type GenericModelSchema<T extends ModelSchemaParamShape> = BaseSchema<T> & Brand<string>;
64
+ export type GenericModelSchema<T extends ModelSchemaParamShape> = BaseSchema<T> & Brand<typeof rdsSchemaBrandName | typeof ddbSchemaBrandName>;
65
65
  export type ModelSchema<T extends ModelSchemaParamShape, UsedMethods extends 'authorization' = never> = Omit<{
66
66
  authorization: <AuthRules extends SchemaAuthorization<any, any, any>>(auth: AuthRules[]) => ModelSchema<SetTypeSubArg<T, 'authorization', AuthRules[]>, UsedMethods | 'authorization'>;
67
67
  }, UsedMethods> & BaseSchema<T> & DDBSchemaBrand;
@@ -198,7 +198,7 @@ function customOperationToGql(typeName, typeDef, authorization, isCustom = false
198
198
  throw new Error(`Unrecognized return type on ${typeName}`);
199
199
  }
200
200
  if (Object.keys(fieldArgs).length > 0) {
201
- const { gqlFields, models } = processFields(typeName, fieldArgs, {});
201
+ const { gqlFields, models } = processFields(typeName, fieldArgs, {}, {});
202
202
  callSignature += `(${gqlFields.join(', ')})`;
203
203
  implicitModels.push(...models);
204
204
  }
@@ -307,6 +307,24 @@ function validateStaticFields(existing, implicitFields) {
307
307
  }
308
308
  }
309
309
  }
310
+ /**
311
+ * Validate that no implicit fields conflict with explicitly defined fields.
312
+ *
313
+ * @param existing An existing field map
314
+ * @param implicitFields A field map inferred from other schema usage
315
+ *
316
+ * @throws An error when an undefined field is used or when a field is used in a way that conflicts with its generated definition
317
+ */
318
+ function validateImpliedFields(existing, implicitFields) {
319
+ if (implicitFields === undefined) {
320
+ return;
321
+ }
322
+ for (const [k, field] of Object.entries(implicitFields)) {
323
+ if (existing[k] && areConflicting(existing[k], field)) {
324
+ throw new Error(`Implicit field ${k} conflicts with the explicit field definition.`);
325
+ }
326
+ }
327
+ }
310
328
  /**
311
329
  * Produces a new field definition object from every field definition object
312
330
  * given as an argument. Performs validation (conflict detection) as objects
@@ -344,7 +362,8 @@ function validateAuth(authorization = []) {
344
362
  *
345
363
  * The computed directives are intended to be appended to the graphql field definition.
346
364
  *
347
- * The computed fields are intended to be aggregated and injected per model.
365
+ * The computed fields will be used to confirm no conflicts between explicit field definitions
366
+ * and implicit auth fields.
348
367
  *
349
368
  * @param authorization A list of authorization rules.
350
369
  * @returns
@@ -590,35 +609,6 @@ const allImpliedFKs = (schema) => {
590
609
  }
591
610
  return fks;
592
611
  };
593
- /**
594
- * Determines if implicit date fields are in effect for a given model. If they are,
595
- * returns those implicit fields.
596
- *
597
- * NOTE: For now, we *only* support the default implicit fields.
598
- *
599
- * @param _model Model to find date fields for.
600
- */
601
- const implicitTimestampFields = (_model) => {
602
- return {
603
- createdAt: (0, ModelField_1.datetime)().required(),
604
- updatedAt: (0, ModelField_1.datetime)().required(),
605
- };
606
- };
607
- /**
608
- * Generates default Pk fields for a model, based on identifier designation.
609
- *
610
- * The fields from this function are just default values. They should be overridden
611
- * by ID field definitions that are explicit in the model.
612
- *
613
- * @param _model Model to find PK fields for.
614
- */
615
- const idFields = (model) => {
616
- const fields = {};
617
- for (const fieldName of model.data.identifier) {
618
- fields[fieldName] = (0, ModelField_1.id)().required();
619
- }
620
- return fields;
621
- };
622
612
  function processFieldLevelAuthRules(fields, authFields) {
623
613
  const fieldLevelAuthRules = {};
624
614
  for (const [fieldName, fieldDef] of Object.entries(fields)) {
@@ -633,9 +623,10 @@ function processFieldLevelAuthRules(fields, authFields) {
633
623
  }
634
624
  return fieldLevelAuthRules;
635
625
  }
636
- function processFields(typeName, fields, fieldLevelAuthRules, identifier, partitionKey, secondaryIndexes = {}) {
626
+ function processFields(typeName, fields, impliedFields, fieldLevelAuthRules, identifier, partitionKey, secondaryIndexes = {}) {
637
627
  const gqlFields = [];
638
628
  const models = [];
629
+ validateImpliedFields(fields, impliedFields);
639
630
  for (const [fieldName, fieldDef] of Object.entries(fields)) {
640
631
  const fieldAuth = fieldLevelAuthRules[fieldName]
641
632
  ? ` ${fieldLevelAuthRules[fieldName]}`
@@ -806,7 +797,7 @@ const schemaPreprocessor = (schema) => {
806
797
  const authString = '';
807
798
  const authFields = {};
808
799
  const fieldLevelAuthRules = processFieldLevelAuthRules(fieldAuthApplicableFields, authFields);
809
- const { gqlFields, models } = processFields(typeName, fields, fieldLevelAuthRules);
800
+ const { gqlFields, models } = processFields(typeName, fields, authFields, fieldLevelAuthRules);
810
801
  topLevelTypes.push(...models);
811
802
  const joined = gqlFields.join('\n ');
812
803
  const model = `type ${typeName} ${authString}\n{\n ${joined}\n}`;
@@ -846,7 +837,7 @@ const schemaPreprocessor = (schema) => {
846
837
  }
847
838
  const fieldLevelAuthRules = processFieldLevelAuthRules(fields, authFields);
848
839
  validateStaticFields(fields, authFields);
849
- const { gqlFields, models } = processFields(typeName, fields, fieldLevelAuthRules, identifier, partitionKey);
840
+ const { gqlFields, models } = processFields(typeName, fields, authFields, fieldLevelAuthRules, identifier, partitionKey);
850
841
  topLevelTypes.push(...models);
851
842
  const joined = gqlFields.join('\n ');
852
843
  const model = `type ${typeName} @model ${authString}\n{\n ${joined}\n}`;
@@ -862,14 +853,7 @@ const schemaPreprocessor = (schema) => {
862
853
  throw new Error(`Model \`${typeName}\` is missing authorization rules. Add global rules to the schema or ensure every model has its own rules.`);
863
854
  }
864
855
  const fieldLevelAuthRules = processFieldLevelAuthRules(fields, authFields);
865
- const { gqlFields, models } = processFields(typeName, {
866
- // ID fields are not merged outside `mergeFieldObjects` to skip
867
- // validation, because the `identifer()` method doesn't specify or
868
- // care what the underlying field type is. We should always just defer
869
- // to whatever is explicitly defined if there's an overlap.
870
- ...idFields(typeDef),
871
- ...mergeFieldObjects(fields, authFields, implicitTimestampFields(typeDef)),
872
- }, fieldLevelAuthRules, identifier, partitionKey, transformedSecondaryIndexes);
856
+ const { gqlFields, models } = processFields(typeName, fields, authFields, fieldLevelAuthRules, identifier, partitionKey, transformedSecondaryIndexes);
873
857
  topLevelTypes.push(...models);
874
858
  const joined = gqlFields.join('\n ');
875
859
  const model = `type ${typeName} @model ${authString}\n{\n ${joined}\n}`;
@@ -1,4 +1,5 @@
1
1
  import { schema } from './ModelSchema';
2
+ import { combine } from './CombineSchema';
2
3
  import { model } from './ModelType';
3
4
  import { id, string, integer, float, boolean, date, time, datetime, timestamp, email, json, phone, url, ipAddress } from './ModelField';
4
5
  import { ref } from './RefType';
@@ -8,4 +9,4 @@ import { customType } from './CustomType';
8
9
  import { enumType } from './EnumType';
9
10
  import { query, mutation, subscription } from './CustomOperation';
10
11
  import { handler } from './Handler';
11
- export { schema, model, ref, customType, enumType as enum, query, mutation, subscription, hasOne, hasMany, belongsTo, manyToMany, allow, id, string, integer, float, boolean, date, time, datetime, timestamp, email, json, phone, url, ipAddress, handler, };
12
+ export { schema, combine, model, ref, customType, enumType as enum, query, mutation, subscription, hasOne, hasMany, belongsTo, manyToMany, allow, id, string, integer, float, boolean, date, time, datetime, timestamp, email, json, phone, url, ipAddress, handler, };
@@ -1,8 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.handler = exports.ipAddress = exports.url = exports.phone = exports.json = exports.email = exports.timestamp = exports.datetime = exports.time = exports.date = exports.boolean = exports.float = exports.integer = exports.string = exports.id = exports.allow = exports.manyToMany = exports.belongsTo = exports.hasMany = exports.hasOne = exports.subscription = exports.mutation = exports.query = exports.enum = exports.customType = exports.ref = exports.model = exports.schema = void 0;
3
+ exports.handler = exports.ipAddress = exports.url = exports.phone = exports.json = exports.email = exports.timestamp = exports.datetime = exports.time = exports.date = exports.boolean = exports.float = exports.integer = exports.string = exports.id = exports.allow = exports.manyToMany = exports.belongsTo = exports.hasMany = exports.hasOne = exports.subscription = exports.mutation = exports.query = exports.enum = exports.customType = exports.ref = exports.model = exports.combine = exports.schema = void 0;
4
4
  const ModelSchema_1 = require("./ModelSchema");
5
5
  Object.defineProperty(exports, "schema", { enumerable: true, get: function () { return ModelSchema_1.schema; } });
6
+ const CombineSchema_1 = require("./CombineSchema");
7
+ Object.defineProperty(exports, "combine", { enumerable: true, get: function () { return CombineSchema_1.combine; } });
6
8
  const ModelType_1 = require("./ModelType");
7
9
  Object.defineProperty(exports, "model", { enumerable: true, get: function () { return ModelType_1.model; } });
8
10
  const ModelField_1 = require("./ModelField");
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Create a type literal of numbers as index strings
3
+ * The resulting literal will include 0 up to (N - 1)
4
+ *
5
+ * @typeParam N - The number of literal values to include
6
+ */
7
+ export type IndexLimitUnion<N extends number, Result extends Array<unknown> = []> = Result['length'] extends N ? Result : IndexLimitUnion<N, [...Result, `${Result['length']}`]>;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Transform into the type intersection of all items in a given tuple
3
+ *
4
+ * @typeParam T - The tuple of types to spread into a type intersection
5
+ */
6
+ export type SpreadTuple<T extends readonly any[]> = T extends [infer F] ? F : T extends [infer F, ...infer R] ? F & SpreadTuple<R> : never;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1 +1,3 @@
1
1
  export { Brand, brand, getBrand } from './Brand';
2
+ export { IndexLimitUnion } from './IndexLimit';
3
+ export { SpreadTuple } from './SpreadTuple';