From 2ae234c3a55d0c63517bca7d75f9c021a9cf6ffd Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Fri, 24 Oct 2025 12:13:00 +0400 Subject: [PATCH] Load providers on project load --- .../core/src/data_sources/config/config.ts | 13 +++++ packages/core/src/data_sources/index.ts | 24 +++++++-- .../core/src/data_sources/model/DataSource.ts | 25 +++++++-- packages/core/src/data_sources/types.ts | 19 +++++++ .../specs/data_sources/model/DataSource.ts | 51 +++++++++++++++++-- 5 files changed, 120 insertions(+), 12 deletions(-) create mode 100644 packages/core/src/data_sources/config/config.ts diff --git a/packages/core/src/data_sources/config/config.ts b/packages/core/src/data_sources/config/config.ts new file mode 100644 index 000000000..7a85ccbb6 --- /dev/null +++ b/packages/core/src/data_sources/config/config.ts @@ -0,0 +1,13 @@ +export interface DataSourcesConfig { + /** + * If true, data source providers will be autoloaded on project load. + * @default false + */ + autoloadProviders?: boolean; +} + +const config: () => DataSourcesConfig = () => ({ + autoloadProviders: false, +}); + +export default config; diff --git a/packages/core/src/data_sources/index.ts b/packages/core/src/data_sources/index.ts index b2d19e979..ebe2f2500 100644 --- a/packages/core/src/data_sources/index.ts +++ b/packages/core/src/data_sources/index.ts @@ -26,19 +26,20 @@ import { ItemManagerModule, ModuleConfig } from '../abstract/Module'; import { AddOptions, collectionEvents, ObjectAny, RemoveOptions } from '../common'; import EditorModel from '../editor/model/Editor'; import { get, set, stringToPath } from '../utils/mixins'; +import defConfig, { DataSourcesConfig } from './config/config'; import DataRecord from './model/DataRecord'; import DataSource from './model/DataSource'; import DataSources from './model/DataSources'; import { DataSourcesEvents, DataSourceProps, DataRecordProps } from './types'; import { Events } from 'backbone'; -export default class DataSourceManager extends ItemManagerModule { +export default class DataSourceManager extends ItemManagerModule { storageKey = 'dataSources'; events = DataSourcesEvents; destroy(): void {} constructor(em: EditorModel) { - super(em, 'DataSources', new DataSources([], em), DataSourcesEvents); + super(em, 'DataSources', new DataSources([], em), DataSourcesEvents, defConfig()); Object.assign(this, Events); // Mixin Backbone.Events } @@ -188,7 +189,24 @@ export default class DataSourceManager extends ItemManagerModule ds.hasProvider); + + if (!!dsWithProviders.length) { + const loadProviders = async () => { + em.trigger(events.providerLoadAllBefore); + const providersToLoad = dsWithProviders.map((ds) => ds.loadProvider()); + await Promise.all(providersToLoad); + em.trigger(events.providerLoadAll); + }; + loadProviders(); + } + } + + return result; } postLoad() { diff --git a/packages/core/src/data_sources/model/DataSource.ts b/packages/core/src/data_sources/model/DataSource.ts index 367a7f99e..15bf1757f 100644 --- a/packages/core/src/data_sources/model/DataSource.ts +++ b/packages/core/src/data_sources/model/DataSource.ts @@ -132,6 +132,13 @@ export default class DataSource { + const dataSource = this; + try { + em.trigger(em.DataSources.events.providerLoadBefore, { dataSource }); + const response = await fetch(url, { method, headers, body }); if (!response.ok) throw new Error(await response.text()); - const providerResult: DataSourceProviderResult = await response.json(); + const result: DataSourceProviderResult = await response.json(); + + if (result?.records) this.setRecords(result.records as any); + if (result?.schema) this.upSchema(result.schema); - if (providerResult?.records) this.setRecords(providerResult.records as any); - if (providerResult?.schema) this.upSchema(providerResult.schema); + em.trigger(em.DataSources.events.providerLoad, { result, dataSource }); } catch (error: any) { - this.em.logError(error.message); + em.logError(error.message); + em.trigger(em.DataSources.events.providerLoadError, { dataSource, error }); } }; diff --git a/packages/core/src/data_sources/types.ts b/packages/core/src/data_sources/types.ts index 1100bc243..5218e1cbb 100644 --- a/packages/core/src/data_sources/types.ts +++ b/packages/core/src/data_sources/types.ts @@ -46,6 +46,8 @@ interface BaseDataSource { * If true will store the data source in the GrapesJS project.json file. */ skipFromStorage?: boolean; + + [key: string]: unknown; } export enum DataFieldPrimitiveType { @@ -215,6 +217,23 @@ export enum DataSourcesEvents { */ pathSource = 'data:pathSource:', + /** + * @event `data:provider:load` Data source provider load. + * @example + * editor.on('data:provider:load', ({ dataSource, result }) => { ... }); + */ + providerLoad = 'data:provider:load', + providerLoadBefore = 'data:provider:load:before', + providerLoadError = 'data:provider:load:error', + + /** + * @event `data:provider:loadAll` Load of all data source providers (eg. on project load). + * @example + * editor.on('data:provider:loadAll', () => { ... }); + */ + providerLoadAll = 'data:provider:loadAll', + providerLoadAllBefore = 'data:provider:loadAll:before', + /** * @event `data` Catch-all event for all the events mentioned above. * @example diff --git a/packages/core/test/specs/data_sources/model/DataSource.ts b/packages/core/test/specs/data_sources/model/DataSource.ts index 33644ebd7..8aea2860a 100644 --- a/packages/core/test/specs/data_sources/model/DataSource.ts +++ b/packages/core/test/specs/data_sources/model/DataSource.ts @@ -179,6 +179,7 @@ describe('DataSource', () => { const addBlogsWithProvider = () => { return dsm.add({ id: 'blogs', + name: 'My blogs', provider: { get: getProviderBlogsGet(), }, @@ -232,8 +233,11 @@ describe('DataSource', () => { expect(ds.getRecords().length).toBe(0); }); - test('ensure records loaded from the provider are not persisted', async () => { + test('records loaded from the provider are not persisted', async () => { const ds = addBlogsWithProvider(); + const eventLoad = jest.fn(); + em.on(dsm.events.providerLoad, eventLoad); + await ds.loadProvider(); expect(editor.getProjectData().dataSources).toEqual([ @@ -241,12 +245,51 @@ describe('DataSource', () => { { id: 'users', records: userRecords }, { id: 'blogs', + name: 'My blogs', schema: getMockSchema(), - provider: { - get: getProviderBlogsGet(), - }, + provider: { get: getProviderBlogsGet() }, }, ]); + expect(eventLoad).toHaveBeenCalledTimes(1); + expect(eventLoad).toHaveBeenCalledWith({ + dataSource: ds, + result: getMockProviderResponse(), + }); + }); + + test('load providers on project load', (done) => { + dsm.getConfig().autoloadProviders = true; + + editor.on(dsm.events.providerLoadAll, () => { + expect(dsm.get('blogs').getResolvedRecords()).toEqual([ + { ...blogRecords[0], author: userRecords[0] }, + { ...blogRecords[1], author: userRecords[1] }, + blogRecords[2], + ]); + + expect(editor.getProjectData().dataSources).toEqual([ + { id: 'categories', records: categoryRecords }, + { id: 'users', records: userRecords }, + { + id: 'blogs', + schema: getMockSchema(), + provider: { get: testApiUrl }, + }, + ]); + + done(); + }); + + editor.loadProjectData({ + dataSources: [ + { id: 'categories', records: categoryRecords }, + { id: 'users', records: userRecords }, + { + id: 'blogs', + provider: { get: testApiUrl }, + }, + ], + }); }); }); });