@objectstack/objectql 9.2.0 → 9.4.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
@@ -98,6 +98,24 @@ interface SchemaRegistryOptions {
98
98
  * (useful in tests).
99
99
  */
100
100
  multiTenant?: boolean;
101
+ /**
102
+ * Policy for the install-time namespace gate (ADR-0048 Phase 1) — installing
103
+ * a package whose `manifest.namespace` is already owned by a *different*
104
+ * installed package.
105
+ *
106
+ * - `'error'` (default): throw {@link NamespaceConflictError} at install time,
107
+ * naming both packages. Makes the namespace land-grab a loud, early failure
108
+ * instead of a mid-install `CREATE TABLE` blow-up.
109
+ * - `'warn'`: log a warning and let the install proceed. For deliberate
110
+ * migrations where the conflict is temporarily expected.
111
+ *
112
+ * Sourced from `OS_METADATA_COLLISION` (`warn` to downgrade) when not set
113
+ * explicitly. Same-package reinstall and shareable platform namespaces
114
+ * (`base`/`system`/`sys`) are never treated as conflicts. (The per-item
115
+ * cross-package collision throw was retired in ADR-0048 §3.4 — distinct
116
+ * package ids are always disambiguable by package-scoped resolution.)
117
+ */
118
+ collisionPolicy?: 'error' | 'warn';
101
119
  }
102
120
  /**
103
121
  * Augment a registered object with implicit system fields.
@@ -132,6 +150,8 @@ declare class SchemaRegistry {
132
150
  private _logLevel;
133
151
  /** Whether to auto-inject multi-tenant system fields. */
134
152
  private readonly multiTenant;
153
+ /** Cross-package base-layer collision policy (ADR-0048). */
154
+ private readonly collisionPolicy;
135
155
  constructor(options?: SchemaRegistryOptions);
136
156
  get logLevel(): RegistryLogLevel;
137
157
  set logLevel(level: RegistryLogLevel);
@@ -267,9 +287,55 @@ declare class SchemaRegistry {
267
287
  */
268
288
  unregisterItem(type: string, name: string): void;
269
289
  /**
270
- * Universal Get Method
271
- */
272
- getItem<T>(type: string, name: string): T | undefined;
290
+ * Universal Get Method.
291
+ *
292
+ * ADR-0048 §3.3 *package-scoped* resolution. When `currentPackageId` is
293
+ * given (the package the caller is resolving within — known from the route /
294
+ * `activeApp._packageId`), a bare name resolves to *that package's* item
295
+ * before any cross-package fallback, so two packages shipping e.g.
296
+ * `page/home` no longer resolve by registration order (first-match-wins).
297
+ * Because package ids are globally unique this is unambiguous. Omitting
298
+ * `currentPackageId` preserves the legacy resolution exactly (backward
299
+ * compatible) and is best-effort: it returns the first match.
300
+ *
301
+ * Precedence (highest first):
302
+ * 1. bare-key runtime/DB overlay (ADR-0005 sanctioned override) — unchanged
303
+ * 2. the `currentPackageId` composite entry (prefer-local)
304
+ * 3. first composite match (legacy first-registered-wins fallback)
305
+ */
306
+ getItem<T>(type: string, name: string, currentPackageId?: string): T | undefined;
307
+ /**
308
+ * Artifact-only lookup (ADR-0010 §3.3). Unlike {@link getItem} — which
309
+ * returns the plain-key entry first, so a runtime/DB-rehydrated row
310
+ * registered under the bare name SHADOWS the packaged artifact — this
311
+ * scans the composite (`<packageId>:<name>`) entries first and only
312
+ * returns an item whose `_packageId` marks a genuine code package
313
+ * (truthy and not the `'sys_metadata'` rehydration sentinel).
314
+ *
315
+ * This is what the protocol's lock/provenance resolution must use:
316
+ * the artifact's `_lock` envelope always wins over an overlay, and an
317
+ * overlay row hydrated into the plain key must never be able to mask
318
+ * it (that masking is exactly the "registry pollution" bug where a
319
+ * locked app's `_lock` read back as undefined after a PUT+GET).
320
+ */
321
+ getArtifactItem<T>(type: string, name: string, currentPackageId?: string): T | undefined;
322
+ /**
323
+ * Remove a plain-key runtime shadow so the packaged artifact registered
324
+ * under a composite key becomes the visible value again. Used by the
325
+ * metadata reset path (`deleteMetaItem`): deleting the `sys_metadata`
326
+ * overlay row must also heal the in-memory registry, otherwise the
327
+ * stale overlay copy keeps shadowing the artifact until restart.
328
+ *
329
+ * Deliberately conservative: the plain-key entry is only deleted when a
330
+ * packaged artifact still exists under a composite key, so the name
331
+ * stays resolvable afterwards. A runtime-only item (no artifact
332
+ * backing) is left untouched. Note the plain entry's own `_packageId`
333
+ * is NOT consulted — the hydration path grafts the artifact envelope
334
+ * onto the shadow (ADR-0010 §3.3), so a stamped `_packageId` does not
335
+ * mean the plain entry IS the artifact registration; artifact loaders
336
+ * always register under a composite key.
337
+ */
338
+ removeRuntimeShadow(type: string, name: string): boolean;
273
339
  /**
274
340
  * Universal List Method
275
341
  */
@@ -290,7 +356,7 @@ declare class SchemaRegistry {
290
356
  enablePackage(id: string): InstalledPackage | undefined;
291
357
  disablePackage(id: string): InstalledPackage | undefined;
292
358
  registerApp(app: any, packageId?: string): void;
293
- getApp(name: string): any;
359
+ getApp(name: string, currentPackageId?: string): any;
294
360
  getAllApps(): any[];
295
361
  /**
296
362
  * Register a navigation contribution — a package injecting nav items into
@@ -621,6 +687,8 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
621
687
  method?: "POST" | "PUT" | "DELETE" | "PATCH" | undefined;
622
688
  bodyExtra?: Record<string, unknown> | undefined;
623
689
  mode?: "custom" | "create" | "delete" | "edit" | undefined;
690
+ opensInNewTab?: boolean | undefined;
691
+ newTabUrl?: string | undefined;
624
692
  timeout?: number | undefined;
625
693
  aria?: {
626
694
  ariaLabel?: string | undefined;
@@ -1244,6 +1312,26 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
1244
1312
  * stale schema into the registry.
1245
1313
  */
1246
1314
  private applyObjectRegistryMutation;
1315
+ /**
1316
+ * Heal the in-memory registry after a metadata reset (overlay-row
1317
+ * delete) on control-plane kernels. Two layers:
1318
+ *
1319
+ * 1. Drop the plain-key runtime shadow so the packaged artifact
1320
+ * (registered under `<packageId>:<name>`) becomes the visible
1321
+ * value again. The shadow is written by the overlay-hydration
1322
+ * paths (`getMetaItems` / `loadMetaFromDb`) and — pre-fix —
1323
+ * survived the reset until restart, leaving stale overlay
1324
+ * content (and a stripped `_lock` envelope) in every
1325
+ * registry-direct read (ADR-0010 §3.3).
1326
+ * 2. When no composite-key artifact exists, fall back to the
1327
+ * MetadataService baseline (FilesystemLoader-sourced types) and
1328
+ * re-register it, preserving the historical refresh behaviour
1329
+ * for items the SchemaRegistry never held as artifacts.
1330
+ *
1331
+ * Best-effort: a failure must never block the delete that already
1332
+ * succeeded; the next full reload fixes the registry anyway.
1333
+ */
1334
+ private restoreArtifactRegistryView;
1247
1335
  /**
1248
1336
  * Ensure a just-PUBLISHED object's physical table exists so it is usable
1249
1337
  * for data CRUD immediately — without a server restart. Registering the
@@ -1748,6 +1836,7 @@ declare class SysMetadataRepository implements MetadataRepository {
1748
1836
  */
1749
1837
  get(ref: MetaRef, opts?: {
1750
1838
  state?: OverlayState;
1839
+ packageId?: string | null;
1751
1840
  }): Promise<MetadataItem | null>;
1752
1841
  /**
1753
1842
  * Resolve a historical version by content hash (ADR-0009).
@@ -2894,9 +2983,15 @@ declare class MetadataFacade {
2894
2983
  */
2895
2984
  register(type: string, name: string, data: any): Promise<void>;
2896
2985
  /**
2897
- * Get a metadata item by type and name
2986
+ * Get a metadata item by type and name.
2987
+ *
2988
+ * `currentPackageId` (ADR-0048) opts into package-scoped resolution: when two
2989
+ * installed packages ship an item of the same `type`/`name`, the registry
2990
+ * prefers the one owned by `currentPackageId` (composite key
2991
+ * `${packageId}:${name}`) before falling back to first-match. Omit it for the
2992
+ * legacy context-free lookup.
2898
2993
  */
2899
- get(type: string, name: string): Promise<any>;
2994
+ get(type: string, name: string, currentPackageId?: string): Promise<any>;
2900
2995
  /**
2901
2996
  * Get the raw entry (with metadata wrapper)
2902
2997
  */
@@ -2974,6 +3069,28 @@ interface ObjectQLPluginOptions {
2974
3069
  * code change.
2975
3070
  */
2976
3071
  skipSchemaSync?: boolean;
3072
+ /**
3073
+ * Hydrate the SchemaRegistry from this kernel's local `sys_metadata`
3074
+ * even when `environmentId` is set.
3075
+ *
3076
+ * By default Phase-2 hydration in `start()` is gated on
3077
+ * `environmentId === undefined`, because the original multi-environment
3078
+ * model assumed project kernels source metadata from a remote artifact /
3079
+ * control-plane proxy and have NO local `sys_metadata` to read. That is
3080
+ * NOT true for an isolated, proxy-free project kernel that persists its
3081
+ * OWN `sys_metadata` locally (e.g. the cloud single-env tenant runtime on
3082
+ * Turso): objects CREATED AT RUNTIME there — not present in the boot
3083
+ * artifact manifest — would otherwise never re-enter the registry after a
3084
+ * restart, so `registry.getObject(name)` returns nothing for them and any
3085
+ * registry consumer (the unknown-`$select` guard, hooks, relationships)
3086
+ * silently degrades.
3087
+ *
3088
+ * Set this ONLY when the kernel's registry is per-instance isolated AND
3089
+ * `sys_metadata` lives on the kernel's own local driver (no control-plane
3090
+ * proxy) — hydrating a proxied kernel would read the wrong database.
3091
+ * Safe to leave unset: hydration tolerates a missing table.
3092
+ */
3093
+ hydrateMetadataFromDb?: boolean;
2977
3094
  }
2978
3095
  declare class ObjectQLPlugin implements Plugin {
2979
3096
  name: string;
@@ -2989,6 +3106,7 @@ declare class ObjectQLPlugin implements Plugin {
2989
3106
  private hostContext?;
2990
3107
  private environmentId?;
2991
3108
  private skipSchemaSync;
3109
+ private hydrateMetadataFromDb;
2992
3110
  /** Unsubscribe handles for metadata-event subscriptions (ADR-0008 PR-7). */
2993
3111
  private metadataUnsubscribes;
2994
3112
  constructor(qlOrOptions?: ObjectQL | ObjectQLPluginOptions, hostContext?: Record<string, any>);
package/dist/index.d.ts CHANGED
@@ -98,6 +98,24 @@ interface SchemaRegistryOptions {
98
98
  * (useful in tests).
99
99
  */
100
100
  multiTenant?: boolean;
101
+ /**
102
+ * Policy for the install-time namespace gate (ADR-0048 Phase 1) — installing
103
+ * a package whose `manifest.namespace` is already owned by a *different*
104
+ * installed package.
105
+ *
106
+ * - `'error'` (default): throw {@link NamespaceConflictError} at install time,
107
+ * naming both packages. Makes the namespace land-grab a loud, early failure
108
+ * instead of a mid-install `CREATE TABLE` blow-up.
109
+ * - `'warn'`: log a warning and let the install proceed. For deliberate
110
+ * migrations where the conflict is temporarily expected.
111
+ *
112
+ * Sourced from `OS_METADATA_COLLISION` (`warn` to downgrade) when not set
113
+ * explicitly. Same-package reinstall and shareable platform namespaces
114
+ * (`base`/`system`/`sys`) are never treated as conflicts. (The per-item
115
+ * cross-package collision throw was retired in ADR-0048 §3.4 — distinct
116
+ * package ids are always disambiguable by package-scoped resolution.)
117
+ */
118
+ collisionPolicy?: 'error' | 'warn';
101
119
  }
102
120
  /**
103
121
  * Augment a registered object with implicit system fields.
@@ -132,6 +150,8 @@ declare class SchemaRegistry {
132
150
  private _logLevel;
133
151
  /** Whether to auto-inject multi-tenant system fields. */
134
152
  private readonly multiTenant;
153
+ /** Cross-package base-layer collision policy (ADR-0048). */
154
+ private readonly collisionPolicy;
135
155
  constructor(options?: SchemaRegistryOptions);
136
156
  get logLevel(): RegistryLogLevel;
137
157
  set logLevel(level: RegistryLogLevel);
@@ -267,9 +287,55 @@ declare class SchemaRegistry {
267
287
  */
268
288
  unregisterItem(type: string, name: string): void;
269
289
  /**
270
- * Universal Get Method
271
- */
272
- getItem<T>(type: string, name: string): T | undefined;
290
+ * Universal Get Method.
291
+ *
292
+ * ADR-0048 §3.3 *package-scoped* resolution. When `currentPackageId` is
293
+ * given (the package the caller is resolving within — known from the route /
294
+ * `activeApp._packageId`), a bare name resolves to *that package's* item
295
+ * before any cross-package fallback, so two packages shipping e.g.
296
+ * `page/home` no longer resolve by registration order (first-match-wins).
297
+ * Because package ids are globally unique this is unambiguous. Omitting
298
+ * `currentPackageId` preserves the legacy resolution exactly (backward
299
+ * compatible) and is best-effort: it returns the first match.
300
+ *
301
+ * Precedence (highest first):
302
+ * 1. bare-key runtime/DB overlay (ADR-0005 sanctioned override) — unchanged
303
+ * 2. the `currentPackageId` composite entry (prefer-local)
304
+ * 3. first composite match (legacy first-registered-wins fallback)
305
+ */
306
+ getItem<T>(type: string, name: string, currentPackageId?: string): T | undefined;
307
+ /**
308
+ * Artifact-only lookup (ADR-0010 §3.3). Unlike {@link getItem} — which
309
+ * returns the plain-key entry first, so a runtime/DB-rehydrated row
310
+ * registered under the bare name SHADOWS the packaged artifact — this
311
+ * scans the composite (`<packageId>:<name>`) entries first and only
312
+ * returns an item whose `_packageId` marks a genuine code package
313
+ * (truthy and not the `'sys_metadata'` rehydration sentinel).
314
+ *
315
+ * This is what the protocol's lock/provenance resolution must use:
316
+ * the artifact's `_lock` envelope always wins over an overlay, and an
317
+ * overlay row hydrated into the plain key must never be able to mask
318
+ * it (that masking is exactly the "registry pollution" bug where a
319
+ * locked app's `_lock` read back as undefined after a PUT+GET).
320
+ */
321
+ getArtifactItem<T>(type: string, name: string, currentPackageId?: string): T | undefined;
322
+ /**
323
+ * Remove a plain-key runtime shadow so the packaged artifact registered
324
+ * under a composite key becomes the visible value again. Used by the
325
+ * metadata reset path (`deleteMetaItem`): deleting the `sys_metadata`
326
+ * overlay row must also heal the in-memory registry, otherwise the
327
+ * stale overlay copy keeps shadowing the artifact until restart.
328
+ *
329
+ * Deliberately conservative: the plain-key entry is only deleted when a
330
+ * packaged artifact still exists under a composite key, so the name
331
+ * stays resolvable afterwards. A runtime-only item (no artifact
332
+ * backing) is left untouched. Note the plain entry's own `_packageId`
333
+ * is NOT consulted — the hydration path grafts the artifact envelope
334
+ * onto the shadow (ADR-0010 §3.3), so a stamped `_packageId` does not
335
+ * mean the plain entry IS the artifact registration; artifact loaders
336
+ * always register under a composite key.
337
+ */
338
+ removeRuntimeShadow(type: string, name: string): boolean;
273
339
  /**
274
340
  * Universal List Method
275
341
  */
@@ -290,7 +356,7 @@ declare class SchemaRegistry {
290
356
  enablePackage(id: string): InstalledPackage | undefined;
291
357
  disablePackage(id: string): InstalledPackage | undefined;
292
358
  registerApp(app: any, packageId?: string): void;
293
- getApp(name: string): any;
359
+ getApp(name: string, currentPackageId?: string): any;
294
360
  getAllApps(): any[];
295
361
  /**
296
362
  * Register a navigation contribution — a package injecting nav items into
@@ -621,6 +687,8 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
621
687
  method?: "POST" | "PUT" | "DELETE" | "PATCH" | undefined;
622
688
  bodyExtra?: Record<string, unknown> | undefined;
623
689
  mode?: "custom" | "create" | "delete" | "edit" | undefined;
690
+ opensInNewTab?: boolean | undefined;
691
+ newTabUrl?: string | undefined;
624
692
  timeout?: number | undefined;
625
693
  aria?: {
626
694
  ariaLabel?: string | undefined;
@@ -1244,6 +1312,26 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
1244
1312
  * stale schema into the registry.
1245
1313
  */
1246
1314
  private applyObjectRegistryMutation;
1315
+ /**
1316
+ * Heal the in-memory registry after a metadata reset (overlay-row
1317
+ * delete) on control-plane kernels. Two layers:
1318
+ *
1319
+ * 1. Drop the plain-key runtime shadow so the packaged artifact
1320
+ * (registered under `<packageId>:<name>`) becomes the visible
1321
+ * value again. The shadow is written by the overlay-hydration
1322
+ * paths (`getMetaItems` / `loadMetaFromDb`) and — pre-fix —
1323
+ * survived the reset until restart, leaving stale overlay
1324
+ * content (and a stripped `_lock` envelope) in every
1325
+ * registry-direct read (ADR-0010 §3.3).
1326
+ * 2. When no composite-key artifact exists, fall back to the
1327
+ * MetadataService baseline (FilesystemLoader-sourced types) and
1328
+ * re-register it, preserving the historical refresh behaviour
1329
+ * for items the SchemaRegistry never held as artifacts.
1330
+ *
1331
+ * Best-effort: a failure must never block the delete that already
1332
+ * succeeded; the next full reload fixes the registry anyway.
1333
+ */
1334
+ private restoreArtifactRegistryView;
1247
1335
  /**
1248
1336
  * Ensure a just-PUBLISHED object's physical table exists so it is usable
1249
1337
  * for data CRUD immediately — without a server restart. Registering the
@@ -1748,6 +1836,7 @@ declare class SysMetadataRepository implements MetadataRepository {
1748
1836
  */
1749
1837
  get(ref: MetaRef, opts?: {
1750
1838
  state?: OverlayState;
1839
+ packageId?: string | null;
1751
1840
  }): Promise<MetadataItem | null>;
1752
1841
  /**
1753
1842
  * Resolve a historical version by content hash (ADR-0009).
@@ -2894,9 +2983,15 @@ declare class MetadataFacade {
2894
2983
  */
2895
2984
  register(type: string, name: string, data: any): Promise<void>;
2896
2985
  /**
2897
- * Get a metadata item by type and name
2986
+ * Get a metadata item by type and name.
2987
+ *
2988
+ * `currentPackageId` (ADR-0048) opts into package-scoped resolution: when two
2989
+ * installed packages ship an item of the same `type`/`name`, the registry
2990
+ * prefers the one owned by `currentPackageId` (composite key
2991
+ * `${packageId}:${name}`) before falling back to first-match. Omit it for the
2992
+ * legacy context-free lookup.
2898
2993
  */
2899
- get(type: string, name: string): Promise<any>;
2994
+ get(type: string, name: string, currentPackageId?: string): Promise<any>;
2900
2995
  /**
2901
2996
  * Get the raw entry (with metadata wrapper)
2902
2997
  */
@@ -2974,6 +3069,28 @@ interface ObjectQLPluginOptions {
2974
3069
  * code change.
2975
3070
  */
2976
3071
  skipSchemaSync?: boolean;
3072
+ /**
3073
+ * Hydrate the SchemaRegistry from this kernel's local `sys_metadata`
3074
+ * even when `environmentId` is set.
3075
+ *
3076
+ * By default Phase-2 hydration in `start()` is gated on
3077
+ * `environmentId === undefined`, because the original multi-environment
3078
+ * model assumed project kernels source metadata from a remote artifact /
3079
+ * control-plane proxy and have NO local `sys_metadata` to read. That is
3080
+ * NOT true for an isolated, proxy-free project kernel that persists its
3081
+ * OWN `sys_metadata` locally (e.g. the cloud single-env tenant runtime on
3082
+ * Turso): objects CREATED AT RUNTIME there — not present in the boot
3083
+ * artifact manifest — would otherwise never re-enter the registry after a
3084
+ * restart, so `registry.getObject(name)` returns nothing for them and any
3085
+ * registry consumer (the unknown-`$select` guard, hooks, relationships)
3086
+ * silently degrades.
3087
+ *
3088
+ * Set this ONLY when the kernel's registry is per-instance isolated AND
3089
+ * `sys_metadata` lives on the kernel's own local driver (no control-plane
3090
+ * proxy) — hydrating a proxied kernel would read the wrong database.
3091
+ * Safe to leave unset: hydration tolerates a missing table.
3092
+ */
3093
+ hydrateMetadataFromDb?: boolean;
2977
3094
  }
2978
3095
  declare class ObjectQLPlugin implements Plugin {
2979
3096
  name: string;
@@ -2989,6 +3106,7 @@ declare class ObjectQLPlugin implements Plugin {
2989
3106
  private hostContext?;
2990
3107
  private environmentId?;
2991
3108
  private skipSchemaSync;
3109
+ private hydrateMetadataFromDb;
2992
3110
  /** Unsubscribe handles for metadata-event subscriptions (ADR-0008 PR-7). */
2993
3111
  private metadataUnsubscribes;
2994
3112
  constructor(qlOrOptions?: ObjectQL | ObjectQLPluginOptions, hostContext?: Record<string, any>);