Skip to content

Commit b1a477b

Browse files
runspiredMehulKChaudhari
authored andcommitted
feat: schema type utils (emberjs#9757)
* minor type improvements * chore: fixup types * add api docs * nice things * account for schema presence * cleanup test
1 parent a08e3f0 commit b1a477b

File tree

20 files changed

+344
-172
lines changed

20 files changed

+344
-172
lines changed

packages/core-types/src/index.ts

+7
Original file line numberDiff line numberDiff line change
@@ -1 +1,8 @@
1+
/**
2+
* This package provides core types, type-utilities, symbols
3+
* and constants used across the WarpDrive ecosystem.
4+
*
5+
* @module @warp-drive/core-types
6+
* @main @warp-drive/core-types
7+
*/
18
export type { StableRecordIdentifier } from './identifier';

packages/core-types/src/schema/fields.ts

+149-6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
/**
2+
* @module @warp-drive/core-types
3+
*/
14
import type { ObjectValue, PrimitiveValue } from '../json/raw';
25

36
/**
@@ -919,25 +922,51 @@ export type FieldSchema =
919922
| LegacyBelongsToField
920923
| LegacyHasManyField;
921924

922-
export type ResourceSchema = {
925+
export type ObjectFieldSchema =
926+
| GenericField
927+
| AliasField
928+
| LocalField
929+
| ObjectField
930+
| SchemaObjectField
931+
| ArrayField
932+
| SchemaArrayField
933+
| DerivedField;
934+
935+
/**
936+
* Represents a schema for a primary resource.
937+
*
938+
* Primary resources are objects with a unique identity of their
939+
* own which may allow them to appear in relationships, or is multiple
940+
* response document.
941+
*
942+
* @typedoc
943+
*/
944+
export interface ResourceSchema {
923945
legacy?: boolean;
946+
924947
/**
925948
* For primary resources, this should be an IdentityField
926949
*
927950
* for schema-objects, this should be either a HashField or null
928951
*
929952
* @typedoc
930953
*/
931-
identity: IdentityField | HashField | null;
954+
identity: IdentityField;
955+
932956
/**
933957
* The name of the schema
934958
*
935959
* For cacheable resources, this should be the
936960
* primary resource type.
937961
*
938962
* For object schemas, this should be the name
939-
* of the object schema. object schemas should
940-
* follow the following guidelines for naming
963+
* of the object schema.
964+
*
965+
* The names of object and resource schemas share
966+
* a single namespace and must not conflict.
967+
*
968+
* We recommend a naming convention for object schemas
969+
* such as below for ensuring uniqueness:
941970
*
942971
* - for globally shared objects: The pattern `$field:${KlassName}` e.g. `$field:AddressObject`
943972
* - for resource-specific objects: The pattern `$${ResourceKlassName}:$field:${KlassName}` e.g. `$User:$field:ReusableAddress`
@@ -946,9 +975,123 @@ export type ResourceSchema = {
946975
* @typedoc
947976
*/
948977
type: string;
949-
traits?: string[];
978+
979+
/**
980+
* The fields that make up the shape of the resource
981+
*
982+
* @typedoc
983+
*/
950984
fields: FieldSchema[];
951-
};
985+
986+
/**
987+
* A list of traits that this resource implements. The fields for these
988+
* traits should still be defined in the fields array.
989+
*
990+
* Each trait should be a string that matches the `type` of another
991+
* resource schema. The trait can be abstract and reference a resource
992+
* type that is never defined as a schema.
993+
*
994+
* @typedoc
995+
*/
996+
traits?: string[];
997+
}
998+
999+
/**
1000+
* Represents a schema for an object that is not
1001+
* a primary resource (has no unique identity of its own).
1002+
*
1003+
* ObjectSchemas may not currently contain relationships.
1004+
*
1005+
* @typedoc
1006+
*/
1007+
export interface ObjectSchema {
1008+
/**
1009+
* Either a HashField from which to calculate an identity or null
1010+
*
1011+
* In the case of `null`, the object's identity will be based
1012+
* on the referential identity of the object in the cache itself
1013+
* when an identity is needed.
1014+
*
1015+
* @typedoc
1016+
*/
1017+
identity: HashField | null;
1018+
1019+
/**
1020+
* The name of the schema
1021+
*
1022+
* The names of object and resource schemas share
1023+
* a single namespace and must not conflict.
1024+
*
1025+
* We recommend a naming convention for object schemas
1026+
* such as below for ensuring uniqueness:
1027+
*
1028+
* - for globally shared objects: The pattern `$field:${KlassName}` e.g. `$field:AddressObject`
1029+
* - for resource-specific objects: The pattern `$${ResourceKlassName}:$field:${KlassName}` e.g. `$User:$field:ReusableAddress`
1030+
* - for inline objects: The pattern `$${ResourceKlassName}.${fieldPath}:$field:anonymous` e.g. `$User.shippingAddress:$field:anonymous`
1031+
*
1032+
* @typedoc
1033+
*/
1034+
type: string;
1035+
1036+
/**
1037+
* The fields that make up the shape of the object
1038+
*
1039+
* @typedoc
1040+
*/
1041+
fields: ObjectFieldSchema[];
1042+
}
1043+
1044+
/**
1045+
* A no-op type utility that enables type-checking resource schema
1046+
* definitions.
1047+
*
1048+
* Will return the passed in schema.
1049+
*
1050+
* This will not validate relationship inverses or related types,
1051+
* as doing so would require a full schema graph to be passed in
1052+
* and no cycles in the graph to be present.
1053+
*
1054+
* @method resourceSchema
1055+
* @static
1056+
* @for @warp-drive/core-types
1057+
* @param {ResourceSchema} schema
1058+
* @returns {ResourceSchema} the passed in schema
1059+
* @public
1060+
*/
1061+
export function resourceSchema<T extends ResourceSchema>(schema: T): T {
1062+
return schema;
1063+
}
1064+
1065+
/**
1066+
* A no-op type utility that enables type-checking object schema
1067+
* definitions.
1068+
*
1069+
* Will return the passed in schema.
1070+
*
1071+
* @method objectSchema
1072+
* @static
1073+
* @for @warp-drive/core-types
1074+
* @param {ObjectSchema} schema
1075+
* @returns {ObjectSchema} the passed in schema
1076+
* @public
1077+
*/
1078+
export function objectSchema<T extends ObjectSchema>(schema: T): T {
1079+
return schema;
1080+
}
1081+
1082+
/**
1083+
* A type utility to narrow a schema to a ResourceSchema
1084+
*
1085+
* @method isResourceSchema
1086+
* @static
1087+
* @for @warp-drive/core-types
1088+
* @param schema
1089+
* @returns {boolean}
1090+
* @public
1091+
*/
1092+
export function isResourceSchema(schema: ResourceSchema | ObjectSchema): schema is ResourceSchema {
1093+
return schema?.identity?.kind === '@id';
1094+
}
9521095

9531096
export type LegacyFieldSchema = LegacyAttributeField | LegacyBelongsToField | LegacyHasManyField;
9541097
export type LegacyRelationshipSchema = LegacyBelongsToField | LegacyHasManyField;

packages/model/src/-private/schema-provider.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import type {
1717
LegacyFieldSchema,
1818
LegacyRelationshipSchema,
1919
ObjectField,
20+
ObjectSchema,
2021
ResourceSchema,
2122
} from '@warp-drive/core-types/schema/fields';
2223

@@ -69,7 +70,7 @@ export class ModelSchemaProvider implements SchemaService {
6970
hashFn(field: HashField | { type: string }): HashFn {
7071
assert(`hashFn is not available with @ember-data/model's SchemaService`);
7172
}
72-
resource(resource: StableRecordIdentifier | { type: string }): ResourceSchema {
73+
resource(resource: StableRecordIdentifier | { type: string }): ResourceSchema | ObjectSchema {
7374
const type = normalizeModelName(resource.type);
7475

7576
if (!this._schemas.has(type)) {
@@ -78,10 +79,10 @@ export class ModelSchemaProvider implements SchemaService {
7879

7980
return this._schemas.get(type)!.schema;
8081
}
81-
registerResources(schemas: ResourceSchema[]): void {
82+
registerResources(schemas: Array<ResourceSchema | ObjectSchema>): void {
8283
assert(`registerResources is not available with @ember-data/model's SchemaService`);
8384
}
84-
registerResource(schema: ResourceSchema): void {
85+
registerResource(schema: ResourceSchema | ObjectSchema): void {
8586
assert(`registerResource is not available with @ember-data/model's SchemaService`);
8687
}
8788
registerTransformation(transform: Transformation): void {

packages/model/src/migration-support.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type {
1515
GenericField,
1616
HashField,
1717
ObjectField,
18+
ObjectSchema,
1819
ResourceSchema,
1920
} from '@warp-drive/core-types/schema/fields';
2021
import { Type } from '@warp-drive/core-types/symbols';
@@ -227,16 +228,16 @@ export class DelegatingSchemaService implements SchemaService {
227228
derivation(field: DerivedField | { type: string }): Derivation {
228229
return this._preferred.derivation(field);
229230
}
230-
resource(resource: StableRecordIdentifier | { type: string }): ResourceSchema {
231+
resource(resource: StableRecordIdentifier | { type: string }): ResourceSchema | ObjectSchema {
231232
if (this._preferred.hasResource(resource)) {
232233
return this._preferred.resource(resource);
233234
}
234235
return this._secondary.resource(resource);
235236
}
236-
registerResources(schemas: ResourceSchema[]): void {
237+
registerResources(schemas: Array<ResourceSchema | ObjectSchema>): void {
237238
this._preferred.registerResources(schemas);
238239
}
239-
registerResource(schema: ResourceSchema): void {
240+
registerResource(schema: ResourceSchema | ObjectSchema): void {
240241
this._preferred.registerResource(schema);
241242
}
242243
registerTransformation(transform: Transformation): void {

packages/request/tsconfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"include": ["src/**/*"],
33
"compilerOptions": {
4-
"target": "ES2024",
4+
"target": "ESNext",
55
"module": "ESNext",
66
"moduleResolution": "bundler",
77
"skipLibCheck": true,

packages/schema-record/src/-private/hooks.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type Store from '@ember-data/store';
22
import { assert } from '@warp-drive/build-config/macros';
33
import type { StableRecordIdentifier } from '@warp-drive/core-types';
4+
import { isResourceSchema } from '@warp-drive/core-types/schema/fields';
45

56
import { SchemaRecord } from './record';
67
import type { SchemaService } from './schema';
@@ -12,7 +13,9 @@ export function instantiateRecord(
1213
createArgs?: Record<string, unknown>
1314
): SchemaRecord {
1415
const schema = store.schema as unknown as SchemaService;
15-
const isLegacy = schema.resource(identifier)?.legacy ?? false;
16+
const resourceSchema = schema.resource(identifier);
17+
assert(`Expected a resource schema`, isResourceSchema(resourceSchema));
18+
const isLegacy = resourceSchema?.legacy ?? false;
1619
const isEditable = isLegacy || store.cache.isNew(identifier);
1720
const record = new SchemaRecord(store, identifier, {
1821
[Editable]: isEditable,

packages/schema-record/src/-private/schema.ts

+19-17
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,18 @@ import type { StableRecordIdentifier } from '@warp-drive/core-types';
1111
import { getOrSetGlobal } from '@warp-drive/core-types/-private';
1212
import type { ObjectValue, Value } from '@warp-drive/core-types/json/raw';
1313
import type { Derivation, HashFn } from '@warp-drive/core-types/schema/concepts';
14-
import type {
15-
ArrayField,
16-
DerivedField,
17-
FieldSchema,
18-
GenericField,
19-
HashField,
20-
LegacyAttributeField,
21-
LegacyRelationshipSchema,
22-
ObjectField,
23-
ResourceSchema,
14+
import {
15+
type ArrayField,
16+
type DerivedField,
17+
type FieldSchema,
18+
type GenericField,
19+
type HashField,
20+
isResourceSchema,
21+
type LegacyAttributeField,
22+
type LegacyRelationshipSchema,
23+
type ObjectField,
24+
type ObjectSchema,
25+
type ResourceSchema,
2426
} from '@warp-drive/core-types/schema/fields';
2527
import { Type } from '@warp-drive/core-types/symbols';
2628
import type { WithPartial } from '@warp-drive/core-types/utils';
@@ -91,13 +93,13 @@ export function registerDerivations(schema: SchemaServiceInterface) {
9193
schema.registerDerivation(_constructor);
9294
}
9395

94-
type InternalSchema = {
95-
original: ResourceSchema;
96+
interface InternalSchema {
97+
original: ResourceSchema | ObjectSchema;
9698
traits: Set<string>;
9799
fields: Map<string, FieldSchema>;
98100
attributes: Record<string, LegacyAttributeField>;
99101
relationships: Record<string, LegacyRelationshipSchema>;
100-
};
102+
}
101103

102104
export type Transformation<T extends Value = Value, PT = unknown> = {
103105
serialize(value: PT, options: Record<string, unknown> | null, record: SchemaRecord): T;
@@ -209,16 +211,16 @@ export class SchemaService implements SchemaServiceInterface {
209211
);
210212
return this._hashFns.get(field.type)!;
211213
}
212-
resource(resource: StableRecordIdentifier | { type: string }): ResourceSchema {
214+
resource(resource: StableRecordIdentifier | { type: string }): ResourceSchema | ObjectSchema {
213215
assert(`No resource registered with name '${resource.type}'`, this._schemas.has(resource.type));
214216
return this._schemas.get(resource.type)!.original;
215217
}
216-
registerResources(schemas: ResourceSchema[]): void {
218+
registerResources(schemas: Array<ResourceSchema | ObjectSchema>): void {
217219
schemas.forEach((schema) => {
218220
this.registerResource(schema);
219221
});
220222
}
221-
registerResource(schema: ResourceSchema): void {
223+
registerResource(schema: ResourceSchema | ObjectSchema): void {
222224
const fields = new Map<string, FieldSchema>();
223225
const relationships: Record<string, LegacyRelationshipSchema> = {};
224226
const attributes: Record<string, LegacyAttributeField> = {};
@@ -237,7 +239,7 @@ export class SchemaService implements SchemaServiceInterface {
237239
}
238240
});
239241

240-
const traits = new Set<string>(schema.traits);
242+
const traits = new Set<string>(isResourceSchema(schema) ? schema.traits : []);
241243
traits.forEach((trait) => {
242244
this._traits.add(trait);
243245
});

packages/store/src/-types/q/schema-service.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import type {
1616
LegacyBelongsToField,
1717
LegacyHasManyField,
1818
ObjectField,
19+
ObjectSchema,
1920
ResourceSchema,
2021
} from '@warp-drive/core-types/schema/fields';
2122

@@ -171,7 +172,7 @@ export interface SchemaService {
171172
* @param {StableRecordIdentifier|{ type: string }} resource
172173
* @return {ResourceSchema}
173174
*/
174-
resource(resource: { type: string } | StableRecordIdentifier): ResourceSchema;
175+
resource(resource: { type: string } | StableRecordIdentifier): ResourceSchema | ObjectSchema;
175176

176177
/**
177178
* Enables registration of multiple ResourceSchemas at once.
@@ -184,7 +185,7 @@ export interface SchemaService {
184185
* @public
185186
* @param schemas
186187
*/
187-
registerResources(schemas: ResourceSchema[]): void;
188+
registerResources(schemas: Array<ResourceSchema | ObjectSchema>): void;
188189

189190
/**
190191
* Enables registration of a single ResourceSchema.
@@ -197,7 +198,7 @@ export interface SchemaService {
197198
* @public
198199
* @param {ResourceSchema} schema
199200
*/
200-
registerResource(schema: ResourceSchema): void;
201+
registerResource(schema: ResourceSchema | ObjectSchema): void;
201202

202203
/**
203204
* Enables registration of a transformation.

0 commit comments

Comments
 (0)