Skip to content

Commit

Permalink
more test fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
runspired committed Dec 17, 2024
1 parent a355111 commit 3e36f67
Show file tree
Hide file tree
Showing 20 changed files with 333 additions and 132 deletions.
2 changes: 1 addition & 1 deletion packages/build-config/src/deprecation-versions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -701,7 +701,7 @@ export const DEPRECATE_NON_EXPLICIT_POLYMORPHISM = '4.7';
* @until 6.0
* @public
*/
export const DEPRECATE_MANY_ARRAY_DUPLICATES = '5.3';
export const DEPRECATE_MANY_ARRAY_DUPLICATES = '4.12'; // '5.3';

/**
* **id: ember-data:deprecate-non-strict-types**
Expand Down
3 changes: 2 additions & 1 deletion packages/graph/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { globalIgnores } from '@warp-drive/internal-config/eslint/ignore.js';
import * as node from '@warp-drive/internal-config/eslint/node.js';
import * as typescript from '@warp-drive/internal-config/eslint/typescript.js';
import { externals } from './vite.config.mjs';

/** @type {import('eslint').Linter.FlatConfig[]} */
export default [
Expand All @@ -11,7 +12,7 @@ export default [
// browser (js/ts) ================
typescript.browser({
srcDirs: ['src'],
allowedImports: ['@ember/debug'],
allowedImports: externals,
}),

// node (module) ================
Expand Down
221 changes: 166 additions & 55 deletions packages/graph/src/-private/debug/assert-polymorphic-type.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,34 @@
/* eslint-disable @typescript-eslint/no-shadow */
import type { CacheCapabilitiesManager } from '@ember-data/store/types';
import type Mixin from '@ember/object/mixin';

import type Store from '@ember-data/store';
import type { CacheCapabilitiesManager, ModelSchema } from '@ember-data/store/types';
import { DEPRECATE_NON_EXPLICIT_POLYMORPHISM } from '@warp-drive/build-config/deprecations';
import { DEBUG } from '@warp-drive/build-config/env';
import { assert } from '@warp-drive/build-config/macros';
import type { StableRecordIdentifier } from '@warp-drive/core-types';

import { isLegacyField, isRelationshipField, temporaryConvertToLegacy, type UpgradedMeta } from '../-edge-definition';

type Model = ModelSchema;

// A pile of soft-lies to deal with mixin APIs
type ModelWithMixinApis = Model & {
isModel?: boolean;
__isMixin?: boolean;
__mixin: Mixin;
PrototypeMixin: Mixin;
detect: (mixin: Model | Mixin | ModelWithMixinApis) => boolean;
prototype: Model;
[Symbol.hasInstance](model: Model): true;
};

function assertModelSchemaIsModel(
schema: ModelSchema | Model | ModelWithMixinApis
): asserts schema is ModelWithMixinApis {
assert(`Expected Schema to be an instance of Model`, 'isModel' in schema && schema.isModel === true);
}

/*
Assert that `addedRecord` has a valid type so it can be added to the
relationship of the `record`.
Expand All @@ -27,6 +50,21 @@ let assertPolymorphicType: (
let assertInheritedSchema: (definition: UpgradedMeta, type: string) => void;

if (DEBUG) {
const checkPolymorphic = function checkPolymorphic(modelClass: ModelSchema, addedModelClass: ModelSchema) {
assertModelSchemaIsModel(modelClass);
assertModelSchemaIsModel(addedModelClass);

if (modelClass.__isMixin) {
return (
modelClass.__mixin.detect(addedModelClass.PrototypeMixin) ||
// handle native class extension e.g. `class Post extends Model.extend(Commentable) {}`
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
modelClass.__mixin.detect(Object.getPrototypeOf(addedModelClass).PrototypeMixin)
);
}
return addedModelClass.prototype instanceof modelClass || modelClass.detect(addedModelClass);
};

function validateSchema(definition: UpgradedMeta, meta: PrintConfig) {
const errors = new Map();

Expand Down Expand Up @@ -61,9 +99,9 @@ if (DEBUG) {
kind: string;
options: {
as?: string;
async: boolean;
async?: boolean;
polymorphic?: boolean;
inverse: string | null;
inverse?: string | null;
};
};
type RelationshipSchemaError = 'name' | 'type' | 'kind' | 'as' | 'async' | 'polymorphic' | 'inverse';
Expand Down Expand Up @@ -211,69 +249,142 @@ if (DEBUG) {
if (parentDefinition.inverseIsImplicit) {
return;
}
let asserted = false;

if (parentDefinition.isPolymorphic) {
let meta = store.schema.fields(addedIdentifier).get(parentDefinition.inverseKey);
assert(
`No '${parentDefinition.inverseKey}' field exists on '${
addedIdentifier.type
}'. To use this type in the polymorphic relationship '${parentDefinition.inverseType}.${
parentDefinition.key
}' the relationships schema definition for ${addedIdentifier.type} should include:${expectedSchema(
parentDefinition
)}`,
meta
);
assert(
`Expected the field ${parentDefinition.inverseKey} to be a relationship`,
meta && isRelationshipField(meta)
);
meta = isLegacyField(meta) ? meta : temporaryConvertToLegacy(meta);
const rawMeta = store.schema.fields(addedIdentifier).get(parentDefinition.inverseKey);
assert(
`You should not specify both options.as and options.inverse as null on ${addedIdentifier.type}.${parentDefinition.inverseKey}, as if there is no inverse field there is no abstract type to conform to. You may have intended for this relationship to be polymorphic, or you may have mistakenly set inverse to null.`,
!(meta.options.inverse === null && meta?.options.as?.length)
);
const errors = validateSchema(parentDefinition, meta);
assert(
`The schema for the relationship '${parentDefinition.inverseKey}' on '${
addedIdentifier.type
}' type does not correctly implement '${parentDefinition.type}' and thus cannot be assigned to the '${
parentDefinition.key
}' relationship in '${
parentIdentifier.type
}'. If using this record in this polymorphic relationship is desired, correct the errors in the schema shown below:${printSchema(
meta,
errors
)}`,
errors.size === 0
`Expected to find a relationship field schema for ${parentDefinition.inverseKey} on ${addedIdentifier.type} but none was found`,
!rawMeta || isRelationshipField(rawMeta)
);
const meta = rawMeta && (isLegacyField(rawMeta) ? rawMeta : temporaryConvertToLegacy(rawMeta));

if (DEPRECATE_NON_EXPLICIT_POLYMORPHISM) {
if (meta?.options?.as) {
asserted = true;
assert(
`No '${parentDefinition.inverseKey}' field exists on '${addedIdentifier.type}'. To use this type in the polymorphic relationship '${parentDefinition.inverseType}.${parentDefinition.key}' the relationships schema definition for ${addedIdentifier.type} should include:${expectedSchema(parentDefinition)}`,
meta
);
assert(
`You should not specify both options.as and options.inverse as null on ${addedIdentifier.type}.${parentDefinition.inverseKey}, as if there is no inverse field there is no abstract type to conform to. You may have intended for this relationship to be polymorphic, or you may have mistakenly set inverse to null.`,
!(meta.options.inverse === null && meta?.options.as?.length > 0)
);
const errors = validateSchema(parentDefinition, meta);
assert(
`The schema for the relationship '${parentDefinition.inverseKey}' on '${addedIdentifier.type}' type does not correctly implement '${parentDefinition.type}' and thus cannot be assigned to the '${parentDefinition.key}' relationship in '${parentIdentifier.type}'. If using this record in this polymorphic relationship is desired, correct the errors in the schema shown below:${printSchema(meta, errors)}`,
errors.size === 0
);
}
} else {
assert(
`No '${parentDefinition.inverseKey}' field exists on '${
addedIdentifier.type
}'. To use this type in the polymorphic relationship '${parentDefinition.inverseType}.${
parentDefinition.key
}' the relationships schema definition for ${addedIdentifier.type} should include:${expectedSchema(
parentDefinition
)}`,
meta
);
assert(
`Expected the field ${parentDefinition.inverseKey} to be a relationship`,
meta && isRelationshipField(meta)
);
assert(
`You should not specify both options.as and options.inverse as null on ${addedIdentifier.type}.${parentDefinition.inverseKey}, as if there is no inverse field there is no abstract type to conform to. You may have intended for this relationship to be polymorphic, or you may have mistakenly set inverse to null.`,
!(meta.options.inverse === null && meta?.options.as?.length)
);
const errors = validateSchema(parentDefinition, meta);
assert(
`The schema for the relationship '${parentDefinition.inverseKey}' on '${
addedIdentifier.type
}' type does not correctly implement '${parentDefinition.type}' and thus cannot be assigned to the '${
parentDefinition.key
}' relationship in '${
parentIdentifier.type
}'. If using this record in this polymorphic relationship is desired, correct the errors in the schema shown below:${printSchema(
meta,
errors
)}`,
errors.size === 0
);
}
} else if (addedIdentifier.type !== parentDefinition.type) {
// if we are not polymorphic
// then the addedIdentifier.type must be the same as the parentDefinition.type
let meta = store.schema.fields(addedIdentifier).get(parentDefinition.inverseKey);
const rawMeta = store.schema.fields(addedIdentifier).get(parentDefinition.inverseKey);
assert(
`Expected the field ${parentDefinition.inverseKey} to be a relationship`,
!meta || isRelationshipField(meta)
`Expected to find a relationship field schema for ${parentDefinition.inverseKey} on ${addedIdentifier.type} but none was found`,
!rawMeta || isRelationshipField(rawMeta)
);
meta = meta && (isLegacyField(meta) ? meta : temporaryConvertToLegacy(meta));
if (meta?.options.as === parentDefinition.type) {
// inverse is likely polymorphic but missing the polymorphic flag
let meta = store.schema.fields({ type: parentDefinition.inverseType }).get(parentDefinition.key);
assert(`Expected the field ${parentDefinition.key} to be a relationship`, meta && isRelationshipField(meta));
meta = isLegacyField(meta) ? meta : temporaryConvertToLegacy(meta);
const errors = validateSchema(definitionWithPolymorphic(inverseDefinition(parentDefinition)), meta);
assert(
`The '<${addedIdentifier.type}>.${
parentDefinition.inverseKey
}' relationship cannot be used polymorphically because '<${parentDefinition.inverseType}>.${
parentDefinition.key
} is not a polymorphic relationship. To use this relationship in a polymorphic manner, fix the following schema issues on the relationships schema for '${
parentDefinition.inverseType
}':${printSchema(meta, errors)}`
);
const meta = rawMeta && (isLegacyField(rawMeta) ? rawMeta : temporaryConvertToLegacy(rawMeta));

if (!DEPRECATE_NON_EXPLICIT_POLYMORPHISM) {
if (meta?.options.as === parentDefinition.type) {
// inverse is likely polymorphic but missing the polymorphic flag
const inverseMeta = store.schema.fields({ type: parentDefinition.inverseType }).get(parentDefinition.key);
assert(
`Expected to find a relationship field schema for ${parentDefinition.inverseKey} on ${addedIdentifier.type} but none was found`,
inverseMeta && isRelationshipField(inverseMeta)
);
const legacyInverseMeta =
inverseMeta && (isLegacyField(inverseMeta) ? inverseMeta : temporaryConvertToLegacy(inverseMeta));
const errors = validateSchema(
definitionWithPolymorphic(inverseDefinition(parentDefinition)),
legacyInverseMeta
);
assert(
`The '<${addedIdentifier.type}>.${parentDefinition.inverseKey}' relationship cannot be used polymorphically because '<${parentDefinition.inverseType}>.${parentDefinition.key} is not a polymorphic relationship. To use this relationship in a polymorphic manner, fix the following schema issues on the relationships schema for '${parentDefinition.inverseType}':${printSchema(legacyInverseMeta, errors)}`
);
} else {
assert(
`The '${addedIdentifier.type}' type does not implement '${parentDefinition.type}' and thus cannot be assigned to the '${parentDefinition.key}' relationship in '${parentIdentifier.type}'. If this relationship should be polymorphic, mark ${parentDefinition.inverseType}.${parentDefinition.key} as \`polymorphic: true\` and ${addedIdentifier.type}.${parentDefinition.inverseKey} as implementing it via \`as: '${parentDefinition.type}'\`.`
);
}
} else {
assert(
`The '${addedIdentifier.type}' type does not implement '${parentDefinition.type}' and thus cannot be assigned to the '${parentDefinition.key}' relationship in '${parentIdentifier.type}'. If this relationship should be polymorphic, mark ${parentDefinition.inverseType}.${parentDefinition.key} as \`polymorphic: true\` and ${addedIdentifier.type}.${parentDefinition.inverseKey} as implementing it via \`as: '${parentDefinition.type}'\`.`
`Expected the field ${parentDefinition.inverseKey} to be a relationship`,
!meta || isRelationshipField(meta)
);
const legacyMeta = meta && (isLegacyField(meta) ? meta : temporaryConvertToLegacy(meta));
if (legacyMeta?.options.as === parentDefinition.type) {
// inverse is likely polymorphic but missing the polymorphic flag
let meta = store.schema.fields({ type: parentDefinition.inverseType }).get(parentDefinition.key);
assert(`Expected the field ${parentDefinition.key} to be a relationship`, meta && isRelationshipField(meta));
meta = isLegacyField(meta) ? meta : temporaryConvertToLegacy(meta);
const errors = validateSchema(definitionWithPolymorphic(inverseDefinition(parentDefinition)), meta);
assert(
`The '<${addedIdentifier.type}>.${
parentDefinition.inverseKey
}' relationship cannot be used polymorphically because '<${parentDefinition.inverseType}>.${
parentDefinition.key
} is not a polymorphic relationship. To use this relationship in a polymorphic manner, fix the following schema issues on the relationships schema for '${
parentDefinition.inverseType
}':${printSchema(meta, errors)}`
);
} else {
assert(
`The '${addedIdentifier.type}' type does not implement '${parentDefinition.type}' and thus cannot be assigned to the '${parentDefinition.key}' relationship in '${parentIdentifier.type}'. If this relationship should be polymorphic, mark ${parentDefinition.inverseType}.${parentDefinition.key} as \`polymorphic: true\` and ${addedIdentifier.type}.${parentDefinition.inverseKey} as implementing it via \`as: '${parentDefinition.type}'\`.`
);
}
}
}

if (DEPRECATE_NON_EXPLICIT_POLYMORPHISM) {
if (!asserted) {
const storeService = (store as unknown as { _store: Store })._store;
const addedModelName = addedIdentifier.type;
const parentModelName = parentIdentifier.type;
const key = parentDefinition.key;
const relationshipModelName = parentDefinition.type;
const relationshipClass = storeService.modelFor(relationshipModelName);
const addedClass = storeService.modelFor(addedModelName);

const assertionMessage = `The '${addedModelName}' type does not implement '${relationshipModelName}' and thus cannot be assigned to the '${key}' relationship in '${parentModelName}'. Make it a descendant of '${relationshipModelName}' or use a mixin of the same name.`;
const isPolymorphic = checkPolymorphic(relationshipClass, addedClass);

assert(assertionMessage, isPolymorphic);
}
}
};
Expand Down
1 change: 1 addition & 0 deletions packages/graph/vite.config.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createConfig } from '@warp-drive/internal-config/vite/config.js';

export const externals = [
'@ember/object/mixin', // type only
'@ember/debug', // assert, deprecate
];

Expand Down
48 changes: 43 additions & 5 deletions packages/model/src/-private/belongs-to.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import { deprecate, warn } from '@ember/debug';
import { computed } from '@ember/object';

import {
DEPRECATE_NON_STRICT_TYPES,
DEPRECATE_RELATIONSHIPS_WITHOUT_ASYNC,
DEPRECATE_RELATIONSHIPS_WITHOUT_INVERSE,
DEPRECATE_RELATIONSHIPS_WITHOUT_TYPE,
DISABLE_6X_DEPRECATIONS,
} from '@warp-drive/build-config/deprecations';
import { DEBUG } from '@warp-drive/build-config/env';
import { assert } from '@warp-drive/build-config/macros';
Expand All @@ -13,7 +15,8 @@ import { RecordStore } from '@warp-drive/core-types/symbols';

import { lookupLegacySupport } from './legacy-relationships-support';
import type { MinimalLegacyRecord } from './model-methods';
import { isElementDescriptor, normalizeModelName } from './util';
import { isElementDescriptor } from './util';
import { dasherize, singularize } from '@ember-data/request-utils/string';
/**
@module @ember-data/model
*/
Expand All @@ -38,6 +41,36 @@ export type NoNull<T> = Exclude<T, null>;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export type RelationshipDecorator<T> = <This>(target: This, key: string, desc?: PropertyDescriptor) => void; // BelongsToDecoratorObject<getT>;

function normalizeType(type: string) {
if (DEPRECATE_RELATIONSHIPS_WITHOUT_TYPE) {
if (!type) {
return;
}
}

if (DEPRECATE_NON_STRICT_TYPES) {
const result = singularize(dasherize(type));

deprecate(
`The resource type '${type}' is not normalized. Update your application code to use '${result}' instead of '${type}'.`,
/* inline-macro-config */ DISABLE_6X_DEPRECATIONS ? true : result === type,
{
id: 'ember-data:deprecate-non-strict-types',
until: '6.0',
for: 'ember-data',
since: {
available: '4.13',
enabled: '5.3',
},
}
);

return result;
}

return type;
}

function _belongsTo<T, Async extends boolean>(
type: string,
options: RelationshipOptions<T, Async>
Expand Down Expand Up @@ -116,7 +149,7 @@ function _belongsTo<T, Async extends boolean>(
}

const meta = {
type: normalizeModelName(type),
type: normalizeType(type),
options: opts,
kind: 'belongsTo',
name: '<Unknown BelongsTo>',
Expand Down Expand Up @@ -354,11 +387,16 @@ export function belongsTo<T>(
type?: TypeFromInstance<NoNull<T>>,
options?: RelationshipOptions<T, boolean>
): RelationshipDecorator<T> {
if (DEBUG) {
if (!DEPRECATE_RELATIONSHIPS_WITHOUT_TYPE) {
assert(
`belongsTo must be invoked with a type and options. Did you mean \`@belongsTo(${type}, { async: false, inverse: null })\`?`,
`belongsTo must be invoked with a type and options. Did you mean \`@belongsTo(<type>, { async: false, inverse: null })\`?`,
!isElementDescriptor(arguments as unknown as unknown[])
);
return _belongsTo(type!, options!);
} else {
return isElementDescriptor(arguments as unknown as any[])
? // @ts-expect-error the inbound signature is strict to convince the user to use the non-deprecated signature
(_belongsTo()(...arguments) as RelationshipDecorator<T>)
: _belongsTo(type!, options!);
}
return _belongsTo(type!, options!);
}
Loading

0 comments on commit 3e36f67

Please sign in to comment.