@objectstack/core 16.0.0 → 17.0.0-rc.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.cjs CHANGED
@@ -60,6 +60,8 @@ __export(index_exports, {
60
60
  SecurePluginContext: () => SecurePluginContext,
61
61
  SemanticVersionManager: () => SemanticVersionManager,
62
62
  ServiceLifecycle: () => ServiceLifecycle,
63
+ UnknownFilterTokenError: () => UnknownFilterTokenError,
64
+ UnresolvedFilterTokenError: () => UnresolvedFilterTokenError,
63
65
  bucketKeyToCalendarRange: () => bucketKeyToCalendarRange,
64
66
  buildPermissionsFromGrants: () => buildPermissionsFromGrants,
65
67
  bulkWrite: () => bulkWrite,
@@ -80,6 +82,7 @@ __export(index_exports, {
80
82
  derivePosture: () => derivePosture,
81
83
  evaluateAuthGate: () => evaluateAuthGate,
82
84
  extractApiKey: () => extractApiKey,
85
+ filterTokenContextFrom: () => filterTokenContextFrom,
83
86
  generateApiKey: () => generateApiKey,
84
87
  generateEd25519KeyPair: () => generateEd25519KeyPair,
85
88
  getEnv: () => getEnv,
@@ -96,8 +99,11 @@ __export(index_exports, {
96
99
  readAuthoredTranslationLayer: () => readAuthoredTranslationLayer,
97
100
  resolveApiKeyPrincipal: () => resolveApiKeyPrincipal,
98
101
  resolveAuthzContext: () => resolveAuthzContext,
102
+ resolveFilterToken: () => resolveFilterToken,
103
+ resolveFilterTokens: () => resolveFilterTokens,
99
104
  resolveLocale: () => resolveLocale,
100
105
  resolveLocalizationContext: () => resolveLocalizationContext,
106
+ resolveUserAuthzGrants: () => resolveUserAuthzGrants,
101
107
  safeExit: () => safeExit,
102
108
  shouldDenyAnonymous: () => shouldDenyAnonymous,
103
109
  signPayload: () => signPayload,
@@ -534,7 +540,7 @@ function createLogger(config) {
534
540
  }
535
541
 
536
542
  // src/kernel.ts
537
- var import_system = require("@objectstack/spec/system");
543
+ var import_system2 = require("@objectstack/spec/system");
538
544
 
539
545
  // src/security/plugin-config-validator.ts
540
546
  var import_zod = require("zod");
@@ -1347,6 +1353,7 @@ function createMemoryMetadata() {
1347
1353
  }
1348
1354
 
1349
1355
  // src/fallbacks/authored-translation-sync.ts
1356
+ var import_system = require("@objectstack/spec/system");
1350
1357
  var OWNER_PROP = "__authoredTranslationSyncOwner";
1351
1358
  var LOCALE_LIKE = /^[a-z]{2,3}([_-]([A-Za-z]{4}|[A-Za-z]{2}|[0-9]{3}))?$/;
1352
1359
  async function readAuthoredTranslationLayer(engine, logger) {
@@ -1376,14 +1383,32 @@ async function readAuthoredTranslationLayer(engine, logger) {
1376
1383
  continue;
1377
1384
  }
1378
1385
  if (!data || typeof data !== "object") continue;
1379
- const locale = typeof data?._meta?.locale === "string" && data._meta.locale || typeof data?.locale === "string" && data.locale || (typeof row?.name === "string" && LOCALE_LIKE.test(row.name) ? row.name : void 0) || void 0;
1386
+ const legacyKeys = import_system.LEGACY_OBJECT_FIRST_KEYS.filter((key) => data[key] !== void 0);
1387
+ if (legacyKeys.length > 0) {
1388
+ logger?.warn?.(
1389
+ `[i18n] authored translation '${row?.name}' uses the retired object-first shape (${legacyKeys.join(", ")}) \u2014 nothing resolves from it; re-author it under 'objects.<object_name>' with a top-level 'locale' \u2014 skipped`
1390
+ );
1391
+ continue;
1392
+ }
1393
+ const locale = typeof data?.locale === "string" && data.locale || (typeof row?.name === "string" && LOCALE_LIKE.test(row.name) ? row.name : void 0) || void 0;
1380
1394
  if (!locale) {
1381
1395
  logger?.warn?.(
1382
- `[i18n] authored translation '${row?.name}' has no resolvable locale (set _meta.locale, or name the item after its BCP-47 locale) \u2014 skipped`
1396
+ `[i18n] authored translation '${row?.name}' has no resolvable locale (set the top-level 'locale', or name the item after its BCP-47 locale) \u2014 skipped`
1383
1397
  );
1384
1398
  continue;
1385
1399
  }
1386
- const { name: _n, locale: _l, _packageId: _p, _provenance: _pr, _lock: _lk, ...payload } = data;
1400
+ const {
1401
+ name: _n,
1402
+ locale: _l,
1403
+ _packageId: _p,
1404
+ _packageVersion: _pv,
1405
+ _provenance: _pr,
1406
+ _lock: _lk,
1407
+ _lockReason: _lr,
1408
+ _lockDocsUrl: _ld,
1409
+ _lockSource: _ls,
1410
+ ...payload
1411
+ } = data;
1387
1412
  byLocale[locale] = deepMerge(byLocale[locale] ?? {}, payload);
1388
1413
  }
1389
1414
  return byLocale;
@@ -1607,7 +1632,7 @@ var ObjectKernel = class {
1607
1632
  */
1608
1633
  preInjectCoreFallbacks() {
1609
1634
  if (this.config.skipSystemValidation) return;
1610
- for (const [serviceName, criticality] of Object.entries(import_system.ServiceRequirementDef)) {
1635
+ for (const [serviceName, criticality] of Object.entries(import_system2.ServiceRequirementDef)) {
1611
1636
  if (criticality !== "core") continue;
1612
1637
  const hasService = this.services.has(serviceName) || this.pluginLoader.hasService(serviceName);
1613
1638
  if (!hasService) {
@@ -1631,7 +1656,7 @@ var ObjectKernel = class {
1631
1656
  this.logger.debug("Validating system service requirements...");
1632
1657
  const missingServices = [];
1633
1658
  const missingCoreServices = [];
1634
- for (const [serviceName, criticality] of Object.entries(import_system.ServiceRequirementDef)) {
1659
+ for (const [serviceName, criticality] of Object.entries(import_system2.ServiceRequirementDef)) {
1635
1660
  const hasService = this.services.has(serviceName) || this.pluginLoader.hasService(serviceName);
1636
1661
  if (!hasService) {
1637
1662
  if (criticality === "required") {
@@ -4410,7 +4435,8 @@ async function resolveAuthzContext(input) {
4410
4435
  positions: [],
4411
4436
  permissions: [],
4412
4437
  systemPermissions: [],
4413
- org_user_ids: []
4438
+ org_user_ids: [],
4439
+ accessible_org_ids: []
4414
4440
  };
4415
4441
  let userId;
4416
4442
  let tenantId;
@@ -4436,6 +4462,33 @@ async function resolveAuthzContext(input) {
4436
4462
  ctx.userId = userId;
4437
4463
  if (tenantId) ctx.tenantId = tenantId;
4438
4464
  if (!ql || typeof ql.find !== "function") return ctx;
4465
+ const grants = await resolveUserAuthzGrants(ql, userId, {
4466
+ tenantId,
4467
+ nowMs: input.nowMs,
4468
+ seedPermissions: ctx.permissions,
4469
+ seedEmail: ctx.email
4470
+ });
4471
+ ctx.positions = grants.positions;
4472
+ ctx.permissions = grants.permissions;
4473
+ ctx.systemPermissions = grants.systemPermissions;
4474
+ ctx.org_user_ids = grants.org_user_ids;
4475
+ ctx.accessible_org_ids = grants.accessible_org_ids;
4476
+ if (grants.tabPermissions) ctx.tabPermissions = grants.tabPermissions;
4477
+ if (grants.posture) ctx.posture = grants.posture;
4478
+ if (grants.email && !ctx.email) ctx.email = grants.email;
4479
+ return ctx;
4480
+ }
4481
+ async function resolveUserAuthzGrants(ql, userId, opts = {}) {
4482
+ const { tenantId } = opts;
4483
+ const grants = {
4484
+ positions: [],
4485
+ permissions: Array.isArray(opts.seedPermissions) ? [...opts.seedPermissions] : [],
4486
+ systemPermissions: [],
4487
+ org_user_ids: [userId],
4488
+ accessible_org_ids: []
4489
+ };
4490
+ if (opts.seedEmail) grants.email = opts.seedEmail;
4491
+ if (!ql || typeof ql.find !== "function") return grants;
4439
4492
  let userRowLoaded = false;
4440
4493
  let userRow;
4441
4494
  const getUserRow = async () => {
@@ -4446,28 +4499,35 @@ async function resolveAuthzContext(input) {
4446
4499
  }
4447
4500
  return userRow;
4448
4501
  };
4449
- if (!ctx.email) {
4502
+ if (!grants.email) {
4450
4503
  const u = await getUserRow();
4451
- if (u?.email) ctx.email = String(u.email);
4504
+ if (u?.email) grants.email = String(u.email);
4452
4505
  }
4453
- const memberWhere = tenantId ? { user_id: userId, organization_id: tenantId } : { user_id: userId };
4454
- const members = await tryFind(ql, "sys_member", memberWhere, 50);
4506
+ const nowMs = opts.nowMs ?? Date.now();
4507
+ const members = await tryFind(ql, "sys_member", { user_id: userId }, 200);
4508
+ const accessibleOrgIds = /* @__PURE__ */ new Set();
4455
4509
  for (const m of members) {
4510
+ if (!isGrantActive(m, nowMs)) continue;
4511
+ const org = m.organization_id ?? m.organizationId;
4512
+ if (typeof org === "string" && org) accessibleOrgIds.add(org);
4513
+ }
4514
+ grants.accessible_org_ids = Array.from(accessibleOrgIds);
4515
+ const activeMembers = tenantId ? members.filter((m) => (m.organization_id ?? m.organizationId) === tenantId) : members;
4516
+ for (const m of activeMembers) {
4456
4517
  if (m.role && typeof m.role === "string") {
4457
4518
  for (const raw of m.role.split(",").map((s) => s.trim()).filter(Boolean)) {
4458
4519
  const r = (0, import_spec.mapMembershipRole)(raw);
4459
- if (!ctx.positions.includes(r)) ctx.positions.push(r);
4520
+ if (!grants.positions.includes(r)) grants.positions.push(r);
4460
4521
  }
4461
4522
  }
4462
4523
  }
4463
- const nowMs = input.nowMs ?? Date.now();
4464
4524
  const userPositionRows = await tryFind(ql, "sys_user_position", { user_id: userId }, 200);
4465
4525
  for (const ur of userPositionRows) {
4466
4526
  const org = ur.organization_id ?? null;
4467
4527
  if (org && tenantId && org !== tenantId) continue;
4468
4528
  if (!isGrantActive(ur, nowMs)) continue;
4469
4529
  const r = ur.position;
4470
- if (typeof r === "string" && r && !ctx.positions.includes(r)) ctx.positions.push(r);
4530
+ if (typeof r === "string" && r && !grants.positions.includes(r)) grants.positions.push(r);
4471
4531
  }
4472
4532
  if (tenantId) {
4473
4533
  const orgMembers = await tryFind(ql, "sys_member", { organization_id: tenantId }, 1e3);
@@ -4475,9 +4535,7 @@ async function resolveAuthzContext(input) {
4475
4535
  orgMembers.map((m) => m.user_id ?? m.userId).filter((v) => typeof v === "string" && v.length > 0)
4476
4536
  );
4477
4537
  ids.add(userId);
4478
- ctx.org_user_ids = Array.from(ids);
4479
- } else {
4480
- ctx.org_user_ids = [userId];
4538
+ grants.org_user_ids = Array.from(ids);
4481
4539
  }
4482
4540
  const upsRowsAll = await tryFind(ql, "sys_user_permission_set", { user_id: userId }, 100);
4483
4541
  const upsRows = upsRowsAll.filter((r) => isGrantActive(r, nowMs));
@@ -4491,9 +4549,9 @@ async function resolveAuthzContext(input) {
4491
4549
  upsRows.filter((r) => (r.organization_id ?? r.organizationId ?? null) === null).map((r) => r.permission_set_id ?? r.permissionSetId).filter(Boolean)
4492
4550
  );
4493
4551
  let hasPlatformAdminGrant = false;
4494
- if (!ctx.positions.includes("everyone")) ctx.positions.push("everyone");
4495
- if (ctx.positions.length > 0) {
4496
- const positionRows = await tryFind(ql, "sys_position", { name: { $in: ctx.positions } }, 100);
4552
+ if (!grants.positions.includes("everyone")) grants.positions.push("everyone");
4553
+ if (grants.positions.length > 0) {
4554
+ const positionRows = await tryFind(ql, "sys_position", { name: { $in: grants.positions } }, 100);
4497
4555
  const positionIds = positionRows.map((r) => r.id).filter(Boolean);
4498
4556
  if (positionIds.length > 0) {
4499
4557
  const rpsRows = await tryFind(ql, "sys_position_permission_set", { position_id: { $in: positionIds } }, 500);
@@ -4508,12 +4566,12 @@ async function resolveAuthzContext(input) {
4508
4566
  const tabRank = { hidden: 0, default_off: 1, default_on: 2, visible: 3 };
4509
4567
  const mergedTabs = {};
4510
4568
  for (const ps of psRows) {
4511
- if (ps.name && !ctx.permissions.includes(ps.name)) ctx.permissions.push(ps.name);
4569
+ if (ps.name && !grants.permissions.includes(ps.name)) grants.permissions.push(ps.name);
4512
4570
  if (ps.name === import_spec.ADMIN_FULL_ACCESS && unscopedUserPsIds.has(ps.id)) hasPlatformAdminGrant = true;
4513
4571
  const sysPerms = typeof ps.system_permissions === "string" ? safeJsonParse2(ps.system_permissions, []) : ps.system_permissions ?? ps.systemPermissions;
4514
4572
  if (Array.isArray(sysPerms)) {
4515
4573
  for (const p of sysPerms) {
4516
- if (typeof p === "string" && !ctx.systemPermissions.includes(p)) ctx.systemPermissions.push(p);
4574
+ if (typeof p === "string" && !grants.systemPermissions.includes(p)) grants.systemPermissions.push(p);
4517
4575
  }
4518
4576
  }
4519
4577
  const tabs = typeof ps.tab_permissions === "string" ? safeJsonParse2(ps.tab_permissions, {}) : ps.tab_permissions ?? ps.tabPermissions;
@@ -4527,20 +4585,22 @@ async function resolveAuthzContext(input) {
4527
4585
  }
4528
4586
  }
4529
4587
  }
4530
- if (Object.keys(mergedTabs).length > 0) ctx.tabPermissions = mergedTabs;
4588
+ if (Object.keys(mergedTabs).length > 0) grants.tabPermissions = mergedTabs;
4531
4589
  }
4532
- if (hasPlatformAdminGrant && !ctx.positions.includes(import_spec.BUILTIN_IDENTITY_PLATFORM_ADMIN)) {
4533
- ctx.positions.unshift(import_spec.BUILTIN_IDENTITY_PLATFORM_ADMIN);
4590
+ if (hasPlatformAdminGrant && !grants.positions.includes(import_spec.BUILTIN_IDENTITY_PLATFORM_ADMIN)) {
4591
+ grants.positions.unshift(import_spec.BUILTIN_IDENTITY_PLATFORM_ADMIN);
4534
4592
  }
4535
- ctx.posture = derivePosture({
4593
+ grants.posture = derivePosture({
4536
4594
  isPlatformAdmin: hasPlatformAdminGrant,
4537
- isTenantAdmin: ctx.permissions.includes(import_spec.ORGANIZATION_ADMIN)
4595
+ // [ADR-0105 D4] Either org-admin capability set resolves the rung — the
4596
+ // wall-less variant differs only by withholding the superuser bits.
4597
+ isTenantAdmin: import_spec.ORGANIZATION_ADMIN_GRANTS.some((n) => grants.permissions.includes(n))
4538
4598
  });
4539
- if (!ctx.permissions.includes("ai_seat")) {
4599
+ if (!grants.permissions.includes("ai_seat")) {
4540
4600
  const aiAccess = (await getUserRow())?.ai_access;
4541
- if (aiAccess === true || aiAccess === 1 || aiAccess === "1") ctx.permissions.push("ai_seat");
4601
+ if (aiAccess === true || aiAccess === 1 || aiAccess === "1") grants.permissions.push("ai_seat");
4542
4602
  }
4543
- return ctx;
4603
+ return grants;
4544
4604
  }
4545
4605
  function isValidTimeZone(tz) {
4546
4606
  try {
@@ -4665,8 +4725,8 @@ function calendarPartsInTzOrUtc(d, tz) {
4665
4725
  day: d.getUTCDate()
4666
4726
  };
4667
4727
  }
4668
- function zonedDateStartToUtcMs(ymd, tz) {
4669
- const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(ymd);
4728
+ function zonedDateStartToUtcMs(ymd2, tz) {
4729
+ const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(ymd2);
4670
4730
  const wallAsUtc = m ? Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3])) : NaN;
4671
4731
  if (!tz || tz === "UTC" || Number.isNaN(wallAsUtc)) return wallAsUtc;
4672
4732
  try {
@@ -4882,6 +4942,209 @@ async function bulkWrite(rows, opts) {
4882
4942
  return results;
4883
4943
  }
4884
4944
 
4945
+ // src/utils/filter-tokens.ts
4946
+ var import_data = require("@objectstack/spec/data");
4947
+ var UnknownFilterTokenError = class extends Error {
4948
+ constructor(token, suggestion) {
4949
+ super(
4950
+ `Unresolvable filter placeholder "{${token}}". ` + (suggestion ? `Did you mean "{${suggestion}}"? ` : "Resolvable placeholders are the context tokens ({current_user_id}, {current_org_id}) and the date macros ({today}, {current_quarter_start}, {30_days_ago}, \u2026). ") + "Sending it to the data engine verbatim would compare it as a literal string and match nothing, which is indistinguishable from an empty result."
4951
+ );
4952
+ this.status = 400;
4953
+ this.code = "FILTER_TOKEN_UNKNOWN";
4954
+ this.name = "UnknownFilterTokenError";
4955
+ this.token = token;
4956
+ this.suggestion = suggestion;
4957
+ }
4958
+ };
4959
+ var UnresolvedFilterTokenError = class extends Error {
4960
+ constructor(token, detail) {
4961
+ super(`Filter placeholder "{${token}}" cannot be resolved: ${detail}`);
4962
+ /** 400, not 500 — see {@link UnknownFilterTokenError}. */
4963
+ this.status = 400;
4964
+ this.code = "FILTER_TOKEN_UNRESOLVED";
4965
+ this.name = "UnresolvedFilterTokenError";
4966
+ this.token = token;
4967
+ }
4968
+ };
4969
+ function ymd(year, month, day) {
4970
+ const p = (n) => String(n).padStart(2, "0");
4971
+ return `${year}-${p(month)}-${p(day)}`;
4972
+ }
4973
+ function proxyDay(now, timezone) {
4974
+ const { year, month, day } = calendarPartsInTzOrUtc(now, timezone);
4975
+ return new Date(Date.UTC(year, month - 1, day));
4976
+ }
4977
+ var asYmd = (d) => ymd(d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate());
4978
+ function startOfPeriod(kind, d) {
4979
+ const r = new Date(d.getTime());
4980
+ switch (kind) {
4981
+ case "week": {
4982
+ const dow = (r.getUTCDay() + 6) % 7;
4983
+ r.setUTCDate(r.getUTCDate() - dow);
4984
+ return r;
4985
+ }
4986
+ case "month":
4987
+ return new Date(Date.UTC(r.getUTCFullYear(), r.getUTCMonth(), 1));
4988
+ case "quarter":
4989
+ return new Date(Date.UTC(r.getUTCFullYear(), Math.floor(r.getUTCMonth() / 3) * 3, 1));
4990
+ case "year":
4991
+ return new Date(Date.UTC(r.getUTCFullYear(), 0, 1));
4992
+ }
4993
+ }
4994
+ function daysInMonth(year, month) {
4995
+ return new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
4996
+ }
4997
+ function addMonthsClamped(d, n) {
4998
+ const year = d.getUTCFullYear();
4999
+ const month = d.getUTCMonth() + n;
5000
+ const targetYear = year + Math.floor(month / 12);
5001
+ const targetMonth = (month % 12 + 12) % 12;
5002
+ const day = Math.min(d.getUTCDate(), daysInMonth(targetYear, targetMonth));
5003
+ return new Date(Date.UTC(
5004
+ targetYear,
5005
+ targetMonth,
5006
+ day,
5007
+ d.getUTCHours(),
5008
+ d.getUTCMinutes(),
5009
+ d.getUTCSeconds(),
5010
+ d.getUTCMilliseconds()
5011
+ ));
5012
+ }
5013
+ function addPeriods(kind, d, n) {
5014
+ switch (kind) {
5015
+ case "week": {
5016
+ const r = new Date(d.getTime());
5017
+ r.setUTCDate(r.getUTCDate() + n * 7);
5018
+ return r;
5019
+ }
5020
+ case "month":
5021
+ return addMonthsClamped(d, n);
5022
+ case "quarter":
5023
+ return addMonthsClamped(d, n * 3);
5024
+ case "year":
5025
+ return addMonthsClamped(d, n * 12);
5026
+ }
5027
+ }
5028
+ function addUnits(unit, d, n) {
5029
+ const r = new Date(d.getTime());
5030
+ switch (unit) {
5031
+ case "minute":
5032
+ r.setUTCMinutes(r.getUTCMinutes() + n);
5033
+ return r;
5034
+ case "hour":
5035
+ r.setUTCHours(r.getUTCHours() + n);
5036
+ return r;
5037
+ case "day":
5038
+ r.setUTCDate(r.getUTCDate() + n);
5039
+ return r;
5040
+ case "week":
5041
+ r.setUTCDate(r.getUTCDate() + n * 7);
5042
+ return r;
5043
+ // Month/year steps clamp rather than overflow — see addMonthsClamped.
5044
+ case "month":
5045
+ return addMonthsClamped(d, n);
5046
+ case "year":
5047
+ return addMonthsClamped(d, n * 12);
5048
+ }
5049
+ }
5050
+ var PERIOD_RE = /^(?:(current|last|next)_)?(week|month|quarter|year)_(start|end)$/;
5051
+ function resolvePeriodToken(token, today) {
5052
+ const m = PERIOD_RE.exec(token);
5053
+ if (!m) return void 0;
5054
+ const rel = m[1] ?? "current";
5055
+ const kind = m[2];
5056
+ const bound = m[3];
5057
+ const offset = rel === "last" ? -1 : rel === "next" ? 1 : 0;
5058
+ const periodStart = startOfPeriod(kind, addPeriods(kind, startOfPeriod(kind, today), offset));
5059
+ if (bound === "start") return asYmd(periodStart);
5060
+ const next = addPeriods(kind, periodStart, 1);
5061
+ next.setUTCDate(next.getUTCDate() - 1);
5062
+ return asYmd(next);
5063
+ }
5064
+ function resolveFilterToken(token, ctx = {}) {
5065
+ const now = ctx.now ?? /* @__PURE__ */ new Date();
5066
+ if (token === "current_user_id") {
5067
+ if (!ctx.userId) {
5068
+ throw new UnresolvedFilterTokenError(
5069
+ token,
5070
+ "the request has no authenticated user. A filter scoped to the signed-in user cannot run for an anonymous or system caller \u2014 gate the surface on authentication, or drop the token from the filter."
5071
+ );
5072
+ }
5073
+ return ctx.userId;
5074
+ }
5075
+ if (token === "current_org_id") {
5076
+ if (!ctx.orgId) {
5077
+ throw new UnresolvedFilterTokenError(
5078
+ token,
5079
+ "the request carries no active organization (ExecutionContext.tenantId is unset). Set the active org on the request, or drop the token from the filter."
5080
+ );
5081
+ }
5082
+ return ctx.orgId;
5083
+ }
5084
+ const today = proxyDay(now, ctx.timezone);
5085
+ switch (token) {
5086
+ case "now":
5087
+ return now.toISOString();
5088
+ case "today":
5089
+ return asYmd(today);
5090
+ case "yesterday":
5091
+ return asYmd(addUnits("day", today, -1));
5092
+ case "tomorrow":
5093
+ return asYmd(addUnits("day", today, 1));
5094
+ }
5095
+ const period = resolvePeriodToken(token, today);
5096
+ if (period !== void 0) return period;
5097
+ const param = (0, import_data.parseDateMacroParam)(token);
5098
+ if (param) {
5099
+ const sign = param.direction === "ago" ? -1 : 1;
5100
+ if (param.unit === "minute" || param.unit === "hour") {
5101
+ return addUnits(param.unit, now, sign * param.n).toISOString();
5102
+ }
5103
+ return asYmd(addUnits(param.unit, today, sign * param.n));
5104
+ }
5105
+ return void 0;
5106
+ }
5107
+ function hasFilterToken(node) {
5108
+ if (typeof node === "string") return (0, import_data.classifyFilterToken)(node) !== null;
5109
+ if (Array.isArray(node)) return node.some(hasFilterToken);
5110
+ if (node && typeof node === "object" && !(node instanceof Date)) {
5111
+ return Object.values(node).some(hasFilterToken);
5112
+ }
5113
+ return false;
5114
+ }
5115
+ function resolveFilterTokens(filter, ctx = {}) {
5116
+ if (filter == null) return filter;
5117
+ if (!hasFilterToken(filter)) return filter;
5118
+ const pinned = { ...ctx, now: ctx.now ?? /* @__PURE__ */ new Date() };
5119
+ const walk = (node) => {
5120
+ if (typeof node === "string") {
5121
+ const cls = (0, import_data.classifyFilterToken)(node);
5122
+ if (!cls) return node;
5123
+ if (cls.kind === "unknown") throw new UnknownFilterTokenError(cls.token, cls.suggestion);
5124
+ const resolved = resolveFilterToken(cls.token, pinned);
5125
+ if (resolved === void 0) throw new UnknownFilterTokenError(cls.token);
5126
+ return resolved;
5127
+ }
5128
+ if (Array.isArray(node)) return node.map(walk);
5129
+ if (node && typeof node === "object") {
5130
+ if (node instanceof Date) return node;
5131
+ const out = {};
5132
+ for (const [k, v] of Object.entries(node)) out[k] = walk(v);
5133
+ return out;
5134
+ }
5135
+ return node;
5136
+ };
5137
+ return walk(filter);
5138
+ }
5139
+ function filterTokenContextFrom(execCtx, now) {
5140
+ return {
5141
+ now,
5142
+ timezone: execCtx?.timezone,
5143
+ userId: execCtx?.userId,
5144
+ orgId: execCtx?.tenantId
5145
+ };
5146
+ }
5147
+
4885
5148
  // src/health-monitor.ts
4886
5149
  var PluginHealthMonitor = class {
4887
5150
  constructor(logger) {
@@ -5827,6 +6090,8 @@ var NamespaceResolver = class {
5827
6090
  SecurePluginContext,
5828
6091
  SemanticVersionManager,
5829
6092
  ServiceLifecycle,
6093
+ UnknownFilterTokenError,
6094
+ UnresolvedFilterTokenError,
5830
6095
  bucketKeyToCalendarRange,
5831
6096
  buildPermissionsFromGrants,
5832
6097
  bulkWrite,
@@ -5847,6 +6112,7 @@ var NamespaceResolver = class {
5847
6112
  derivePosture,
5848
6113
  evaluateAuthGate,
5849
6114
  extractApiKey,
6115
+ filterTokenContextFrom,
5850
6116
  generateApiKey,
5851
6117
  generateEd25519KeyPair,
5852
6118
  getEnv,
@@ -5863,8 +6129,11 @@ var NamespaceResolver = class {
5863
6129
  readAuthoredTranslationLayer,
5864
6130
  resolveApiKeyPrincipal,
5865
6131
  resolveAuthzContext,
6132
+ resolveFilterToken,
6133
+ resolveFilterTokens,
5866
6134
  resolveLocale,
5867
6135
  resolveLocalizationContext,
6136
+ resolveUserAuthzGrants,
5868
6137
  safeExit,
5869
6138
  shouldDenyAnonymous,
5870
6139
  signPayload,