From efa2591f35b9cc7f6c2ef29f2df757039e0a2658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AF=86=E7=A0=810000?= Date: Fri, 22 May 2026 23:57:40 +0800 Subject: [PATCH] Expose ensureTable on plugin scope --- components/Scope.ts | 12 ++++- components/componentLoader.ts | 9 +++- unitTests/components/componentLoader.test.js | 49 ++++++++++++++++++++ 3 files changed, 67 insertions(+), 3 deletions(-) diff --git a/components/Scope.ts b/components/Scope.ts index 167bfc3bc..01b2de01c 100644 --- a/components/Scope.ts +++ b/components/Scope.ts @@ -1,7 +1,7 @@ import { type Logger } from '../utility/logging/logger.ts'; import { loggerWithTag } from '../utility/logging/harper_logger.ts'; import { EventEmitter, once } from 'node:events'; -import { databaseEventsEmitter } from '../resources/databases.ts'; +import { databaseEventsEmitter, table } from '../resources/databases.ts'; import { server, type Server } from '../server/Server.ts'; import { EntryHandler, type EntryHandlerEventMap, type onEntryEventHandler } from './EntryHandler.ts'; import { OptionsWatcher, OptionsWatcherEventMap } from './OptionsWatcher.ts'; @@ -39,6 +39,7 @@ export class Scope extends EventEmitter { #directory: string; #appName: string; #pluginName: string; + #origin: string; #entryHandler?: EntryHandler; #entryHandlers: EntryHandler[]; #logger: Logger; @@ -57,12 +58,14 @@ export class Scope extends EventEmitter { pluginName: string, directory: string, configFilePath: string, - applicationScope: ApplicationScope + applicationScope: ApplicationScope, + origin: string = appName ) { super(); this.#appName = appName; this.#pluginName = pluginName; + this.#origin = typeof origin === 'string' ? origin : appName; this.#directory = directory; this.#configFilePath = configFilePath; this.#logger = loggerWithTag(this.#appName); @@ -128,6 +131,11 @@ export class Scope extends EventEmitter { return this.#configFilePath; } + ensureTable(options: any): TableResourceType { + options.origin = this.#origin; + return table(options); + } + #handleOptionsWatcherReady(): void { // This previously created the default entry handler immediately, but now we wait for the user to call `handleEntry` // The issue was that since the component loader was awaiting `scope.ready()` and then calling `pluginModule.handleApplication(scope)`, diff --git a/components/componentLoader.ts b/components/componentLoader.ts index 38cad930e..575909248 100644 --- a/components/componentLoader.ts +++ b/components/componentLoader.ts @@ -444,7 +444,14 @@ export async function loadComponent( // New Plugin API (`handleApplication`) if (resources.isWorker && extensionModule.handleApplication) { - const scope = new Scope(appName || 'harper', componentName, componentDirectory, configPath, applicationScope); + const scope = new Scope( + appName || 'harper', + componentName, + componentDirectory, + configPath, + applicationScope, + origin + ); onMessageByType(ITC_EVENT_TYPES.SHUTDOWN, () => scope.close()); diff --git a/unitTests/components/componentLoader.test.js b/unitTests/components/componentLoader.test.js index 5595c9654..baf726499 100644 --- a/unitTests/components/componentLoader.test.js +++ b/unitTests/components/componentLoader.test.js @@ -156,6 +156,55 @@ describe('ComponentLoader Status Integration', function () { assert.match(loadedCalls[0].args[1], /loaded successfully/); }); + it('should expose ensureTable on handleApplication scope with component origin', async function () { + const componentDirName = 'scope-ensure-table-component'; + const componentDir = path.join(tempDir, componentDirName); + const pluginName = 'scopeEnsureTablePlugin'; + const origin = 'test-origin'; + let capturedScope; + + mkdirSync(componentDir); + writeFileSync(path.join(componentDir, 'harperdb-config.yaml'), `${pluginName}: {}`); + + componentLoader.TRUSTED_RESOURCE_PLUGINS[pluginName] = { + handleApplication(scope) { + capturedScope = scope; + }, + }; + + try { + await componentLoader.loadComponent( + componentDir, + { + isWorker: true, + set: sinon.stub(), + }, + origin + ); + } finally { + delete componentLoader.TRUSTED_RESOURCE_PLUGINS[pluginName]; + } + + assert.equal(typeof capturedScope?.ensureTable, 'function', 'scope.ensureTable should be exposed'); + + let assignedOrigin; + const options = new Proxy( + {}, + { + set(target, property, value) { + if (property === 'origin') { + assignedOrigin = value; + throw new Error('stop before table call'); + } + return Reflect.set(target, property, value); + }, + } + ); + + assert.throws(() => capturedScope.ensureTable(options), /stop before table call/); + assert.equal(assignedOrigin, origin, 'scope.ensureTable should preserve the component origin'); + }); + // TODO: Does the plugin API have an equivalent mechanism? it.skip('should mark component as failed when it loads no functionality', async function () { // Create a component directory without config