@objectstack/core 4.0.1 → 4.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@objectstack/core",
3
- "version": "4.0.1",
3
+ "version": "4.0.3",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Microkernel Core for ObjectStack",
6
6
  "type": "module",
@@ -14,15 +14,15 @@
14
14
  }
15
15
  },
16
16
  "devDependencies": {
17
- "@types/node": "^25.5.0",
17
+ "@types/node": "^25.6.0",
18
18
  "typescript": "^6.0.2",
19
- "vitest": "^4.1.2"
19
+ "vitest": "^4.1.4"
20
20
  },
21
21
  "dependencies": {
22
22
  "pino": "^10.3.1",
23
23
  "pino-pretty": "^13.1.3",
24
24
  "zod": "^4.3.6",
25
- "@objectstack/spec": "4.0.1"
25
+ "@objectstack/spec": "4.0.3"
26
26
  },
27
27
  "peerDependencies": {
28
28
  "pino": "^8.0.0"
@@ -6,8 +6,8 @@ import { createMemoryI18n, resolveLocale } from './memory-i18n';
6
6
  import { CORE_FALLBACK_FACTORIES } from './index';
7
7
 
8
8
  describe('CORE_FALLBACK_FACTORIES', () => {
9
- it('should have exactly 4 entries: cache, queue, job, i18n', () => {
10
- expect(Object.keys(CORE_FALLBACK_FACTORIES)).toEqual(['cache', 'queue', 'job', 'i18n']);
9
+ it('should have exactly 5 entries: metadata, cache, queue, job, i18n', () => {
10
+ expect(Object.keys(CORE_FALLBACK_FACTORIES)).toEqual(['metadata', 'cache', 'queue', 'job', 'i18n']);
11
11
  });
12
12
 
13
13
  it('should map to factory functions', () => {
@@ -4,11 +4,13 @@ import { createMemoryCache } from './memory-cache.js';
4
4
  import { createMemoryQueue } from './memory-queue.js';
5
5
  import { createMemoryJob } from './memory-job.js';
6
6
  import { createMemoryI18n } from './memory-i18n.js';
7
+ import { createMemoryMetadata } from './memory-metadata.js';
7
8
 
8
9
  export { createMemoryCache } from './memory-cache.js';
9
10
  export { createMemoryQueue } from './memory-queue.js';
10
11
  export { createMemoryJob } from './memory-job.js';
11
12
  export { createMemoryI18n, resolveLocale } from './memory-i18n.js';
13
+ export { createMemoryMetadata } from './memory-metadata.js';
12
14
 
13
15
  /**
14
16
  * Map of core-criticality service names to their in-memory fallback factories.
@@ -16,6 +18,7 @@ export { createMemoryI18n, resolveLocale } from './memory-i18n.js';
16
18
  * when no real plugin provides the service.
17
19
  */
18
20
  export const CORE_FALLBACK_FACTORIES: Record<string, () => Record<string, any>> = {
21
+ metadata: createMemoryMetadata,
19
22
  cache: createMemoryCache,
20
23
  queue: createMemoryQueue,
21
24
  job: createMemoryJob,
@@ -0,0 +1,50 @@
1
+ // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
+
3
+ /**
4
+ * In-memory metadata service fallback.
5
+ *
6
+ * Implements the IMetadataService contract with a simple Map-of-Maps store.
7
+ * Used by ObjectKernel as an automatic fallback when no real metadata plugin
8
+ * (e.g. MetadataPlugin with file-system persistence) is registered.
9
+ */
10
+ export function createMemoryMetadata() {
11
+ // type -> name -> data
12
+ const store = new Map<string, Map<string, any>>();
13
+
14
+ function getTypeMap(type: string): Map<string, any> {
15
+ let map = store.get(type);
16
+ if (!map) {
17
+ map = new Map();
18
+ store.set(type, map);
19
+ }
20
+ return map;
21
+ }
22
+
23
+ return {
24
+ _fallback: true, _serviceName: 'metadata',
25
+ async register(type: string, name: string, data: any): Promise<void> {
26
+ getTypeMap(type).set(name, data);
27
+ },
28
+ async get(type: string, name: string): Promise<any> {
29
+ return getTypeMap(type).get(name);
30
+ },
31
+ async list(type: string): Promise<any[]> {
32
+ return Array.from(getTypeMap(type).values());
33
+ },
34
+ async unregister(type: string, name: string): Promise<void> {
35
+ getTypeMap(type).delete(name);
36
+ },
37
+ async exists(type: string, name: string): Promise<boolean> {
38
+ return getTypeMap(type).has(name);
39
+ },
40
+ async listNames(type: string): Promise<string[]> {
41
+ return Array.from(getTypeMap(type).keys());
42
+ },
43
+ async getObject(name: string): Promise<any> {
44
+ return getTypeMap('object').get(name);
45
+ },
46
+ async listObjects(): Promise<any[]> {
47
+ return Array.from(getTypeMap('object').values());
48
+ },
49
+ };
50
+ }
package/src/kernel.ts CHANGED
@@ -213,6 +213,28 @@ export class ObjectKernel {
213
213
  return this;
214
214
  }
215
215
 
216
+ /**
217
+ * Pre-inject in-memory fallbacks for 'core' services that were not registered
218
+ * by plugins during Phase 1. Called before Phase 2 so that all core services
219
+ * (e.g. 'metadata', 'cache', 'queue') are resolvable via ctx.getService()
220
+ * when plugin start() methods execute.
221
+ */
222
+ private preInjectCoreFallbacks() {
223
+ if (this.config.skipSystemValidation) return;
224
+ for (const [serviceName, criticality] of Object.entries(ServiceRequirementDef)) {
225
+ if (criticality !== 'core') continue;
226
+ const hasService = this.services.has(serviceName) || this.pluginLoader.hasService(serviceName);
227
+ if (!hasService) {
228
+ const factory = CORE_FALLBACK_FACTORIES[serviceName];
229
+ if (factory) {
230
+ const fallback = factory();
231
+ this.registerService(serviceName, fallback);
232
+ this.logger.debug(`[Kernel] Pre-injected in-memory fallback for '${serviceName}' before Phase 2`);
233
+ }
234
+ }
235
+ }
236
+ }
237
+
216
238
  /**
217
239
  * Validate Critical System Requirements
218
240
  */
@@ -291,6 +313,12 @@ export class ObjectKernel {
291
313
  await this.initPluginWithTimeout(plugin);
292
314
  }
293
315
 
316
+ // Pre-inject in-memory fallbacks for 'core' services that were not
317
+ // registered by any plugin during Phase 1. This ensures services like
318
+ // 'metadata', 'cache', 'queue', etc. are always available when plugins
319
+ // call ctx.getService() during their start() methods.
320
+ this.preInjectCoreFallbacks();
321
+
294
322
  // Phase 2: Start - Plugins execute business logic
295
323
  this.logger.info('Phase 2: Start plugins');
296
324
  this.state = 'running';