Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Datasource types #6300

Merged
merged 9 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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: 'Eliseo@gardner.biz',
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: 'Jayne_Kuhic@sydney.com',
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: 'Nikita@garfield.biz',
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: 'Lew@alysha.tv',
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: 'Hayden@althea.biz',
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