Refactor types

This commit is contained in:
Alexandre Bodin 2023-09-20 10:07:46 +02:00
parent 22ea624841
commit e01234c008
26 changed files with 370 additions and 349 deletions

View File

@ -1,11 +1,9 @@
/* eslint-disable import/no-extraneous-dependencies */
import knex, { Knex } from 'knex';
import SqliteClient from 'knex/lib/dialects/sqlite3/index';
class LegacySqliteClient extends SqliteClient {
_driver() {
/* eslint-disable-next-line import/no-extraneous-dependencies */
return require('sqlite3');
}
}

View File

@ -1,6 +1,6 @@
import { isString } from 'lodash/fp';
import type { Database } from '..';
import type { ID } from '../typings';
import type { ID } from '../types';
type Params = Record<string, unknown>;
type Data = Record<string, unknown>;

View File

@ -26,7 +26,7 @@ import {
} from 'lodash/fp';
import { mapAsync } from '@strapi/utils';
import * as types from '../types';
import * as types from '../utils/types';
import { createField } from '../fields';
import { createQueryBuilder } from '../query';
import { createRepository } from './entity-repository';
@ -38,7 +38,7 @@ import {
isOneToAny,
hasOrderColumn,
hasInverseOrderColumn,
} from '../metadata/relations';
} from '../metadata';
import {
deletePreviousOneToAnyRelations,
deletePreviousAnyToOneRelations,
@ -52,8 +52,8 @@ import {
} from './relations/cloning/regular-relations';
import { DatabaseError } from '../errors';
import type { Database } from '..';
import type { Meta } from '../metadata/types';
import type { ID } from '../typings';
import type { Meta } from '../metadata';
import type { ID } from '../types';
type Params = {
where?: any;

View File

@ -3,7 +3,7 @@ import type { Knex } from 'knex';
import { createQueryBuilder } from '../query';
import type { Database } from '..';
import type { MorphJoinTable, Relation } from '../metadata/types';
import type { MorphJoinTable, Relation } from '../types';
type Rows = Record<string, unknown>[];

View File

@ -10,12 +10,11 @@ import {
isAnyToOne,
hasOrderColumn,
hasInverseOrderColumn,
} from '../metadata/relations';
} from '../metadata';
import { createQueryBuilder } from '../query';
import { addSchema } from '../utils/knex';
import type { Database } from '..';
import type { ID } from '../typings';
import type { Relation } from '../metadata/types';
import type { ID, Relation } from '../types';
declare module 'knex' {
namespace Knex {

View File

@ -2,7 +2,7 @@ import { castArray, maxBy } from 'lodash/fp';
import _ from 'lodash';
import { InvalidRelationError } from '../errors';
import type { ID } from '../typings';
import type { ID } from '../types';
interface Link {
id: ID;

View File

@ -1,8 +1,7 @@
import { Knex } from 'knex';
import { cleanInverseOrderColumn } from '../../regular-relations';
import type { Relation } from '../../../metadata/types';
import { ID } from '../../../typings';
import type { ID, Relation } from '../../../types';
const replaceRegularRelations = async ({
targetId,

View File

@ -11,7 +11,7 @@ import DatetimeField from './datetime';
import TimestampField from './timestamp';
import BooleanField from './boolean';
import type { Attribute } from '../metadata/types';
import type { Attribute } from '../types';
const typeToFieldMap: Record<string, typeof Field> = {
increments: Field,

View File

@ -1,4 +1,4 @@
import type { Meta, Model } from '../metadata/types';
import type { Meta } from '../metadata';
export type Action =
| 'beforeCreate'

View File

@ -1,11 +1,31 @@
import _ from 'lodash/fp';
import * as types from '../types';
import { createRelation } from './relations';
import { Metadata, Relation } from './types';
import type { Attribute, Model, Meta, ComponentLinkMeta } from './types';
import * as types from '../utils/types';
import {
createRelation,
getJoinTableName,
isPolymorphic,
isBidirectional,
isAnyToOne,
isOneToAny,
hasOrderColumn,
hasInverseOrderColumn,
isManyToAny,
} from './relations';
import { Metadata, Meta, ComponentLinkMeta } from './metadata';
import type { Attribute, Model, Relation } from '../types';
export { Metadata };
export type { Metadata, Meta };
export {
getJoinTableName,
isPolymorphic,
isBidirectional,
isAnyToOne,
isOneToAny,
hasOrderColumn,
hasInverseOrderColumn,
isManyToAny,
};
// TODO: check if there isn't an attribute with an id already
/**
@ -21,17 +41,16 @@ export const createMetadata = (models: Model[] = []): Metadata => {
}
metadata.add({
singularName: model.singularName,
uid: model.uid,
tableName: model.tableName,
...model,
attributes: {
id: {
type: 'increments',
},
...model.attributes,
},
lifecycles: model.lifecycles ?? ({} as Meta['lifecycles']),
lifecycles: model.lifecycles ?? {},
indexes: model.indexes || [],
foreignKeys: model.foreignKeys || [],
columnToAttribute: {},
});
}
@ -98,11 +117,13 @@ const hasComponentsOrDz = (model: Meta): model is ComponentLinkMeta => {
// NOTE: we might just move the compo logic outside this layer too at some point
const createCompoLinkModelMeta = (baseModelMeta: Meta): Meta => {
const name = `${baseModelMeta.tableName}_components`;
return {
// TODO: make sure there can't be any conflicts with a prefix
// singularName: 'compo',
uid: `${baseModelMeta.tableName}_components`,
tableName: `${baseModelMeta.tableName}_components`,
singularName: name,
uid: name,
tableName: name,
attributes: {
id: {
type: 'increments',
@ -161,6 +182,7 @@ const createCompoLinkModelMeta = (baseModelMeta: Meta): Meta => {
onDelete: 'CASCADE',
},
],
lifecycles: {},
columnToAttribute: {},
};
};

View File

@ -0,0 +1,44 @@
import type { Model } from '../types';
import type { ForeignKey, Index } from '../schema/types';
import type { Action, SubscriberFn } from '../lifecycles';
export interface ComponentLinkMeta extends Meta {
componentLink: Meta;
}
export interface Meta extends Model {
columnToAttribute: Record<string, string>;
componentLink?: Meta;
indexes: Index[];
foreignKeys: ForeignKey[];
lifecycles: Partial<Record<Action, SubscriberFn>>;
}
export class Metadata extends Map<string, Meta> {
get(key: string): Meta {
if (!super.has(key)) {
throw new Error(`Metadata for "${key}" not found`);
}
return super.get(key) as Meta;
}
add(meta: Meta) {
return this.set(meta.uid, meta);
}
/**
* Validate the DB metadata, throwing an error if a duplicate DB table name is detected
*/
validate() {
const seenTables = new Map();
for (const meta of this.values()) {
if (seenTables.get(meta.tableName)) {
throw new Error(
`DB table "${meta.tableName}" already exists. Change the collectionName of the related content type.`
);
}
seenTables.set(meta.tableName, true);
}
}
}

View File

@ -1,6 +1,7 @@
import _ from 'lodash/fp';
import type { Meta, RelationalAttribute, Relation, Metadata, MorphJoinTable } from './types';
import type { Meta, Metadata } from './metadata';
import type { RelationalAttribute, Relation, MorphJoinTable } from '../types';
interface JoinColumnOptions {
attribute: (Relation.OneToOne | Relation.ManyToOne) & Relation.Owner;
@ -245,6 +246,7 @@ const createMorphToMany = (
const typeColumnName = `${morphColumnName}_type`;
metadata.add({
singularName: joinTableName,
uid: joinTableName,
tableName: joinTableName,
attributes: {
@ -299,6 +301,7 @@ const createMorphToMany = (
onDelete: 'CASCADE',
},
],
lifecycles: {},
columnToAttribute: {},
});
@ -429,6 +432,7 @@ const createJoinTable = (
}
const metadataSchema: Meta = {
singularName: joinTableName,
uid: joinTableName,
tableName: joinTableName,
attributes: {
@ -480,6 +484,7 @@ const createJoinTable = (
onDelete: 'CASCADE',
},
],
lifecycles: {},
columnToAttribute: {},
};

View File

@ -1,269 +0,0 @@
import type { Action, SubscriberFn } from '../lifecycles';
import type { ForeignKey, Index } from '../schema/types';
export interface ColumnInfo {
unsigned?: boolean;
defaultTo?: unknown;
}
export type Attribute = ScalarAttribute | RelationalAttribute;
export type RelationalAttribute =
| Relation.OneToOne
| Relation.OneToMany
| Relation.ManyToOne
| Relation.ManyToMany
| Relation.MorphMany
| Relation.MorphOne
| Relation.MorphToOne
| Relation.MorphToMany;
export interface BasAttribute {
type: string;
columnName?: string;
default?: any;
column?: ColumnInfo;
required?: boolean;
unique?: boolean;
component?: string;
repeatable?: boolean;
columnType?: {
type: string;
args: unknown[];
};
searchable?: boolean;
}
export interface ScalarAttribute extends BasAttribute {
type:
| 'increments'
| 'password'
| 'email'
| 'string'
| 'enumeration'
| 'uid'
| 'richtext'
| 'text'
| 'json'
| 'integer'
| 'biginteger'
| 'float'
| 'decimal'
| 'boolean'
| 'date'
| 'time'
| 'datetime'
| 'timestamp';
}
export interface JoinColumn {
name: string;
referencedColumn: string;
referencedTable?: string;
}
export interface BaseJoinTable {
name: string;
joinColumn: JoinColumn;
orderBy?: Record<string, 'asc' | 'desc'>;
on?: Record<string, unknown>;
pivotColumns: string[];
inverseJoinColumn: {
name: string;
referencedColumn: string;
};
}
export interface JoinTable extends BaseJoinTable {
orderColumnName?: string;
inverseOrderColumnName?: string;
}
export interface OrderedJoinTable extends BaseJoinTable {
orderColumnName: string;
inverseOrderColumnName: string;
}
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace Relation {
export type Owner = {
inversedBy: string;
};
export type WithTarget = {
target: string;
};
export type Bidirectional = OneToOne | OneToMany | ManyToOne | ManyToMany;
type BaseBidirectional = {
type: 'relation';
relation: 'oneToOne' | 'oneToMany' | 'manyToOne' | 'manyToMany';
target: string;
inversedBy?: string;
mappedBy?: string;
joinTable: BidirectionalAttributeJoinTable;
};
export type OneToOne = BaseBidirectional & {
relation: 'oneToOne';
useJoinTable?: boolean;
joinTable?: JoinTable;
joinColumn?: JoinColumn;
owner?: boolean;
};
export type OneToMany = BaseBidirectional & {
relation: 'oneToMany';
joinTable: OrderedJoinTable;
joinColumn?: JoinColumn;
owner?: boolean;
};
export type ManyToOne = BaseBidirectional & {
relation: 'manyToOne';
useJoinTable?: boolean;
joinTable?: JoinTable;
joinColumn?: JoinColumn;
owner?: boolean;
};
export type ManyToMany = BaseBidirectional & {
relation: 'manyToMany';
joinTable: OrderedJoinTable;
};
export type Morph = MorphMany | MorphOne | MorphToOne | MorphToMany;
export type MorphMany = {
type: 'relation';
relation: 'morphMany';
target: string;
morphBy: string;
joinTable: MorphJoinTable;
};
export type MorphOne = {
type: 'relation';
relation: 'morphOne';
target: string;
morphBy: string;
};
export type MorphToOne = {
type: 'relation';
relation: 'morphToOne';
owner?: boolean;
morphColumn: MorphColumn;
};
export type MorphToMany = {
type: 'relation';
relation: 'morphToMany';
joinTable: MorphJoinTable;
};
}
export interface BidirectionalAttributeJoinTable extends JoinTable {
orderColumnName: string;
inverseOrderColumnName: string;
}
export interface MorphColumn {
typeField?: string;
typeColumn: {
name: string;
};
idColumn: {
name: string;
referencedColumn: string;
};
}
export interface MorphJoinTable {
name: string;
joinColumn: JoinColumn;
orderBy?: Record<string, 'asc' | 'desc'>;
on?: Record<string, unknown>;
pivotColumns: string[];
morphColumn: MorphColumn;
}
export interface BaseRelationalAttribute {
type: 'relation';
target: string;
useJoinTable?: boolean;
joinTable?: JoinTable | MorphJoinTable;
morphBy?: string;
inversedBy?: string;
owner?: boolean;
morphColumn?: MorphColumn;
joinColumn?: JoinColumn;
// TODO: remove this
component?: string;
}
export interface MorphRelationalAttribute extends BaseRelationalAttribute {
relation: 'morphMany' | 'morphOne' | 'morphToOne' | 'morphToMany';
morphColumn: MorphColumn;
morphBy: string;
joinTable: MorphJoinTable;
target: string;
}
export interface Meta {
singularName?: string;
uid: string;
tableName: string;
attributes: Record<string, Attribute>;
indexes: Index[];
foreignKeys?: ForeignKey[];
lifecycles?: Record<Action, SubscriberFn>;
columnToAttribute: Record<string, string>;
componentLink?: Meta;
}
export interface ComponentLinkMeta extends Meta {
componentLink: Meta;
}
export interface Model {
uid: string;
tableName: string;
singularName: string;
attributes: Record<string, Attribute>;
lifecycles?: Record<Action, SubscriberFn>;
indexes: Index[];
componentLink?: Meta;
columnToAttribute?: Record<string, string>;
foreignKeys?: Record<string, unknown>[];
}
export class Metadata extends Map<string, Meta> {
get(key: string): Meta {
if (!super.has(key)) {
throw new Error(`Metadata for "${key}" not found`);
}
return super.get(key) as Meta;
}
add(meta: Meta) {
return this.set(meta.uid, meta);
}
/**
* Validate the DB metadata, throwing an error if a duplicate DB table name is detected
*/
validate() {
const seenTables = new Map();
for (const meta of this.values()) {
if (seenTables.get(meta.tableName)) {
throw new Error(
`DB table "${meta.tableName}" already exists. Change the collectionName of the related content type.`
);
}
seenTables.set(meta.tableName, true);
}
}
}

View File

@ -1,6 +1,6 @@
import _ from 'lodash/fp';
import * as types from '../../types';
import * as types from '../../utils/types';
import { createJoin } from './join';
import { toColumnName } from './transform';

View File

@ -1,10 +1,10 @@
import _ from 'lodash/fp';
import { Rec, fromRow } from '../transform';
import { fromRow } from '../transform';
import type { QueryBuilder } from '../../query-builder';
import type { Database } from '../../..';
import type { Meta, RelationalAttribute, Relation } from '../../../metadata/types';
import { ID } from '../../../typings';
import type { Meta } from '../../../metadata';
import { ID, RelationalAttribute, Relation } from '../../../types';
type Context = {
db: Database;

View File

@ -1,7 +1,7 @@
import _ from 'lodash/fp';
import * as types from '../../../types';
import type { Meta } from '../../../metadata/types';
import * as types from '../../../utils/types';
import type { Meta } from '../../../metadata';
import type { QueryBuilder } from '../../query-builder';
import type { Database } from '../../..';

View File

@ -1,7 +1,7 @@
import _ from 'lodash/fp';
import type { Knex } from 'knex';
import * as types from '../../types';
import * as types from '../../utils/types';
import { toColumnName } from './transform';
import type { Ctx } from '../types';

View File

@ -6,7 +6,7 @@ import type { Database } from '../../..';
import { applyPopulate } from '../populate';
import { fromRow } from '../transform';
import { Meta } from '../../../metadata/types';
import { Meta } from '../../../metadata';
const knexQueryDone = Symbol('knexQueryDone');
const knexPerformingQuery = Symbol('knexPerformingQuery');

View File

@ -1,9 +1,9 @@
import _ from 'lodash/fp';
import * as types from '../../types';
import * as types from '../../utils/types';
import { createField } from '../../fields';
import type { Meta } from '../../metadata/types';
import type { Meta } from '../../metadata';
type Row = Record<string, unknown> | null;
export type Rec = Record<string, unknown> | null;

View File

@ -3,14 +3,14 @@ import { isArray, castArray, keys, isPlainObject } from 'lodash/fp';
import type { Knex } from 'knex';
import { isOperatorOfType } from '@strapi/utils';
import * as types from '../../types';
import * as types from '../../utils/types';
import { createField } from '../../fields';
import { createJoin } from './join';
import { toColumnName } from './transform';
import { isKnexQuery } from '../../utils/knex';
import type { Attribute } from '../../metadata/types';
import type { Ctx } from '../types';
import type { Attribute } from '../../types';
const isObj = (value: unknown): value is Record<string, unknown> => isPlainObject(value);

View File

@ -1,7 +1,8 @@
import * as types from '../types';
import * as types from '../utils/types';
import type { Metadata, Meta, Attribute } from '../metadata/types';
import type { Metadata, Meta } from '../metadata';
import type { Column, Schema, Table } from './types';
import type { Attribute } from '../types';
const createColumn = (name: string, attribute: Attribute): Column => {
const { type, args = [], ...opts } = getColumnType(attribute);

View File

@ -1,36 +1,223 @@
import type { Attribute, ScalarAttribute, RelationalAttribute } from '../metadata/types';
import type { Action, SubscriberFn } from '../lifecycles';
import type { ForeignKey, Index } from '../schema/types';
const SCALAR_TYPES = [
'increments',
'password',
'email',
'string',
'uid',
'richtext',
'text',
'json',
'enumeration',
'integer',
'biginteger',
'float',
'decimal',
'date',
'time',
'datetime',
'timestamp',
'boolean',
];
export type ID = string | number;
export interface ColumnInfo {
unsigned?: boolean;
defaultTo?: unknown;
}
const STRING_TYPES = ['string', 'text', 'uid', 'email', 'enumeration', 'richtext'];
const NUMBER_TYPES = ['biginteger', 'integer', 'decimal', 'float'];
export type Attribute = ScalarAttribute | RelationalAttribute;
export const isString = (type: string) => STRING_TYPES.includes(type);
export const isNumber = (type: string) => NUMBER_TYPES.includes(type);
export const isScalar = (type: string) => SCALAR_TYPES.includes(type);
export const isComponent = (type: string) => type === 'component';
export const isDynamicZone = (type: string) => type === 'dynamiczone';
export const isRelation = (type: string) => type === 'relation';
export const isScalarAttribute = (attribute: Attribute): attribute is ScalarAttribute =>
isScalar(attribute.type);
export const isRelationalAttribute = (attribute: Attribute): attribute is RelationalAttribute =>
isRelation(attribute.type);
export type RelationalAttribute =
| Relation.OneToOne
| Relation.OneToMany
| Relation.ManyToOne
| Relation.ManyToMany
| Relation.MorphMany
| Relation.MorphOne
| Relation.MorphToOne
| Relation.MorphToMany;
export interface BasAttribute {
type: string;
columnName?: string;
default?: any;
column?: ColumnInfo;
required?: boolean;
unique?: boolean;
component?: string;
repeatable?: boolean;
columnType?: {
type: string;
args: unknown[];
};
searchable?: boolean;
}
export interface ScalarAttribute extends BasAttribute {
type:
| 'increments'
| 'password'
| 'email'
| 'string'
| 'enumeration'
| 'uid'
| 'richtext'
| 'text'
| 'json'
| 'integer'
| 'biginteger'
| 'float'
| 'decimal'
| 'boolean'
| 'date'
| 'time'
| 'datetime'
| 'timestamp';
}
export interface JoinColumn {
name: string;
referencedColumn: string;
referencedTable?: string;
}
export interface BaseJoinTable {
name: string;
joinColumn: JoinColumn;
orderBy?: Record<string, 'asc' | 'desc'>;
on?: Record<string, unknown>;
pivotColumns: string[];
inverseJoinColumn: {
name: string;
referencedColumn: string;
};
}
export interface JoinTable extends BaseJoinTable {
orderColumnName?: string;
inverseOrderColumnName?: string;
}
export interface OrderedJoinTable extends BaseJoinTable {
orderColumnName: string;
inverseOrderColumnName: string;
}
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace Relation {
export type Owner = {
inversedBy: string;
};
export type WithTarget = {
target: string;
};
export type Bidirectional = OneToOne | OneToMany | ManyToOne | ManyToMany;
type BaseBidirectional = {
type: 'relation';
relation: 'oneToOne' | 'oneToMany' | 'manyToOne' | 'manyToMany';
target: string;
inversedBy?: string;
mappedBy?: string;
joinTable: BidirectionalAttributeJoinTable;
};
export type OneToOne = BaseBidirectional & {
relation: 'oneToOne';
useJoinTable?: boolean;
joinTable?: JoinTable;
joinColumn?: JoinColumn;
owner?: boolean;
};
export type OneToMany = BaseBidirectional & {
relation: 'oneToMany';
joinTable: OrderedJoinTable;
joinColumn?: JoinColumn;
owner?: boolean;
};
export type ManyToOne = BaseBidirectional & {
relation: 'manyToOne';
useJoinTable?: boolean;
joinTable?: JoinTable;
joinColumn?: JoinColumn;
owner?: boolean;
};
export type ManyToMany = BaseBidirectional & {
relation: 'manyToMany';
joinTable: OrderedJoinTable;
};
export type Morph = MorphMany | MorphOne | MorphToOne | MorphToMany;
export type MorphMany = {
type: 'relation';
relation: 'morphMany';
target: string;
morphBy: string;
joinTable: MorphJoinTable;
};
export type MorphOne = {
type: 'relation';
relation: 'morphOne';
target: string;
morphBy: string;
};
export type MorphToOne = {
type: 'relation';
relation: 'morphToOne';
owner?: boolean;
morphColumn: MorphColumn;
};
export type MorphToMany = {
type: 'relation';
relation: 'morphToMany';
joinTable: MorphJoinTable;
};
}
export interface BidirectionalAttributeJoinTable extends JoinTable {
orderColumnName: string;
inverseOrderColumnName: string;
}
export interface MorphColumn {
typeField?: string;
typeColumn: {
name: string;
};
idColumn: {
name: string;
referencedColumn: string;
};
}
export interface MorphJoinTable {
name: string;
joinColumn: JoinColumn;
orderBy?: Record<string, 'asc' | 'desc'>;
on?: Record<string, unknown>;
pivotColumns: string[];
morphColumn: MorphColumn;
}
export interface BaseRelationalAttribute {
type: 'relation';
target: string;
useJoinTable?: boolean;
joinTable?: JoinTable | MorphJoinTable;
morphBy?: string;
inversedBy?: string;
owner?: boolean;
morphColumn?: MorphColumn;
joinColumn?: JoinColumn;
// TODO: remove this
component?: string;
}
export interface MorphRelationalAttribute extends BaseRelationalAttribute {
relation: 'morphMany' | 'morphOne' | 'morphToOne' | 'morphToMany';
morphColumn: MorphColumn;
morphBy: string;
joinTable: MorphJoinTable;
target: string;
}
export interface Model {
uid: string;
tableName: string;
singularName: string;
attributes: Record<string, Attribute>;
indexes?: Index[];
foreignKeys?: ForeignKey[];
lifecycles?: Partial<Record<Action, SubscriberFn>>;
}

View File

@ -1 +0,0 @@
export type ID = string | number;

View File

@ -0,0 +1,36 @@
import type { Attribute, ScalarAttribute, RelationalAttribute } from '../types';
const SCALAR_TYPES = [
'increments',
'password',
'email',
'string',
'uid',
'richtext',
'text',
'json',
'enumeration',
'integer',
'biginteger',
'float',
'decimal',
'date',
'time',
'datetime',
'timestamp',
'boolean',
];
const STRING_TYPES = ['string', 'text', 'uid', 'email', 'enumeration', 'richtext'];
const NUMBER_TYPES = ['biginteger', 'integer', 'decimal', 'float'];
export const isString = (type: string) => STRING_TYPES.includes(type);
export const isNumber = (type: string) => NUMBER_TYPES.includes(type);
export const isScalar = (type: string) => SCALAR_TYPES.includes(type);
export const isComponent = (type: string) => type === 'component';
export const isDynamicZone = (type: string) => type === 'dynamiczone';
export const isRelation = (type: string) => type === 'relation';
export const isScalarAttribute = (attribute: Attribute): attribute is ScalarAttribute =>
isScalar(attribute.type);
export const isRelationalAttribute = (attribute: Attribute): attribute is RelationalAttribute =>
isRelation(attribute.type);

View File

@ -1,7 +1,7 @@
import { getJoinTableName } from '../../metadata/relations';
import { getJoinTableName } from '../../metadata';
import type { Database } from '../..';
import type { Relation } from '../../metadata/types';
import type { Relation } from '../../types';
type Link = {
relation: Relation.Bidirectional & { inversedBy: string };