@objectstack/objectql 7.0.0 → 7.2.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,7 +1,7 @@
1
1
  import { ServiceObject, ObjectOwnership, HookContext, QueryAST, EngineQueryOptions, DataEngineInsertOptions, EngineUpdateOptions, EngineDeleteOptions, EngineCountOptions, EngineAggregateOptions, DateGranularityValue, Hook } from '@objectstack/spec/data';
2
- import { ObjectStackManifest, InstalledPackage, ExecutionContext } from '@objectstack/spec/kernel';
2
+ import { ObjectStackManifest, InstalledPackage, MetadataValidationResult, MetadataLock, MetadataProvenance, ExecutionContext } from '@objectstack/spec/kernel';
3
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';
4
+ import { MetadataRepository, MetaRef, MetadataItem, PutOptions, PutResult, DeleteOptions, DeleteResult, MetadataWriteIntent, ListFilter, MetadataItemHeader, HistoryOptions, MetadataEvent, WatchFilter } from '@objectstack/metadata-core';
5
5
  import { ObjectStackProtocol, MetadataCacheRequest, MetadataCacheResponse, BatchUpdateRequest, BatchUpdateResponse, UpdateManyDataRequest, DeleteManyDataRequest } from '@objectstack/spec/api';
6
6
  import { IDataEngine, DriverInterface, Logger, Plugin, PluginContext, ObjectKernel } from '@objectstack/core';
7
7
  import { IFeedService, IRealtimeService } from '@objectstack/spec/contracts';
@@ -270,6 +270,12 @@ declare class SchemaRegistry {
270
270
  reset(): void;
271
271
  }
272
272
 
273
+ /**
274
+ * Re-export the canonical validation-result type so callers in this
275
+ * package don't need to dual-import from `@objectstack/spec/kernel`.
276
+ */
277
+ type MetadataDiagnostics = MetadataValidationResult;
278
+
273
279
  declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
274
280
  private engine;
275
281
  private getServicesRegistry?;
@@ -488,23 +494,82 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
488
494
  description?: string | undefined;
489
495
  }[];
490
496
  }>;
497
+ /**
498
+ * Sweep all (or filtered) metadata types and report entries that
499
+ * fail spec validation. Powers the Studio governance view
500
+ * (`GET /api/v1/meta/diagnostics`) and `os doctor`-style CLI
501
+ * checks.
502
+ *
503
+ * `severity` defaults to `'error'` — only entries with at least
504
+ * one Zod error issue are returned. `'warning'` includes
505
+ * everything we surface (warnings are reserved for a future lint
506
+ * layer on top of spec validation).
507
+ *
508
+ * `type` may be either a singular (`'view'`) or plural (`'views'`)
509
+ * identifier; the underlying `getMetaItems` already normalises.
510
+ *
511
+ * Implementation note: leverages the `_diagnostics` already
512
+ * decorated onto items by `getMetaItems()` to avoid running
513
+ * `safeParse()` twice. For types whose schema is unregistered we
514
+ * skip silently (they cannot be validated and should not appear
515
+ * as "valid" either — they are simply opaque to this report).
516
+ */
517
+ getMetaDiagnostics(request?: {
518
+ type?: string;
519
+ severity?: 'error' | 'warning';
520
+ organizationId?: string;
521
+ packageId?: string;
522
+ }): Promise<{
523
+ entries: Array<{
524
+ type: string;
525
+ name: string;
526
+ diagnostics: MetadataDiagnostics;
527
+ }>;
528
+ total: number;
529
+ scannedTypes: number;
530
+ scannedItems: number;
531
+ /**
532
+ * Per-type aggregate stats — count of items and the list of
533
+ * packages contributing to each type. Computed in the same
534
+ * sweep so the Studio directory page can render tile counts
535
+ * and a package filter in one round-trip.
536
+ */
537
+ stats: Record<string, {
538
+ count: number;
539
+ packages: string[];
540
+ }>;
541
+ }>;
491
542
  getMetaItems(request: {
492
543
  type: string;
493
544
  packageId?: string;
494
545
  organizationId?: string;
495
546
  }): Promise<{
496
547
  type: string;
497
- items: unknown[];
548
+ items: any[];
498
549
  }>;
499
550
  getMetaItem(request: {
500
551
  type: string;
501
552
  name: string;
502
553
  packageId?: string;
503
554
  organizationId?: string;
555
+ state?: 'active' | 'draft';
504
556
  }): Promise<{
557
+ type: string;
558
+ name: string;
559
+ item: {} | null;
560
+ } | {
561
+ editable: boolean;
562
+ deletable: boolean;
563
+ resettable: boolean;
564
+ packageVersion?: string | undefined;
565
+ packageId?: string | undefined;
566
+ provenance?: "package" | "env-forced" | "org" | undefined;
567
+ lockSource?: "artifact" | "package" | "env-forced" | undefined;
568
+ lockReason?: string | undefined;
505
569
  type: string;
506
570
  name: string;
507
571
  item: unknown;
572
+ lock: "full" | "none" | "no-overlay" | "no-delete";
508
573
  }>;
509
574
  /**
510
575
  * Phase 3a-layered-get: return the 3 layers of a metadata item
@@ -532,6 +597,58 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
532
597
  overlay: unknown | null;
533
598
  overlayScope: 'org' | 'env' | null;
534
599
  effective: unknown | null;
600
+ /**
601
+ * Load-time validation result for the effective payload — same
602
+ * shape attached to getMetaItems/getMetaItem by
603
+ * decorateMetadataItem. Undefined for types without a registered
604
+ * Zod schema (function/service/router). Lets the Studio edit
605
+ * page surface invalid-metadata banners + inline field errors
606
+ * without a second round-trip.
607
+ */
608
+ _diagnostics?: MetadataDiagnostics;
609
+ lock: MetadataLock;
610
+ lockReason?: string;
611
+ lockSource?: 'artifact' | 'package' | 'env-forced' | 'overlay';
612
+ provenance?: MetadataProvenance;
613
+ packageId?: string;
614
+ packageVersion?: string;
615
+ editable: boolean;
616
+ deletable: boolean;
617
+ resettable: boolean;
618
+ }>;
619
+ /**
620
+ * ADR-0010 §3.6 / Phase 4.1 — read the metadata-protection audit log
621
+ * for a single item. Returns the most-recent rows of
622
+ * `sys_metadata_audit` for this (type, name) tuple, sorted newest
623
+ * first. Refused (`denied`) and forced (`forced`) writes both appear
624
+ * here — they never reach the `history` endpoint, which only tracks
625
+ * successful body snapshots.
626
+ *
627
+ * The table is provisioned by `platform-objects` and is the
628
+ * compliance surface for the lock-enforcement story. When the
629
+ * environment has not yet provisioned the table (legacy install
630
+ * prior to ADR-0010) the call returns `{ events: [] }` instead of
631
+ * raising, keeping the Studio tab harmless.
632
+ */
633
+ auditMetaItem(request: {
634
+ type: string;
635
+ name: string;
636
+ organizationId?: string | null;
637
+ limit?: number;
638
+ }): Promise<{
639
+ events: Array<{
640
+ id: unknown;
641
+ occurredAt: string;
642
+ actor: string;
643
+ source: string | null;
644
+ operation: 'save' | 'publish' | 'rollback' | 'delete' | 'reset';
645
+ outcome: 'allowed' | 'denied' | 'forced';
646
+ code: string;
647
+ lockState: MetadataLock | null;
648
+ lockOverridden: boolean;
649
+ requestId: string | null;
650
+ note: string | null;
651
+ }>;
535
652
  }>;
536
653
  getUiView(request: {
537
654
  object: string;
@@ -565,7 +682,7 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
565
682
  label: string | undefined;
566
683
  required: boolean;
567
684
  readonly: boolean;
568
- type: "number" | "boolean" | "tags" | "date" | "file" | "code" | "datetime" | "signature" | "progress" | "url" | "text" | "textarea" | "email" | "phone" | "password" | "markdown" | "html" | "richtext" | "currency" | "percent" | "time" | "toggle" | "select" | "multiselect" | "radio" | "checkboxes" | "lookup" | "master_detail" | "tree" | "image" | "avatar" | "video" | "audio" | "formula" | "summary" | "autonumber" | "composite" | "repeater" | "location" | "address" | "json" | "color" | "rating" | "slider" | "qrcode" | "vector";
685
+ type: "number" | "boolean" | "tags" | "date" | "record" | "file" | "code" | "datetime" | "signature" | "progress" | "url" | "text" | "textarea" | "email" | "phone" | "password" | "markdown" | "html" | "richtext" | "currency" | "percent" | "time" | "toggle" | "select" | "multiselect" | "radio" | "checkboxes" | "lookup" | "master_detail" | "tree" | "image" | "avatar" | "video" | "audio" | "formula" | "summary" | "autonumber" | "composite" | "repeater" | "location" | "address" | "json" | "color" | "rating" | "slider" | "qrcode" | "vector";
569
686
  colSpan: number;
570
687
  }[];
571
688
  }[];
@@ -762,8 +879,81 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
762
879
  private static envWritableTypes;
763
880
  /** Test hook — clear the memoised env-writable cache. */
764
881
  static resetEnvWritableCache(): void;
882
+ /**
883
+ * Types that opt into runtime creation of brand-new items (ADR-0005
884
+ * extension — two-tier model). A type may have
885
+ * `allowOrgOverride: false` (cannot overlay artifact-shipped items)
886
+ * yet still set `allowRuntimeCreate: true` (users can author new
887
+ * items in `sys_metadata`). The two flags are orthogonal; see
888
+ * {@link isArtifactBacked} for how the protocol decides which gate
889
+ * applies to a given save/delete.
890
+ */
891
+ /**
892
+ * Set of type names that have a static entry in
893
+ * `DEFAULT_METADATA_TYPE_REGISTRY`. Anything outside this set is
894
+ * runtime-registered (plugin-provided types like `theme`, `api`,
895
+ * `connector`) — the listing endpoint at `getMetaTypes()` synthesises
896
+ * those with `allowRuntimeCreate: true`, so this gate must agree.
897
+ */
898
+ private static readonly STATIC_REGISTRY_TYPES;
899
+ private static readonly RUNTIME_CREATE_ALLOWED_TYPES;
765
900
  /** Normalize plural→singular before consulting the allow-list. */
766
901
  private static isOverlayAllowed;
902
+ /** Does this type permit creating brand-new (artifact-free) items? */
903
+ private static isRuntimeCreateAllowed;
904
+ /**
905
+ * Does an artifact (npm-package-loaded) item exist at `(type, name)`?
906
+ *
907
+ * The schema registry's `_packageId` tag is set only when
908
+ * `registerItem(..., packageId)` is called with a truthy packageId
909
+ * — and only artifact loaders do that. DB-rehydrated items
910
+ * (sys_metadata rows registered back into the registry by
911
+ * `getMetaItems` / `loadMetaFromDb`) call `registerItem` without a
912
+ * packageId, so they carry no `_packageId` and are correctly
913
+ * excluded here.
914
+ *
915
+ * Used by the two-tier authorization model to distinguish
916
+ * "overlaying a packaged item" (requires `allowOrgOverride`) from
917
+ * "authoring a DB-only item" (requires only `allowRuntimeCreate`).
918
+ */
919
+ private isArtifactBacked;
920
+ /**
921
+ * Look up an item from the artifact registry across both the requested
922
+ * type and its singular/plural twin. Returns `undefined` when the
923
+ * registry is unavailable or the item is not artifact-backed.
924
+ */
925
+ private lookupArtifactItem;
926
+ /**
927
+ * Resolve the effective `_lock` for an item by consulting the
928
+ * artifact registry first, then the persisted overlay row. Artifact
929
+ * always wins — by design, an overlay cannot loosen a packaged
930
+ * lock (ADR-0010 §3.3).
931
+ *
932
+ * Returns `'none'` when nothing is locked, which is the common
933
+ * case. Safe to call when `environmentId` is undefined (control-
934
+ * plane bootstrap) — the lock check is only meaningful in tenant
935
+ * scope and the caller is expected to also gate on `environmentId`.
936
+ */
937
+ private getEffectiveLock;
938
+ /**
939
+ * Best-effort audit-row writer (ADR-0010 §3.6). Failures here are
940
+ * logged but never block the underlying decision: an environment
941
+ * without the audit table provisioned (legacy installs before this
942
+ * ADR landed) still answers normal API calls, just without the
943
+ * compliance trail. Phase 2 will make the audit table a hard
944
+ * dependency.
945
+ */
946
+ private recordMetadataAudit;
947
+ /**
948
+ * Phase 1 L3 enforcement for write operations (save / publish /
949
+ * rollback). Returns null on allow. Returns the structured `Error`
950
+ * the caller should `throw` on deny — also records the denial in
951
+ * the audit log so refused attempts are visible in compliance
952
+ * reports (refused writes never reach sys_metadata_history).
953
+ */
954
+ private assertLockAllowsWrite;
955
+ /** Counterpart of {@link assertLockAllowsWrite} for delete. */
956
+ private assertLockAllowsDelete;
767
957
  /**
768
958
  * Mirror an object-type overlay write into the in-memory engine
769
959
  * registry so subsequent CRUD finds the new schema. Idempotent and
@@ -783,16 +973,19 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
783
973
  parentVersion?: string | null;
784
974
  actor?: string;
785
975
  force?: boolean;
976
+ mode?: 'draft' | 'publish';
786
977
  }): Promise<{
787
978
  success: boolean;
788
979
  version: string;
789
980
  seq: number;
981
+ state: string;
790
982
  message: string;
791
983
  } | {
792
984
  success: boolean;
793
985
  message: string;
794
986
  version?: undefined;
795
987
  seq?: undefined;
988
+ state?: undefined;
796
989
  }>;
797
990
  /**
798
991
  * Yield the durable change-log for a single metadata item — every
@@ -813,6 +1006,78 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
813
1006
  }): Promise<{
814
1007
  events: _objectstack_metadata_core.MetadataEvent[];
815
1008
  }>;
1009
+ /**
1010
+ * Promote the pending draft overlay to the live (`active`) row.
1011
+ * Records a history event with `op='publish'`. 404 (`[no_draft]`)
1012
+ * when there is nothing to publish.
1013
+ */
1014
+ publishMetaItem(request: {
1015
+ type: string;
1016
+ name: string;
1017
+ organizationId?: string;
1018
+ actor?: string;
1019
+ message?: string;
1020
+ }): Promise<{
1021
+ success: boolean;
1022
+ version: string;
1023
+ seq: number;
1024
+ message?: string;
1025
+ }>;
1026
+ /**
1027
+ * Restore the body recorded at history `toVersion` as the new
1028
+ * live row. Writes a history event with `op='revert'`. 404
1029
+ * (`[version_not_found]`) when the target version doesn't exist;
1030
+ * 409 (`[version_not_restorable]`) when the target is a delete
1031
+ * tombstone (no body to bring back).
1032
+ */
1033
+ rollbackMetaItem(request: {
1034
+ type: string;
1035
+ name: string;
1036
+ toVersion: number;
1037
+ organizationId?: string;
1038
+ actor?: string;
1039
+ message?: string;
1040
+ }): Promise<{
1041
+ success: boolean;
1042
+ version: string;
1043
+ seq: number;
1044
+ restoredFromVersion: number;
1045
+ message?: string;
1046
+ }>;
1047
+ /**
1048
+ * Compute a shallow structural diff between two historical
1049
+ * versions of a metadata item. Either side may be omitted: when
1050
+ * `toVersion` is undefined the current active body is used; when
1051
+ * `fromVersion` is undefined the immediately previous history row
1052
+ * is used. Returns `{ added, removed, changed }` keyed by JSON
1053
+ * pointer-style paths for primitive leaves; nested objects/arrays
1054
+ * are reported as a single change record.
1055
+ */
1056
+ diffMetaItem(request: {
1057
+ type: string;
1058
+ name: string;
1059
+ fromVersion?: number;
1060
+ toVersion?: number;
1061
+ organizationId?: string;
1062
+ }): Promise<{
1063
+ type: string;
1064
+ name: string;
1065
+ fromVersion: number | null;
1066
+ toVersion: number | null;
1067
+ added: Array<{
1068
+ path: string;
1069
+ value: unknown;
1070
+ }>;
1071
+ removed: Array<{
1072
+ path: string;
1073
+ value: unknown;
1074
+ }>;
1075
+ changed: Array<{
1076
+ path: string;
1077
+ from: unknown;
1078
+ to: unknown;
1079
+ }>;
1080
+ }>;
816
1081
  /**
817
1082
  * Remove a customization overlay row for the given metadata item, so the
818
1083
  * next read falls through to the artifact-loaded default. Implements the
@@ -825,6 +1090,7 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
825
1090
  organizationId?: string;
826
1091
  parentVersion?: string | null;
827
1092
  actor?: string;
1093
+ state?: 'active' | 'draft';
828
1094
  }): Promise<{
829
1095
  success: boolean;
830
1096
  message?: string;
@@ -884,6 +1150,28 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
884
1150
  feedUnsubscribe(request: any): Promise<any>;
885
1151
  }
886
1152
 
1153
+ /**
1154
+ * Overlay-row lifecycle state.
1155
+ *
1156
+ * - `'active'` → the published, live overlay. `getMetaItem` (the
1157
+ * default read path) and runtime loaders observe this row.
1158
+ * - `'draft'` → an unpublished pending change. Lives alongside the
1159
+ * active row (one of each per `(org,type,name)`). Promoted to
1160
+ * `active` via {@link SysMetadataRepository.promoteDraft}.
1161
+ *
1162
+ * Other lifecycle values defined on `sys_metadata.state` (`'archived'`,
1163
+ * `'deprecated'`) are not yet plumbed through the overlay write path;
1164
+ * they remain reserved for future flows (item retirement, freeze).
1165
+ */
1166
+ type OverlayState = 'active' | 'draft';
1167
+ /**
1168
+ * Extended history operation tag. The base `'create' | 'update' |
1169
+ * 'delete'` operations are emitted by the canonical put/delete paths.
1170
+ * `'publish'` is recorded when a draft is promoted, `'revert'` when a
1171
+ * historical version is restored. Both are surfaced as MetadataEvent
1172
+ * `.op` values via `history()`.
1173
+ */
1174
+ type ExtendedOperation = 'create' | 'update' | 'publish' | 'revert' | 'delete';
887
1175
  /**
888
1176
  * Sub-set of the ObjectQL engine shape we depend on. Kept narrow so
889
1177
  * tests can stub it with a plain mock. Mirrors the real engine's
@@ -963,8 +1251,14 @@ declare class SysMetadataRepository implements MetadataRepository {
963
1251
  /**
964
1252
  * Read the current overlay row. Returns null if no row exists —
965
1253
  * callers (e.g. LayeredRepository) fall through to lower layers.
1254
+ *
1255
+ * `opts.state` selects which lifecycle row to read: defaults to the
1256
+ * live published row (`'active'`). Pass `'draft'` to read the pending
1257
+ * unpublished revision (if any).
966
1258
  */
967
- get(ref: MetaRef): Promise<MetadataItem | null>;
1259
+ get(ref: MetaRef, opts?: {
1260
+ state?: OverlayState;
1261
+ }): Promise<MetadataItem | null>;
968
1262
  /**
969
1263
  * Resolve a historical version by content hash (ADR-0009).
970
1264
  *
@@ -974,8 +1268,55 @@ declare class SysMetadataRepository implements MetadataRepository {
974
1268
  * them.
975
1269
  */
976
1270
  getByHash(ref: MetaRef, hash: string): Promise<MetadataItem | null>;
977
- put(ref: MetaRef, spec: unknown, opts: PutOptions): Promise<PutResult>;
978
- delete(ref: MetaRef, opts: DeleteOptions): Promise<DeleteResult>;
1271
+ put(ref: MetaRef, spec: unknown, opts: PutOptions & {
1272
+ state?: OverlayState;
1273
+ opType?: ExtendedOperation;
1274
+ }): Promise<PutResult>;
1275
+ delete(ref: MetaRef, opts: DeleteOptions & {
1276
+ state?: OverlayState;
1277
+ }): Promise<DeleteResult>;
1278
+ /**
1279
+ * Promote the pending draft row for `ref` into the live (`active`)
1280
+ * overlay. Atomic: reads the draft inside the same transaction, runs
1281
+ * the canonical `put` to upsert the active row (which appends a
1282
+ * history event with `operation_type='publish'`), then deletes the
1283
+ * draft row.
1284
+ *
1285
+ * Errors if no draft exists (callers should 404). The active row's
1286
+ * `parentVersion` is computed from the current active hash so this
1287
+ * also surfaces optimistic-lock conflicts when something else has
1288
+ * published in between (e.g. another admin reverted to an older
1289
+ * version since the draft was authored).
1290
+ */
1291
+ promoteDraft(ref: MetaRef, opts: {
1292
+ actor: string;
1293
+ source?: string;
1294
+ message?: string;
1295
+ intent?: MetadataWriteIntent;
1296
+ }): Promise<{
1297
+ version: string;
1298
+ seq: number;
1299
+ item: MetadataItem;
1300
+ }>;
1301
+ /**
1302
+ * Restore the body recorded in history at `targetVersion` (per-org
1303
+ * lineage counter) as the new active row. Writes a history event
1304
+ * with `operation_type='revert'` so the audit trail captures the
1305
+ * intent. Does NOT touch any draft row.
1306
+ *
1307
+ * Throws `[version_not_found]` (404) if the target version row is
1308
+ * missing or is a delete tombstone (no body to restore).
1309
+ */
1310
+ restoreVersion(ref: MetaRef, targetVersion: number, opts: {
1311
+ actor: string;
1312
+ source?: string;
1313
+ message?: string;
1314
+ intent?: MetadataWriteIntent;
1315
+ }): Promise<{
1316
+ version: string;
1317
+ seq: number;
1318
+ item: MetadataItem;
1319
+ }>;
979
1320
  list(filter: ListFilter): AsyncIterable<MetadataItemHeader>;
980
1321
  /**
981
1322
  * Yield every history event for `(org, type?, name?)` from the
@@ -996,6 +1337,19 @@ declare class SysMetadataRepository implements MetadataRepository {
996
1337
  /** Shut down all watch iterators. */
997
1338
  close(): void;
998
1339
  private assertOpen;
1340
+ /**
1341
+ * Defense-in-depth authorization gate.
1342
+ *
1343
+ * `intent` defaults to `'override-artifact'` (the historical strict
1344
+ * behavior). The protocol layer passes `'runtime-only'` after it has
1345
+ * verified — via the schema registry — that no artifact item exists
1346
+ * at `(type, name)`. In that case we accept types with
1347
+ * `allowRuntimeCreate: true`, even when `allowOrgOverride` is false.
1348
+ *
1349
+ * The env-var escape hatch (`OBJECTSTACK_METADATA_WRITABLE`) still
1350
+ * applies to BOTH intents, so operators can opt into artifact
1351
+ * overrides at runtime for emergency fixes.
1352
+ */
999
1353
  private assertAllowed;
1000
1354
  private whereFor;
1001
1355
  private fullRef;