Skip to content

Commit

Permalink
Datasource types (#6300)
Browse files Browse the repository at this point in the history
  • Loading branch information
mohamedsalem401 authored Nov 6, 2024
1 parent 4e97d63 commit ff80932
Show file tree
Hide file tree
Showing 18 changed files with 117 additions and 86 deletions.
7 changes: 4 additions & 3 deletions packages/core/src/data_sources/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import { get, stringToPath } from '../utils/mixins';
import DataRecord from './model/DataRecord';
import DataSource from './model/DataSource';
import DataSources from './model/DataSources';
import { DataSourcesEvents, DataSourceProps } from './types';
import { DataSourcesEvents, DataSourceProps, DataRecordProps } from './types';
import { Events } from 'backbone';

export default class DataSourceManager extends ItemManagerModule<ModuleConfig, DataSources> {
Expand All @@ -68,10 +68,11 @@ export default class DataSourceManager extends ItemManagerModule<ModuleConfig, D
* ]
* });
*/
add(props: DataSourceProps, opts: AddOptions = {}) {
add<DRProps extends DataRecordProps>(props: DataSourceProps<DRProps>, opts: AddOptions = {}): DataSource<DRProps> {
const { all } = this;
props.id = props.id || this._createId();
return all.add(props, opts);

return all.add(props, opts) as DataSource<DRProps>;
}

/**
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/data_sources/model/DataRecord.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

import { keys } from 'underscore';
import { Model, SetOptions } from '../../common';
import { DataRecordProps, DataSourcesEvents } from '../types';
import { DataRecordProps, DataSourcesEvents, DeepPartialDot } from '../types';
import DataRecords from './DataRecords';
import DataSource from './DataSource';
import EditorModel from '../../editor/model/Editor';
Expand Down Expand Up @@ -135,7 +135,7 @@ export default class DataRecord<T extends DataRecordProps = DataRecordProps> ext
* // Sets 'name' property to 'newValue'
*/
set<A extends _StringKey<T>>(
attributeName: Partial<T> | A,
attributeName: DeepPartialDot<T> | A,
value?: SetOptions | T[A] | undefined,
options?: SetOptions | undefined,
): this;
Expand Down
49 changes: 23 additions & 26 deletions packages/core/src/data_sources/model/DataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,15 @@

import { AddOptions, collectionEvents, CombinedModelConstructorOptions, Model, RemoveOptions } from '../../common';
import EditorModel from '../../editor/model/Editor';
import { DataSourceProps } from '../types';
import { DataSourceTransformers, DataSourceType, SingleRecordType } from '../types';
import { DataSourceTransformers, DataSourceType, DataSourceProps, RecordPropsType, DataRecordProps } from '../types';
import DataRecord from './DataRecord';
import DataRecords from './DataRecords';
import DataSources from './DataSources';

interface DataSourceOptions extends CombinedModelConstructorOptions<{ em: EditorModel }, DataSource> {}

export default class DataSource<
DS extends DataSourceType = DataSourceType,
DR extends SingleRecordType<DS['records']> = SingleRecordType<DS['records']>,
> extends Model<DS> {
export default class DataSource<DRProps extends DataRecordProps = DataRecordProps> extends Model<
DataSourceType<DRProps>
> {
transformers: DataSourceTransformers;

/**
Expand All @@ -56,31 +53,31 @@ export default class DataSource<
return {
records: [],
transformers: {},
} as unknown as Partial<DS>;
} as unknown as DataSourceType<DRProps>;
}

/**
* Initializes a new instance of the `DataSource` class.
* It sets up the transformers and initializes the collection of records.
* If the `records` property is not an instance of `DataRecords`, it will be converted into one.
*
* @param {DataSourceProps} props - Properties to initialize the data source.
* @param {DataSourceProps<DRProps>} props - Properties to initialize the data source.
* @param {DataSourceOptions} opts - Options to initialize the data source.
* @name constructor
*/
constructor(props: DataSourceProps<DS>, opts: DataSourceOptions) {
constructor(props: DataSourceProps<DRProps>, opts: DataSourceOptions) {
super(
{
...props,
records: [],
} as unknown as DS,
} as unknown as DataSourceType<DRProps>,
opts,
);
const { records, transformers } = props;
this.transformers = transformers || {};
this.transformers = transformers || ({} as DataSourceTransformers);

if (!(records instanceof DataRecords)) {
this.set({ records: new DataRecords(records!, { dataSource: this }) } as Partial<DS>);
this.set({ records: new DataRecords(records!, { dataSource: this }) } as Partial<DataSourceType<DRProps>>);
}

this.listenTo(this.records, 'add', this.onAdd);
Expand All @@ -90,11 +87,11 @@ export default class DataSource<
/**
* Retrieves the collection of records associated with this data source.
*
* @returns {DataRecords} The collection of data records.
* @returns {DataRecords<DRProps>} The collection of data records.
* @name records
*/
get records() {
return this.attributes.records as NonNullable<DS['records']>;
return this.attributes.records as NonNullable<DataRecords<DRProps>>;
}

/**
Expand All @@ -111,42 +108,42 @@ export default class DataSource<
* Handles the `add` event for records in the data source.
* This method triggers a change event on the newly added record.
*
* @param {DataRecord} dr - The data record that was added.
* @param {DataRecord<DRProps>} dr - The data record that was added.
* @private
* @name onAdd
*/
onAdd(dr: DataRecord) {
onAdd(dr: DataRecord<DRProps>) {
dr.triggerChange();
}

/**
* Adds a new record to the data source.
*
* @param {DataRecordProps} record - The properties of the record to add.
* @param {DRProps} record - The properties of the record to add.
* @param {AddOptions} [opts] - Options to apply when adding the record.
* @returns {DataRecord} The added data record.
* @name addRecord
*/
addRecord(record: DR, opts?: AddOptions) {
addRecord(record: DRProps, opts?: AddOptions) {
return this.records.add(record, opts);
}

/**
* Retrieves a record from the data source by its ID.
*
* @param {string | number} id - The ID of the record to retrieve.
* @returns {DataRecord | undefined} The data record, or `undefined` if no record is found with the given ID.
* @returns {DataRecord<DRProps> | undefined} The data record, or `undefined` if no record is found with the given ID.
* @name getRecord
*/
getRecord(id: string | number) {
return this.records.get(id) as DR | undefined;
return this.records.get(id) as DataRecord | undefined;
}

/**
* Retrieves all records from the data source.
* Each record is processed with the `getRecord` method to apply any read transformers.
*
* @returns {Array<DataRecord | undefined>} An array of data records.
* @returns {Array<DataRecord<DRProps> | undefined>} An array of data records.
* @name getRecords
*/
getRecords() {
Expand All @@ -158,10 +155,10 @@ export default class DataSource<
*
* @param {string | number} id - The ID of the record to remove.
* @param {RemoveOptions} [opts] - Options to apply when removing the record.
* @returns {DataRecord | undefined} The removed data record, or `undefined` if no record is found with the given ID.
* @returns {DataRecord<DRProps> | undefined} The removed data record, or `undefined` if no record is found with the given ID.
* @name removeRecord
*/
removeRecord(id: string | number, opts?: RemoveOptions): DataRecord | undefined {
removeRecord(id: string | number, opts?: RemoveOptions) {
const record = this.getRecord(id);
if (record?.mutable === false && !opts?.dangerously) {
throw new Error('Cannot remove immutable record');
Expand All @@ -173,11 +170,11 @@ export default class DataSource<
/**
* Replaces the existing records in the data source with a new set of records.
*
* @param {Array<DataRecordProps>} records - An array of data record properties to set.
* @param {Array<DRProps>} records - An array of data record properties to set.
* @returns {Array<DataRecord>} An array of the added data records.
* @name setRecords
*/
setRecords(records: DR[]) {
setRecords(records: DRProps[]) {
this.records.reset([], { silent: true });

records.forEach((record) => {
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/data_sources/model/DataSources.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Collection } from '../../common';
import EditorModel from '../../editor/model/Editor';
import { DataSourceProps } from '../types';
import { DataRecordProps, DataSourceProps } from '../types';
import DataSource from './DataSource';

export default class DataSources extends Collection<DataSource> {
em: EditorModel;

constructor(models: DataSource[] | DataSourceProps[], em: EditorModel) {
constructor(models: DataSource[] | DataSourceProps<DataRecordProps>[], em: EditorModel) {
super(models, em);
this.em = em;

Expand Down
35 changes: 28 additions & 7 deletions packages/core/src/data_sources/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Collection, ObjectAny } from '../common';
import { ObjectAny } from '../common';
import ComponentDataVariable from './model/ComponentDataVariable';
import DataRecord from './model/DataRecord';
import DataRecords from './model/DataRecords';
Expand All @@ -17,6 +17,8 @@ export interface DataRecordProps extends ObjectAny {
* Specifies if the record is mutable. Defaults to `true`.
*/
mutable?: boolean;

[key: string]: any;
}

export interface DataVariableListener {
Expand All @@ -40,15 +42,13 @@ interface BaseDataSource {
*/
skipFromStorage?: boolean;
}
export interface DataSourceType<DR extends DataRecordProps = DataRecordProps> extends BaseDataSource {
export interface DataSourceType<DR extends DataRecordProps> extends BaseDataSource {
records: DataRecords<DR>;
}
export interface DataSourceProps<DS extends DataSourceType = DataSourceType> extends BaseDataSource {
records?: DataRecords<ExtractRecordType<DS>> | DataRecord<ExtractRecordType<DS>>[] | ExtractRecordType<DS>[];
export interface DataSourceProps<DR extends DataRecordProps> extends BaseDataSource {
records?: DataRecords<DR> | DataRecord<DR>[] | DR[];
}
export type ExtractRecordType<T> = T extends { records: DataRecords<infer DR> } ? DR : never;
export type SingleRecordType<T> = T extends Collection<infer U> ? U : never;

export type RecordPropsType<T> = T extends DataRecord<infer U> ? U : never;
export interface DataSourceTransformers {
onRecordSetValue?: (args: { id: string | number; key: string; value: any }) => any;
}
Expand Down Expand Up @@ -93,3 +93,24 @@ export enum DataSourcesEvents {
all = 'data',
}
/**{END_EVENTS}*/
type DotSeparatedKeys<T> = T extends object
? {
[K in keyof T]: K extends string
? T[K] extends object
? `${K}` | `${K}.${DotSeparatedKeys<T[K]>}`
: `${K}`
: never;
}[keyof T]
: never;

export type DeepPartialDot<T> = {
[P in DotSeparatedKeys<T>]?: P extends `${infer K}.${infer Rest}`
? K extends keyof T
? Rest extends DotSeparatedKeys<T[K]>
? DeepPartialDot<T[K]>[Rest]
: never
: never
: P extends keyof T
? T[P]
: never;
};
3 changes: 2 additions & 1 deletion packages/core/test/specs/data_sources/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import EditorModel from '../../../src/editor/model/Editor';
describe('DataSourceManager', () => {
let em: EditorModel;
let dsm: DataSourceManager;
const dsTest: DataSourceProps = {
type Record = { id: string; name: string };
const dsTest: DataSourceProps<Record> = {
id: 'ds1',
records: [
{ id: 'id1', name: 'Name1' },
Expand Down
19 changes: 13 additions & 6 deletions packages/core/test/specs/data_sources/jsonplaceholder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,46 @@ import { setupTestEditor } from '../../common';
import EditorModel from '../../../src/editor/model/Editor';
import htmlFormat from 'pretty';

type Comment = {
postId: number;
id: string;
name: string;
email: string;
body: string;
};
function getComments() {
const json = [
{
postId: 1,
id: 1,
id: '1',
name: 'id labore ex et quam laborum',
email: '[email protected]',
body: 'laudantium enim quasi est quidem magnam voluptate ipsam eos\ntempora quo necessitatibus\ndolor quam autem quasi\nreiciendis et nam sapiente accusantium',
},
{
postId: 1,
id: 2,
id: '2',
name: 'quo vero reiciendis velit similique earum',
email: '[email protected]',
body: 'est natus enim nihil est dolore omnis voluptatem numquam\net omnis occaecati quod ullam at\nvoluptatem error expedita pariatur\nnihil sint nostrum voluptatem reiciendis et',
},
{
postId: 1,
id: 3,
id: '3',
name: 'odio adipisci rerum aut animi',
email: '[email protected]',
body: 'quia molestiae reprehenderit quasi aspernatur\naut expedita occaecati aliquam eveniet laudantium\nomnis quibusdam delectus saepe quia accusamus maiores nam est\ncum et ducimus et vero voluptates excepturi deleniti ratione',
},
{
postId: 1,
id: 4,
id: '4',
name: 'alias odio sit',
email: '[email protected]',
body: 'non et atque\noccaecati deserunt quas accusantium unde odit nobis qui voluptatem\nquia voluptas consequuntur itaque dolor\net qui rerum deleniti ut occaecati',
},
{
postId: 1,
id: 5,
id: '5',
name: 'vero eaque aliquid doloribus et culpa',
email: '[email protected]',
body: 'harum non quasi et ratione\ntempore iure ex voluptates in ratione\nharum architecto fugit inventore cupiditate\nvoluptates magni quo et',
Expand All @@ -64,7 +71,7 @@ describe('JsonPlaceholder Usage', () => {

test('should render a list of comments from jsonplaceholder api', async () => {
const comments = getComments();
const dataSource: DataSourceProps = {
const dataSource: DataSourceProps<Comment> = {
id: 'comments',
records: comments as any,
};
Expand Down
Loading

0 comments on commit ff80932

Please sign in to comment.