@objectstack/objectql 4.1.1 → 5.0.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 +237 -1
- package/dist/index.d.ts +237 -1
- package/dist/index.js +833 -19
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +830 -17
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -5
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
|
|
@@ -418,6 +448,7 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
|
|
|
418
448
|
object: string;
|
|
419
449
|
id: string;
|
|
420
450
|
data: any;
|
|
451
|
+
expectedVersion?: string;
|
|
421
452
|
context?: any;
|
|
422
453
|
}): Promise<{
|
|
423
454
|
object: string;
|
|
@@ -427,12 +458,33 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
|
|
|
427
458
|
deleteData(request: {
|
|
428
459
|
object: string;
|
|
429
460
|
id: string;
|
|
461
|
+
expectedVersion?: string;
|
|
430
462
|
context?: any;
|
|
431
463
|
}): Promise<{
|
|
432
464
|
object: string;
|
|
433
465
|
id: string;
|
|
434
466
|
success: boolean;
|
|
435
467
|
}>;
|
|
468
|
+
/**
|
|
469
|
+
* Optimistic Concurrency Control gate shared by updateData/deleteData.
|
|
470
|
+
*
|
|
471
|
+
* When the caller passes a non-empty `expectedVersion` token (typically
|
|
472
|
+
* the `updated_at` value they read), this fetches the current record
|
|
473
|
+
* and compares its `updated_at` against the token. Mismatch → throw
|
|
474
|
+
* `ConcurrentUpdateError` which the REST layer maps to 409.
|
|
475
|
+
*
|
|
476
|
+
* Behaviour:
|
|
477
|
+
* - Empty/missing token → no check (opt-in semantics; existing callers
|
|
478
|
+
* that haven't yet adopted OCC are unaffected).
|
|
479
|
+
* - Record not found → no check; downstream `engine.update` will
|
|
480
|
+
* surface the usual `RECORD_NOT_FOUND` 404. We intentionally do not
|
|
481
|
+
* treat "missing record" as a concurrency conflict.
|
|
482
|
+
* - Record has no `updated_at` field (timestamps disabled) → no check.
|
|
483
|
+
* Logging would be noisy here; OCC is opt-in and the absence of a
|
|
484
|
+
* version column is an explicit "this object doesn't support OCC"
|
|
485
|
+
* signal.
|
|
486
|
+
*/
|
|
487
|
+
private assertVersionMatch;
|
|
436
488
|
/**
|
|
437
489
|
* Cross-object substring search across all registered objects that opt in
|
|
438
490
|
* via `enable.searchable !== false` and `enable.apiEnabled !== false`.
|
|
@@ -539,14 +591,53 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
|
|
|
539
591
|
private static readonly OVERLAY_ALLOWED_TYPES;
|
|
540
592
|
/** Normalize plural→singular before consulting the allow-list. */
|
|
541
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;
|
|
542
605
|
saveMetaItem(request: {
|
|
543
606
|
type: string;
|
|
544
607
|
name: string;
|
|
545
608
|
item?: any;
|
|
546
609
|
organizationId?: string;
|
|
610
|
+
parentVersion?: string | null;
|
|
611
|
+
actor?: string;
|
|
547
612
|
}): Promise<{
|
|
548
613
|
success: boolean;
|
|
614
|
+
version: string;
|
|
615
|
+
seq: number;
|
|
549
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[];
|
|
550
641
|
}>;
|
|
551
642
|
/**
|
|
552
643
|
* Remove a customization overlay row for the given metadata item, so the
|
|
@@ -558,10 +649,13 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
|
|
|
558
649
|
type: string;
|
|
559
650
|
name: string;
|
|
560
651
|
organizationId?: string;
|
|
652
|
+
parentVersion?: string | null;
|
|
653
|
+
actor?: string;
|
|
561
654
|
}): Promise<{
|
|
562
655
|
success: boolean;
|
|
563
656
|
message?: string;
|
|
564
657
|
reset?: boolean;
|
|
658
|
+
seq?: number;
|
|
565
659
|
}>;
|
|
566
660
|
/**
|
|
567
661
|
* Hydrate SchemaRegistry from the database on startup.
|
|
@@ -592,6 +686,132 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
|
|
|
592
686
|
feedUnsubscribe(request: any): Promise<any>;
|
|
593
687
|
}
|
|
594
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
|
+
put(ref: MetaRef, spec: unknown, opts: PutOptions): Promise<PutResult>;
|
|
771
|
+
delete(ref: MetaRef, opts: DeleteOptions): Promise<DeleteResult>;
|
|
772
|
+
list(filter: ListFilter): AsyncIterable<MetadataItemHeader>;
|
|
773
|
+
/**
|
|
774
|
+
* Yield every history event for `(org, type?, name?)` from the
|
|
775
|
+
* durable log, ordered by per-(type,name) `version` ascending. When
|
|
776
|
+
* `filter.type`/`filter.name` are unset the consumer gets the full
|
|
777
|
+
* org-scoped event stream — still ordered by version within each
|
|
778
|
+
* (type,name) bucket, then by `recorded_at` across buckets (we sort
|
|
779
|
+
* client-side because the test engine doesn't honor `orderBy`).
|
|
780
|
+
*/
|
|
781
|
+
history(ref: MetaRef, opts?: HistoryOptions): AsyncIterable<MetadataEvent>;
|
|
782
|
+
/**
|
|
783
|
+
* Live event stream. Fires for every successful put/delete on THIS
|
|
784
|
+
* instance — cross-replica fan-out is M1. Manual AsyncIterator (not
|
|
785
|
+
* an async generator) so we can deterministically tear down via
|
|
786
|
+
* `iter.return()`, matching the pattern used by InMemoryRepository.
|
|
787
|
+
*/
|
|
788
|
+
watch(filter: WatchFilter, since?: number): AsyncIterable<MetadataEvent>;
|
|
789
|
+
/** Shut down all watch iterators. */
|
|
790
|
+
close(): void;
|
|
791
|
+
private assertOpen;
|
|
792
|
+
private assertAllowed;
|
|
793
|
+
private whereFor;
|
|
794
|
+
private fullRef;
|
|
795
|
+
private rowToItem;
|
|
796
|
+
private broadcast;
|
|
797
|
+
private matchesFilter;
|
|
798
|
+
/**
|
|
799
|
+
* Per-org monotonic event sequence. Reads `MAX(event_seq) + 1` from
|
|
800
|
+
* `sys_metadata_history` scoped by `organization_id`. MUST be called
|
|
801
|
+
* inside a transaction (the only caller is the put/delete txn body) —
|
|
802
|
+
* concurrent writers in the same org race otherwise.
|
|
803
|
+
*/
|
|
804
|
+
private nextEventSeq;
|
|
805
|
+
/**
|
|
806
|
+
* Per-(org,type,name) lineage counter. Reads from history (not from
|
|
807
|
+
* `sys_metadata.version`) so delete + recreate continues incrementing
|
|
808
|
+
* instead of restarting at 1.
|
|
809
|
+
*/
|
|
810
|
+
private nextItemVersion;
|
|
811
|
+
/** Lightweight UUID-ish id for history rows; sufficient for an audit log. */
|
|
812
|
+
private uuid;
|
|
813
|
+
}
|
|
814
|
+
|
|
595
815
|
type HookHandler = (context: HookContext) => Promise<void> | void;
|
|
596
816
|
/**
|
|
597
817
|
* Per-object hook entry with priority support
|
|
@@ -1493,9 +1713,25 @@ declare class ObjectQLPlugin implements Plugin {
|
|
|
1493
1713
|
private hostContext?;
|
|
1494
1714
|
private projectId?;
|
|
1495
1715
|
private skipSchemaSync;
|
|
1716
|
+
/** Unsubscribe handles for metadata-event subscriptions (ADR-0008 PR-7). */
|
|
1717
|
+
private metadataUnsubscribes;
|
|
1496
1718
|
constructor(qlOrOptions?: ObjectQL | ObjectQLPluginOptions, hostContext?: Record<string, any>);
|
|
1497
1719
|
init: (ctx: PluginContext) => Promise<void>;
|
|
1498
1720
|
start: (ctx: PluginContext) => Promise<void>;
|
|
1721
|
+
stop: (ctx: PluginContext) => Promise<void>;
|
|
1722
|
+
/**
|
|
1723
|
+
* Subscribe to `object` metadata events from the metadata service and
|
|
1724
|
+
* invalidate the SchemaRegistry merge cache on each event (ADR-0008
|
|
1725
|
+
* PR-7). For create/update we also re-load the affected object from
|
|
1726
|
+
* the metadata service so subsequent reads see the new definition;
|
|
1727
|
+
* for delete we unregister it from every contributing package.
|
|
1728
|
+
*
|
|
1729
|
+
* Events are filtered to the canonical `object` type — view/dashboard
|
|
1730
|
+
* /flow edits go through their own consumers (Studio SSE, REST cache).
|
|
1731
|
+
*
|
|
1732
|
+
* Stored unsubscribe handle is invoked from {@link stop}.
|
|
1733
|
+
*/
|
|
1734
|
+
private subscribeToMetadataEvents;
|
|
1499
1735
|
/**
|
|
1500
1736
|
* Register built-in audit hooks for auto-stamping created_by/updated_by
|
|
1501
1737
|
* and fetching previousData for update/delete operations. These are
|
|
@@ -1676,4 +1912,4 @@ declare function convertIntrospectedSchemaToObjects(introspectedSchema: Introspe
|
|
|
1676
1912
|
skipSystemColumns?: boolean;
|
|
1677
1913
|
}): ServiceObject[];
|
|
1678
1914
|
|
|
1679
|
-
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 };
|
|
1915
|
+
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
|
|
@@ -418,6 +448,7 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
|
|
|
418
448
|
object: string;
|
|
419
449
|
id: string;
|
|
420
450
|
data: any;
|
|
451
|
+
expectedVersion?: string;
|
|
421
452
|
context?: any;
|
|
422
453
|
}): Promise<{
|
|
423
454
|
object: string;
|
|
@@ -427,12 +458,33 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
|
|
|
427
458
|
deleteData(request: {
|
|
428
459
|
object: string;
|
|
429
460
|
id: string;
|
|
461
|
+
expectedVersion?: string;
|
|
430
462
|
context?: any;
|
|
431
463
|
}): Promise<{
|
|
432
464
|
object: string;
|
|
433
465
|
id: string;
|
|
434
466
|
success: boolean;
|
|
435
467
|
}>;
|
|
468
|
+
/**
|
|
469
|
+
* Optimistic Concurrency Control gate shared by updateData/deleteData.
|
|
470
|
+
*
|
|
471
|
+
* When the caller passes a non-empty `expectedVersion` token (typically
|
|
472
|
+
* the `updated_at` value they read), this fetches the current record
|
|
473
|
+
* and compares its `updated_at` against the token. Mismatch → throw
|
|
474
|
+
* `ConcurrentUpdateError` which the REST layer maps to 409.
|
|
475
|
+
*
|
|
476
|
+
* Behaviour:
|
|
477
|
+
* - Empty/missing token → no check (opt-in semantics; existing callers
|
|
478
|
+
* that haven't yet adopted OCC are unaffected).
|
|
479
|
+
* - Record not found → no check; downstream `engine.update` will
|
|
480
|
+
* surface the usual `RECORD_NOT_FOUND` 404. We intentionally do not
|
|
481
|
+
* treat "missing record" as a concurrency conflict.
|
|
482
|
+
* - Record has no `updated_at` field (timestamps disabled) → no check.
|
|
483
|
+
* Logging would be noisy here; OCC is opt-in and the absence of a
|
|
484
|
+
* version column is an explicit "this object doesn't support OCC"
|
|
485
|
+
* signal.
|
|
486
|
+
*/
|
|
487
|
+
private assertVersionMatch;
|
|
436
488
|
/**
|
|
437
489
|
* Cross-object substring search across all registered objects that opt in
|
|
438
490
|
* via `enable.searchable !== false` and `enable.apiEnabled !== false`.
|
|
@@ -539,14 +591,53 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
|
|
|
539
591
|
private static readonly OVERLAY_ALLOWED_TYPES;
|
|
540
592
|
/** Normalize plural→singular before consulting the allow-list. */
|
|
541
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;
|
|
542
605
|
saveMetaItem(request: {
|
|
543
606
|
type: string;
|
|
544
607
|
name: string;
|
|
545
608
|
item?: any;
|
|
546
609
|
organizationId?: string;
|
|
610
|
+
parentVersion?: string | null;
|
|
611
|
+
actor?: string;
|
|
547
612
|
}): Promise<{
|
|
548
613
|
success: boolean;
|
|
614
|
+
version: string;
|
|
615
|
+
seq: number;
|
|
549
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[];
|
|
550
641
|
}>;
|
|
551
642
|
/**
|
|
552
643
|
* Remove a customization overlay row for the given metadata item, so the
|
|
@@ -558,10 +649,13 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
|
|
|
558
649
|
type: string;
|
|
559
650
|
name: string;
|
|
560
651
|
organizationId?: string;
|
|
652
|
+
parentVersion?: string | null;
|
|
653
|
+
actor?: string;
|
|
561
654
|
}): Promise<{
|
|
562
655
|
success: boolean;
|
|
563
656
|
message?: string;
|
|
564
657
|
reset?: boolean;
|
|
658
|
+
seq?: number;
|
|
565
659
|
}>;
|
|
566
660
|
/**
|
|
567
661
|
* Hydrate SchemaRegistry from the database on startup.
|
|
@@ -592,6 +686,132 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
|
|
|
592
686
|
feedUnsubscribe(request: any): Promise<any>;
|
|
593
687
|
}
|
|
594
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
|
+
put(ref: MetaRef, spec: unknown, opts: PutOptions): Promise<PutResult>;
|
|
771
|
+
delete(ref: MetaRef, opts: DeleteOptions): Promise<DeleteResult>;
|
|
772
|
+
list(filter: ListFilter): AsyncIterable<MetadataItemHeader>;
|
|
773
|
+
/**
|
|
774
|
+
* Yield every history event for `(org, type?, name?)` from the
|
|
775
|
+
* durable log, ordered by per-(type,name) `version` ascending. When
|
|
776
|
+
* `filter.type`/`filter.name` are unset the consumer gets the full
|
|
777
|
+
* org-scoped event stream — still ordered by version within each
|
|
778
|
+
* (type,name) bucket, then by `recorded_at` across buckets (we sort
|
|
779
|
+
* client-side because the test engine doesn't honor `orderBy`).
|
|
780
|
+
*/
|
|
781
|
+
history(ref: MetaRef, opts?: HistoryOptions): AsyncIterable<MetadataEvent>;
|
|
782
|
+
/**
|
|
783
|
+
* Live event stream. Fires for every successful put/delete on THIS
|
|
784
|
+
* instance — cross-replica fan-out is M1. Manual AsyncIterator (not
|
|
785
|
+
* an async generator) so we can deterministically tear down via
|
|
786
|
+
* `iter.return()`, matching the pattern used by InMemoryRepository.
|
|
787
|
+
*/
|
|
788
|
+
watch(filter: WatchFilter, since?: number): AsyncIterable<MetadataEvent>;
|
|
789
|
+
/** Shut down all watch iterators. */
|
|
790
|
+
close(): void;
|
|
791
|
+
private assertOpen;
|
|
792
|
+
private assertAllowed;
|
|
793
|
+
private whereFor;
|
|
794
|
+
private fullRef;
|
|
795
|
+
private rowToItem;
|
|
796
|
+
private broadcast;
|
|
797
|
+
private matchesFilter;
|
|
798
|
+
/**
|
|
799
|
+
* Per-org monotonic event sequence. Reads `MAX(event_seq) + 1` from
|
|
800
|
+
* `sys_metadata_history` scoped by `organization_id`. MUST be called
|
|
801
|
+
* inside a transaction (the only caller is the put/delete txn body) —
|
|
802
|
+
* concurrent writers in the same org race otherwise.
|
|
803
|
+
*/
|
|
804
|
+
private nextEventSeq;
|
|
805
|
+
/**
|
|
806
|
+
* Per-(org,type,name) lineage counter. Reads from history (not from
|
|
807
|
+
* `sys_metadata.version`) so delete + recreate continues incrementing
|
|
808
|
+
* instead of restarting at 1.
|
|
809
|
+
*/
|
|
810
|
+
private nextItemVersion;
|
|
811
|
+
/** Lightweight UUID-ish id for history rows; sufficient for an audit log. */
|
|
812
|
+
private uuid;
|
|
813
|
+
}
|
|
814
|
+
|
|
595
815
|
type HookHandler = (context: HookContext) => Promise<void> | void;
|
|
596
816
|
/**
|
|
597
817
|
* Per-object hook entry with priority support
|
|
@@ -1493,9 +1713,25 @@ declare class ObjectQLPlugin implements Plugin {
|
|
|
1493
1713
|
private hostContext?;
|
|
1494
1714
|
private projectId?;
|
|
1495
1715
|
private skipSchemaSync;
|
|
1716
|
+
/** Unsubscribe handles for metadata-event subscriptions (ADR-0008 PR-7). */
|
|
1717
|
+
private metadataUnsubscribes;
|
|
1496
1718
|
constructor(qlOrOptions?: ObjectQL | ObjectQLPluginOptions, hostContext?: Record<string, any>);
|
|
1497
1719
|
init: (ctx: PluginContext) => Promise<void>;
|
|
1498
1720
|
start: (ctx: PluginContext) => Promise<void>;
|
|
1721
|
+
stop: (ctx: PluginContext) => Promise<void>;
|
|
1722
|
+
/**
|
|
1723
|
+
* Subscribe to `object` metadata events from the metadata service and
|
|
1724
|
+
* invalidate the SchemaRegistry merge cache on each event (ADR-0008
|
|
1725
|
+
* PR-7). For create/update we also re-load the affected object from
|
|
1726
|
+
* the metadata service so subsequent reads see the new definition;
|
|
1727
|
+
* for delete we unregister it from every contributing package.
|
|
1728
|
+
*
|
|
1729
|
+
* Events are filtered to the canonical `object` type — view/dashboard
|
|
1730
|
+
* /flow edits go through their own consumers (Studio SSE, REST cache).
|
|
1731
|
+
*
|
|
1732
|
+
* Stored unsubscribe handle is invoked from {@link stop}.
|
|
1733
|
+
*/
|
|
1734
|
+
private subscribeToMetadataEvents;
|
|
1499
1735
|
/**
|
|
1500
1736
|
* Register built-in audit hooks for auto-stamping created_by/updated_by
|
|
1501
1737
|
* and fetching previousData for update/delete operations. These are
|
|
@@ -1676,4 +1912,4 @@ declare function convertIntrospectedSchemaToObjects(introspectedSchema: Introspe
|
|
|
1676
1912
|
skipSystemColumns?: boolean;
|
|
1677
1913
|
}): ServiceObject[];
|
|
1678
1914
|
|
|
1679
|
-
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 };
|
|
1915
|
+
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 };
|