@objectstack/objectql 4.2.0 → 5.1.0

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/dist/index.d.mts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { ServiceObject, ObjectOwnership, HookContext, QueryAST, EngineQueryOptions, DataEngineInsertOptions, EngineUpdateOptions, EngineDeleteOptions, EngineCountOptions, EngineAggregateOptions, DateGranularityValue, Hook } from '@objectstack/spec/data';
2
2
  import { ObjectStackManifest, InstalledPackage, ExecutionContext } from '@objectstack/spec/kernel';
3
+ import * as _objectstack_metadata_core from '@objectstack/metadata-core';
4
+ import { MetadataRepository, MetaRef, MetadataItem, PutOptions, PutResult, DeleteOptions, DeleteResult, ListFilter, MetadataItemHeader, HistoryOptions, MetadataEvent, WatchFilter } from '@objectstack/metadata-core';
3
5
  import { ObjectStackProtocol, MetadataCacheRequest, MetadataCacheResponse, BatchUpdateRequest, BatchUpdateResponse, UpdateManyDataRequest, DeleteManyDataRequest } from '@objectstack/spec/api';
4
6
  import { IDataEngine, DriverInterface, Logger, Plugin, PluginContext, ObjectKernel } from '@objectstack/core';
5
7
  import { IFeedService, IRealtimeService } from '@objectstack/spec/contracts';
@@ -244,6 +246,21 @@ declare class SchemaRegistry {
244
246
  id: string;
245
247
  globs: string[];
246
248
  }[];
249
+ /**
250
+ * Invalidate the merged-schema cache for a single FQN (or short name).
251
+ *
252
+ * Call this from event-driven consumers (ADR-0008 M0 PR-7) when an
253
+ * upstream metadata change makes the cached merged definition stale.
254
+ * The contributor list is preserved — only the cached merge result is
255
+ * dropped, so the next `resolveObject(fqn)` recomputes from scratch.
256
+ *
257
+ * Accepts either an FQN (`acme__contact`) or a bare short name
258
+ * (`contact`); for the latter, all entries whose suffix matches the
259
+ * name are invalidated.
260
+ */
261
+ invalidate(fqnOrName: string): void;
262
+ /** Drop every entry from the merged-schema cache. */
263
+ invalidateAll(): void;
247
264
  /**
248
265
  * Clear all registry state. Use only for testing.
249
266
  */
@@ -263,7 +280,20 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
263
280
  * metadata even if several projects share the same physical database.
264
281
  */
265
282
  private projectId?;
283
+ /**
284
+ * Lazily-instantiated SysMetadataRepository per organization. Keyed by
285
+ * `${organizationId ?? '__env__'}`. Repositories are stateful — they
286
+ * carry the per-org `seqCounter` and watch subscribers — so we cache
287
+ * them rather than constructing one per call.
288
+ */
289
+ private overlayRepos;
266
290
  constructor(engine: IDataEngine, getServicesRegistry?: () => Map<string, any>, getFeedService?: () => IFeedService | undefined, projectId?: string);
291
+ /**
292
+ * Lazily obtain a SysMetadataRepository for the given organization.
293
+ * Env-wide overlays (organizationId == null) share a singleton under
294
+ * the `__env__` key.
295
+ */
296
+ private getOverlayRepo;
267
297
  /**
268
298
  * One-time guard for ensuring the overlay-uniqueness UNIQUE INDEX exists
269
299
  * on `sys_metadata`. ADR-0005: scopes overlays by
@@ -561,14 +591,53 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
561
591
  private static readonly OVERLAY_ALLOWED_TYPES;
562
592
  /** Normalize plural→singular before consulting the allow-list. */
563
593
  private static isOverlayAllowed;
594
+ /**
595
+ * Mirror an object-type overlay write into the in-memory engine
596
+ * registry so subsequent CRUD finds the new schema. Idempotent and
597
+ * safe to call after a successful persistence call. For the legacy
598
+ * write path this is invoked BEFORE persistence (historical behavior
599
+ * preserved); for the PR-10d.3 repository path it is invoked only
600
+ * AFTER `put()` resolves successfully, so a failed write — DB error,
601
+ * optimistic-lock conflict, validation failure — never leaks a
602
+ * stale schema into the registry.
603
+ */
604
+ private applyObjectRegistryMutation;
564
605
  saveMetaItem(request: {
565
606
  type: string;
566
607
  name: string;
567
608
  item?: any;
568
609
  organizationId?: string;
610
+ parentVersion?: string | null;
611
+ actor?: string;
569
612
  }): Promise<{
570
613
  success: boolean;
614
+ version: string;
615
+ seq: number;
571
616
  message: string;
617
+ } | {
618
+ success: boolean;
619
+ message: string;
620
+ version?: undefined;
621
+ seq?: undefined;
622
+ }>;
623
+ /**
624
+ * Yield the durable change-log for a single metadata item — every
625
+ * put/delete recorded in `sys_metadata_history` for `(org, type, name)`,
626
+ * in event_seq order. Powers the Studio "History" tab and any
627
+ * client-side audit timeline.
628
+ *
629
+ * Returns `[]` for non-overlay-allowed types (the legacy raw-engine
630
+ * path doesn't record history) instead of throwing — callers can treat
631
+ * "no history" uniformly.
632
+ */
633
+ historyMetaItem(request: {
634
+ type: string;
635
+ name: string;
636
+ organizationId?: string;
637
+ sinceSeq?: number;
638
+ limit?: number;
639
+ }): Promise<{
640
+ events: _objectstack_metadata_core.MetadataEvent[];
572
641
  }>;
573
642
  /**
574
643
  * Remove a customization overlay row for the given metadata item, so the
@@ -580,10 +649,13 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
580
649
  type: string;
581
650
  name: string;
582
651
  organizationId?: string;
652
+ parentVersion?: string | null;
653
+ actor?: string;
583
654
  }): Promise<{
584
655
  success: boolean;
585
656
  message?: string;
586
657
  reset?: boolean;
658
+ seq?: number;
587
659
  }>;
588
660
  /**
589
661
  * Hydrate SchemaRegistry from the database on startup.
@@ -614,6 +686,141 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
614
686
  feedUnsubscribe(request: any): Promise<any>;
615
687
  }
616
688
 
689
+ /**
690
+ * Sub-set of the ObjectQL engine shape we depend on. Kept narrow so
691
+ * tests can stub it with a plain mock. Mirrors the real engine's
692
+ * `options.context` pattern so transactions can thread through.
693
+ */
694
+ interface SysMetadataEngine {
695
+ find(table: string, options: {
696
+ where: Record<string, unknown>;
697
+ limit?: number;
698
+ orderBy?: any;
699
+ context?: any;
700
+ }): Promise<any[]>;
701
+ findOne(table: string, options: {
702
+ where: Record<string, unknown>;
703
+ context?: any;
704
+ }): Promise<any | null>;
705
+ insert(table: string, data: Record<string, unknown>, options?: {
706
+ context?: any;
707
+ }): Promise<{
708
+ id: string;
709
+ }>;
710
+ update(table: string, data: Record<string, unknown>, options: {
711
+ where: Record<string, unknown>;
712
+ context?: any;
713
+ }): Promise<{
714
+ id: string;
715
+ }>;
716
+ delete(table: string, options: {
717
+ where: Record<string, unknown>;
718
+ context?: any;
719
+ }): Promise<{
720
+ deleted: number;
721
+ }>;
722
+ /**
723
+ * Optional. Falls through to direct callback invocation if the
724
+ * underlying driver lacks ACID support (matches the real
725
+ * `ObjectQL.transaction` semantics). Repository code must not rely on
726
+ * rollback for correctness against in-memory drivers.
727
+ */
728
+ transaction?<T>(callback: (trxCtx: any) => Promise<T>, baseContext?: any): Promise<T>;
729
+ }
730
+ interface SysMetadataRepositoryOptions {
731
+ engine: SysMetadataEngine;
732
+ /**
733
+ * Tenancy scope. `null` writes to env-wide overlay rows; a string
734
+ * scopes to one organization (the supported shared-DB tenant model
735
+ * — see ADR-0005 amendment).
736
+ */
737
+ organizationId?: string | null;
738
+ /** Org label embedded in returned MetaRefs. Defaults to organizationId or `"system"`. */
739
+ orgLabel?: string;
740
+ }
741
+ declare class SysMetadataRepository implements MetadataRepository {
742
+ private readonly engine;
743
+ private readonly organizationId;
744
+ private readonly orgLabel;
745
+ /**
746
+ * Local seq counter for in-memory watch() event broadcasts. Mirrors
747
+ * the durable `event_seq` we write into `sys_metadata_history` on
748
+ * each successful put/delete — assigned AFTER the transaction commits
749
+ * so we never broadcast events that got rolled back.
750
+ */
751
+ private seqCounter;
752
+ private readonly watchers;
753
+ private closed;
754
+ /** Table name for the durable event log. */
755
+ private readonly historyTable;
756
+ constructor(opts: SysMetadataRepositoryOptions);
757
+ /**
758
+ * Run `cb` inside `engine.transaction(...)` if the engine supports it,
759
+ * otherwise fall through to a direct call. Matches the real
760
+ * `ObjectQL.transaction` semantics — in-memory drivers (and our test
761
+ * fakes) get no rollback, which is acceptable because production
762
+ * always runs on a SQL driver with real ACID.
763
+ */
764
+ private withTxn;
765
+ /**
766
+ * Read the current overlay row. Returns null if no row exists —
767
+ * callers (e.g. LayeredRepository) fall through to lower layers.
768
+ */
769
+ get(ref: MetaRef): Promise<MetadataItem | null>;
770
+ /**
771
+ * Resolve a historical version by content hash (ADR-0009).
772
+ *
773
+ * Looks up `sys_metadata_history` by `(organization_id, type, name,
774
+ * checksum)`. Returns null if no row matches. `executionPinned` types
775
+ * are guaranteed to find their body here because history GC skips
776
+ * them.
777
+ */
778
+ getByHash(ref: MetaRef, hash: string): Promise<MetadataItem | null>;
779
+ put(ref: MetaRef, spec: unknown, opts: PutOptions): Promise<PutResult>;
780
+ delete(ref: MetaRef, opts: DeleteOptions): Promise<DeleteResult>;
781
+ list(filter: ListFilter): AsyncIterable<MetadataItemHeader>;
782
+ /**
783
+ * Yield every history event for `(org, type?, name?)` from the
784
+ * durable log, ordered by per-(type,name) `version` ascending. When
785
+ * `filter.type`/`filter.name` are unset the consumer gets the full
786
+ * org-scoped event stream — still ordered by version within each
787
+ * (type,name) bucket, then by `recorded_at` across buckets (we sort
788
+ * client-side because the test engine doesn't honor `orderBy`).
789
+ */
790
+ history(ref: MetaRef, opts?: HistoryOptions): AsyncIterable<MetadataEvent>;
791
+ /**
792
+ * Live event stream. Fires for every successful put/delete on THIS
793
+ * instance — cross-replica fan-out is M1. Manual AsyncIterator (not
794
+ * an async generator) so we can deterministically tear down via
795
+ * `iter.return()`, matching the pattern used by InMemoryRepository.
796
+ */
797
+ watch(filter: WatchFilter, since?: number): AsyncIterable<MetadataEvent>;
798
+ /** Shut down all watch iterators. */
799
+ close(): void;
800
+ private assertOpen;
801
+ private assertAllowed;
802
+ private whereFor;
803
+ private fullRef;
804
+ private rowToItem;
805
+ private broadcast;
806
+ private matchesFilter;
807
+ /**
808
+ * Per-org monotonic event sequence. Reads `MAX(event_seq) + 1` from
809
+ * `sys_metadata_history` scoped by `organization_id`. MUST be called
810
+ * inside a transaction (the only caller is the put/delete txn body) —
811
+ * concurrent writers in the same org race otherwise.
812
+ */
813
+ private nextEventSeq;
814
+ /**
815
+ * Per-(org,type,name) lineage counter. Reads from history (not from
816
+ * `sys_metadata.version`) so delete + recreate continues incrementing
817
+ * instead of restarting at 1.
818
+ */
819
+ private nextItemVersion;
820
+ /** Lightweight UUID-ish id for history rows; sufficient for an audit log. */
821
+ private uuid;
822
+ }
823
+
617
824
  type HookHandler = (context: HookContext) => Promise<void> | void;
618
825
  /**
619
826
  * Per-object hook entry with priority support
@@ -1515,9 +1722,25 @@ declare class ObjectQLPlugin implements Plugin {
1515
1722
  private hostContext?;
1516
1723
  private projectId?;
1517
1724
  private skipSchemaSync;
1725
+ /** Unsubscribe handles for metadata-event subscriptions (ADR-0008 PR-7). */
1726
+ private metadataUnsubscribes;
1518
1727
  constructor(qlOrOptions?: ObjectQL | ObjectQLPluginOptions, hostContext?: Record<string, any>);
1519
1728
  init: (ctx: PluginContext) => Promise<void>;
1520
1729
  start: (ctx: PluginContext) => Promise<void>;
1730
+ stop: (ctx: PluginContext) => Promise<void>;
1731
+ /**
1732
+ * Subscribe to `object` metadata events from the metadata service and
1733
+ * invalidate the SchemaRegistry merge cache on each event (ADR-0008
1734
+ * PR-7). For create/update we also re-load the affected object from
1735
+ * the metadata service so subsequent reads see the new definition;
1736
+ * for delete we unregister it from every contributing package.
1737
+ *
1738
+ * Events are filtered to the canonical `object` type — view/dashboard
1739
+ * /flow edits go through their own consumers (Studio SSE, REST cache).
1740
+ *
1741
+ * Stored unsubscribe handle is invoked from {@link stop}.
1742
+ */
1743
+ private subscribeToMetadataEvents;
1521
1744
  /**
1522
1745
  * Register built-in audit hooks for auto-stamping created_by/updated_by
1523
1746
  * and fetching previousData for update/delete operations. These are
@@ -1698,4 +1921,4 @@ declare function convertIntrospectedSchemaToObjects(introspectedSchema: Introspe
1698
1921
  skipSystemColumns?: boolean;
1699
1922
  }): ServiceObject[];
1700
1923
 
1701
- export { type BindHooksOptions, type BindHooksResult, DEFAULT_EXTENDER_PRIORITY, DEFAULT_OWNER_PRIORITY, type EngineMiddleware, type FieldValidationError, type HookEntry, type HookHandler, type HookMetricLabel, type HookMetricOutcome, type HookMetricsRecorder, type HookSkipReason, InMemoryHookMetricsRecorder, type IntrospectedColumn, type IntrospectedForeignKey, type IntrospectedSchema, type IntrospectedTable, MetadataFacade, type ObjectContributor, ObjectQL, type ObjectQLHostContext, type ObjectQLKernelOptions, ObjectQLPlugin, ObjectRepository, ObjectStackProtocolImplementation, type OperationContext, RESERVED_NAMESPACES, SchemaRegistry, type SchemaRegistryOptions, ScopedContext, ValidationError, type WrapDeclarativeOptions, applyInMemoryAggregation, applySystemFields, bindHooksToEngine, bucketDateValue, computeFQN, convertIntrospectedSchemaToObjects, createObjectQLKernel, noopHookMetricsRecorder, parseFQN, toTitleCase, validateRecord, wrapDeclarativeHook };
1924
+ export { type BindHooksOptions, type BindHooksResult, DEFAULT_EXTENDER_PRIORITY, DEFAULT_OWNER_PRIORITY, type EngineMiddleware, type FieldValidationError, type HookEntry, type HookHandler, type HookMetricLabel, type HookMetricOutcome, type HookMetricsRecorder, type HookSkipReason, InMemoryHookMetricsRecorder, type IntrospectedColumn, type IntrospectedForeignKey, type IntrospectedSchema, type IntrospectedTable, MetadataFacade, type ObjectContributor, ObjectQL, type ObjectQLHostContext, type ObjectQLKernelOptions, ObjectQLPlugin, ObjectRepository, ObjectStackProtocolImplementation, type OperationContext, RESERVED_NAMESPACES, SchemaRegistry, type SchemaRegistryOptions, ScopedContext, type SysMetadataEngine, SysMetadataRepository, type SysMetadataRepositoryOptions, ValidationError, type WrapDeclarativeOptions, applyInMemoryAggregation, applySystemFields, bindHooksToEngine, bucketDateValue, computeFQN, convertIntrospectedSchemaToObjects, createObjectQLKernel, noopHookMetricsRecorder, parseFQN, toTitleCase, validateRecord, wrapDeclarativeHook };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { ServiceObject, ObjectOwnership, HookContext, QueryAST, EngineQueryOptions, DataEngineInsertOptions, EngineUpdateOptions, EngineDeleteOptions, EngineCountOptions, EngineAggregateOptions, DateGranularityValue, Hook } from '@objectstack/spec/data';
2
2
  import { ObjectStackManifest, InstalledPackage, ExecutionContext } from '@objectstack/spec/kernel';
3
+ import * as _objectstack_metadata_core from '@objectstack/metadata-core';
4
+ import { MetadataRepository, MetaRef, MetadataItem, PutOptions, PutResult, DeleteOptions, DeleteResult, ListFilter, MetadataItemHeader, HistoryOptions, MetadataEvent, WatchFilter } from '@objectstack/metadata-core';
3
5
  import { ObjectStackProtocol, MetadataCacheRequest, MetadataCacheResponse, BatchUpdateRequest, BatchUpdateResponse, UpdateManyDataRequest, DeleteManyDataRequest } from '@objectstack/spec/api';
4
6
  import { IDataEngine, DriverInterface, Logger, Plugin, PluginContext, ObjectKernel } from '@objectstack/core';
5
7
  import { IFeedService, IRealtimeService } from '@objectstack/spec/contracts';
@@ -244,6 +246,21 @@ declare class SchemaRegistry {
244
246
  id: string;
245
247
  globs: string[];
246
248
  }[];
249
+ /**
250
+ * Invalidate the merged-schema cache for a single FQN (or short name).
251
+ *
252
+ * Call this from event-driven consumers (ADR-0008 M0 PR-7) when an
253
+ * upstream metadata change makes the cached merged definition stale.
254
+ * The contributor list is preserved — only the cached merge result is
255
+ * dropped, so the next `resolveObject(fqn)` recomputes from scratch.
256
+ *
257
+ * Accepts either an FQN (`acme__contact`) or a bare short name
258
+ * (`contact`); for the latter, all entries whose suffix matches the
259
+ * name are invalidated.
260
+ */
261
+ invalidate(fqnOrName: string): void;
262
+ /** Drop every entry from the merged-schema cache. */
263
+ invalidateAll(): void;
247
264
  /**
248
265
  * Clear all registry state. Use only for testing.
249
266
  */
@@ -263,7 +280,20 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
263
280
  * metadata even if several projects share the same physical database.
264
281
  */
265
282
  private projectId?;
283
+ /**
284
+ * Lazily-instantiated SysMetadataRepository per organization. Keyed by
285
+ * `${organizationId ?? '__env__'}`. Repositories are stateful — they
286
+ * carry the per-org `seqCounter` and watch subscribers — so we cache
287
+ * them rather than constructing one per call.
288
+ */
289
+ private overlayRepos;
266
290
  constructor(engine: IDataEngine, getServicesRegistry?: () => Map<string, any>, getFeedService?: () => IFeedService | undefined, projectId?: string);
291
+ /**
292
+ * Lazily obtain a SysMetadataRepository for the given organization.
293
+ * Env-wide overlays (organizationId == null) share a singleton under
294
+ * the `__env__` key.
295
+ */
296
+ private getOverlayRepo;
267
297
  /**
268
298
  * One-time guard for ensuring the overlay-uniqueness UNIQUE INDEX exists
269
299
  * on `sys_metadata`. ADR-0005: scopes overlays by
@@ -561,14 +591,53 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
561
591
  private static readonly OVERLAY_ALLOWED_TYPES;
562
592
  /** Normalize plural→singular before consulting the allow-list. */
563
593
  private static isOverlayAllowed;
594
+ /**
595
+ * Mirror an object-type overlay write into the in-memory engine
596
+ * registry so subsequent CRUD finds the new schema. Idempotent and
597
+ * safe to call after a successful persistence call. For the legacy
598
+ * write path this is invoked BEFORE persistence (historical behavior
599
+ * preserved); for the PR-10d.3 repository path it is invoked only
600
+ * AFTER `put()` resolves successfully, so a failed write — DB error,
601
+ * optimistic-lock conflict, validation failure — never leaks a
602
+ * stale schema into the registry.
603
+ */
604
+ private applyObjectRegistryMutation;
564
605
  saveMetaItem(request: {
565
606
  type: string;
566
607
  name: string;
567
608
  item?: any;
568
609
  organizationId?: string;
610
+ parentVersion?: string | null;
611
+ actor?: string;
569
612
  }): Promise<{
570
613
  success: boolean;
614
+ version: string;
615
+ seq: number;
571
616
  message: string;
617
+ } | {
618
+ success: boolean;
619
+ message: string;
620
+ version?: undefined;
621
+ seq?: undefined;
622
+ }>;
623
+ /**
624
+ * Yield the durable change-log for a single metadata item — every
625
+ * put/delete recorded in `sys_metadata_history` for `(org, type, name)`,
626
+ * in event_seq order. Powers the Studio "History" tab and any
627
+ * client-side audit timeline.
628
+ *
629
+ * Returns `[]` for non-overlay-allowed types (the legacy raw-engine
630
+ * path doesn't record history) instead of throwing — callers can treat
631
+ * "no history" uniformly.
632
+ */
633
+ historyMetaItem(request: {
634
+ type: string;
635
+ name: string;
636
+ organizationId?: string;
637
+ sinceSeq?: number;
638
+ limit?: number;
639
+ }): Promise<{
640
+ events: _objectstack_metadata_core.MetadataEvent[];
572
641
  }>;
573
642
  /**
574
643
  * Remove a customization overlay row for the given metadata item, so the
@@ -580,10 +649,13 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
580
649
  type: string;
581
650
  name: string;
582
651
  organizationId?: string;
652
+ parentVersion?: string | null;
653
+ actor?: string;
583
654
  }): Promise<{
584
655
  success: boolean;
585
656
  message?: string;
586
657
  reset?: boolean;
658
+ seq?: number;
587
659
  }>;
588
660
  /**
589
661
  * Hydrate SchemaRegistry from the database on startup.
@@ -614,6 +686,141 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
614
686
  feedUnsubscribe(request: any): Promise<any>;
615
687
  }
616
688
 
689
+ /**
690
+ * Sub-set of the ObjectQL engine shape we depend on. Kept narrow so
691
+ * tests can stub it with a plain mock. Mirrors the real engine's
692
+ * `options.context` pattern so transactions can thread through.
693
+ */
694
+ interface SysMetadataEngine {
695
+ find(table: string, options: {
696
+ where: Record<string, unknown>;
697
+ limit?: number;
698
+ orderBy?: any;
699
+ context?: any;
700
+ }): Promise<any[]>;
701
+ findOne(table: string, options: {
702
+ where: Record<string, unknown>;
703
+ context?: any;
704
+ }): Promise<any | null>;
705
+ insert(table: string, data: Record<string, unknown>, options?: {
706
+ context?: any;
707
+ }): Promise<{
708
+ id: string;
709
+ }>;
710
+ update(table: string, data: Record<string, unknown>, options: {
711
+ where: Record<string, unknown>;
712
+ context?: any;
713
+ }): Promise<{
714
+ id: string;
715
+ }>;
716
+ delete(table: string, options: {
717
+ where: Record<string, unknown>;
718
+ context?: any;
719
+ }): Promise<{
720
+ deleted: number;
721
+ }>;
722
+ /**
723
+ * Optional. Falls through to direct callback invocation if the
724
+ * underlying driver lacks ACID support (matches the real
725
+ * `ObjectQL.transaction` semantics). Repository code must not rely on
726
+ * rollback for correctness against in-memory drivers.
727
+ */
728
+ transaction?<T>(callback: (trxCtx: any) => Promise<T>, baseContext?: any): Promise<T>;
729
+ }
730
+ interface SysMetadataRepositoryOptions {
731
+ engine: SysMetadataEngine;
732
+ /**
733
+ * Tenancy scope. `null` writes to env-wide overlay rows; a string
734
+ * scopes to one organization (the supported shared-DB tenant model
735
+ * — see ADR-0005 amendment).
736
+ */
737
+ organizationId?: string | null;
738
+ /** Org label embedded in returned MetaRefs. Defaults to organizationId or `"system"`. */
739
+ orgLabel?: string;
740
+ }
741
+ declare class SysMetadataRepository implements MetadataRepository {
742
+ private readonly engine;
743
+ private readonly organizationId;
744
+ private readonly orgLabel;
745
+ /**
746
+ * Local seq counter for in-memory watch() event broadcasts. Mirrors
747
+ * the durable `event_seq` we write into `sys_metadata_history` on
748
+ * each successful put/delete — assigned AFTER the transaction commits
749
+ * so we never broadcast events that got rolled back.
750
+ */
751
+ private seqCounter;
752
+ private readonly watchers;
753
+ private closed;
754
+ /** Table name for the durable event log. */
755
+ private readonly historyTable;
756
+ constructor(opts: SysMetadataRepositoryOptions);
757
+ /**
758
+ * Run `cb` inside `engine.transaction(...)` if the engine supports it,
759
+ * otherwise fall through to a direct call. Matches the real
760
+ * `ObjectQL.transaction` semantics — in-memory drivers (and our test
761
+ * fakes) get no rollback, which is acceptable because production
762
+ * always runs on a SQL driver with real ACID.
763
+ */
764
+ private withTxn;
765
+ /**
766
+ * Read the current overlay row. Returns null if no row exists —
767
+ * callers (e.g. LayeredRepository) fall through to lower layers.
768
+ */
769
+ get(ref: MetaRef): Promise<MetadataItem | null>;
770
+ /**
771
+ * Resolve a historical version by content hash (ADR-0009).
772
+ *
773
+ * Looks up `sys_metadata_history` by `(organization_id, type, name,
774
+ * checksum)`. Returns null if no row matches. `executionPinned` types
775
+ * are guaranteed to find their body here because history GC skips
776
+ * them.
777
+ */
778
+ getByHash(ref: MetaRef, hash: string): Promise<MetadataItem | null>;
779
+ put(ref: MetaRef, spec: unknown, opts: PutOptions): Promise<PutResult>;
780
+ delete(ref: MetaRef, opts: DeleteOptions): Promise<DeleteResult>;
781
+ list(filter: ListFilter): AsyncIterable<MetadataItemHeader>;
782
+ /**
783
+ * Yield every history event for `(org, type?, name?)` from the
784
+ * durable log, ordered by per-(type,name) `version` ascending. When
785
+ * `filter.type`/`filter.name` are unset the consumer gets the full
786
+ * org-scoped event stream — still ordered by version within each
787
+ * (type,name) bucket, then by `recorded_at` across buckets (we sort
788
+ * client-side because the test engine doesn't honor `orderBy`).
789
+ */
790
+ history(ref: MetaRef, opts?: HistoryOptions): AsyncIterable<MetadataEvent>;
791
+ /**
792
+ * Live event stream. Fires for every successful put/delete on THIS
793
+ * instance — cross-replica fan-out is M1. Manual AsyncIterator (not
794
+ * an async generator) so we can deterministically tear down via
795
+ * `iter.return()`, matching the pattern used by InMemoryRepository.
796
+ */
797
+ watch(filter: WatchFilter, since?: number): AsyncIterable<MetadataEvent>;
798
+ /** Shut down all watch iterators. */
799
+ close(): void;
800
+ private assertOpen;
801
+ private assertAllowed;
802
+ private whereFor;
803
+ private fullRef;
804
+ private rowToItem;
805
+ private broadcast;
806
+ private matchesFilter;
807
+ /**
808
+ * Per-org monotonic event sequence. Reads `MAX(event_seq) + 1` from
809
+ * `sys_metadata_history` scoped by `organization_id`. MUST be called
810
+ * inside a transaction (the only caller is the put/delete txn body) —
811
+ * concurrent writers in the same org race otherwise.
812
+ */
813
+ private nextEventSeq;
814
+ /**
815
+ * Per-(org,type,name) lineage counter. Reads from history (not from
816
+ * `sys_metadata.version`) so delete + recreate continues incrementing
817
+ * instead of restarting at 1.
818
+ */
819
+ private nextItemVersion;
820
+ /** Lightweight UUID-ish id for history rows; sufficient for an audit log. */
821
+ private uuid;
822
+ }
823
+
617
824
  type HookHandler = (context: HookContext) => Promise<void> | void;
618
825
  /**
619
826
  * Per-object hook entry with priority support
@@ -1515,9 +1722,25 @@ declare class ObjectQLPlugin implements Plugin {
1515
1722
  private hostContext?;
1516
1723
  private projectId?;
1517
1724
  private skipSchemaSync;
1725
+ /** Unsubscribe handles for metadata-event subscriptions (ADR-0008 PR-7). */
1726
+ private metadataUnsubscribes;
1518
1727
  constructor(qlOrOptions?: ObjectQL | ObjectQLPluginOptions, hostContext?: Record<string, any>);
1519
1728
  init: (ctx: PluginContext) => Promise<void>;
1520
1729
  start: (ctx: PluginContext) => Promise<void>;
1730
+ stop: (ctx: PluginContext) => Promise<void>;
1731
+ /**
1732
+ * Subscribe to `object` metadata events from the metadata service and
1733
+ * invalidate the SchemaRegistry merge cache on each event (ADR-0008
1734
+ * PR-7). For create/update we also re-load the affected object from
1735
+ * the metadata service so subsequent reads see the new definition;
1736
+ * for delete we unregister it from every contributing package.
1737
+ *
1738
+ * Events are filtered to the canonical `object` type — view/dashboard
1739
+ * /flow edits go through their own consumers (Studio SSE, REST cache).
1740
+ *
1741
+ * Stored unsubscribe handle is invoked from {@link stop}.
1742
+ */
1743
+ private subscribeToMetadataEvents;
1521
1744
  /**
1522
1745
  * Register built-in audit hooks for auto-stamping created_by/updated_by
1523
1746
  * and fetching previousData for update/delete operations. These are
@@ -1698,4 +1921,4 @@ declare function convertIntrospectedSchemaToObjects(introspectedSchema: Introspe
1698
1921
  skipSystemColumns?: boolean;
1699
1922
  }): ServiceObject[];
1700
1923
 
1701
- export { type BindHooksOptions, type BindHooksResult, DEFAULT_EXTENDER_PRIORITY, DEFAULT_OWNER_PRIORITY, type EngineMiddleware, type FieldValidationError, type HookEntry, type HookHandler, type HookMetricLabel, type HookMetricOutcome, type HookMetricsRecorder, type HookSkipReason, InMemoryHookMetricsRecorder, type IntrospectedColumn, type IntrospectedForeignKey, type IntrospectedSchema, type IntrospectedTable, MetadataFacade, type ObjectContributor, ObjectQL, type ObjectQLHostContext, type ObjectQLKernelOptions, ObjectQLPlugin, ObjectRepository, ObjectStackProtocolImplementation, type OperationContext, RESERVED_NAMESPACES, SchemaRegistry, type SchemaRegistryOptions, ScopedContext, ValidationError, type WrapDeclarativeOptions, applyInMemoryAggregation, applySystemFields, bindHooksToEngine, bucketDateValue, computeFQN, convertIntrospectedSchemaToObjects, createObjectQLKernel, noopHookMetricsRecorder, parseFQN, toTitleCase, validateRecord, wrapDeclarativeHook };
1924
+ export { type BindHooksOptions, type BindHooksResult, DEFAULT_EXTENDER_PRIORITY, DEFAULT_OWNER_PRIORITY, type EngineMiddleware, type FieldValidationError, type HookEntry, type HookHandler, type HookMetricLabel, type HookMetricOutcome, type HookMetricsRecorder, type HookSkipReason, InMemoryHookMetricsRecorder, type IntrospectedColumn, type IntrospectedForeignKey, type IntrospectedSchema, type IntrospectedTable, MetadataFacade, type ObjectContributor, ObjectQL, type ObjectQLHostContext, type ObjectQLKernelOptions, ObjectQLPlugin, ObjectRepository, ObjectStackProtocolImplementation, type OperationContext, RESERVED_NAMESPACES, SchemaRegistry, type SchemaRegistryOptions, ScopedContext, type SysMetadataEngine, SysMetadataRepository, type SysMetadataRepositoryOptions, ValidationError, type WrapDeclarativeOptions, applyInMemoryAggregation, applySystemFields, bindHooksToEngine, bucketDateValue, computeFQN, convertIntrospectedSchemaToObjects, createObjectQLKernel, noopHookMetricsRecorder, parseFQN, toTitleCase, validateRecord, wrapDeclarativeHook };