@objectstack/objectql 7.5.0 → 7.7.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
@@ -81,11 +81,13 @@ type RegistryLogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent';
81
81
  */
82
82
  interface SchemaRegistryOptions {
83
83
  /**
84
- * Whether the host kernel runs in multi-tenant mode. When `true`, the
85
- * registry auto-injects `organization_id` (lookup → sys_organization)
86
- * into every registered user object that doesn't already declare it and
87
- * isn't `managedBy` an external subsystem or explicitly opted-out via
88
- * `systemFields: false`.
84
+ * Whether the host kernel runs in multi-tenant mode. The `organization_id`
85
+ * column itself is auto-injected regardless of this flag (lookup →
86
+ * sys_organization, on every registered object that doesn't already declare
87
+ * it, isn't `managedBy` an external subsystem, and hasn't opted out via
88
+ * `systemFields`/`tenancy.enabled:false`). When `true` the injected column
89
+ * is additionally INDEXED — single-tenant stacks skip the index since
90
+ * nothing ever filters by organization.
89
91
  *
90
92
  * Sourced from the `OS_MULTI_TENANT` env var when not explicitly set —
91
93
  * matches how the SecurityPlugin and CLI startup banner pick the mode.
@@ -107,9 +109,12 @@ interface SchemaRegistryOptions {
107
109
  * via the natural `{ ...sys, ...authored }` merge.
108
110
  *
109
111
  * Currently injects:
110
- * - `organization_id` — multi-tenant deployments. Required-false (the
111
- * SecurityPlugin populates it on insert; nullable rows are still
112
- * filtered out by the `tenant_isolation` RLS USING clause).
112
+ * - `organization_id` — always provisioned (unless the object opts out via
113
+ * `systemFields`/`tenancy.enabled:false` or is `better-auth` managed) so
114
+ * the column never depends on the global multi-tenant flag. Required-false;
115
+ * org-scoping populates it on insert in multi-tenant mode, and it stays
116
+ * NULL on single-tenant stacks. Only the column's INDEX is gated on
117
+ * `multiTenant` (no per-tenant filtering exists single-tenant).
113
118
  * - `created_at` / `created_by` / `updated_at` / `updated_by` — audit
114
119
  * fields. Marked `system: true, readonly: true` so detail views can
115
120
  * surface them in a dedicated "System Information" section while
@@ -419,6 +424,7 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
419
424
  graphql?: string | undefined;
420
425
  packages?: string | undefined;
421
426
  workflow?: string | undefined;
427
+ approvals?: string | undefined;
422
428
  realtime?: string | undefined;
423
429
  notifications?: string | undefined;
424
430
  ai?: string | undefined;
@@ -549,14 +555,14 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
549
555
  provider: "api";
550
556
  read?: {
551
557
  url: string;
552
- method: "GET" | "POST" | "PATCH" | "PUT" | "DELETE";
558
+ method: "POST" | "PATCH" | "PUT" | "DELETE" | "GET";
553
559
  headers?: Record<string, string> | undefined;
554
560
  params?: Record<string, unknown> | undefined;
555
561
  body?: unknown;
556
562
  } | undefined;
557
563
  write?: {
558
564
  url: string;
559
- method: "GET" | "POST" | "PATCH" | "PUT" | "DELETE";
565
+ method: "POST" | "PATCH" | "PUT" | "DELETE" | "GET";
560
566
  headers?: Record<string, string> | undefined;
561
567
  params?: Record<string, unknown> | undefined;
562
568
  body?: unknown;
@@ -1849,7 +1855,7 @@ declare class ObjectQL implements IDataEngine {
1849
1855
  * **fail-closed** — the write throws rather than persist cleartext.
1850
1856
  *
1851
1857
  * Mirrors the Settings subsystem's ICryptoProvider wiring; the host (e.g.
1852
- * `serve`) injects `InMemoryCryptoProvider` in dev and a KMS/Vault-backed
1858
+ * `serve`) injects `LocalCryptoProvider` in dev and a KMS/Vault-backed
1853
1859
  * provider in production.
1854
1860
  */
1855
1861
  setCryptoProvider(provider: ICryptoProvider): void;
@@ -2379,7 +2385,7 @@ declare function wrapDeclarativeHook(meta: Hook, handler: HookHandler, opts?: Wr
2379
2385
 
2380
2386
  interface FieldValidationError {
2381
2387
  field: string;
2382
- code: 'required' | 'min_length' | 'max_length' | 'min_value' | 'max_value' | 'invalid_email' | 'invalid_url' | 'invalid_phone' | 'invalid_number' | 'invalid_boolean' | 'invalid_date' | 'invalid_option' | 'invalid_transition' | 'rule_violation';
2388
+ code: 'required' | 'min_length' | 'max_length' | 'min_value' | 'max_value' | 'invalid_email' | 'invalid_url' | 'invalid_phone' | 'invalid_number' | 'invalid_boolean' | 'invalid_date' | 'invalid_option' | 'invalid_transition' | 'rule_violation' | 'invalid_format' | 'invalid_json' | 'json_schema_violation';
2383
2389
  message: string;
2384
2390
  /** Allowed values for select/multiselect, when applicable. */
2385
2391
  options?: string[];
package/dist/index.d.ts CHANGED
@@ -81,11 +81,13 @@ type RegistryLogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent';
81
81
  */
82
82
  interface SchemaRegistryOptions {
83
83
  /**
84
- * Whether the host kernel runs in multi-tenant mode. When `true`, the
85
- * registry auto-injects `organization_id` (lookup → sys_organization)
86
- * into every registered user object that doesn't already declare it and
87
- * isn't `managedBy` an external subsystem or explicitly opted-out via
88
- * `systemFields: false`.
84
+ * Whether the host kernel runs in multi-tenant mode. The `organization_id`
85
+ * column itself is auto-injected regardless of this flag (lookup →
86
+ * sys_organization, on every registered object that doesn't already declare
87
+ * it, isn't `managedBy` an external subsystem, and hasn't opted out via
88
+ * `systemFields`/`tenancy.enabled:false`). When `true` the injected column
89
+ * is additionally INDEXED — single-tenant stacks skip the index since
90
+ * nothing ever filters by organization.
89
91
  *
90
92
  * Sourced from the `OS_MULTI_TENANT` env var when not explicitly set —
91
93
  * matches how the SecurityPlugin and CLI startup banner pick the mode.
@@ -107,9 +109,12 @@ interface SchemaRegistryOptions {
107
109
  * via the natural `{ ...sys, ...authored }` merge.
108
110
  *
109
111
  * Currently injects:
110
- * - `organization_id` — multi-tenant deployments. Required-false (the
111
- * SecurityPlugin populates it on insert; nullable rows are still
112
- * filtered out by the `tenant_isolation` RLS USING clause).
112
+ * - `organization_id` — always provisioned (unless the object opts out via
113
+ * `systemFields`/`tenancy.enabled:false` or is `better-auth` managed) so
114
+ * the column never depends on the global multi-tenant flag. Required-false;
115
+ * org-scoping populates it on insert in multi-tenant mode, and it stays
116
+ * NULL on single-tenant stacks. Only the column's INDEX is gated on
117
+ * `multiTenant` (no per-tenant filtering exists single-tenant).
113
118
  * - `created_at` / `created_by` / `updated_at` / `updated_by` — audit
114
119
  * fields. Marked `system: true, readonly: true` so detail views can
115
120
  * surface them in a dedicated "System Information" section while
@@ -419,6 +424,7 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
419
424
  graphql?: string | undefined;
420
425
  packages?: string | undefined;
421
426
  workflow?: string | undefined;
427
+ approvals?: string | undefined;
422
428
  realtime?: string | undefined;
423
429
  notifications?: string | undefined;
424
430
  ai?: string | undefined;
@@ -549,14 +555,14 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
549
555
  provider: "api";
550
556
  read?: {
551
557
  url: string;
552
- method: "GET" | "POST" | "PATCH" | "PUT" | "DELETE";
558
+ method: "POST" | "PATCH" | "PUT" | "DELETE" | "GET";
553
559
  headers?: Record<string, string> | undefined;
554
560
  params?: Record<string, unknown> | undefined;
555
561
  body?: unknown;
556
562
  } | undefined;
557
563
  write?: {
558
564
  url: string;
559
- method: "GET" | "POST" | "PATCH" | "PUT" | "DELETE";
565
+ method: "POST" | "PATCH" | "PUT" | "DELETE" | "GET";
560
566
  headers?: Record<string, string> | undefined;
561
567
  params?: Record<string, unknown> | undefined;
562
568
  body?: unknown;
@@ -1849,7 +1855,7 @@ declare class ObjectQL implements IDataEngine {
1849
1855
  * **fail-closed** — the write throws rather than persist cleartext.
1850
1856
  *
1851
1857
  * Mirrors the Settings subsystem's ICryptoProvider wiring; the host (e.g.
1852
- * `serve`) injects `InMemoryCryptoProvider` in dev and a KMS/Vault-backed
1858
+ * `serve`) injects `LocalCryptoProvider` in dev and a KMS/Vault-backed
1853
1859
  * provider in production.
1854
1860
  */
1855
1861
  setCryptoProvider(provider: ICryptoProvider): void;
@@ -2379,7 +2385,7 @@ declare function wrapDeclarativeHook(meta: Hook, handler: HookHandler, opts?: Wr
2379
2385
 
2380
2386
  interface FieldValidationError {
2381
2387
  field: string;
2382
- code: 'required' | 'min_length' | 'max_length' | 'min_value' | 'max_value' | 'invalid_email' | 'invalid_url' | 'invalid_phone' | 'invalid_number' | 'invalid_boolean' | 'invalid_date' | 'invalid_option' | 'invalid_transition' | 'rule_violation';
2388
+ code: 'required' | 'min_length' | 'max_length' | 'min_value' | 'max_value' | 'invalid_email' | 'invalid_url' | 'invalid_phone' | 'invalid_number' | 'invalid_boolean' | 'invalid_date' | 'invalid_option' | 'invalid_transition' | 'rule_violation' | 'invalid_format' | 'invalid_json' | 'json_schema_violation';
2383
2389
  message: string;
2384
2390
  /** Allowed values for select/multiselect, when applicable. */
2385
2391
  options?: string[];
package/dist/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
@@ -100,7 +110,7 @@ function applySystemFields(schema, opts) {
100
110
  if (schema.managedBy === "better-auth") return schema;
101
111
  const sf = typeof schema.systemFields === "object" && schema.systemFields !== null ? schema.systemFields : void 0;
102
112
  const tenancyDisabled = schema.tenancy?.enabled === false;
103
- const wantTenant = opts.multiTenant && sf?.tenant !== false && !tenancyDisabled;
113
+ const wantTenant = sf?.tenant !== false && !tenancyDisabled;
104
114
  const wantAudit = sf?.audit !== false;
105
115
  const additions = {};
106
116
  if (wantTenant && !schema.fields?.organization_id) {
@@ -109,11 +119,11 @@ function applySystemFields(schema, opts) {
109
119
  reference: "sys_organization",
110
120
  label: "Organization",
111
121
  required: false,
112
- indexed: true,
122
+ indexed: opts.multiTenant,
113
123
  hidden: true,
114
124
  readonly: true,
115
125
  system: true,
116
- description: "Tenant scope (auto-populated by SecurityPlugin on insert)."
126
+ description: "Tenant scope (auto-populated by org-scoping on insert; NULL on single-tenant stacks)."
117
127
  };
118
128
  }
119
129
  if (wantAudit) {
@@ -5333,12 +5343,25 @@ function validateRecord(objectSchema, data, mode) {
5333
5343
 
5334
5344
  // src/validation/rule-validator.ts
5335
5345
  var import_formula2 = require("@objectstack/formula");
5346
+ var import_ajv = __toESM(require("ajv"));
5347
+ var ajv = new import_ajv.default({ allErrors: true, strict: false });
5348
+ var jsonSchemaCache = /* @__PURE__ */ new WeakMap();
5336
5349
  function needsPriorRecord(objectSchema) {
5337
5350
  const rules = objectSchema?.validations;
5338
5351
  if (!Array.isArray(rules)) return false;
5339
- return rules.some(
5340
- (r) => r != null && typeof r === "object" && (r.type === "state_machine" || r.type === "cross_field" || r.type === "script")
5341
- );
5352
+ return rules.some((r) => ruleNeedsPrior(r));
5353
+ }
5354
+ function ruleNeedsPrior(r) {
5355
+ if (r == null || typeof r !== "object") return false;
5356
+ const type = r.type;
5357
+ if (type === "state_machine" || type === "cross_field" || type === "script") {
5358
+ return true;
5359
+ }
5360
+ if (type === "conditional") {
5361
+ const c = r;
5362
+ return ruleNeedsPrior(c.then) || ruleNeedsPrior(c.otherwise);
5363
+ }
5364
+ return false;
5342
5365
  }
5343
5366
  function toExpression(cond) {
5344
5367
  return typeof cond === "string" ? { dialect: "cel", source: cond } : cond;
@@ -5348,6 +5371,7 @@ function evaluateValidationRules(objectSchema, data, mode, opts = {}) {
5348
5371
  if (!Array.isArray(rules) || rules.length === 0 || !data) return;
5349
5372
  const previous = opts.previous ?? void 0;
5350
5373
  const merged = { ...previous ?? {}, ...data };
5374
+ const ctx = { data, merged, previous, mode, logger: opts.logger };
5351
5375
  const errors = [];
5352
5376
  const ordered = rules.filter((r) => r != null && typeof r === "object").filter((r) => r.active !== false).filter((r) => {
5353
5377
  const events = r.events ?? ["insert", "update"];
@@ -5356,11 +5380,7 @@ function evaluateValidationRules(objectSchema, data, mode, opts = {}) {
5356
5380
  for (const rule of ordered) {
5357
5381
  let violation = null;
5358
5382
  try {
5359
- if (rule.type === "state_machine") {
5360
- violation = checkStateMachine(rule, mode, data, previous);
5361
- } else if (rule.type === "script" || rule.type === "cross_field") {
5362
- violation = checkPredicate(rule, merged, previous, opts.logger);
5363
- }
5383
+ violation = evaluateRule(rule, ctx);
5364
5384
  } catch (err) {
5365
5385
  opts.logger?.warn?.(`Validation rule '${rule.name}' threw \u2014 skipped`, err);
5366
5386
  continue;
@@ -5377,6 +5397,23 @@ function evaluateValidationRules(objectSchema, data, mode, opts = {}) {
5377
5397
  }
5378
5398
  if (errors.length > 0) throw new ValidationError(errors);
5379
5399
  }
5400
+ function evaluateRule(rule, ctx) {
5401
+ switch (rule.type) {
5402
+ case "state_machine":
5403
+ return checkStateMachine(rule, ctx.mode, ctx.data, ctx.previous);
5404
+ case "script":
5405
+ case "cross_field":
5406
+ return checkPredicate(rule, ctx.merged, ctx.previous, ctx.logger);
5407
+ case "format":
5408
+ return checkFormat(rule, ctx.data, ctx.logger);
5409
+ case "json_schema":
5410
+ return checkJsonSchema(rule, ctx.data, ctx.logger);
5411
+ case "conditional":
5412
+ return checkConditional(rule, ctx);
5413
+ default:
5414
+ return null;
5415
+ }
5416
+ }
5380
5417
  function checkStateMachine(rule, mode, data, previous) {
5381
5418
  if (mode === "insert" || !previous) return null;
5382
5419
  if (!(rule.field in data)) return null;
@@ -5416,6 +5453,99 @@ function checkPredicate(rule, record, previous, logger) {
5416
5453
  }
5417
5454
  return null;
5418
5455
  }
5456
+ var EMAIL_RE2 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
5457
+ var PHONE_RE2 = /^\+?[\d\s().-]{7,20}$/;
5458
+ function checkFormat(rule, data, logger) {
5459
+ if (!(rule.field in data)) return null;
5460
+ const value = data[rule.field];
5461
+ if (value === null || value === void 0 || value === "") return null;
5462
+ const str = String(value);
5463
+ if (rule.regex) {
5464
+ let re;
5465
+ try {
5466
+ re = new RegExp(rule.regex);
5467
+ } catch {
5468
+ logger?.warn?.(`Validation rule '${rule.name}' has an invalid regex \u2014 skipped`);
5469
+ return null;
5470
+ }
5471
+ if (!re.test(str)) return formatViolation(rule);
5472
+ }
5473
+ if (rule.format && !matchesNamedFormat(rule.format, str)) {
5474
+ return formatViolation(rule);
5475
+ }
5476
+ return null;
5477
+ }
5478
+ function matchesNamedFormat(format, str) {
5479
+ switch (format) {
5480
+ case "email":
5481
+ return EMAIL_RE2.test(str);
5482
+ case "phone":
5483
+ return PHONE_RE2.test(str);
5484
+ case "url":
5485
+ try {
5486
+ new URL(str);
5487
+ return true;
5488
+ } catch {
5489
+ return false;
5490
+ }
5491
+ case "json":
5492
+ try {
5493
+ JSON.parse(str);
5494
+ return true;
5495
+ } catch {
5496
+ return false;
5497
+ }
5498
+ default:
5499
+ return true;
5500
+ }
5501
+ }
5502
+ function formatViolation(rule) {
5503
+ return { field: rule.field, code: "invalid_format", message: rule.message };
5504
+ }
5505
+ function checkJsonSchema(rule, data, logger) {
5506
+ if (!(rule.field in data)) return null;
5507
+ let value = data[rule.field];
5508
+ if (value === null || value === void 0) return null;
5509
+ if (typeof value === "string") {
5510
+ try {
5511
+ value = JSON.parse(value);
5512
+ } catch {
5513
+ return { field: rule.field, code: "invalid_json", message: rule.message };
5514
+ }
5515
+ }
5516
+ let validate = jsonSchemaCache.get(rule.schema);
5517
+ if (!validate) {
5518
+ try {
5519
+ validate = ajv.compile(rule.schema);
5520
+ } catch (err) {
5521
+ logger?.warn?.(
5522
+ `Validation rule '${rule.name}' has an uncompilable JSON Schema \u2014 skipped`,
5523
+ err
5524
+ );
5525
+ return null;
5526
+ }
5527
+ jsonSchemaCache.set(rule.schema, validate);
5528
+ }
5529
+ if (!validate(value)) {
5530
+ return { field: rule.field, code: "json_schema_violation", message: rule.message };
5531
+ }
5532
+ return null;
5533
+ }
5534
+ function checkConditional(rule, ctx) {
5535
+ const result = import_formula2.ExpressionEngine.evaluate(toExpression(rule.when), {
5536
+ record: ctx.merged,
5537
+ previous: ctx.previous ?? void 0
5538
+ });
5539
+ if (!result.ok) {
5540
+ ctx.logger?.warn?.(
5541
+ `Validation rule '${rule.name}' when-predicate failed to evaluate (${result.error.kind}: ${result.error.message}) \u2014 skipped`
5542
+ );
5543
+ return null;
5544
+ }
5545
+ const branch = result.value === true ? rule.then : rule.otherwise;
5546
+ if (!branch || branch.active === false) return null;
5547
+ return evaluateRule(branch, ctx);
5548
+ }
5419
5549
  function legalNextStates(objectSchema, field, currentState) {
5420
5550
  const rules = objectSchema?.validations;
5421
5551
  if (!Array.isArray(rules)) return null;
@@ -6376,7 +6506,7 @@ var _ObjectQL = class _ObjectQL {
6376
6506
  * **fail-closed** — the write throws rather than persist cleartext.
6377
6507
  *
6378
6508
  * Mirrors the Settings subsystem's ICryptoProvider wiring; the host (e.g.
6379
- * `serve`) injects `InMemoryCryptoProvider` in dev and a KMS/Vault-backed
6509
+ * `serve`) injects `LocalCryptoProvider` in dev and a KMS/Vault-backed
6380
6510
  * provider in production.
6381
6511
  */
6382
6512
  setCryptoProvider(provider) {
@@ -6414,7 +6544,7 @@ var _ObjectQL = class _ObjectQL {
6414
6544
  }
6415
6545
  if (!this.cryptoProvider) {
6416
6546
  throw new Error(
6417
- `Cannot persist secret field "${object}.${field}": no CryptoProvider is registered. Wire one via engine.setCryptoProvider(...) (e.g. InMemoryCryptoProvider in dev, a KMS/Vault provider in production). Refusing to store cleartext (fail-closed).`
6547
+ `Cannot persist secret field "${object}.${field}": no CryptoProvider is registered. Wire one via engine.setCryptoProvider(...) (e.g. LocalCryptoProvider in dev, a KMS/Vault provider in production). Refusing to store cleartext (fail-closed).`
6418
6548
  );
6419
6549
  }
6420
6550
  const plain = typeof value === "string" ? value : JSON.stringify(value);
@@ -6948,7 +7078,11 @@ var _ObjectQL = class _ObjectQL {
6948
7078
  const driver = this.getDriver(object);
6949
7079
  let id = data.id;
6950
7080
  if (!id && options?.where && typeof options.where === "object" && "id" in options.where) {
6951
- id = options.where.id;
7081
+ const whereId = options.where.id;
7082
+ const t = typeof whereId;
7083
+ if (whereId !== null && (t === "string" || t === "number" || t === "bigint")) {
7084
+ id = whereId;
7085
+ }
6952
7086
  }
6953
7087
  const opCtx = {
6954
7088
  object,
@@ -7673,6 +7807,7 @@ var MetadataFacade = class {
7673
7807
 
7674
7808
  // src/plugin.ts
7675
7809
  var import_system3 = require("@objectstack/spec/system");
7810
+ var import_metadata_core3 = require("@objectstack/metadata-core");
7676
7811
  function hasLoadMetaFromDb(service) {
7677
7812
  return typeof service === "object" && service !== null && typeof service["loadMetaFromDb"] === "function";
7678
7813
  }
@@ -7709,6 +7844,21 @@ var ObjectQLPlugin = class {
7709
7844
  ctx.logger.info("ObjectQL engine registered", {
7710
7845
  services: ["objectql", "data", "manifest"]
7711
7846
  });
7847
+ if (this.environmentId === void 0) {
7848
+ this.ql.registerApp({
7849
+ id: "com.objectstack.metadata-objects",
7850
+ name: "Metadata Platform Objects",
7851
+ version: "1.0.0",
7852
+ type: "plugin",
7853
+ scope: "system",
7854
+ objects: [
7855
+ import_metadata_core3.SysMetadataObject,
7856
+ import_metadata_core3.SysMetadataHistoryObject,
7857
+ import_metadata_core3.SysMetadataAuditObject,
7858
+ import_metadata_core3.SysViewDefinitionObject
7859
+ ]
7860
+ });
7861
+ }
7712
7862
  const protocolShim = new ObjectStackProtocolImplementation(
7713
7863
  this.ql,
7714
7864
  () => ctx.getServices ? ctx.getServices() : /* @__PURE__ */ new Map(),