|
1 | 1 | // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. |
2 | 2 |
|
3 | 3 | import { ObjectQL } from './engine.js'; |
4 | | -import { ObjectStackProtocolImplementation } from '@objectstack/metadata-protocol'; |
| 4 | +import { assembleMetadataProtocol } from '@objectstack/metadata-protocol'; |
5 | 5 | import { Plugin, PluginContext } from '@objectstack/core'; |
6 | 6 | import { StorageNameMapping } from '@objectstack/spec/system'; |
7 | | -import { SERVICE_SELF_INFO_KEY, type ServiceSelfInfo } from '@objectstack/spec/api'; |
8 | 7 | import { LifecycleService } from './lifecycle/lifecycle-service.js'; |
9 | 8 | import { lifecycleSettingsManifest } from './lifecycle/lifecycle-settings.js'; |
10 | | -import { |
11 | | - SysMetadataObject, |
12 | | - SysMetadataHistoryObject, |
13 | | - SysMetadataCommitObject, |
14 | | - SysMetadataAuditObject, |
15 | | - SysViewDefinitionObject, |
16 | | -} from '@objectstack/metadata-core'; |
17 | 9 |
|
18 | 10 | export type { Plugin, PluginContext }; |
19 | 11 |
|
@@ -263,114 +255,12 @@ export class ObjectQLPlugin implements Plugin { |
263 | 255 | }); |
264 | 256 |
|
265 | 257 | if (this.registerProtocol) { |
266 | | - // Register the metadata-storage objects this engine's own protocol reads |
267 | | - // and writes — `sys_metadata` (loadMetaFromDb / getMetaItems / saveMetaItem), |
268 | | - // its history/audit siblings, and `sys_view_definition`. Doing it here |
269 | | - // guarantees their tables get schema-synced in start() even when no |
270 | | - // MetadataPlugin is present (e.g. standalone "host config" apps, where the |
271 | | - // CLI auto-registers a bare ObjectQLPlugin and nothing else owns these |
272 | | - // tables → "no such table: sys_metadata" on every read). |
273 | | - // |
274 | | - // Gated on `environmentId === undefined` — the SAME condition that gates |
275 | | - // `restoreMetadataFromDb` below: platform / standalone kernels own their |
276 | | - // local sys_metadata, whereas per-project (cloud) kernels source metadata |
277 | | - // from the control plane and must NOT provision these tables locally. |
278 | | - // Definitions live in @objectstack/metadata-core (shared by this protocol |
279 | | - // and the metadata layer's DatabaseLoader). registerApp is idempotent, so |
280 | | - // a MetadataPlugin that also registers them is harmless. |
281 | | - if (this.environmentId === undefined) { |
282 | | - this.ql.registerApp({ |
283 | | - id: 'com.objectstack.metadata-objects', |
284 | | - name: 'Metadata Platform Objects', |
285 | | - version: '1.0.0', |
286 | | - type: 'plugin', |
287 | | - scope: 'system', |
288 | | - objects: [ |
289 | | - SysMetadataObject, |
290 | | - SysMetadataHistoryObject, |
291 | | - SysMetadataCommitObject, |
292 | | - SysMetadataAuditObject, |
293 | | - SysViewDefinitionObject, |
294 | | - ], |
295 | | - }); |
296 | | - } |
297 | | - |
298 | | - // Register Protocol Implementation |
299 | | - const protocolShim = new ObjectStackProtocolImplementation( |
300 | | - this.ql, |
301 | | - () => ctx.getServices ? ctx.getServices() : new Map(), |
302 | | - this.environmentId, |
303 | | - ); |
304 | | - |
305 | | - ctx.registerService('protocol', protocolShim); |
306 | | - ctx.logger.info('Protocol service registered'); |
307 | | - |
308 | | - // ── Runtime-authored hook/action rebind on authoring (#2588, #2605) ── |
309 | | - // The protocol is the ONE choke point every metadata-authoring surface |
310 | | - // funnels through (rest-server PUT /meta, dispatcher, publish-drafts, AI |
311 | | - // builders). When a `hook` or `action` row lands (direct-active save, |
312 | | - // publish) or is deleted, re-sync the authored set so the change is live |
313 | | - // without a restart. Draft saves are skipped — drafts are not live by |
314 | | - // design. Fire-and-forget: a resync failure is logged, never fails the |
315 | | - // write. |
316 | | - this.subscribeMetadataRebind(ctx, protocolShim); |
317 | | - |
318 | | - // Register an `analytics` service adapter that maps the dispatcher's |
319 | | - // expected interface (query / getMeta / generateSql) onto the |
320 | | - // protocol shim's `analyticsQuery`. Without this, HttpDispatcher's |
321 | | - // `handleAnalytics` cannot resolve a service and `/api/v1/analytics/*` |
322 | | - // returns ROUTE_NOT_FOUND, even though discovery advertises the route |
323 | | - // (objectql's getDiscovery hardcodes `analytics: enabled:true`). The |
324 | | - // adapter delegates `query` to the cube → engine.aggregate translator |
325 | | - // already implemented in protocol.ts; getMeta/generateSql return a |
326 | | - // structured "not implemented" payload so callers see something |
327 | | - // useful instead of a 500. |
328 | | - ctx.registerService('analytics', { |
329 | | - // Honest capabilities (ADR-0076 D12, #2462): this adapter is a |
330 | | - // deliberate lightweight fallback, not the full analytics engine — |
331 | | - // self-identify so discovery reports it as 'degraded' instead of |
332 | | - // 'available'. AnalyticsServicePlugin replaces this service (via |
333 | | - // ctx.replaceService) with the real engine, which carries no marker. |
334 | | - [SERVICE_SELF_INFO_KEY]: { |
335 | | - status: 'degraded', |
336 | | - handlerReady: true, |
337 | | - message: 'Lightweight ObjectQL analytics fallback — install @objectstack/service-analytics for the full engine', |
338 | | - } satisfies ServiceSelfInfo, |
339 | | - // HttpDispatcher passes the raw POST body (AnalyticsQuery shape: |
340 | | - // `{ cube, measures, dimensions, where?, filters?, ... }`). The |
341 | | - // protocol shim's `analyticsQuery` expects the wrapped envelope |
342 | | - // `{ cube, query }` and destructures `request.query` for dims / |
343 | | - // measures. Reshape here so the destructure resolves to the |
344 | | - // analytics query instead of `undefined` (which caused |
345 | | - // "Cannot read properties of undefined (reading 'dimensions')"). |
346 | | - // |
347 | | - // `analyticsQuery` also returns its own `{ success, data: { rows, |
348 | | - // fields } }` envelope. HttpDispatcher wraps service responses |
349 | | - // again with `success(result)`, so without unwrapping here the |
350 | | - // client sees `{success, data:{success, data:{rows, fields}}}` — |
351 | | - // KPI widgets read `data.rows` and silently get nothing. Unwrap |
352 | | - // to the inner `{ rows, fields }` payload so a single wrap from |
353 | | - // the dispatcher yields the canonical shape. |
354 | | - query: async (body: any) => { |
355 | | - const envelope = body && typeof body === 'object' && 'query' in body && 'cube' in body |
356 | | - ? body |
357 | | - : { cube: body?.cube, query: body }; |
358 | | - const result = await protocolShim.analyticsQuery(envelope); |
359 | | - // Unwrap an inner `{ success, data }` envelope (one level only). |
360 | | - if (result && typeof result === 'object' && 'success' in result && 'data' in result) { |
361 | | - return (result as any).data; |
362 | | - } |
363 | | - return result; |
364 | | - }, |
365 | | - getMeta: async () => ({ |
366 | | - cubes: [], |
367 | | - message: 'Analytics meta endpoint not implemented by ObjectQL adapter', |
368 | | - }), |
369 | | - generateSql: async (_body: any) => ({ |
370 | | - sql: null, |
371 | | - message: 'Analytics SQL generation not implemented by ObjectQL adapter', |
372 | | - }), |
373 | | - }); |
| 258 | + // ADR-0076 Step 2 PR-C: the ONE assembly lives in |
| 259 | + // @objectstack/metadata-protocol — this built-in mode is the |
| 260 | + // single-kernel convenience mount of the same code path the |
| 261 | + // MetadataProtocolPlugin uses (identical objects/protocol/analytics). |
| 262 | + const protocolShim = assembleMetadataProtocol(ctx, this.ql, this.environmentId); |
| 263 | + this.subscribeMetadataRebind(ctx, protocolShim); |
374 | 264 | } else { |
375 | 265 | ctx.logger.info('registerProtocol=false — protocol assembly delegated to MetadataProtocolPlugin (ADR-0076 Step 2, #2462)'); |
376 | 266 | } |
|
0 commit comments