@open-mercato/shared 0.6.3-develop.3525.3.cc18b8a43f → 0.6.3-develop.3574.1.f3c5dcf345

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.
@@ -1,2 +1,2 @@
1
- [build:shared] found 209 entry points
1
+ [build:shared] found 211 entry points
2
2
  [build:shared] built successfully
@@ -1,12 +1,6 @@
1
- function escapeRegex(input) {
2
- return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3
- }
1
+ import { matchWildcardPattern } from "@open-mercato/shared/lib/patterns/wildcard";
4
2
  function matchesEventPattern(pattern, eventId) {
5
- if (pattern === eventId) return true;
6
- if (pattern === "*") return true;
7
- const escaped = escapeRegex(pattern).replace(/\\\*/g, ".*");
8
- const regex = new RegExp("^" + escaped + "$");
9
- return regex.test(eventId);
3
+ return matchWildcardPattern(eventId, pattern);
10
4
  }
11
5
  function collectSyncSubscribers(allSyncSubscribers, eventId) {
12
6
  return allSyncSubscribers.filter((s) => matchesEventPattern(s.metadata.event, eventId)).sort((a, b) => (a.metadata.priority ?? 50) - (b.metadata.priority ?? 50));
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/crud/sync-event-runner.ts"],
4
- "sourcesContent": ["import type { SyncCrudEventPayload } from './sync-event-types'\nimport type { SyncSubscriberEntry } from './sync-subscriber-store'\n\n// ---------------------------------------------------------------------------\n// Event pattern matching\n// ---------------------------------------------------------------------------\n\nfunction escapeRegex(input: string): string {\n // Escape all characters with special meaning in regular expressions\n return input.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\nexport function matchesEventPattern(pattern: string, eventId: string): boolean {\n if (pattern === eventId) return true\n if (pattern === '*') return true\n // Escape the pattern for literal use in a RegExp, then turn \"\\*\" into \".*\" for wildcard support\n const escaped = escapeRegex(pattern).replace(/\\\\\\*/g, '.*')\n const regex = new RegExp('^' + escaped + '$')\n return regex.test(eventId)\n}\n\n// ---------------------------------------------------------------------------\n// Collect matching subscribers\n// ---------------------------------------------------------------------------\n\nexport function collectSyncSubscribers(\n allSyncSubscribers: SyncSubscriberEntry[],\n eventId: string,\n): SyncSubscriberEntry[] {\n return allSyncSubscribers\n .filter((s) => matchesEventPattern(s.metadata.event, eventId))\n .sort((a, b) => (a.metadata.priority ?? 50) - (b.metadata.priority ?? 50))\n}\n\n// ---------------------------------------------------------------------------\n// Run sync before-event subscribers\n// ---------------------------------------------------------------------------\n\nexport async function runSyncBeforeEvent(\n subscribers: SyncSubscriberEntry[],\n payload: SyncCrudEventPayload,\n ctx: { resolve: <T = unknown>(name: string) => T },\n): Promise<{ ok: boolean; errorBody?: Record<string, unknown>; errorStatus?: number; modifiedPayload?: Record<string, unknown> }> {\n let currentPayload = payload.payload\n\n for (const subscriber of subscribers) {\n const result = await subscriber.handler({ ...payload, payload: currentPayload }, ctx)\n\n if (result?.ok === false) {\n const body = result.body ?? { error: result.message ?? 'Operation blocked', subscriberId: subscriber.metadata.id }\n return { ok: false, errorBody: body, errorStatus: result.status ?? 422 }\n }\n\n if (result?.modifiedPayload) {\n currentPayload = { ...currentPayload, ...result.modifiedPayload }\n }\n }\n\n return { ok: true, modifiedPayload: currentPayload !== payload.payload ? currentPayload : undefined }\n}\n\n// ---------------------------------------------------------------------------\n// Run sync after-event subscribers\n// ---------------------------------------------------------------------------\n\nexport async function runSyncAfterEvent(\n subscribers: SyncSubscriberEntry[],\n payload: SyncCrudEventPayload,\n ctx: { resolve: <T = unknown>(name: string) => T },\n): Promise<void> {\n for (const subscriber of subscribers) {\n try {\n await subscriber.handler(payload, ctx)\n } catch (error) {\n console.error(`[sync-event] after-subscriber failed: ${subscriber.metadata.id}`, error)\n }\n }\n}\n"],
5
- "mappings": "AAOA,SAAS,YAAY,OAAuB;AAE1C,SAAO,MAAM,QAAQ,uBAAuB,MAAM;AACpD;AAEO,SAAS,oBAAoB,SAAiB,SAA0B;AAC7E,MAAI,YAAY,QAAS,QAAO;AAChC,MAAI,YAAY,IAAK,QAAO;AAE5B,QAAM,UAAU,YAAY,OAAO,EAAE,QAAQ,SAAS,IAAI;AAC1D,QAAM,QAAQ,IAAI,OAAO,MAAM,UAAU,GAAG;AAC5C,SAAO,MAAM,KAAK,OAAO;AAC3B;AAMO,SAAS,uBACd,oBACA,SACuB;AACvB,SAAO,mBACJ,OAAO,CAAC,MAAM,oBAAoB,EAAE,SAAS,OAAO,OAAO,CAAC,EAC5D,KAAK,CAAC,GAAG,OAAO,EAAE,SAAS,YAAY,OAAO,EAAE,SAAS,YAAY,GAAG;AAC7E;AAMA,eAAsB,mBACpB,aACA,SACA,KACgI;AAChI,MAAI,iBAAiB,QAAQ;AAE7B,aAAW,cAAc,aAAa;AACpC,UAAM,SAAS,MAAM,WAAW,QAAQ,EAAE,GAAG,SAAS,SAAS,eAAe,GAAG,GAAG;AAEpF,QAAI,QAAQ,OAAO,OAAO;AACxB,YAAM,OAAO,OAAO,QAAQ,EAAE,OAAO,OAAO,WAAW,qBAAqB,cAAc,WAAW,SAAS,GAAG;AACjH,aAAO,EAAE,IAAI,OAAO,WAAW,MAAM,aAAa,OAAO,UAAU,IAAI;AAAA,IACzE;AAEA,QAAI,QAAQ,iBAAiB;AAC3B,uBAAiB,EAAE,GAAG,gBAAgB,GAAG,OAAO,gBAAgB;AAAA,IAClE;AAAA,EACF;AAEA,SAAO,EAAE,IAAI,MAAM,iBAAiB,mBAAmB,QAAQ,UAAU,iBAAiB,OAAU;AACtG;AAMA,eAAsB,kBACpB,aACA,SACA,KACe;AACf,aAAW,cAAc,aAAa;AACpC,QAAI;AACF,YAAM,WAAW,QAAQ,SAAS,GAAG;AAAA,IACvC,SAAS,OAAO;AACd,cAAQ,MAAM,yCAAyC,WAAW,SAAS,EAAE,IAAI,KAAK;AAAA,IACxF;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import type { SyncCrudEventPayload } from './sync-event-types'\nimport type { SyncSubscriberEntry } from './sync-subscriber-store'\nimport { matchWildcardPattern } from '@open-mercato/shared/lib/patterns/wildcard'\n\n// ---------------------------------------------------------------------------\n// Event pattern matching\n// ---------------------------------------------------------------------------\n\nexport function matchesEventPattern(pattern: string, eventId: string): boolean {\n return matchWildcardPattern(eventId, pattern)\n}\n\n// ---------------------------------------------------------------------------\n// Collect matching subscribers\n// ---------------------------------------------------------------------------\n\nexport function collectSyncSubscribers(\n allSyncSubscribers: SyncSubscriberEntry[],\n eventId: string,\n): SyncSubscriberEntry[] {\n return allSyncSubscribers\n .filter((s) => matchesEventPattern(s.metadata.event, eventId))\n .sort((a, b) => (a.metadata.priority ?? 50) - (b.metadata.priority ?? 50))\n}\n\n// ---------------------------------------------------------------------------\n// Run sync before-event subscribers\n// ---------------------------------------------------------------------------\n\nexport async function runSyncBeforeEvent(\n subscribers: SyncSubscriberEntry[],\n payload: SyncCrudEventPayload,\n ctx: { resolve: <T = unknown>(name: string) => T },\n): Promise<{ ok: boolean; errorBody?: Record<string, unknown>; errorStatus?: number; modifiedPayload?: Record<string, unknown> }> {\n let currentPayload = payload.payload\n\n for (const subscriber of subscribers) {\n const result = await subscriber.handler({ ...payload, payload: currentPayload }, ctx)\n\n if (result?.ok === false) {\n const body = result.body ?? { error: result.message ?? 'Operation blocked', subscriberId: subscriber.metadata.id }\n return { ok: false, errorBody: body, errorStatus: result.status ?? 422 }\n }\n\n if (result?.modifiedPayload) {\n currentPayload = { ...currentPayload, ...result.modifiedPayload }\n }\n }\n\n return { ok: true, modifiedPayload: currentPayload !== payload.payload ? currentPayload : undefined }\n}\n\n// ---------------------------------------------------------------------------\n// Run sync after-event subscribers\n// ---------------------------------------------------------------------------\n\nexport async function runSyncAfterEvent(\n subscribers: SyncSubscriberEntry[],\n payload: SyncCrudEventPayload,\n ctx: { resolve: <T = unknown>(name: string) => T },\n): Promise<void> {\n for (const subscriber of subscribers) {\n try {\n await subscriber.handler(payload, ctx)\n } catch (error) {\n console.error(`[sync-event] after-subscriber failed: ${subscriber.metadata.id}`, error)\n }\n }\n}\n"],
5
+ "mappings": "AAEA,SAAS,4BAA4B;AAM9B,SAAS,oBAAoB,SAAiB,SAA0B;AAC7E,SAAO,qBAAqB,SAAS,OAAO;AAC9C;AAMO,SAAS,uBACd,oBACA,SACuB;AACvB,SAAO,mBACJ,OAAO,CAAC,MAAM,oBAAoB,EAAE,SAAS,OAAO,OAAO,CAAC,EAC5D,KAAK,CAAC,GAAG,OAAO,EAAE,SAAS,YAAY,OAAO,EAAE,SAAS,YAAY,GAAG;AAC7E;AAMA,eAAsB,mBACpB,aACA,SACA,KACgI;AAChI,MAAI,iBAAiB,QAAQ;AAE7B,aAAW,cAAc,aAAa;AACpC,UAAM,SAAS,MAAM,WAAW,QAAQ,EAAE,GAAG,SAAS,SAAS,eAAe,GAAG,GAAG;AAEpF,QAAI,QAAQ,OAAO,OAAO;AACxB,YAAM,OAAO,OAAO,QAAQ,EAAE,OAAO,OAAO,WAAW,qBAAqB,cAAc,WAAW,SAAS,GAAG;AACjH,aAAO,EAAE,IAAI,OAAO,WAAW,MAAM,aAAa,OAAO,UAAU,IAAI;AAAA,IACzE;AAEA,QAAI,QAAQ,iBAAiB;AAC3B,uBAAiB,EAAE,GAAG,gBAAgB,GAAG,OAAO,gBAAgB;AAAA,IAClE;AAAA,EACF;AAEA,SAAO,EAAE,IAAI,MAAM,iBAAiB,mBAAmB,QAAQ,UAAU,iBAAiB,OAAU;AACtG;AAMA,eAAsB,kBACpB,aACA,SACA,KACe;AACf,aAAW,cAAc,aAAa;AACpC,QAAI;AACF,YAAM,WAAW,QAAQ,SAAS,GAAG;AAAA,IACvC,SAAS,OAAO;AACd,cAAQ,MAAM,yCAAyC,WAAW,SAAS,EAAE,IAAI,KAAK;AAAA,IACxF;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,12 +1,4 @@
1
- const singleSegmentPatternCache = /* @__PURE__ */ new Map();
2
- function getSingleSegmentPatternRegex(pattern) {
3
- const existing = singleSegmentPatternCache.get(pattern);
4
- if (existing) return existing;
5
- const regexPattern = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^.]+");
6
- const compiled = new RegExp(`^${regexPattern}$`);
7
- singleSegmentPatternCache.set(pattern, compiled);
8
- return compiled;
9
- }
1
+ import { matchWildcardPattern } from "@open-mercato/shared/lib/patterns/wildcard";
10
2
  function matchEventPattern(eventName, pattern, options) {
11
3
  const mode = options?.mode ?? "single-segment";
12
4
  if (pattern === "*") return true;
@@ -23,7 +15,7 @@ function matchEventPattern(eventName, pattern, options) {
23
15
  }
24
16
  return false;
25
17
  }
26
- return getSingleSegmentPatternRegex(pattern).test(eventName);
18
+ return matchWildcardPattern(eventName, pattern, { singleSegmentWildcard: true });
27
19
  }
28
20
  function matchAnyEventPattern(eventName, patterns, options) {
29
21
  for (const pattern of patterns) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/events/patterns.ts"],
4
- "sourcesContent": ["export type EventPatternMode = 'single-segment' | 'prefix'\n\ntype MatchEventPatternOptions = {\n mode?: EventPatternMode\n}\n\nconst singleSegmentPatternCache = new Map<string, RegExp>()\n\nfunction getSingleSegmentPatternRegex(pattern: string): RegExp {\n const existing = singleSegmentPatternCache.get(pattern)\n if (existing) return existing\n\n const regexPattern = pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\*/g, '[^.]+')\n\n const compiled = new RegExp(`^${regexPattern}$`)\n singleSegmentPatternCache.set(pattern, compiled)\n return compiled\n}\n\nexport function matchEventPattern(\n eventName: string,\n pattern: string,\n options?: MatchEventPatternOptions,\n): boolean {\n const mode = options?.mode ?? 'single-segment'\n\n if (pattern === '*') return true\n if (pattern === eventName) return true\n if (!pattern.includes('*')) return false\n\n if (mode === 'prefix') {\n if (pattern.endsWith('.*')) {\n const prefix = pattern.slice(0, -2)\n return eventName.startsWith(prefix + '.')\n }\n\n if (pattern.endsWith('*')) {\n const prefix = pattern.slice(0, -1)\n return eventName.startsWith(prefix)\n }\n\n return false\n }\n\n return getSingleSegmentPatternRegex(pattern).test(eventName)\n}\n\nexport function matchAnyEventPattern(\n eventName: string,\n patterns: Iterable<string>,\n options?: MatchEventPatternOptions,\n): boolean {\n for (const pattern of patterns) {\n if (matchEventPattern(eventName, pattern, options)) {\n return true\n }\n }\n\n return false\n}\n\nexport function matchWebhookEventPattern(eventName: string, pattern: string): boolean {\n return matchEventPattern(eventName, pattern, { mode: 'prefix' })\n}\n\nexport function matchAnyWebhookEventPattern(eventName: string, patterns: Iterable<string>): boolean {\n return matchAnyEventPattern(eventName, patterns, { mode: 'prefix' })\n}\n"],
5
- "mappings": "AAMA,MAAM,4BAA4B,oBAAI,IAAoB;AAE1D,SAAS,6BAA6B,SAAyB;AAC7D,QAAM,WAAW,0BAA0B,IAAI,OAAO;AACtD,MAAI,SAAU,QAAO;AAErB,QAAM,eAAe,QAClB,QAAQ,qBAAqB,MAAM,EACnC,QAAQ,OAAO,OAAO;AAEzB,QAAM,WAAW,IAAI,OAAO,IAAI,YAAY,GAAG;AAC/C,4BAA0B,IAAI,SAAS,QAAQ;AAC/C,SAAO;AACT;AAEO,SAAS,kBACd,WACA,SACA,SACS;AACT,QAAM,OAAO,SAAS,QAAQ;AAE9B,MAAI,YAAY,IAAK,QAAO;AAC5B,MAAI,YAAY,UAAW,QAAO;AAClC,MAAI,CAAC,QAAQ,SAAS,GAAG,EAAG,QAAO;AAEnC,MAAI,SAAS,UAAU;AACrB,QAAI,QAAQ,SAAS,IAAI,GAAG;AAC1B,YAAM,SAAS,QAAQ,MAAM,GAAG,EAAE;AAClC,aAAO,UAAU,WAAW,SAAS,GAAG;AAAA,IAC1C;AAEA,QAAI,QAAQ,SAAS,GAAG,GAAG;AACzB,YAAM,SAAS,QAAQ,MAAM,GAAG,EAAE;AAClC,aAAO,UAAU,WAAW,MAAM;AAAA,IACpC;AAEA,WAAO;AAAA,EACT;AAEA,SAAO,6BAA6B,OAAO,EAAE,KAAK,SAAS;AAC7D;AAEO,SAAS,qBACd,WACA,UACA,SACS;AACT,aAAW,WAAW,UAAU;AAC9B,QAAI,kBAAkB,WAAW,SAAS,OAAO,GAAG;AAClD,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,yBAAyB,WAAmB,SAA0B;AACpF,SAAO,kBAAkB,WAAW,SAAS,EAAE,MAAM,SAAS,CAAC;AACjE;AAEO,SAAS,4BAA4B,WAAmB,UAAqC;AAClG,SAAO,qBAAqB,WAAW,UAAU,EAAE,MAAM,SAAS,CAAC;AACrE;",
4
+ "sourcesContent": ["import { matchWildcardPattern } from '@open-mercato/shared/lib/patterns/wildcard'\n\nexport type EventPatternMode = 'single-segment' | 'prefix'\n\ntype MatchEventPatternOptions = {\n mode?: EventPatternMode\n}\n\nexport function matchEventPattern(\n eventName: string,\n pattern: string,\n options?: MatchEventPatternOptions,\n): boolean {\n const mode = options?.mode ?? 'single-segment'\n\n if (pattern === '*') return true\n if (pattern === eventName) return true\n if (!pattern.includes('*')) return false\n\n if (mode === 'prefix') {\n if (pattern.endsWith('.*')) {\n const prefix = pattern.slice(0, -2)\n return eventName.startsWith(prefix + '.')\n }\n\n if (pattern.endsWith('*')) {\n const prefix = pattern.slice(0, -1)\n return eventName.startsWith(prefix)\n }\n\n return false\n }\n\n return matchWildcardPattern(eventName, pattern, { singleSegmentWildcard: true })\n}\n\nexport function matchAnyEventPattern(\n eventName: string,\n patterns: Iterable<string>,\n options?: MatchEventPatternOptions,\n): boolean {\n for (const pattern of patterns) {\n if (matchEventPattern(eventName, pattern, options)) {\n return true\n }\n }\n\n return false\n}\n\nexport function matchWebhookEventPattern(eventName: string, pattern: string): boolean {\n return matchEventPattern(eventName, pattern, { mode: 'prefix' })\n}\n\nexport function matchAnyWebhookEventPattern(eventName: string, patterns: Iterable<string>): boolean {\n return matchAnyEventPattern(eventName, patterns, { mode: 'prefix' })\n}\n"],
5
+ "mappings": "AAAA,SAAS,4BAA4B;AAQ9B,SAAS,kBACd,WACA,SACA,SACS;AACT,QAAM,OAAO,SAAS,QAAQ;AAE9B,MAAI,YAAY,IAAK,QAAO;AAC5B,MAAI,YAAY,UAAW,QAAO;AAClC,MAAI,CAAC,QAAQ,SAAS,GAAG,EAAG,QAAO;AAEnC,MAAI,SAAS,UAAU;AACrB,QAAI,QAAQ,SAAS,IAAI,GAAG;AAC1B,YAAM,SAAS,QAAQ,MAAM,GAAG,EAAE;AAClC,aAAO,UAAU,WAAW,SAAS,GAAG;AAAA,IAC1C;AAEA,QAAI,QAAQ,SAAS,GAAG,GAAG;AACzB,YAAM,SAAS,QAAQ,MAAM,GAAG,EAAE;AAClC,aAAO,UAAU,WAAW,MAAM;AAAA,IACpC;AAEA,WAAO;AAAA,EACT;AAEA,SAAO,qBAAqB,WAAW,SAAS,EAAE,uBAAuB,KAAK,CAAC;AACjF;AAEO,SAAS,qBACd,WACA,UACA,SACS;AACT,aAAW,WAAW,UAAU;AAC9B,QAAI,kBAAkB,WAAW,SAAS,OAAO,GAAG;AAClD,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,yBAAyB,WAAmB,SAA0B;AACpF,SAAO,kBAAkB,WAAW,SAAS,EAAE,MAAM,SAAS,CAAC;AACjE;AAEO,SAAS,4BAA4B,WAAmB,UAAqC;AAClG,SAAO,qBAAqB,WAAW,UAAU,EAAE,MAAM,SAAS,CAAC;AACrE;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,42 @@
1
+ function matchWildcardSegment(pattern, value, options) {
2
+ let patternIndex = 0;
3
+ let valueIndex = 0;
4
+ let starIndex = -1;
5
+ let valueRetryIndex = 0;
6
+ while (valueIndex < value.length) {
7
+ const patternChar = pattern[patternIndex];
8
+ const valueChar = value[valueIndex];
9
+ if (patternChar === "*") {
10
+ starIndex = patternIndex;
11
+ valueRetryIndex = valueIndex;
12
+ patternIndex += 1;
13
+ continue;
14
+ }
15
+ if (patternChar === valueChar) {
16
+ patternIndex += 1;
17
+ valueIndex += 1;
18
+ continue;
19
+ }
20
+ if (starIndex !== -1 && (!options.singleSegmentWildcard || value[valueRetryIndex] !== ".")) {
21
+ patternIndex = starIndex + 1;
22
+ valueRetryIndex += 1;
23
+ valueIndex = valueRetryIndex;
24
+ continue;
25
+ }
26
+ return false;
27
+ }
28
+ while (pattern[patternIndex] === "*") {
29
+ patternIndex += 1;
30
+ }
31
+ return patternIndex === pattern.length;
32
+ }
33
+ function matchWildcardPattern(value, pattern, options = {}) {
34
+ if (pattern === value) return true;
35
+ if (pattern === "*") return true;
36
+ if (!pattern.includes("*")) return false;
37
+ return matchWildcardSegment(pattern, value, options);
38
+ }
39
+ export {
40
+ matchWildcardPattern
41
+ };
42
+ //# sourceMappingURL=wildcard.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/lib/patterns/wildcard.ts"],
4
+ "sourcesContent": ["export type WildcardMatchOptions = {\n singleSegmentWildcard?: boolean\n}\n\nfunction matchWildcardSegment(pattern: string, value: string, options: WildcardMatchOptions): boolean {\n let patternIndex = 0\n let valueIndex = 0\n let starIndex = -1\n let valueRetryIndex = 0\n\n while (valueIndex < value.length) {\n const patternChar = pattern[patternIndex]\n const valueChar = value[valueIndex]\n\n if (patternChar === '*') {\n starIndex = patternIndex\n valueRetryIndex = valueIndex\n patternIndex += 1\n continue\n }\n\n if (patternChar === valueChar) {\n patternIndex += 1\n valueIndex += 1\n continue\n }\n\n if (\n starIndex !== -1\n && (!options.singleSegmentWildcard || value[valueRetryIndex] !== '.')\n ) {\n patternIndex = starIndex + 1\n valueRetryIndex += 1\n valueIndex = valueRetryIndex\n continue\n }\n\n return false\n }\n\n while (pattern[patternIndex] === '*') {\n patternIndex += 1\n }\n\n return patternIndex === pattern.length\n}\n\nexport function matchWildcardPattern(\n value: string,\n pattern: string,\n options: WildcardMatchOptions = {},\n): boolean {\n if (pattern === value) return true\n if (pattern === '*') return true\n if (!pattern.includes('*')) return false\n\n return matchWildcardSegment(pattern, value, options)\n}\n\n"],
5
+ "mappings": "AAIA,SAAS,qBAAqB,SAAiB,OAAe,SAAwC;AACpG,MAAI,eAAe;AACnB,MAAI,aAAa;AACjB,MAAI,YAAY;AAChB,MAAI,kBAAkB;AAEtB,SAAO,aAAa,MAAM,QAAQ;AAChC,UAAM,cAAc,QAAQ,YAAY;AACxC,UAAM,YAAY,MAAM,UAAU;AAElC,QAAI,gBAAgB,KAAK;AACvB,kBAAY;AACZ,wBAAkB;AAClB,sBAAgB;AAChB;AAAA,IACF;AAEA,QAAI,gBAAgB,WAAW;AAC7B,sBAAgB;AAChB,oBAAc;AACd;AAAA,IACF;AAEA,QACE,cAAc,OACV,CAAC,QAAQ,yBAAyB,MAAM,eAAe,MAAM,MACjE;AACA,qBAAe,YAAY;AAC3B,yBAAmB;AACnB,mBAAa;AACb;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAEA,SAAO,QAAQ,YAAY,MAAM,KAAK;AACpC,oBAAgB;AAAA,EAClB;AAEA,SAAO,iBAAiB,QAAQ;AAClC;AAEO,SAAS,qBACd,OACA,SACA,UAAgC,CAAC,GACxB;AACT,MAAI,YAAY,MAAO,QAAO;AAC9B,MAAI,YAAY,IAAK,QAAO;AAC5B,MAAI,CAAC,QAAQ,SAAS,GAAG,EAAG,QAAO;AAEnC,SAAO,qBAAqB,SAAS,OAAO,OAAO;AACrD;",
6
+ "names": []
7
+ }
@@ -0,0 +1,31 @@
1
+ import { RE2JS } from "re2js";
2
+ const DEFAULT_MAX_PATTERN_LENGTH = 500;
3
+ const DEFAULT_MAX_INPUT_LENGTH = 1e4;
4
+ const MAX_REGEX_CACHE_SIZE = 256;
5
+ const compiledRegexCache = /* @__PURE__ */ new Map();
6
+ function getCachedRegex(pattern) {
7
+ const cached = compiledRegexCache.get(pattern);
8
+ if (cached) return cached;
9
+ const compiled = RE2JS.compile(pattern);
10
+ if (compiledRegexCache.size >= MAX_REGEX_CACHE_SIZE) {
11
+ const oldestKey = compiledRegexCache.keys().next().value;
12
+ if (oldestKey) compiledRegexCache.delete(oldestKey);
13
+ }
14
+ compiledRegexCache.set(pattern, compiled);
15
+ return compiled;
16
+ }
17
+ function testLinearRegex(pattern, input, options = {}) {
18
+ const maxPatternLength = options.maxPatternLength ?? DEFAULT_MAX_PATTERN_LENGTH;
19
+ const maxInputLength = options.maxInputLength ?? DEFAULT_MAX_INPUT_LENGTH;
20
+ if (pattern.length > maxPatternLength) return { ok: false, reason: "pattern_too_long" };
21
+ if (input.length > maxInputLength) return { ok: false, reason: "input_too_long" };
22
+ try {
23
+ return { ok: true, matched: getCachedRegex(pattern).test(input) };
24
+ } catch {
25
+ return { ok: false, reason: "invalid_pattern" };
26
+ }
27
+ }
28
+ export {
29
+ testLinearRegex
30
+ };
31
+ //# sourceMappingURL=linear.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/lib/regex/linear.ts"],
4
+ "sourcesContent": ["import { RE2JS } from 're2js'\n\nconst DEFAULT_MAX_PATTERN_LENGTH = 500\nconst DEFAULT_MAX_INPUT_LENGTH = 10_000\nconst MAX_REGEX_CACHE_SIZE = 256\n\nconst compiledRegexCache = new Map<string, RE2JS>()\n\nexport type LinearRegexTestOptions = {\n maxPatternLength?: number\n maxInputLength?: number\n}\n\nexport type LinearRegexTestResult =\n | { ok: true; matched: boolean }\n | { ok: false; reason: 'pattern_too_long' | 'input_too_long' | 'invalid_pattern' }\n\nfunction getCachedRegex(pattern: string): RE2JS {\n const cached = compiledRegexCache.get(pattern)\n if (cached) return cached\n\n const compiled = RE2JS.compile(pattern)\n if (compiledRegexCache.size >= MAX_REGEX_CACHE_SIZE) {\n const oldestKey = compiledRegexCache.keys().next().value\n if (oldestKey) compiledRegexCache.delete(oldestKey)\n }\n compiledRegexCache.set(pattern, compiled)\n return compiled\n}\n\nexport function testLinearRegex(\n pattern: string,\n input: string,\n options: LinearRegexTestOptions = {},\n): LinearRegexTestResult {\n const maxPatternLength = options.maxPatternLength ?? DEFAULT_MAX_PATTERN_LENGTH\n const maxInputLength = options.maxInputLength ?? DEFAULT_MAX_INPUT_LENGTH\n\n if (pattern.length > maxPatternLength) return { ok: false, reason: 'pattern_too_long' }\n if (input.length > maxInputLength) return { ok: false, reason: 'input_too_long' }\n\n try {\n return { ok: true, matched: getCachedRegex(pattern).test(input) }\n } catch {\n return { ok: false, reason: 'invalid_pattern' }\n }\n}\n\n"],
5
+ "mappings": "AAAA,SAAS,aAAa;AAEtB,MAAM,6BAA6B;AACnC,MAAM,2BAA2B;AACjC,MAAM,uBAAuB;AAE7B,MAAM,qBAAqB,oBAAI,IAAmB;AAWlD,SAAS,eAAe,SAAwB;AAC9C,QAAM,SAAS,mBAAmB,IAAI,OAAO;AAC7C,MAAI,OAAQ,QAAO;AAEnB,QAAM,WAAW,MAAM,QAAQ,OAAO;AACtC,MAAI,mBAAmB,QAAQ,sBAAsB;AACnD,UAAM,YAAY,mBAAmB,KAAK,EAAE,KAAK,EAAE;AACnD,QAAI,UAAW,oBAAmB,OAAO,SAAS;AAAA,EACpD;AACA,qBAAmB,IAAI,SAAS,QAAQ;AACxC,SAAO;AACT;AAEO,SAAS,gBACd,SACA,OACA,UAAkC,CAAC,GACZ;AACvB,QAAM,mBAAmB,QAAQ,oBAAoB;AACrD,QAAM,iBAAiB,QAAQ,kBAAkB;AAEjD,MAAI,QAAQ,SAAS,iBAAkB,QAAO,EAAE,IAAI,OAAO,QAAQ,mBAAmB;AACtF,MAAI,MAAM,SAAS,eAAgB,QAAO,EAAE,IAAI,OAAO,QAAQ,iBAAiB;AAEhF,MAAI;AACF,WAAO,EAAE,IAAI,MAAM,SAAS,eAAe,OAAO,EAAE,KAAK,KAAK,EAAE;AAAA,EAClE,QAAQ;AACN,WAAO,EAAE,IAAI,OAAO,QAAQ,kBAAkB;AAAA,EAChD;AACF;",
6
+ "names": []
7
+ }
@@ -1,4 +1,4 @@
1
- const APP_VERSION = "0.6.3-develop.3525.3.cc18b8a43f";
1
+ const APP_VERSION = "0.6.3-develop.3574.1.f3c5dcf345";
2
2
  const appVersion = APP_VERSION;
3
3
  export {
4
4
  APP_VERSION,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/version.ts"],
4
- "sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.6.3-develop.3525.3.cc18b8a43f'\nexport const appVersion = APP_VERSION\n"],
4
+ "sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.6.3-develop.3574.1.f3c5dcf345'\nexport const appVersion = APP_VERSION\n"],
5
5
  "mappings": "AACO,MAAM,cAAc;AACpB,MAAM,aAAa;",
6
6
  "names": []
7
7
  }
@@ -1,4 +1,7 @@
1
1
  import { z } from "zod";
2
+ import { testLinearRegex } from "../../lib/regex/linear.js";
3
+ const MAX_CUSTOM_FIELD_REGEX_PATTERN_LENGTH = 500;
4
+ const MAX_CUSTOM_FIELD_REGEX_INPUT_LENGTH = 1e4;
2
5
  const VALIDATION_RULES = [
3
6
  "required",
4
7
  "date",
@@ -23,7 +26,11 @@ const validationRuleSchema = z.discriminatedUnion("rule", [
23
26
  z.object({ rule: z.literal("gte"), param: z.number(), message: z.string().min(1) }),
24
27
  z.object({ rule: z.literal("eq"), param: z.any(), message: z.string().min(1) }),
25
28
  z.object({ rule: z.literal("ne"), param: z.any(), message: z.string().min(1) }),
26
- z.object({ rule: z.literal("regex"), param: z.string().min(1), message: z.string().min(1) })
29
+ z.object({
30
+ rule: z.literal("regex"),
31
+ param: z.string().min(1),
32
+ message: z.string().min(1)
33
+ })
27
34
  ]);
28
35
  const validationRulesArraySchema = z.array(validationRuleSchema).max(32);
29
36
  function evalRule(rule, value, kind) {
@@ -66,12 +73,11 @@ function evalRule(rule, value, kind) {
66
73
  return value !== rule.param ? null : rule.message;
67
74
  case "regex":
68
75
  if (isEmpty(value)) return null;
69
- try {
70
- const re = new RegExp(rule.param);
71
- return re.test(String(value)) ? null : rule.message;
72
- } catch {
73
- return rule.message;
74
- }
76
+ const regexResult = testLinearRegex(String(rule.param), String(value), {
77
+ maxPatternLength: MAX_CUSTOM_FIELD_REGEX_PATTERN_LENGTH,
78
+ maxInputLength: MAX_CUSTOM_FIELD_REGEX_INPUT_LENGTH
79
+ });
80
+ return regexResult.ok && regexResult.matched ? null : rule.message;
75
81
  default:
76
82
  return null;
77
83
  }
@@ -94,6 +100,8 @@ function validateValuesAgainstDefs(values, defs) {
94
100
  return { ok: Object.keys(errors).length === 0, fieldErrors: errors };
95
101
  }
96
102
  export {
103
+ MAX_CUSTOM_FIELD_REGEX_INPUT_LENGTH,
104
+ MAX_CUSTOM_FIELD_REGEX_PATTERN_LENGTH,
97
105
  VALIDATION_RULES,
98
106
  validateValuesAgainstDefs,
99
107
  validationRuleSchema,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/entities/validation.ts"],
4
- "sourcesContent": ["import { z } from 'zod'\n\n// Supported rule types for custom fields validation\nexport const VALIDATION_RULES = [\n 'required',\n 'date',\n 'integer',\n 'float',\n 'lt',\n 'lte',\n 'gt',\n 'gte',\n 'eq',\n 'ne',\n 'regex',\n] as const\n\nexport type ValidationRuleKind = typeof VALIDATION_RULES[number]\n\nexport const validationRuleSchema = z.discriminatedUnion('rule', [\n z.object({ rule: z.literal('required'), message: z.string().min(1) }),\n z.object({ rule: z.literal('date'), message: z.string().min(1) }),\n z.object({ rule: z.literal('integer'), message: z.string().min(1) }),\n z.object({ rule: z.literal('float'), message: z.string().min(1) }),\n z.object({ rule: z.literal('lt'), param: z.number(), message: z.string().min(1) }),\n z.object({ rule: z.literal('lte'), param: z.number(), message: z.string().min(1) }),\n z.object({ rule: z.literal('gt'), param: z.number(), message: z.string().min(1) }),\n z.object({ rule: z.literal('gte'), param: z.number(), message: z.string().min(1) }),\n z.object({ rule: z.literal('eq'), param: z.any(), message: z.string().min(1) }),\n z.object({ rule: z.literal('ne'), param: z.any(), message: z.string().min(1) }),\n z.object({ rule: z.literal('regex'), param: z.string().min(1), message: z.string().min(1) }),\n])\n\nexport type ValidationRule = z.infer<typeof validationRuleSchema>\n\nexport const validationRulesArraySchema = z.array(validationRuleSchema).max(32)\n\nexport type CustomFieldDefLike = {\n key: string\n kind: string\n configJson?: any\n}\n\n// Evaluate a single rule against a value\nfunction evalRule(rule: ValidationRule, value: any, kind: string): string | null {\n const isEmpty = (v: any) => v == null || (typeof v === 'string' && v.trim() === '') || (Array.isArray(v) && v.length === 0)\n\n switch (rule.rule) {\n case 'required':\n return isEmpty(value) ? rule.message : null\n case 'date': {\n if (isEmpty(value)) return null\n const d = new Date(String(value))\n return isNaN(d.getTime()) ? rule.message : null\n }\n case 'integer': {\n if (isEmpty(value)) return null\n const n = Number(value)\n return Number.isInteger(n) ? null : rule.message\n }\n case 'float': {\n if (isEmpty(value)) return null\n const n = Number(value)\n return Number.isFinite(n) ? null : rule.message\n }\n case 'lt':\n if (isEmpty(value)) return null\n return Number(value) < (rule as any).param ? null : rule.message\n case 'lte':\n if (isEmpty(value)) return null\n return Number(value) <= (rule as any).param ? null : rule.message\n case 'gt':\n if (isEmpty(value)) return null\n return Number(value) > (rule as any).param ? null : rule.message\n case 'gte':\n if (isEmpty(value)) return null\n return Number(value) >= (rule as any).param ? null : rule.message\n case 'eq':\n if (isEmpty(value)) return null\n return value === (rule as any).param ? null : rule.message\n case 'ne':\n if (isEmpty(value)) return null\n return value !== (rule as any).param ? null : rule.message\n case 'regex':\n if (isEmpty(value)) return null\n try {\n const re = new RegExp((rule as any).param)\n return re.test(String(value)) ? null : rule.message\n } catch {\n // Invalid regex in definition: consider it failed safe and return message\n return rule.message\n }\n default:\n return null\n }\n}\n\nexport function validateValuesAgainstDefs(\n values: Record<string, any>,\n defs: CustomFieldDefLike[],\n): { ok: boolean; fieldErrors: Record<string, string> } {\n const errors: Record<string, string> = {}\n for (const def of defs) {\n const cfg = def?.configJson || {}\n const rules: ValidationRule[] = Array.isArray(cfg.validation) ? cfg.validation : []\n if (rules.length === 0) continue\n const value = values[def.key]\n for (const r of rules) {\n const msg = evalRule(r as any, value, def.kind)\n if (msg) {\n errors[`cf_${def.key}`] = msg\n break\n }\n }\n }\n return { ok: Object.keys(errors).length === 0, fieldErrors: errors }\n}\n\n"],
5
- "mappings": "AAAA,SAAS,SAAS;AAGX,MAAM,mBAAmB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAIO,MAAM,uBAAuB,EAAE,mBAAmB,QAAQ;AAAA,EAC/D,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,UAAU,GAAG,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC;AAAA,EACpE,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC;AAAA,EAChE,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,SAAS,GAAG,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC;AAAA,EACnE,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,OAAO,GAAG,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC;AAAA,EACjE,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,IAAI,GAAG,OAAO,EAAE,OAAO,GAAG,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC;AAAA,EACjF,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,KAAK,GAAG,OAAO,EAAE,OAAO,GAAG,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC;AAAA,EAClF,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,IAAI,GAAG,OAAO,EAAE,OAAO,GAAG,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC;AAAA,EACjF,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,KAAK,GAAG,OAAO,EAAE,OAAO,GAAG,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC;AAAA,EAClF,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,IAAI,GAAG,OAAO,EAAE,IAAI,GAAG,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC;AAAA,EAC9E,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,IAAI,GAAG,OAAO,EAAE,IAAI,GAAG,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC;AAAA,EAC9E,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,OAAO,GAAG,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,GAAG,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC;AAC7F,CAAC;AAIM,MAAM,6BAA6B,EAAE,MAAM,oBAAoB,EAAE,IAAI,EAAE;AAS9E,SAAS,SAAS,MAAsB,OAAY,MAA6B;AAC/E,QAAM,UAAU,CAAC,MAAW,KAAK,QAAS,OAAO,MAAM,YAAY,EAAE,KAAK,MAAM,MAAQ,MAAM,QAAQ,CAAC,KAAK,EAAE,WAAW;AAEzH,UAAQ,KAAK,MAAM;AAAA,IACjB,KAAK;AACH,aAAO,QAAQ,KAAK,IAAI,KAAK,UAAU;AAAA,IACzC,KAAK,QAAQ;AACX,UAAI,QAAQ,KAAK,EAAG,QAAO;AAC3B,YAAM,IAAI,IAAI,KAAK,OAAO,KAAK,CAAC;AAChC,aAAO,MAAM,EAAE,QAAQ,CAAC,IAAI,KAAK,UAAU;AAAA,IAC7C;AAAA,IACA,KAAK,WAAW;AACd,UAAI,QAAQ,KAAK,EAAG,QAAO;AAC3B,YAAM,IAAI,OAAO,KAAK;AACtB,aAAO,OAAO,UAAU,CAAC,IAAI,OAAO,KAAK;AAAA,IAC3C;AAAA,IACA,KAAK,SAAS;AACZ,UAAI,QAAQ,KAAK,EAAG,QAAO;AAC3B,YAAM,IAAI,OAAO,KAAK;AACtB,aAAO,OAAO,SAAS,CAAC,IAAI,OAAO,KAAK;AAAA,IAC1C;AAAA,IACA,KAAK;AACH,UAAI,QAAQ,KAAK,EAAG,QAAO;AAC3B,aAAO,OAAO,KAAK,IAAK,KAAa,QAAQ,OAAO,KAAK;AAAA,IAC3D,KAAK;AACH,UAAI,QAAQ,KAAK,EAAG,QAAO;AAC3B,aAAO,OAAO,KAAK,KAAM,KAAa,QAAQ,OAAO,KAAK;AAAA,IAC5D,KAAK;AACH,UAAI,QAAQ,KAAK,EAAG,QAAO;AAC3B,aAAO,OAAO,KAAK,IAAK,KAAa,QAAQ,OAAO,KAAK;AAAA,IAC3D,KAAK;AACH,UAAI,QAAQ,KAAK,EAAG,QAAO;AAC3B,aAAO,OAAO,KAAK,KAAM,KAAa,QAAQ,OAAO,KAAK;AAAA,IAC5D,KAAK;AACH,UAAI,QAAQ,KAAK,EAAG,QAAO;AAC3B,aAAO,UAAW,KAAa,QAAQ,OAAO,KAAK;AAAA,IACrD,KAAK;AACH,UAAI,QAAQ,KAAK,EAAG,QAAO;AAC3B,aAAO,UAAW,KAAa,QAAQ,OAAO,KAAK;AAAA,IACrD,KAAK;AACH,UAAI,QAAQ,KAAK,EAAG,QAAO;AAC3B,UAAI;AACF,cAAM,KAAK,IAAI,OAAQ,KAAa,KAAK;AACzC,eAAO,GAAG,KAAK,OAAO,KAAK,CAAC,IAAI,OAAO,KAAK;AAAA,MAC9C,QAAQ;AAEN,eAAO,KAAK;AAAA,MACd;AAAA,IACF;AACE,aAAO;AAAA,EACX;AACF;AAEO,SAAS,0BACd,QACA,MACsD;AACtD,QAAM,SAAiC,CAAC;AACxC,aAAW,OAAO,MAAM;AACtB,UAAM,MAAM,KAAK,cAAc,CAAC;AAChC,UAAM,QAA0B,MAAM,QAAQ,IAAI,UAAU,IAAI,IAAI,aAAa,CAAC;AAClF,QAAI,MAAM,WAAW,EAAG;AACxB,UAAM,QAAQ,OAAO,IAAI,GAAG;AAC5B,eAAW,KAAK,OAAO;AACrB,YAAM,MAAM,SAAS,GAAU,OAAO,IAAI,IAAI;AAC9C,UAAI,KAAK;AACP,eAAO,MAAM,IAAI,GAAG,EAAE,IAAI;AAC1B;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO,EAAE,IAAI,OAAO,KAAK,MAAM,EAAE,WAAW,GAAG,aAAa,OAAO;AACrE;",
4
+ "sourcesContent": ["import { z } from 'zod'\nimport { testLinearRegex } from '../../lib/regex/linear'\n\nexport const MAX_CUSTOM_FIELD_REGEX_PATTERN_LENGTH = 500\nexport const MAX_CUSTOM_FIELD_REGEX_INPUT_LENGTH = 10_000\n\n// Supported rule types for custom fields validation\nexport const VALIDATION_RULES = [\n 'required',\n 'date',\n 'integer',\n 'float',\n 'lt',\n 'lte',\n 'gt',\n 'gte',\n 'eq',\n 'ne',\n 'regex',\n] as const\n\nexport type ValidationRuleKind = typeof VALIDATION_RULES[number]\n\nexport const validationRuleSchema = z.discriminatedUnion('rule', [\n z.object({ rule: z.literal('required'), message: z.string().min(1) }),\n z.object({ rule: z.literal('date'), message: z.string().min(1) }),\n z.object({ rule: z.literal('integer'), message: z.string().min(1) }),\n z.object({ rule: z.literal('float'), message: z.string().min(1) }),\n z.object({ rule: z.literal('lt'), param: z.number(), message: z.string().min(1) }),\n z.object({ rule: z.literal('lte'), param: z.number(), message: z.string().min(1) }),\n z.object({ rule: z.literal('gt'), param: z.number(), message: z.string().min(1) }),\n z.object({ rule: z.literal('gte'), param: z.number(), message: z.string().min(1) }),\n z.object({ rule: z.literal('eq'), param: z.any(), message: z.string().min(1) }),\n z.object({ rule: z.literal('ne'), param: z.any(), message: z.string().min(1) }),\n z.object({\n rule: z.literal('regex'),\n param: z.string().min(1),\n message: z.string().min(1),\n }),\n])\n\nexport type ValidationRule = z.infer<typeof validationRuleSchema>\n\nexport const validationRulesArraySchema = z.array(validationRuleSchema).max(32)\n\nexport type CustomFieldDefLike = {\n key: string\n kind: string\n configJson?: any\n}\n\n// Evaluate a single rule against a value\nfunction evalRule(rule: ValidationRule, value: any, kind: string): string | null {\n const isEmpty = (v: any) => v == null || (typeof v === 'string' && v.trim() === '') || (Array.isArray(v) && v.length === 0)\n\n switch (rule.rule) {\n case 'required':\n return isEmpty(value) ? rule.message : null\n case 'date': {\n if (isEmpty(value)) return null\n const d = new Date(String(value))\n return isNaN(d.getTime()) ? rule.message : null\n }\n case 'integer': {\n if (isEmpty(value)) return null\n const n = Number(value)\n return Number.isInteger(n) ? null : rule.message\n }\n case 'float': {\n if (isEmpty(value)) return null\n const n = Number(value)\n return Number.isFinite(n) ? null : rule.message\n }\n case 'lt':\n if (isEmpty(value)) return null\n return Number(value) < (rule as any).param ? null : rule.message\n case 'lte':\n if (isEmpty(value)) return null\n return Number(value) <= (rule as any).param ? null : rule.message\n case 'gt':\n if (isEmpty(value)) return null\n return Number(value) > (rule as any).param ? null : rule.message\n case 'gte':\n if (isEmpty(value)) return null\n return Number(value) >= (rule as any).param ? null : rule.message\n case 'eq':\n if (isEmpty(value)) return null\n return value === (rule as any).param ? null : rule.message\n case 'ne':\n if (isEmpty(value)) return null\n return value !== (rule as any).param ? null : rule.message\n case 'regex':\n if (isEmpty(value)) return null\n const regexResult = testLinearRegex(String((rule as any).param), String(value), {\n maxPatternLength: MAX_CUSTOM_FIELD_REGEX_PATTERN_LENGTH,\n maxInputLength: MAX_CUSTOM_FIELD_REGEX_INPUT_LENGTH,\n })\n return regexResult.ok && regexResult.matched ? null : rule.message\n default:\n return null\n }\n}\n\nexport function validateValuesAgainstDefs(\n values: Record<string, any>,\n defs: CustomFieldDefLike[],\n): { ok: boolean; fieldErrors: Record<string, string> } {\n const errors: Record<string, string> = {}\n for (const def of defs) {\n const cfg = def?.configJson || {}\n const rules: ValidationRule[] = Array.isArray(cfg.validation) ? cfg.validation : []\n if (rules.length === 0) continue\n const value = values[def.key]\n for (const r of rules) {\n const msg = evalRule(r as any, value, def.kind)\n if (msg) {\n errors[`cf_${def.key}`] = msg\n break\n }\n }\n }\n return { ok: Object.keys(errors).length === 0, fieldErrors: errors }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,3 +1,4 @@
1
+ import { matchWildcardPattern } from "@open-mercato/shared/lib/patterns/wildcard";
1
2
  import {
2
3
  applyInjectionWidgetOverridesToEntries,
3
4
  applyInjectionWidgetOverridesToTables
@@ -324,8 +325,7 @@ async function getResolvedEntriesForSpot(spotId) {
324
325
  for (const [candidateSpotId, candidateEntries] of table.entries()) {
325
326
  if (candidateSpotId === spotId) continue;
326
327
  if (!candidateSpotId.includes("*")) continue;
327
- const pattern = new RegExp(`^${candidateSpotId.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`);
328
- if (!pattern.test(spotId)) continue;
328
+ if (!matchWildcardPattern(spotId, candidateSpotId)) continue;
329
329
  wildcardEntries.push(...candidateEntries);
330
330
  }
331
331
  const dedupedEntries = /* @__PURE__ */ new Map();
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/widgets/injection-loader.ts"],
4
- "sourcesContent": ["import type { ModuleInjectionWidgetEntry } from '../registry'\nimport type {\n InjectionAnyWidgetModule,\n InjectionDataWidgetModule,\n InjectionWidgetMetadata,\n InjectionWidgetModule,\n InjectionSpotId,\n ModuleInjectionSlot,\n ModuleInjectionTable,\n InjectionWidgetPlacement,\n} from './injection'\nimport {\n applyInjectionWidgetOverridesToEntries,\n applyInjectionWidgetOverridesToTables,\n} from '../overrides'\n\ntype LoadedWidgetModule = InjectionWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }\ntype LoadedDataWidgetModule = InjectionDataWidgetModule & { metadata: InjectionWidgetMetadata }\n\nexport type LoadedInjectionWidget = LoadedWidgetModule & {\n moduleId: string\n key: string\n placement?: {\n groupId?: string\n groupLabel?: string\n groupDescription?: string\n column?: 1 | 2\n kind?: 'tab' | 'group' | 'stack'\n [k: string]: unknown\n }\n}\n\nexport type LoadedInjectionDataWidget = LoadedDataWidgetModule & {\n moduleId: string\n key: string\n placement?: {\n groupId?: string\n groupLabel?: string\n groupDescription?: string\n column?: 1 | 2\n kind?: 'tab' | 'group' | 'stack'\n [k: string]: unknown\n }\n}\n\ntype WidgetEntry = ModuleInjectionWidgetEntry & { moduleId: string }\n\n// Registration pattern for publishable packages\nlet _coreInjectionWidgetEntries: ModuleInjectionWidgetEntry[] | null = null\nlet _coreInjectionTables: Array<{ moduleId: string; table: ModuleInjectionTable }> | null = null\nlet _enabledModuleIds: ReadonlySet<string> | null = null\nlet _injectionRegistryVersion = 0\nconst GLOBAL_INJECTION_WIDGETS_KEY = '__openMercatoCoreInjectionWidgetEntries__'\nconst GLOBAL_INJECTION_TABLES_KEY = '__openMercatoCoreInjectionTables__'\nconst GLOBAL_ENABLED_MODULE_IDS_KEY = '__openMercatoEnabledModuleIds__'\nconst GLOBAL_INJECTION_REGISTRY_VERSION_KEY = '__openMercatoCoreInjectionRegistryVersion__'\nconst INJECTION_REGISTRY_CHANGED_EVENT = '__openMercatoInjectionRegistryChanged__'\n\nfunction readGlobalInjectionWidgets(): ModuleInjectionWidgetEntry[] | null {\n try {\n const value = (globalThis as Record<string, unknown>)[GLOBAL_INJECTION_WIDGETS_KEY]\n return Array.isArray(value) ? (value as ModuleInjectionWidgetEntry[]) : null\n } catch {\n return null\n }\n}\n\nfunction writeGlobalInjectionWidgets(entries: ModuleInjectionWidgetEntry[]) {\n try {\n ;(globalThis as Record<string, unknown>)[GLOBAL_INJECTION_WIDGETS_KEY] = entries\n } catch {\n // ignore global assignment failures\n }\n}\n\nfunction readGlobalEnabledModuleIds(): ReadonlySet<string> | null {\n try {\n const value = (globalThis as Record<string, unknown>)[GLOBAL_ENABLED_MODULE_IDS_KEY]\n if (value instanceof Set) return value as ReadonlySet<string>\n return null\n } catch {\n return null\n }\n}\n\nfunction writeGlobalEnabledModuleIds(ids: ReadonlySet<string>) {\n try {\n ;(globalThis as Record<string, unknown>)[GLOBAL_ENABLED_MODULE_IDS_KEY] = ids\n } catch {\n // ignore global assignment failures\n }\n}\n\nfunction readGlobalInjectionTables(): Array<{ moduleId: string; table: ModuleInjectionTable }> | null {\n try {\n const value = (globalThis as Record<string, unknown>)[GLOBAL_INJECTION_TABLES_KEY]\n return Array.isArray(value) ? (value as Array<{ moduleId: string; table: ModuleInjectionTable }>) : null\n } catch {\n return null\n }\n}\n\nfunction writeGlobalInjectionTables(tables: Array<{ moduleId: string; table: ModuleInjectionTable }>) {\n try {\n ;(globalThis as Record<string, unknown>)[GLOBAL_INJECTION_TABLES_KEY] = tables\n } catch {\n // ignore global assignment failures\n }\n}\n\nfunction readGlobalInjectionRegistryVersion(): number | null {\n try {\n const value = (globalThis as Record<string, unknown>)[GLOBAL_INJECTION_REGISTRY_VERSION_KEY]\n return typeof value === 'number' ? value : null\n } catch {\n return null\n }\n}\n\nfunction writeGlobalInjectionRegistryVersion(version: number) {\n try {\n ;(globalThis as Record<string, unknown>)[GLOBAL_INJECTION_REGISTRY_VERSION_KEY] = version\n } catch {\n // ignore global assignment failures\n }\n}\n\nfunction notifyInjectionRegistryChanged() {\n _injectionRegistryVersion += 1\n writeGlobalInjectionRegistryVersion(_injectionRegistryVersion)\n invalidateInjectionWidgetCache()\n\n if (typeof window === 'undefined') return\n\n window.dispatchEvent(new CustomEvent(INJECTION_REGISTRY_CHANGED_EVENT, {\n detail: { version: _injectionRegistryVersion },\n }))\n}\n\nexport function registerCoreInjectionWidgets(entries: ModuleInjectionWidgetEntry[]) {\n if (_coreInjectionWidgetEntries !== null && process.env.NODE_ENV === 'development') {\n console.debug('[Bootstrap] Core injection widgets re-registered (this may occur during HMR)')\n }\n const finalEntries = applyInjectionWidgetOverridesToEntries(entries)\n _coreInjectionWidgetEntries = finalEntries\n writeGlobalInjectionWidgets(finalEntries)\n notifyInjectionRegistryChanged()\n}\n\nexport function getCoreInjectionWidgets(): ModuleInjectionWidgetEntry[] {\n const globalEntries = readGlobalInjectionWidgets()\n if (globalEntries) return globalEntries\n if (!_coreInjectionWidgetEntries) {\n // On client-side, bootstrap doesn't run - return empty array gracefully\n if (typeof window !== 'undefined') {\n return []\n }\n throw new Error('[Bootstrap] Core injection widgets not registered. Call registerCoreInjectionWidgets() at bootstrap.')\n }\n return _coreInjectionWidgetEntries\n}\n\nexport function registerCoreInjectionTables(tables: Array<{ moduleId: string; table: ModuleInjectionTable }>) {\n if (_coreInjectionTables !== null && process.env.NODE_ENV === 'development') {\n console.debug('[Bootstrap] Core injection tables re-registered (this may occur during HMR)')\n }\n const finalTables = applyInjectionWidgetOverridesToTables(tables)\n _coreInjectionTables = finalTables\n writeGlobalInjectionTables(finalTables)\n notifyInjectionRegistryChanged()\n}\n\n/**\n * Register the canonical set of enabled module IDs for the running app.\n *\n * This is the authoritative signal used by `requiredModules` widget gating \u2014\n * deriving \"enabled\" from injection tables or widget entries is unreliable\n * because modules without injection widgets (for example `ai_assistant`) do\n * not contribute entries to either source. Bootstrap callers should pass\n * every module ID present in the app's module registry.\n */\nexport function registerEnabledModuleIds(moduleIds: Iterable<string>) {\n const next = new Set<string>()\n for (const moduleId of moduleIds) {\n if (typeof moduleId === 'string' && moduleId.length > 0) next.add(moduleId)\n }\n if (_enabledModuleIds !== null && process.env.NODE_ENV === 'development') {\n console.debug('[Bootstrap] Enabled module IDs re-registered (this may occur during HMR)')\n }\n _enabledModuleIds = next\n writeGlobalEnabledModuleIds(next)\n notifyInjectionRegistryChanged()\n}\n\nexport function getEnabledModuleIds(): ReadonlySet<string> | null {\n return readGlobalEnabledModuleIds() ?? _enabledModuleIds\n}\n\nexport function getInjectionRegistryVersion(): number {\n const globalVersion = readGlobalInjectionRegistryVersion()\n if (globalVersion !== null) return globalVersion\n return _injectionRegistryVersion\n}\n\nexport function subscribeToInjectionRegistryChanges(listener: () => void): () => void {\n if (typeof window === 'undefined') {\n return () => {}\n }\n\n const handler = () => listener()\n window.addEventListener(INJECTION_REGISTRY_CHANGED_EVENT, handler)\n return () => {\n window.removeEventListener(INJECTION_REGISTRY_CHANGED_EVENT, handler)\n }\n}\n\nexport function getCoreInjectionTables(): Array<{ moduleId: string; table: ModuleInjectionTable }> {\n const globalTables = readGlobalInjectionTables()\n if (globalTables) return globalTables\n if (!_coreInjectionTables) {\n // On client-side, bootstrap doesn't run - return empty array gracefully\n if (typeof window !== 'undefined') {\n return []\n }\n throw new Error('[Bootstrap] Core injection tables not registered. Call registerCoreInjectionTables() at bootstrap.')\n }\n return _coreInjectionTables\n}\n\nlet widgetEntriesPromise: Promise<WidgetEntry[]> | null = null\ntype TableEntry = {\n widgetId: string\n moduleId: string\n priority: number\n placement?: ModuleInjectionSlot extends infer S\n ? S extends { widgetId: string }\n ? Omit<S, 'widgetId' | 'priority'>\n : never\n : never\n}\nlet injectionTablePromise: Promise<Map<InjectionSpotId, TableEntry[]>> | null = null\n\nfunction isInjectionSlotObject(value: ModuleInjectionSlot): value is InjectionWidgetPlacement & { widgetId: string; priority?: number } {\n return typeof value === 'object' && value !== null && 'widgetId' in value\n}\n\n/**\n * Invalidate the widget entries and widget module cache.\n * Call this when the generated registry is updated or modules are reloaded.\n */\nexport function invalidateInjectionWidgetCache() {\n widgetEntriesPromise = null\n injectionTablePromise = null\n widgetCache.clear()\n warnedRequiredModuleSkips.clear()\n}\n\nasync function loadWidgetEntries(): Promise<WidgetEntry[]> {\n if (!widgetEntriesPromise) {\n const promise = Promise.resolve().then(() =>\n getCoreInjectionWidgets().map((entry) => ({\n ...entry,\n moduleId: entry.moduleId || 'unknown',\n }))\n )\n widgetEntriesPromise = promise.catch((err) => {\n if (widgetEntriesPromise === promise) {\n widgetEntriesPromise = null\n }\n throw err\n })\n }\n return widgetEntriesPromise\n}\n\nasync function loadInjectionTable(): Promise<Map<InjectionSpotId, TableEntry[]>> {\n if (!injectionTablePromise) {\n const promise = Promise.resolve().then(() => {\n const list = getCoreInjectionTables()\n const table = new Map<InjectionSpotId, TableEntry[]>()\n\n for (const entry of list) {\n const injectionTable = entry.table ?? {}\n for (const [spotId, widgetIds] of Object.entries(injectionTable)) {\n const widgets = Array.isArray(widgetIds) ? widgetIds : [widgetIds]\n const existing = table.get(spotId) ?? []\n for (const widgetEntry of widgets) {\n if (typeof widgetEntry === 'string') {\n existing.push({ widgetId: widgetEntry, moduleId: entry.moduleId, priority: 0 })\n continue\n }\n if (isInjectionSlotObject(widgetEntry)) {\n const { widgetId, priority = 0, ...placement } = widgetEntry\n existing.push({\n widgetId,\n moduleId: entry.moduleId,\n priority: typeof priority === 'number' ? priority : 0,\n placement,\n })\n continue\n }\n }\n table.set(spotId, existing)\n }\n }\n\n for (const [spotId, widgets] of table.entries()) {\n table.set(spotId, widgets.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)))\n }\n\n return table\n })\n injectionTablePromise = promise.catch((err) => {\n if (injectionTablePromise === promise) {\n injectionTablePromise = null\n }\n throw err\n })\n }\n return injectionTablePromise\n}\n\nconst widgetCache = new Map<string, Promise<InjectionAnyWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }>>()\n\nfunction isDataWidgetModule(widget: Record<string, unknown>): widget is LoadedDataWidgetModule {\n const keys = [\n 'columns',\n 'rowActions',\n 'bulkActions',\n 'filters',\n 'fields',\n 'steps',\n 'badge',\n 'menuItems',\n ]\n return keys.some((key) => key in widget)\n}\n\nfunction ensureValidInjectionModule(mod: unknown, key: string, moduleId: string): (InjectionAnyWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }) {\n if (!mod || typeof mod !== 'object') {\n throw new Error(`Invalid injection widget module \"${key}\" from \"${moduleId}\" (expected object export)`)\n }\n const widget = (mod as { default?: InjectionAnyWidgetModule<any, any> }).default ?? (mod as InjectionAnyWidgetModule<any, any>)\n if (!widget || typeof widget !== 'object') {\n throw new Error(`Invalid injection widget export \"${key}\" from \"${moduleId}\" (missing default export)`) \n }\n if (!('metadata' in widget) || !widget.metadata || typeof widget.metadata !== 'object') {\n throw new Error(`Injection widget \"${key}\" from \"${moduleId}\" is missing metadata`)\n }\n const metadata = widget.metadata\n if (typeof metadata.id !== 'string' || metadata.id.length === 0) {\n throw new Error(`Injection widget \"${key}\" from \"${moduleId}\" metadata.id must be a non-empty string`)\n }\n const normalized = {\n ...widget,\n metadata,\n }\n\n if ('Widget' in normalized && typeof normalized.Widget === 'function') {\n if (typeof metadata.title !== 'string' || metadata.title.length === 0) {\n throw new Error(`Injection widget \"${metadata.id}\" from \"${moduleId}\" must have a title`)\n }\n return normalized\n }\n\n if (!isDataWidgetModule(normalized as Record<string, unknown>)) {\n throw new Error(\n `Injection widget \"${metadata.id}\" from \"${moduleId}\" must export either Widget component or a declarative data payload`\n )\n }\n\n return normalized\n}\n\nfunction isLoadedInjectionWidget(\n module: InjectionAnyWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }\n): module is LoadedWidgetModule {\n return 'Widget' in module && typeof module.Widget === 'function'\n}\n\nfunction isLoadedInjectionDataWidget(\n module: InjectionAnyWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }\n): module is LoadedDataWidgetModule {\n return !isLoadedInjectionWidget(module)\n}\n\nasync function loadEntry(entry: WidgetEntry): Promise<InjectionAnyWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }> {\n if (!widgetCache.has(entry.key)) {\n const promise = entry.loader().then((mod) => ensureValidInjectionModule(mod, entry.key, entry.moduleId))\n widgetCache.set(entry.key, promise)\n }\n return widgetCache.get(entry.key)!\n}\n\nfunction getEnabledModuleIdsForInjection(): ReadonlySet<string> {\n // Prefer the explicit enabled-modules registry populated by bootstrap.\n // This is the only signal that includes modules without injection widgets\n // (for example `ai_assistant`), so it is required for `requiredModules`\n // gating to be sound.\n const explicit = readGlobalEnabledModuleIds() ?? _enabledModuleIds\n if (explicit) return explicit\n\n // Fallback: derive from injection tables and widget entries. This keeps\n // older bootstrap paths (and callers that have not yet wired\n // `registerEnabledModuleIds`) working \u2014 at the cost of mis-classifying\n // dependency modules that ship no widgets. New apps MUST call\n // `registerEnabledModuleIds` to get accurate gating.\n const enabled = new Set<string>()\n const tables = readGlobalInjectionTables() ?? _coreInjectionTables ?? []\n for (const entry of tables) {\n if (entry?.moduleId) enabled.add(entry.moduleId)\n }\n const entries = readGlobalInjectionWidgets() ?? _coreInjectionWidgetEntries ?? []\n for (const entry of entries) {\n if (entry?.moduleId) enabled.add(entry.moduleId)\n }\n return enabled\n}\n\nfunction widgetMissingRequiredModules(\n metadata: InjectionWidgetMetadata,\n enabledModuleIds: ReadonlySet<string>,\n): string[] {\n const required = metadata.requiredModules\n if (!Array.isArray(required) || required.length === 0) return []\n const missing: string[] = []\n for (const moduleId of required) {\n if (typeof moduleId !== 'string' || moduleId.length === 0) continue\n if (!enabledModuleIds.has(moduleId)) missing.push(moduleId)\n }\n return missing\n}\n\nconst warnedRequiredModuleSkips = new Set<string>()\n\nfunction warnSkippedWidget(metadataId: string, missingModules: string[]) {\n const key = `${metadataId}:${missingModules.join(',')}`\n if (warnedRequiredModuleSkips.has(key)) return\n warnedRequiredModuleSkips.add(key)\n if (process.env.NODE_ENV === 'development') {\n console.debug(\n `[InjectionLoader] Skipping widget \"${metadataId}\" \u2014 required module(s) not enabled: ${missingModules.join(', ')}`,\n )\n }\n}\n\nasync function getResolvedEntriesForSpot(spotId: InjectionSpotId): Promise<TableEntry[]> {\n const table = await loadInjectionTable()\n const exactEntries = table.get(spotId) ?? []\n const wildcardEntries: TableEntry[] = []\n\n for (const [candidateSpotId, candidateEntries] of table.entries()) {\n if (candidateSpotId === spotId) continue\n if (!candidateSpotId.includes('*')) continue\n const pattern = new RegExp(`^${candidateSpotId.replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&').replace(/\\*/g, '.*')}$`)\n if (!pattern.test(spotId)) continue\n wildcardEntries.push(...candidateEntries)\n }\n\n const dedupedEntries = new Map<string, TableEntry>()\n for (const entry of [...exactEntries, ...wildcardEntries]) {\n const cacheKey = `${entry.moduleId}:${entry.widgetId}`\n const previous = dedupedEntries.get(cacheKey)\n if (!previous || (entry.priority ?? 0) > (previous.priority ?? 0)) {\n dedupedEntries.set(cacheKey, entry)\n }\n }\n\n return Array.from(dedupedEntries.values()).sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0))\n}\n\nexport async function loadAllInjectionWidgets(): Promise<LoadedInjectionWidget[]> {\n const widgetEntries = await loadWidgetEntries()\n const enabledModuleIds = getEnabledModuleIdsForInjection()\n const loaded = await Promise.all(\n widgetEntries.map(async (entry) => {\n const module = await loadEntry(entry)\n if (!isLoadedInjectionWidget(module)) return null\n const missing = widgetMissingRequiredModules(module.metadata, enabledModuleIds)\n if (missing.length > 0) {\n warnSkippedWidget(module.metadata.id, missing)\n return null\n }\n return { ...module, moduleId: entry.moduleId, key: entry.key }\n })\n )\n const byId = new Map<string, LoadedInjectionWidget>()\n for (const widget of loaded) {\n if (!widget) continue\n if (!byId.has(widget.metadata.id)) {\n byId.set(widget.metadata.id, widget)\n }\n }\n return Array.from(byId.values())\n}\n\nexport async function loadInjectionWidgetById(widgetId: string): Promise<LoadedInjectionWidget | null> {\n const widgetEntries = await loadWidgetEntries()\n const enabledModuleIds = getEnabledModuleIdsForInjection()\n for (const entry of widgetEntries) {\n const module = await loadEntry(entry)\n if (!isLoadedInjectionWidget(module)) continue\n if (module.metadata.id === widgetId) {\n const missing = widgetMissingRequiredModules(module.metadata, enabledModuleIds)\n if (missing.length > 0) {\n warnSkippedWidget(module.metadata.id, missing)\n return null\n }\n return { ...module, moduleId: entry.moduleId, key: entry.key }\n }\n }\n return null\n}\n\nexport async function loadInjectionDataWidgetById(widgetId: string): Promise<LoadedInjectionDataWidget | null> {\n const widgetEntries = await loadWidgetEntries()\n const enabledModuleIds = getEnabledModuleIdsForInjection()\n for (const entry of widgetEntries) {\n const module = await loadEntry(entry)\n if (!isLoadedInjectionDataWidget(module)) continue\n if (module.metadata.id === widgetId) {\n const missing = widgetMissingRequiredModules(module.metadata, enabledModuleIds)\n if (missing.length > 0) {\n warnSkippedWidget(module.metadata.id, missing)\n return null\n }\n return { ...module, moduleId: entry.moduleId, key: entry.key }\n }\n }\n return null\n}\n\nexport async function loadInjectionWidgetsForSpot(spotId: InjectionSpotId): Promise<LoadedInjectionWidget[]> {\n const entries = await getResolvedEntriesForSpot(spotId)\n const widgets: LoadedInjectionWidget[] = []\n for (const { widgetId, placement, priority } of entries) {\n const widget = await loadInjectionWidgetById(widgetId)\n if (!widget) continue\n const combinedPlacement = placement\n ? { ...placement, priority: typeof priority === 'number' ? priority : 0 }\n : { priority: typeof priority === 'number' ? priority : 0 }\n widgets.push({ ...widget, placement: combinedPlacement })\n }\n return widgets\n}\n\nexport async function loadInjectionDataWidgetsForSpot(spotId: InjectionSpotId): Promise<LoadedInjectionDataWidget[]> {\n const entries = await getResolvedEntriesForSpot(spotId)\n const widgets: LoadedInjectionDataWidget[] = []\n for (const { widgetId, placement, priority } of entries) {\n const widget = await loadInjectionDataWidgetById(widgetId)\n if (!widget) continue\n const combinedPlacement = placement\n ? { ...placement, priority: typeof priority === 'number' ? priority : 0 }\n : { priority: typeof priority === 'number' ? priority : 0 }\n widgets.push({ ...widget, placement: combinedPlacement })\n }\n return widgets\n}\n"],
5
- "mappings": "AAWA;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAkCP,IAAI,8BAAmE;AACvE,IAAI,uBAAwF;AAC5F,IAAI,oBAAgD;AACpD,IAAI,4BAA4B;AAChC,MAAM,+BAA+B;AACrC,MAAM,8BAA8B;AACpC,MAAM,gCAAgC;AACtC,MAAM,wCAAwC;AAC9C,MAAM,mCAAmC;AAEzC,SAAS,6BAAkE;AACzE,MAAI;AACF,UAAM,QAAS,WAAuC,4BAA4B;AAClF,WAAO,MAAM,QAAQ,KAAK,IAAK,QAAyC;AAAA,EAC1E,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,4BAA4B,SAAuC;AAC1E,MAAI;AACF;AAAC,IAAC,WAAuC,4BAA4B,IAAI;AAAA,EAC3E,QAAQ;AAAA,EAER;AACF;AAEA,SAAS,6BAAyD;AAChE,MAAI;AACF,UAAM,QAAS,WAAuC,6BAA6B;AACnF,QAAI,iBAAiB,IAAK,QAAO;AACjC,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,4BAA4B,KAA0B;AAC7D,MAAI;AACF;AAAC,IAAC,WAAuC,6BAA6B,IAAI;AAAA,EAC5E,QAAQ;AAAA,EAER;AACF;AAEA,SAAS,4BAA6F;AACpG,MAAI;AACF,UAAM,QAAS,WAAuC,2BAA2B;AACjF,WAAO,MAAM,QAAQ,KAAK,IAAK,QAAqE;AAAA,EACtG,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,2BAA2B,QAAkE;AACpG,MAAI;AACF;AAAC,IAAC,WAAuC,2BAA2B,IAAI;AAAA,EAC1E,QAAQ;AAAA,EAER;AACF;AAEA,SAAS,qCAAoD;AAC3D,MAAI;AACF,UAAM,QAAS,WAAuC,qCAAqC;AAC3F,WAAO,OAAO,UAAU,WAAW,QAAQ;AAAA,EAC7C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,oCAAoC,SAAiB;AAC5D,MAAI;AACF;AAAC,IAAC,WAAuC,qCAAqC,IAAI;AAAA,EACpF,QAAQ;AAAA,EAER;AACF;AAEA,SAAS,iCAAiC;AACxC,+BAA6B;AAC7B,sCAAoC,yBAAyB;AAC7D,iCAA+B;AAE/B,MAAI,OAAO,WAAW,YAAa;AAEnC,SAAO,cAAc,IAAI,YAAY,kCAAkC;AAAA,IACrE,QAAQ,EAAE,SAAS,0BAA0B;AAAA,EAC/C,CAAC,CAAC;AACJ;AAEO,SAAS,6BAA6B,SAAuC;AAClF,MAAI,gCAAgC,QAAQ,QAAQ,IAAI,aAAa,eAAe;AAClF,YAAQ,MAAM,8EAA8E;AAAA,EAC9F;AACA,QAAM,eAAe,uCAAuC,OAAO;AACnE,gCAA8B;AAC9B,8BAA4B,YAAY;AACxC,iCAA+B;AACjC;AAEO,SAAS,0BAAwD;AACtE,QAAM,gBAAgB,2BAA2B;AACjD,MAAI,cAAe,QAAO;AAC1B,MAAI,CAAC,6BAA6B;AAEhC,QAAI,OAAO,WAAW,aAAa;AACjC,aAAO,CAAC;AAAA,IACV;AACA,UAAM,IAAI,MAAM,sGAAsG;AAAA,EACxH;AACA,SAAO;AACT;AAEO,SAAS,4BAA4B,QAAkE;AAC5G,MAAI,yBAAyB,QAAQ,QAAQ,IAAI,aAAa,eAAe;AAC3E,YAAQ,MAAM,6EAA6E;AAAA,EAC7F;AACA,QAAM,cAAc,sCAAsC,MAAM;AAChE,yBAAuB;AACvB,6BAA2B,WAAW;AACtC,iCAA+B;AACjC;AAWO,SAAS,yBAAyB,WAA6B;AACpE,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,YAAY,WAAW;AAChC,QAAI,OAAO,aAAa,YAAY,SAAS,SAAS,EAAG,MAAK,IAAI,QAAQ;AAAA,EAC5E;AACA,MAAI,sBAAsB,QAAQ,QAAQ,IAAI,aAAa,eAAe;AACxE,YAAQ,MAAM,0EAA0E;AAAA,EAC1F;AACA,sBAAoB;AACpB,8BAA4B,IAAI;AAChC,iCAA+B;AACjC;AAEO,SAAS,sBAAkD;AAChE,SAAO,2BAA2B,KAAK;AACzC;AAEO,SAAS,8BAAsC;AACpD,QAAM,gBAAgB,mCAAmC;AACzD,MAAI,kBAAkB,KAAM,QAAO;AACnC,SAAO;AACT;AAEO,SAAS,oCAAoC,UAAkC;AACpF,MAAI,OAAO,WAAW,aAAa;AACjC,WAAO,MAAM;AAAA,IAAC;AAAA,EAChB;AAEA,QAAM,UAAU,MAAM,SAAS;AAC/B,SAAO,iBAAiB,kCAAkC,OAAO;AACjE,SAAO,MAAM;AACX,WAAO,oBAAoB,kCAAkC,OAAO;AAAA,EACtE;AACF;AAEO,SAAS,yBAAmF;AACjG,QAAM,eAAe,0BAA0B;AAC/C,MAAI,aAAc,QAAO;AACzB,MAAI,CAAC,sBAAsB;AAEzB,QAAI,OAAO,WAAW,aAAa;AACjC,aAAO,CAAC;AAAA,IACV;AACA,UAAM,IAAI,MAAM,oGAAoG;AAAA,EACtH;AACA,SAAO;AACT;AAEA,IAAI,uBAAsD;AAW1D,IAAI,wBAA4E;AAEhF,SAAS,sBAAsB,OAAyG;AACtI,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,cAAc;AACtE;AAMO,SAAS,iCAAiC;AAC/C,yBAAuB;AACvB,0BAAwB;AACxB,cAAY,MAAM;AAClB,4BAA0B,MAAM;AAClC;AAEA,eAAe,oBAA4C;AACzD,MAAI,CAAC,sBAAsB;AACzB,UAAM,UAAU,QAAQ,QAAQ,EAAE;AAAA,MAAK,MACrC,wBAAwB,EAAE,IAAI,CAAC,WAAW;AAAA,QACxC,GAAG;AAAA,QACH,UAAU,MAAM,YAAY;AAAA,MAC9B,EAAE;AAAA,IACJ;AACA,2BAAuB,QAAQ,MAAM,CAAC,QAAQ;AAC5C,UAAI,yBAAyB,SAAS;AACpC,+BAAuB;AAAA,MACzB;AACA,YAAM;AAAA,IACR,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAEA,eAAe,qBAAkE;AAC/E,MAAI,CAAC,uBAAuB;AAC1B,UAAM,UAAU,QAAQ,QAAQ,EAAE,KAAK,MAAM;AAC3C,YAAM,OAAO,uBAAuB;AACpC,YAAM,QAAQ,oBAAI,IAAmC;AAErD,iBAAW,SAAS,MAAM;AACxB,cAAM,iBAAiB,MAAM,SAAS,CAAC;AACvC,mBAAW,CAAC,QAAQ,SAAS,KAAK,OAAO,QAAQ,cAAc,GAAG;AAChE,gBAAM,UAAU,MAAM,QAAQ,SAAS,IAAI,YAAY,CAAC,SAAS;AACjE,gBAAM,WAAW,MAAM,IAAI,MAAM,KAAK,CAAC;AACvC,qBAAW,eAAe,SAAS;AACjC,gBAAI,OAAO,gBAAgB,UAAU;AACnC,uBAAS,KAAK,EAAE,UAAU,aAAa,UAAU,MAAM,UAAU,UAAU,EAAE,CAAC;AAC9E;AAAA,YACF;AACA,gBAAI,sBAAsB,WAAW,GAAG;AACtC,oBAAM,EAAE,UAAU,WAAW,GAAG,GAAG,UAAU,IAAI;AACjD,uBAAS,KAAK;AAAA,gBACZ;AAAA,gBACA,UAAU,MAAM;AAAA,gBAChB,UAAU,OAAO,aAAa,WAAW,WAAW;AAAA,gBACpD;AAAA,cACF,CAAC;AACD;AAAA,YACF;AAAA,UACF;AACA,gBAAM,IAAI,QAAQ,QAAQ;AAAA,QAC5B;AAAA,MACF;AAEA,iBAAW,CAAC,QAAQ,OAAO,KAAK,MAAM,QAAQ,GAAG;AAC/C,cAAM,IAAI,QAAQ,QAAQ,KAAK,CAAC,GAAG,OAAO,EAAE,YAAY,MAAM,EAAE,YAAY,EAAE,CAAC;AAAA,MACjF;AAEA,aAAO;AAAA,IACT,CAAC;AACD,4BAAwB,QAAQ,MAAM,CAAC,QAAQ;AAC7C,UAAI,0BAA0B,SAAS;AACrC,gCAAwB;AAAA,MAC1B;AACA,YAAM;AAAA,IACR,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAEA,MAAM,cAAc,oBAAI,IAAiG;AAEzH,SAAS,mBAAmB,QAAmE;AAC7F,QAAM,OAAO;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,SAAO,KAAK,KAAK,CAAC,QAAQ,OAAO,MAAM;AACzC;AAEA,SAAS,2BAA2B,KAAc,KAAa,UAAgG;AAC7J,MAAI,CAAC,OAAO,OAAO,QAAQ,UAAU;AACnC,UAAM,IAAI,MAAM,oCAAoC,GAAG,WAAW,QAAQ,4BAA4B;AAAA,EACxG;AACA,QAAM,SAAU,IAAyD,WAAY;AACrF,MAAI,CAAC,UAAU,OAAO,WAAW,UAAU;AACzC,UAAM,IAAI,MAAM,oCAAoC,GAAG,WAAW,QAAQ,4BAA4B;AAAA,EACxG;AACA,MAAI,EAAE,cAAc,WAAW,CAAC,OAAO,YAAY,OAAO,OAAO,aAAa,UAAU;AACtF,UAAM,IAAI,MAAM,qBAAqB,GAAG,WAAW,QAAQ,uBAAuB;AAAA,EACpF;AACA,QAAM,WAAW,OAAO;AACxB,MAAI,OAAO,SAAS,OAAO,YAAY,SAAS,GAAG,WAAW,GAAG;AAC/D,UAAM,IAAI,MAAM,qBAAqB,GAAG,WAAW,QAAQ,0CAA0C;AAAA,EACvG;AACA,QAAM,aAAa;AAAA,IACjB,GAAG;AAAA,IACH;AAAA,EACF;AAEA,MAAI,YAAY,cAAc,OAAO,WAAW,WAAW,YAAY;AACrE,QAAI,OAAO,SAAS,UAAU,YAAY,SAAS,MAAM,WAAW,GAAG;AACrE,YAAM,IAAI,MAAM,qBAAqB,SAAS,EAAE,WAAW,QAAQ,qBAAqB;AAAA,IAC1F;AACA,WAAO;AAAA,EACT;AAEA,MAAI,CAAC,mBAAmB,UAAqC,GAAG;AAC9D,UAAM,IAAI;AAAA,MACR,qBAAqB,SAAS,EAAE,WAAW,QAAQ;AAAA,IACrD;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,wBACP,QAC8B;AAC9B,SAAO,YAAY,UAAU,OAAO,OAAO,WAAW;AACxD;AAEA,SAAS,4BACP,QACkC;AAClC,SAAO,CAAC,wBAAwB,MAAM;AACxC;AAEA,eAAe,UAAU,OAAyG;AAChI,MAAI,CAAC,YAAY,IAAI,MAAM,GAAG,GAAG;AAC/B,UAAM,UAAU,MAAM,OAAO,EAAE,KAAK,CAAC,QAAQ,2BAA2B,KAAK,MAAM,KAAK,MAAM,QAAQ,CAAC;AACvG,gBAAY,IAAI,MAAM,KAAK,OAAO;AAAA,EACpC;AACA,SAAO,YAAY,IAAI,MAAM,GAAG;AAClC;AAEA,SAAS,kCAAuD;AAK9D,QAAM,WAAW,2BAA2B,KAAK;AACjD,MAAI,SAAU,QAAO;AAOrB,QAAM,UAAU,oBAAI,IAAY;AAChC,QAAM,SAAS,0BAA0B,KAAK,wBAAwB,CAAC;AACvE,aAAW,SAAS,QAAQ;AAC1B,QAAI,OAAO,SAAU,SAAQ,IAAI,MAAM,QAAQ;AAAA,EACjD;AACA,QAAM,UAAU,2BAA2B,KAAK,+BAA+B,CAAC;AAChF,aAAW,SAAS,SAAS;AAC3B,QAAI,OAAO,SAAU,SAAQ,IAAI,MAAM,QAAQ;AAAA,EACjD;AACA,SAAO;AACT;AAEA,SAAS,6BACP,UACA,kBACU;AACV,QAAM,WAAW,SAAS;AAC1B,MAAI,CAAC,MAAM,QAAQ,QAAQ,KAAK,SAAS,WAAW,EAAG,QAAO,CAAC;AAC/D,QAAM,UAAoB,CAAC;AAC3B,aAAW,YAAY,UAAU;AAC/B,QAAI,OAAO,aAAa,YAAY,SAAS,WAAW,EAAG;AAC3D,QAAI,CAAC,iBAAiB,IAAI,QAAQ,EAAG,SAAQ,KAAK,QAAQ;AAAA,EAC5D;AACA,SAAO;AACT;AAEA,MAAM,4BAA4B,oBAAI,IAAY;AAElD,SAAS,kBAAkB,YAAoB,gBAA0B;AACvE,QAAM,MAAM,GAAG,UAAU,IAAI,eAAe,KAAK,GAAG,CAAC;AACrD,MAAI,0BAA0B,IAAI,GAAG,EAAG;AACxC,4BAA0B,IAAI,GAAG;AACjC,MAAI,QAAQ,IAAI,aAAa,eAAe;AAC1C,YAAQ;AAAA,MACN,sCAAsC,UAAU,4CAAuC,eAAe,KAAK,IAAI,CAAC;AAAA,IAClH;AAAA,EACF;AACF;AAEA,eAAe,0BAA0B,QAAgD;AACvF,QAAM,QAAQ,MAAM,mBAAmB;AACvC,QAAM,eAAe,MAAM,IAAI,MAAM,KAAK,CAAC;AAC3C,QAAM,kBAAgC,CAAC;AAEvC,aAAW,CAAC,iBAAiB,gBAAgB,KAAK,MAAM,QAAQ,GAAG;AACjE,QAAI,oBAAoB,OAAQ;AAChC,QAAI,CAAC,gBAAgB,SAAS,GAAG,EAAG;AACpC,UAAM,UAAU,IAAI,OAAO,IAAI,gBAAgB,QAAQ,sBAAsB,MAAM,EAAE,QAAQ,OAAO,IAAI,CAAC,GAAG;AAC5G,QAAI,CAAC,QAAQ,KAAK,MAAM,EAAG;AAC3B,oBAAgB,KAAK,GAAG,gBAAgB;AAAA,EAC1C;AAEA,QAAM,iBAAiB,oBAAI,IAAwB;AACnD,aAAW,SAAS,CAAC,GAAG,cAAc,GAAG,eAAe,GAAG;AACzD,UAAM,WAAW,GAAG,MAAM,QAAQ,IAAI,MAAM,QAAQ;AACpD,UAAM,WAAW,eAAe,IAAI,QAAQ;AAC5C,QAAI,CAAC,aAAa,MAAM,YAAY,MAAM,SAAS,YAAY,IAAI;AACjE,qBAAe,IAAI,UAAU,KAAK;AAAA,IACpC;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,eAAe,OAAO,CAAC,EAAE,KAAK,CAAC,GAAG,OAAO,EAAE,YAAY,MAAM,EAAE,YAAY,EAAE;AACjG;AAEA,eAAsB,0BAA4D;AAChF,QAAM,gBAAgB,MAAM,kBAAkB;AAC9C,QAAM,mBAAmB,gCAAgC;AACzD,QAAM,SAAS,MAAM,QAAQ;AAAA,IAC3B,cAAc,IAAI,OAAO,UAAU;AACjC,YAAM,SAAS,MAAM,UAAU,KAAK;AACpC,UAAI,CAAC,wBAAwB,MAAM,EAAG,QAAO;AAC7C,YAAM,UAAU,6BAA6B,OAAO,UAAU,gBAAgB;AAC9E,UAAI,QAAQ,SAAS,GAAG;AACtB,0BAAkB,OAAO,SAAS,IAAI,OAAO;AAC7C,eAAO;AAAA,MACT;AACA,aAAO,EAAE,GAAG,QAAQ,UAAU,MAAM,UAAU,KAAK,MAAM,IAAI;AAAA,IAC/D,CAAC;AAAA,EACH;AACA,QAAM,OAAO,oBAAI,IAAmC;AACpD,aAAW,UAAU,QAAQ;AAC3B,QAAI,CAAC,OAAQ;AACb,QAAI,CAAC,KAAK,IAAI,OAAO,SAAS,EAAE,GAAG;AACjC,WAAK,IAAI,OAAO,SAAS,IAAI,MAAM;AAAA,IACrC;AAAA,EACF;AACA,SAAO,MAAM,KAAK,KAAK,OAAO,CAAC;AACjC;AAEA,eAAsB,wBAAwB,UAAyD;AACrG,QAAM,gBAAgB,MAAM,kBAAkB;AAC9C,QAAM,mBAAmB,gCAAgC;AACzD,aAAW,SAAS,eAAe;AACjC,UAAM,SAAS,MAAM,UAAU,KAAK;AACpC,QAAI,CAAC,wBAAwB,MAAM,EAAG;AACtC,QAAI,OAAO,SAAS,OAAO,UAAU;AACnC,YAAM,UAAU,6BAA6B,OAAO,UAAU,gBAAgB;AAC9E,UAAI,QAAQ,SAAS,GAAG;AACtB,0BAAkB,OAAO,SAAS,IAAI,OAAO;AAC7C,eAAO;AAAA,MACT;AACA,aAAO,EAAE,GAAG,QAAQ,UAAU,MAAM,UAAU,KAAK,MAAM,IAAI;AAAA,IAC/D;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAsB,4BAA4B,UAA6D;AAC7G,QAAM,gBAAgB,MAAM,kBAAkB;AAC9C,QAAM,mBAAmB,gCAAgC;AACzD,aAAW,SAAS,eAAe;AACjC,UAAM,SAAS,MAAM,UAAU,KAAK;AACpC,QAAI,CAAC,4BAA4B,MAAM,EAAG;AAC1C,QAAI,OAAO,SAAS,OAAO,UAAU;AACnC,YAAM,UAAU,6BAA6B,OAAO,UAAU,gBAAgB;AAC9E,UAAI,QAAQ,SAAS,GAAG;AACtB,0BAAkB,OAAO,SAAS,IAAI,OAAO;AAC7C,eAAO;AAAA,MACT;AACA,aAAO,EAAE,GAAG,QAAQ,UAAU,MAAM,UAAU,KAAK,MAAM,IAAI;AAAA,IAC/D;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAsB,4BAA4B,QAA2D;AAC3G,QAAM,UAAU,MAAM,0BAA0B,MAAM;AACtD,QAAM,UAAmC,CAAC;AAC1C,aAAW,EAAE,UAAU,WAAW,SAAS,KAAK,SAAS;AACvD,UAAM,SAAS,MAAM,wBAAwB,QAAQ;AACrD,QAAI,CAAC,OAAQ;AACb,UAAM,oBAAoB,YACtB,EAAE,GAAG,WAAW,UAAU,OAAO,aAAa,WAAW,WAAW,EAAE,IACtE,EAAE,UAAU,OAAO,aAAa,WAAW,WAAW,EAAE;AAC5D,YAAQ,KAAK,EAAE,GAAG,QAAQ,WAAW,kBAAkB,CAAC;AAAA,EAC1D;AACA,SAAO;AACT;AAEA,eAAsB,gCAAgC,QAA+D;AACnH,QAAM,UAAU,MAAM,0BAA0B,MAAM;AACtD,QAAM,UAAuC,CAAC;AAC9C,aAAW,EAAE,UAAU,WAAW,SAAS,KAAK,SAAS;AACvD,UAAM,SAAS,MAAM,4BAA4B,QAAQ;AACzD,QAAI,CAAC,OAAQ;AACb,UAAM,oBAAoB,YACtB,EAAE,GAAG,WAAW,UAAU,OAAO,aAAa,WAAW,WAAW,EAAE,IACtE,EAAE,UAAU,OAAO,aAAa,WAAW,WAAW,EAAE;AAC5D,YAAQ,KAAK,EAAE,GAAG,QAAQ,WAAW,kBAAkB,CAAC;AAAA,EAC1D;AACA,SAAO;AACT;",
4
+ "sourcesContent": ["import type { ModuleInjectionWidgetEntry } from '../registry'\nimport { matchWildcardPattern } from '@open-mercato/shared/lib/patterns/wildcard'\nimport type {\n InjectionAnyWidgetModule,\n InjectionDataWidgetModule,\n InjectionWidgetMetadata,\n InjectionWidgetModule,\n InjectionSpotId,\n ModuleInjectionSlot,\n ModuleInjectionTable,\n InjectionWidgetPlacement,\n} from './injection'\nimport {\n applyInjectionWidgetOverridesToEntries,\n applyInjectionWidgetOverridesToTables,\n} from '../overrides'\n\ntype LoadedWidgetModule = InjectionWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }\ntype LoadedDataWidgetModule = InjectionDataWidgetModule & { metadata: InjectionWidgetMetadata }\n\nexport type LoadedInjectionWidget = LoadedWidgetModule & {\n moduleId: string\n key: string\n placement?: {\n groupId?: string\n groupLabel?: string\n groupDescription?: string\n column?: 1 | 2\n kind?: 'tab' | 'group' | 'stack'\n [k: string]: unknown\n }\n}\n\nexport type LoadedInjectionDataWidget = LoadedDataWidgetModule & {\n moduleId: string\n key: string\n placement?: {\n groupId?: string\n groupLabel?: string\n groupDescription?: string\n column?: 1 | 2\n kind?: 'tab' | 'group' | 'stack'\n [k: string]: unknown\n }\n}\n\ntype WidgetEntry = ModuleInjectionWidgetEntry & { moduleId: string }\n\n// Registration pattern for publishable packages\nlet _coreInjectionWidgetEntries: ModuleInjectionWidgetEntry[] | null = null\nlet _coreInjectionTables: Array<{ moduleId: string; table: ModuleInjectionTable }> | null = null\nlet _enabledModuleIds: ReadonlySet<string> | null = null\nlet _injectionRegistryVersion = 0\nconst GLOBAL_INJECTION_WIDGETS_KEY = '__openMercatoCoreInjectionWidgetEntries__'\nconst GLOBAL_INJECTION_TABLES_KEY = '__openMercatoCoreInjectionTables__'\nconst GLOBAL_ENABLED_MODULE_IDS_KEY = '__openMercatoEnabledModuleIds__'\nconst GLOBAL_INJECTION_REGISTRY_VERSION_KEY = '__openMercatoCoreInjectionRegistryVersion__'\nconst INJECTION_REGISTRY_CHANGED_EVENT = '__openMercatoInjectionRegistryChanged__'\n\nfunction readGlobalInjectionWidgets(): ModuleInjectionWidgetEntry[] | null {\n try {\n const value = (globalThis as Record<string, unknown>)[GLOBAL_INJECTION_WIDGETS_KEY]\n return Array.isArray(value) ? (value as ModuleInjectionWidgetEntry[]) : null\n } catch {\n return null\n }\n}\n\nfunction writeGlobalInjectionWidgets(entries: ModuleInjectionWidgetEntry[]) {\n try {\n ;(globalThis as Record<string, unknown>)[GLOBAL_INJECTION_WIDGETS_KEY] = entries\n } catch {\n // ignore global assignment failures\n }\n}\n\nfunction readGlobalEnabledModuleIds(): ReadonlySet<string> | null {\n try {\n const value = (globalThis as Record<string, unknown>)[GLOBAL_ENABLED_MODULE_IDS_KEY]\n if (value instanceof Set) return value as ReadonlySet<string>\n return null\n } catch {\n return null\n }\n}\n\nfunction writeGlobalEnabledModuleIds(ids: ReadonlySet<string>) {\n try {\n ;(globalThis as Record<string, unknown>)[GLOBAL_ENABLED_MODULE_IDS_KEY] = ids\n } catch {\n // ignore global assignment failures\n }\n}\n\nfunction readGlobalInjectionTables(): Array<{ moduleId: string; table: ModuleInjectionTable }> | null {\n try {\n const value = (globalThis as Record<string, unknown>)[GLOBAL_INJECTION_TABLES_KEY]\n return Array.isArray(value) ? (value as Array<{ moduleId: string; table: ModuleInjectionTable }>) : null\n } catch {\n return null\n }\n}\n\nfunction writeGlobalInjectionTables(tables: Array<{ moduleId: string; table: ModuleInjectionTable }>) {\n try {\n ;(globalThis as Record<string, unknown>)[GLOBAL_INJECTION_TABLES_KEY] = tables\n } catch {\n // ignore global assignment failures\n }\n}\n\nfunction readGlobalInjectionRegistryVersion(): number | null {\n try {\n const value = (globalThis as Record<string, unknown>)[GLOBAL_INJECTION_REGISTRY_VERSION_KEY]\n return typeof value === 'number' ? value : null\n } catch {\n return null\n }\n}\n\nfunction writeGlobalInjectionRegistryVersion(version: number) {\n try {\n ;(globalThis as Record<string, unknown>)[GLOBAL_INJECTION_REGISTRY_VERSION_KEY] = version\n } catch {\n // ignore global assignment failures\n }\n}\n\nfunction notifyInjectionRegistryChanged() {\n _injectionRegistryVersion += 1\n writeGlobalInjectionRegistryVersion(_injectionRegistryVersion)\n invalidateInjectionWidgetCache()\n\n if (typeof window === 'undefined') return\n\n window.dispatchEvent(new CustomEvent(INJECTION_REGISTRY_CHANGED_EVENT, {\n detail: { version: _injectionRegistryVersion },\n }))\n}\n\nexport function registerCoreInjectionWidgets(entries: ModuleInjectionWidgetEntry[]) {\n if (_coreInjectionWidgetEntries !== null && process.env.NODE_ENV === 'development') {\n console.debug('[Bootstrap] Core injection widgets re-registered (this may occur during HMR)')\n }\n const finalEntries = applyInjectionWidgetOverridesToEntries(entries)\n _coreInjectionWidgetEntries = finalEntries\n writeGlobalInjectionWidgets(finalEntries)\n notifyInjectionRegistryChanged()\n}\n\nexport function getCoreInjectionWidgets(): ModuleInjectionWidgetEntry[] {\n const globalEntries = readGlobalInjectionWidgets()\n if (globalEntries) return globalEntries\n if (!_coreInjectionWidgetEntries) {\n // On client-side, bootstrap doesn't run - return empty array gracefully\n if (typeof window !== 'undefined') {\n return []\n }\n throw new Error('[Bootstrap] Core injection widgets not registered. Call registerCoreInjectionWidgets() at bootstrap.')\n }\n return _coreInjectionWidgetEntries\n}\n\nexport function registerCoreInjectionTables(tables: Array<{ moduleId: string; table: ModuleInjectionTable }>) {\n if (_coreInjectionTables !== null && process.env.NODE_ENV === 'development') {\n console.debug('[Bootstrap] Core injection tables re-registered (this may occur during HMR)')\n }\n const finalTables = applyInjectionWidgetOverridesToTables(tables)\n _coreInjectionTables = finalTables\n writeGlobalInjectionTables(finalTables)\n notifyInjectionRegistryChanged()\n}\n\n/**\n * Register the canonical set of enabled module IDs for the running app.\n *\n * This is the authoritative signal used by `requiredModules` widget gating \u2014\n * deriving \"enabled\" from injection tables or widget entries is unreliable\n * because modules without injection widgets (for example `ai_assistant`) do\n * not contribute entries to either source. Bootstrap callers should pass\n * every module ID present in the app's module registry.\n */\nexport function registerEnabledModuleIds(moduleIds: Iterable<string>) {\n const next = new Set<string>()\n for (const moduleId of moduleIds) {\n if (typeof moduleId === 'string' && moduleId.length > 0) next.add(moduleId)\n }\n if (_enabledModuleIds !== null && process.env.NODE_ENV === 'development') {\n console.debug('[Bootstrap] Enabled module IDs re-registered (this may occur during HMR)')\n }\n _enabledModuleIds = next\n writeGlobalEnabledModuleIds(next)\n notifyInjectionRegistryChanged()\n}\n\nexport function getEnabledModuleIds(): ReadonlySet<string> | null {\n return readGlobalEnabledModuleIds() ?? _enabledModuleIds\n}\n\nexport function getInjectionRegistryVersion(): number {\n const globalVersion = readGlobalInjectionRegistryVersion()\n if (globalVersion !== null) return globalVersion\n return _injectionRegistryVersion\n}\n\nexport function subscribeToInjectionRegistryChanges(listener: () => void): () => void {\n if (typeof window === 'undefined') {\n return () => {}\n }\n\n const handler = () => listener()\n window.addEventListener(INJECTION_REGISTRY_CHANGED_EVENT, handler)\n return () => {\n window.removeEventListener(INJECTION_REGISTRY_CHANGED_EVENT, handler)\n }\n}\n\nexport function getCoreInjectionTables(): Array<{ moduleId: string; table: ModuleInjectionTable }> {\n const globalTables = readGlobalInjectionTables()\n if (globalTables) return globalTables\n if (!_coreInjectionTables) {\n // On client-side, bootstrap doesn't run - return empty array gracefully\n if (typeof window !== 'undefined') {\n return []\n }\n throw new Error('[Bootstrap] Core injection tables not registered. Call registerCoreInjectionTables() at bootstrap.')\n }\n return _coreInjectionTables\n}\n\nlet widgetEntriesPromise: Promise<WidgetEntry[]> | null = null\ntype TableEntry = {\n widgetId: string\n moduleId: string\n priority: number\n placement?: ModuleInjectionSlot extends infer S\n ? S extends { widgetId: string }\n ? Omit<S, 'widgetId' | 'priority'>\n : never\n : never\n}\nlet injectionTablePromise: Promise<Map<InjectionSpotId, TableEntry[]>> | null = null\n\nfunction isInjectionSlotObject(value: ModuleInjectionSlot): value is InjectionWidgetPlacement & { widgetId: string; priority?: number } {\n return typeof value === 'object' && value !== null && 'widgetId' in value\n}\n\n/**\n * Invalidate the widget entries and widget module cache.\n * Call this when the generated registry is updated or modules are reloaded.\n */\nexport function invalidateInjectionWidgetCache() {\n widgetEntriesPromise = null\n injectionTablePromise = null\n widgetCache.clear()\n warnedRequiredModuleSkips.clear()\n}\n\nasync function loadWidgetEntries(): Promise<WidgetEntry[]> {\n if (!widgetEntriesPromise) {\n const promise = Promise.resolve().then(() =>\n getCoreInjectionWidgets().map((entry) => ({\n ...entry,\n moduleId: entry.moduleId || 'unknown',\n }))\n )\n widgetEntriesPromise = promise.catch((err) => {\n if (widgetEntriesPromise === promise) {\n widgetEntriesPromise = null\n }\n throw err\n })\n }\n return widgetEntriesPromise\n}\n\nasync function loadInjectionTable(): Promise<Map<InjectionSpotId, TableEntry[]>> {\n if (!injectionTablePromise) {\n const promise = Promise.resolve().then(() => {\n const list = getCoreInjectionTables()\n const table = new Map<InjectionSpotId, TableEntry[]>()\n\n for (const entry of list) {\n const injectionTable = entry.table ?? {}\n for (const [spotId, widgetIds] of Object.entries(injectionTable)) {\n const widgets = Array.isArray(widgetIds) ? widgetIds : [widgetIds]\n const existing = table.get(spotId) ?? []\n for (const widgetEntry of widgets) {\n if (typeof widgetEntry === 'string') {\n existing.push({ widgetId: widgetEntry, moduleId: entry.moduleId, priority: 0 })\n continue\n }\n if (isInjectionSlotObject(widgetEntry)) {\n const { widgetId, priority = 0, ...placement } = widgetEntry\n existing.push({\n widgetId,\n moduleId: entry.moduleId,\n priority: typeof priority === 'number' ? priority : 0,\n placement,\n })\n continue\n }\n }\n table.set(spotId, existing)\n }\n }\n\n for (const [spotId, widgets] of table.entries()) {\n table.set(spotId, widgets.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)))\n }\n\n return table\n })\n injectionTablePromise = promise.catch((err) => {\n if (injectionTablePromise === promise) {\n injectionTablePromise = null\n }\n throw err\n })\n }\n return injectionTablePromise\n}\n\nconst widgetCache = new Map<string, Promise<InjectionAnyWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }>>()\n\nfunction isDataWidgetModule(widget: Record<string, unknown>): widget is LoadedDataWidgetModule {\n const keys = [\n 'columns',\n 'rowActions',\n 'bulkActions',\n 'filters',\n 'fields',\n 'steps',\n 'badge',\n 'menuItems',\n ]\n return keys.some((key) => key in widget)\n}\n\nfunction ensureValidInjectionModule(mod: unknown, key: string, moduleId: string): (InjectionAnyWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }) {\n if (!mod || typeof mod !== 'object') {\n throw new Error(`Invalid injection widget module \"${key}\" from \"${moduleId}\" (expected object export)`)\n }\n const widget = (mod as { default?: InjectionAnyWidgetModule<any, any> }).default ?? (mod as InjectionAnyWidgetModule<any, any>)\n if (!widget || typeof widget !== 'object') {\n throw new Error(`Invalid injection widget export \"${key}\" from \"${moduleId}\" (missing default export)`) \n }\n if (!('metadata' in widget) || !widget.metadata || typeof widget.metadata !== 'object') {\n throw new Error(`Injection widget \"${key}\" from \"${moduleId}\" is missing metadata`)\n }\n const metadata = widget.metadata\n if (typeof metadata.id !== 'string' || metadata.id.length === 0) {\n throw new Error(`Injection widget \"${key}\" from \"${moduleId}\" metadata.id must be a non-empty string`)\n }\n const normalized = {\n ...widget,\n metadata,\n }\n\n if ('Widget' in normalized && typeof normalized.Widget === 'function') {\n if (typeof metadata.title !== 'string' || metadata.title.length === 0) {\n throw new Error(`Injection widget \"${metadata.id}\" from \"${moduleId}\" must have a title`)\n }\n return normalized\n }\n\n if (!isDataWidgetModule(normalized as Record<string, unknown>)) {\n throw new Error(\n `Injection widget \"${metadata.id}\" from \"${moduleId}\" must export either Widget component or a declarative data payload`\n )\n }\n\n return normalized\n}\n\nfunction isLoadedInjectionWidget(\n module: InjectionAnyWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }\n): module is LoadedWidgetModule {\n return 'Widget' in module && typeof module.Widget === 'function'\n}\n\nfunction isLoadedInjectionDataWidget(\n module: InjectionAnyWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }\n): module is LoadedDataWidgetModule {\n return !isLoadedInjectionWidget(module)\n}\n\nasync function loadEntry(entry: WidgetEntry): Promise<InjectionAnyWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }> {\n if (!widgetCache.has(entry.key)) {\n const promise = entry.loader().then((mod) => ensureValidInjectionModule(mod, entry.key, entry.moduleId))\n widgetCache.set(entry.key, promise)\n }\n return widgetCache.get(entry.key)!\n}\n\nfunction getEnabledModuleIdsForInjection(): ReadonlySet<string> {\n // Prefer the explicit enabled-modules registry populated by bootstrap.\n // This is the only signal that includes modules without injection widgets\n // (for example `ai_assistant`), so it is required for `requiredModules`\n // gating to be sound.\n const explicit = readGlobalEnabledModuleIds() ?? _enabledModuleIds\n if (explicit) return explicit\n\n // Fallback: derive from injection tables and widget entries. This keeps\n // older bootstrap paths (and callers that have not yet wired\n // `registerEnabledModuleIds`) working \u2014 at the cost of mis-classifying\n // dependency modules that ship no widgets. New apps MUST call\n // `registerEnabledModuleIds` to get accurate gating.\n const enabled = new Set<string>()\n const tables = readGlobalInjectionTables() ?? _coreInjectionTables ?? []\n for (const entry of tables) {\n if (entry?.moduleId) enabled.add(entry.moduleId)\n }\n const entries = readGlobalInjectionWidgets() ?? _coreInjectionWidgetEntries ?? []\n for (const entry of entries) {\n if (entry?.moduleId) enabled.add(entry.moduleId)\n }\n return enabled\n}\n\nfunction widgetMissingRequiredModules(\n metadata: InjectionWidgetMetadata,\n enabledModuleIds: ReadonlySet<string>,\n): string[] {\n const required = metadata.requiredModules\n if (!Array.isArray(required) || required.length === 0) return []\n const missing: string[] = []\n for (const moduleId of required) {\n if (typeof moduleId !== 'string' || moduleId.length === 0) continue\n if (!enabledModuleIds.has(moduleId)) missing.push(moduleId)\n }\n return missing\n}\n\nconst warnedRequiredModuleSkips = new Set<string>()\n\nfunction warnSkippedWidget(metadataId: string, missingModules: string[]) {\n const key = `${metadataId}:${missingModules.join(',')}`\n if (warnedRequiredModuleSkips.has(key)) return\n warnedRequiredModuleSkips.add(key)\n if (process.env.NODE_ENV === 'development') {\n console.debug(\n `[InjectionLoader] Skipping widget \"${metadataId}\" \u2014 required module(s) not enabled: ${missingModules.join(', ')}`,\n )\n }\n}\n\nasync function getResolvedEntriesForSpot(spotId: InjectionSpotId): Promise<TableEntry[]> {\n const table = await loadInjectionTable()\n const exactEntries = table.get(spotId) ?? []\n const wildcardEntries: TableEntry[] = []\n\n for (const [candidateSpotId, candidateEntries] of table.entries()) {\n if (candidateSpotId === spotId) continue\n if (!candidateSpotId.includes('*')) continue\n if (!matchWildcardPattern(spotId, candidateSpotId)) continue\n wildcardEntries.push(...candidateEntries)\n }\n\n const dedupedEntries = new Map<string, TableEntry>()\n for (const entry of [...exactEntries, ...wildcardEntries]) {\n const cacheKey = `${entry.moduleId}:${entry.widgetId}`\n const previous = dedupedEntries.get(cacheKey)\n if (!previous || (entry.priority ?? 0) > (previous.priority ?? 0)) {\n dedupedEntries.set(cacheKey, entry)\n }\n }\n\n return Array.from(dedupedEntries.values()).sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0))\n}\n\nexport async function loadAllInjectionWidgets(): Promise<LoadedInjectionWidget[]> {\n const widgetEntries = await loadWidgetEntries()\n const enabledModuleIds = getEnabledModuleIdsForInjection()\n const loaded = await Promise.all(\n widgetEntries.map(async (entry) => {\n const module = await loadEntry(entry)\n if (!isLoadedInjectionWidget(module)) return null\n const missing = widgetMissingRequiredModules(module.metadata, enabledModuleIds)\n if (missing.length > 0) {\n warnSkippedWidget(module.metadata.id, missing)\n return null\n }\n return { ...module, moduleId: entry.moduleId, key: entry.key }\n })\n )\n const byId = new Map<string, LoadedInjectionWidget>()\n for (const widget of loaded) {\n if (!widget) continue\n if (!byId.has(widget.metadata.id)) {\n byId.set(widget.metadata.id, widget)\n }\n }\n return Array.from(byId.values())\n}\n\nexport async function loadInjectionWidgetById(widgetId: string): Promise<LoadedInjectionWidget | null> {\n const widgetEntries = await loadWidgetEntries()\n const enabledModuleIds = getEnabledModuleIdsForInjection()\n for (const entry of widgetEntries) {\n const module = await loadEntry(entry)\n if (!isLoadedInjectionWidget(module)) continue\n if (module.metadata.id === widgetId) {\n const missing = widgetMissingRequiredModules(module.metadata, enabledModuleIds)\n if (missing.length > 0) {\n warnSkippedWidget(module.metadata.id, missing)\n return null\n }\n return { ...module, moduleId: entry.moduleId, key: entry.key }\n }\n }\n return null\n}\n\nexport async function loadInjectionDataWidgetById(widgetId: string): Promise<LoadedInjectionDataWidget | null> {\n const widgetEntries = await loadWidgetEntries()\n const enabledModuleIds = getEnabledModuleIdsForInjection()\n for (const entry of widgetEntries) {\n const module = await loadEntry(entry)\n if (!isLoadedInjectionDataWidget(module)) continue\n if (module.metadata.id === widgetId) {\n const missing = widgetMissingRequiredModules(module.metadata, enabledModuleIds)\n if (missing.length > 0) {\n warnSkippedWidget(module.metadata.id, missing)\n return null\n }\n return { ...module, moduleId: entry.moduleId, key: entry.key }\n }\n }\n return null\n}\n\nexport async function loadInjectionWidgetsForSpot(spotId: InjectionSpotId): Promise<LoadedInjectionWidget[]> {\n const entries = await getResolvedEntriesForSpot(spotId)\n const widgets: LoadedInjectionWidget[] = []\n for (const { widgetId, placement, priority } of entries) {\n const widget = await loadInjectionWidgetById(widgetId)\n if (!widget) continue\n const combinedPlacement = placement\n ? { ...placement, priority: typeof priority === 'number' ? priority : 0 }\n : { priority: typeof priority === 'number' ? priority : 0 }\n widgets.push({ ...widget, placement: combinedPlacement })\n }\n return widgets\n}\n\nexport async function loadInjectionDataWidgetsForSpot(spotId: InjectionSpotId): Promise<LoadedInjectionDataWidget[]> {\n const entries = await getResolvedEntriesForSpot(spotId)\n const widgets: LoadedInjectionDataWidget[] = []\n for (const { widgetId, placement, priority } of entries) {\n const widget = await loadInjectionDataWidgetById(widgetId)\n if (!widget) continue\n const combinedPlacement = placement\n ? { ...placement, priority: typeof priority === 'number' ? priority : 0 }\n : { priority: typeof priority === 'number' ? priority : 0 }\n widgets.push({ ...widget, placement: combinedPlacement })\n }\n return widgets\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/shared",
3
- "version": "0.6.3-develop.3525.3.cc18b8a43f",
3
+ "version": "0.6.3-develop.3574.1.f3c5dcf345",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -92,9 +92,10 @@
92
92
  "@mikro-orm/core": "^7.0.17",
93
93
  "@mikro-orm/decorators": "^7.0.17",
94
94
  "@mikro-orm/postgresql": "^7.0.17",
95
- "@open-mercato/cache": "0.6.3-develop.3525.3.cc18b8a43f",
95
+ "@open-mercato/cache": "0.6.3-develop.3574.1.f3c5dcf345",
96
96
  "dotenv": "^17.4.2",
97
97
  "rate-limiter-flexible": "^11.1.0",
98
+ "re2js": "2.8.2",
98
99
  "reflect-metadata": "^0.2.2",
99
100
  "sanitize-html": "^2.17.4",
100
101
  "undici": "^8.3.0"
@@ -22,6 +22,11 @@ describe('matchesEventPattern', () => {
22
22
  expect(matchesEventPattern('example.*', 'example.todo.created')).toBe(true)
23
23
  expect(matchesEventPattern('example.*', 'example.item.updated')).toBe(true)
24
24
  })
25
+
26
+ it('treats regex metacharacters as literals', () => {
27
+ expect(matchesEventPattern('example.(todo).*', 'example.(todo).created')).toBe(true)
28
+ expect(matchesEventPattern('example.+.*', 'example.todo.created')).toBe(false)
29
+ })
25
30
  })
26
31
 
27
32
  describe('collectSyncSubscribers', () => {
@@ -1,22 +1,13 @@
1
1
  import type { SyncCrudEventPayload } from './sync-event-types'
2
2
  import type { SyncSubscriberEntry } from './sync-subscriber-store'
3
+ import { matchWildcardPattern } from '@open-mercato/shared/lib/patterns/wildcard'
3
4
 
4
5
  // ---------------------------------------------------------------------------
5
6
  // Event pattern matching
6
7
  // ---------------------------------------------------------------------------
7
8
 
8
- function escapeRegex(input: string): string {
9
- // Escape all characters with special meaning in regular expressions
10
- return input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
11
- }
12
-
13
9
  export function matchesEventPattern(pattern: string, eventId: string): boolean {
14
- if (pattern === eventId) return true
15
- if (pattern === '*') return true
16
- // Escape the pattern for literal use in a RegExp, then turn "\*" into ".*" for wildcard support
17
- const escaped = escapeRegex(pattern).replace(/\\\*/g, '.*')
18
- const regex = new RegExp('^' + escaped + '$')
19
- return regex.test(eventId)
10
+ return matchWildcardPattern(eventId, pattern)
20
11
  }
21
12
 
22
13
  // ---------------------------------------------------------------------------
@@ -19,6 +19,11 @@ describe('matchEventPattern', () => {
19
19
  expect(matchEventPattern('customers.person', 'customers.*')).toBe(true)
20
20
  expect(matchEventPattern('customers.person.deleted', 'customers.*')).toBe(false)
21
21
  })
22
+
23
+ it('treats regex metacharacters as literal pattern characters', () => {
24
+ expect(matchEventPattern('customers.(person).created', 'customers.(person).*')).toBe(true)
25
+ expect(matchEventPattern('customers.person.created', 'customers.+.*')).toBe(false)
26
+ })
22
27
  })
23
28
 
24
29
  describe('prefix mode', () => {
@@ -1,24 +1,11 @@
1
+ import { matchWildcardPattern } from '@open-mercato/shared/lib/patterns/wildcard'
2
+
1
3
  export type EventPatternMode = 'single-segment' | 'prefix'
2
4
 
3
5
  type MatchEventPatternOptions = {
4
6
  mode?: EventPatternMode
5
7
  }
6
8
 
7
- const singleSegmentPatternCache = new Map<string, RegExp>()
8
-
9
- function getSingleSegmentPatternRegex(pattern: string): RegExp {
10
- const existing = singleSegmentPatternCache.get(pattern)
11
- if (existing) return existing
12
-
13
- const regexPattern = pattern
14
- .replace(/[.+^${}()|[\]\\]/g, '\\$&')
15
- .replace(/\*/g, '[^.]+')
16
-
17
- const compiled = new RegExp(`^${regexPattern}$`)
18
- singleSegmentPatternCache.set(pattern, compiled)
19
- return compiled
20
- }
21
-
22
9
  export function matchEventPattern(
23
10
  eventName: string,
24
11
  pattern: string,
@@ -44,7 +31,7 @@ export function matchEventPattern(
44
31
  return false
45
32
  }
46
33
 
47
- return getSingleSegmentPatternRegex(pattern).test(eventName)
34
+ return matchWildcardPattern(eventName, pattern, { singleSegmentWildcard: true })
48
35
  }
49
36
 
50
37
  export function matchAnyEventPattern(
@@ -0,0 +1,27 @@
1
+ import { matchWildcardPattern } from '../wildcard'
2
+
3
+ describe('matchWildcardPattern', () => {
4
+ it('matches exact values and the global wildcard', () => {
5
+ expect(matchWildcardPattern('customers.person.created', 'customers.person.created')).toBe(true)
6
+ expect(matchWildcardPattern('customers.person.created', '*')).toBe(true)
7
+ expect(matchWildcardPattern('customers.person.created', 'sales.order.created')).toBe(false)
8
+ })
9
+
10
+ it('matches multi-segment wildcard patterns without regex execution', () => {
11
+ expect(matchWildcardPattern('example.todo.created', 'example.*')).toBe(true)
12
+ expect(matchWildcardPattern('example.todo.created', 'example.*.created')).toBe(true)
13
+ expect(matchWildcardPattern('example.todo.created', '*.todo.*')).toBe(true)
14
+ expect(matchWildcardPattern('example.todo.created', 'customers.*')).toBe(false)
15
+ })
16
+
17
+ it('can restrict wildcards to a single dotted segment', () => {
18
+ expect(matchWildcardPattern('customers.person', 'customers.*', { singleSegmentWildcard: true })).toBe(true)
19
+ expect(matchWildcardPattern('customers.person.deleted', 'customers.*', { singleSegmentWildcard: true })).toBe(false)
20
+ })
21
+
22
+ it('treats regex metacharacters as literals', () => {
23
+ expect(matchWildcardPattern('cache:(tenant)[1]', 'cache:(tenant)[*]')).toBe(true)
24
+ expect(matchWildcardPattern('cache:(tenant)[1]', 'cache:.+')).toBe(false)
25
+ })
26
+ })
27
+
@@ -0,0 +1,59 @@
1
+ export type WildcardMatchOptions = {
2
+ singleSegmentWildcard?: boolean
3
+ }
4
+
5
+ function matchWildcardSegment(pattern: string, value: string, options: WildcardMatchOptions): boolean {
6
+ let patternIndex = 0
7
+ let valueIndex = 0
8
+ let starIndex = -1
9
+ let valueRetryIndex = 0
10
+
11
+ while (valueIndex < value.length) {
12
+ const patternChar = pattern[patternIndex]
13
+ const valueChar = value[valueIndex]
14
+
15
+ if (patternChar === '*') {
16
+ starIndex = patternIndex
17
+ valueRetryIndex = valueIndex
18
+ patternIndex += 1
19
+ continue
20
+ }
21
+
22
+ if (patternChar === valueChar) {
23
+ patternIndex += 1
24
+ valueIndex += 1
25
+ continue
26
+ }
27
+
28
+ if (
29
+ starIndex !== -1
30
+ && (!options.singleSegmentWildcard || value[valueRetryIndex] !== '.')
31
+ ) {
32
+ patternIndex = starIndex + 1
33
+ valueRetryIndex += 1
34
+ valueIndex = valueRetryIndex
35
+ continue
36
+ }
37
+
38
+ return false
39
+ }
40
+
41
+ while (pattern[patternIndex] === '*') {
42
+ patternIndex += 1
43
+ }
44
+
45
+ return patternIndex === pattern.length
46
+ }
47
+
48
+ export function matchWildcardPattern(
49
+ value: string,
50
+ pattern: string,
51
+ options: WildcardMatchOptions = {},
52
+ ): boolean {
53
+ if (pattern === value) return true
54
+ if (pattern === '*') return true
55
+ if (!pattern.includes('*')) return false
56
+
57
+ return matchWildcardSegment(pattern, value, options)
58
+ }
59
+
@@ -0,0 +1,48 @@
1
+ import { RE2JS } from 're2js'
2
+
3
+ const DEFAULT_MAX_PATTERN_LENGTH = 500
4
+ const DEFAULT_MAX_INPUT_LENGTH = 10_000
5
+ const MAX_REGEX_CACHE_SIZE = 256
6
+
7
+ const compiledRegexCache = new Map<string, RE2JS>()
8
+
9
+ export type LinearRegexTestOptions = {
10
+ maxPatternLength?: number
11
+ maxInputLength?: number
12
+ }
13
+
14
+ export type LinearRegexTestResult =
15
+ | { ok: true; matched: boolean }
16
+ | { ok: false; reason: 'pattern_too_long' | 'input_too_long' | 'invalid_pattern' }
17
+
18
+ function getCachedRegex(pattern: string): RE2JS {
19
+ const cached = compiledRegexCache.get(pattern)
20
+ if (cached) return cached
21
+
22
+ const compiled = RE2JS.compile(pattern)
23
+ if (compiledRegexCache.size >= MAX_REGEX_CACHE_SIZE) {
24
+ const oldestKey = compiledRegexCache.keys().next().value
25
+ if (oldestKey) compiledRegexCache.delete(oldestKey)
26
+ }
27
+ compiledRegexCache.set(pattern, compiled)
28
+ return compiled
29
+ }
30
+
31
+ export function testLinearRegex(
32
+ pattern: string,
33
+ input: string,
34
+ options: LinearRegexTestOptions = {},
35
+ ): LinearRegexTestResult {
36
+ const maxPatternLength = options.maxPatternLength ?? DEFAULT_MAX_PATTERN_LENGTH
37
+ const maxInputLength = options.maxInputLength ?? DEFAULT_MAX_INPUT_LENGTH
38
+
39
+ if (pattern.length > maxPatternLength) return { ok: false, reason: 'pattern_too_long' }
40
+ if (input.length > maxInputLength) return { ok: false, reason: 'input_too_long' }
41
+
42
+ try {
43
+ return { ok: true, matched: getCachedRegex(pattern).test(input) }
44
+ } catch {
45
+ return { ok: false, reason: 'invalid_pattern' }
46
+ }
47
+ }
48
+
@@ -1,5 +1,9 @@
1
1
  /** @jest-environment node */
2
- import { validateValuesAgainstDefs } from '../validation'
2
+ import {
3
+ MAX_CUSTOM_FIELD_REGEX_PATTERN_LENGTH,
4
+ MAX_CUSTOM_FIELD_REGEX_INPUT_LENGTH,
5
+ validateValuesAgainstDefs,
6
+ } from '../validation'
3
7
 
4
8
  describe('validateValuesAgainstDefs', () => {
5
9
  it('validates required and integer/float comparisons', () => {
@@ -48,5 +52,96 @@ describe('validateValuesAgainstDefs', () => {
48
52
  r = validateValuesAgainstDefs({ code: 'ok', status: 'open' }, defs as any)
49
53
  expect(r.ok).toBe(true)
50
54
  })
51
- })
52
55
 
56
+ it('evaluates dangerous backtracking regex rules in bounded time', () => {
57
+ const defs = [
58
+ {
59
+ key: 'eu_vat_number',
60
+ kind: 'text',
61
+ configJson: {
62
+ validation: [
63
+ {
64
+ rule: 'regex',
65
+ param: '^(GB|FR|DE|IT|ES|PL|NL|BE|SE|AT|DK|FI|PT|IE|GR|CZ|RO|HU|SK|BG|HR|SI|LT|LV|EE|LU|MT|CY)?([0-9A-Za-z]+)*$',
66
+ message: 'bad vat',
67
+ },
68
+ ],
69
+ },
70
+ },
71
+ ]
72
+
73
+ const startedAt = Date.now()
74
+ const result = validateValuesAgainstDefs(
75
+ { eu_vat_number: `${'A'.repeat(64)}!` },
76
+ defs as any,
77
+ )
78
+
79
+ expect(Date.now() - startedAt).toBeLessThan(2_000)
80
+ expect(result.ok).toBe(false)
81
+ expect(result.fieldErrors['cf_eu_vat_number']).toBe('bad vat')
82
+ })
83
+
84
+ it('fails closed for unsupported regex syntax', () => {
85
+ const defs = [
86
+ {
87
+ key: 'code',
88
+ kind: 'text',
89
+ configJson: {
90
+ validation: [
91
+ { rule: 'regex', param: '(?=safe)safe', message: 'unsupported regex' },
92
+ ],
93
+ },
94
+ },
95
+ ]
96
+
97
+ const result = validateValuesAgainstDefs({ code: 'safe' }, defs as any)
98
+
99
+ expect(result.ok).toBe(false)
100
+ expect(result.fieldErrors['cf_code']).toBe('unsupported regex')
101
+ })
102
+
103
+ it('fails closed before testing oversized regex input values', () => {
104
+ const defs = [
105
+ {
106
+ key: 'body',
107
+ kind: 'multiline',
108
+ configJson: {
109
+ validation: [
110
+ { rule: 'regex', param: '^a+$', message: 'body too large' },
111
+ ],
112
+ },
113
+ },
114
+ ]
115
+
116
+ const result = validateValuesAgainstDefs(
117
+ { body: 'a'.repeat(MAX_CUSTOM_FIELD_REGEX_INPUT_LENGTH + 1) },
118
+ defs as any,
119
+ )
120
+
121
+ expect(result.ok).toBe(false)
122
+ expect(result.fieldErrors['cf_body']).toBe('body too large')
123
+ })
124
+
125
+ it('fails closed before testing oversized regex patterns', () => {
126
+ const defs = [
127
+ {
128
+ key: 'code',
129
+ kind: 'text',
130
+ configJson: {
131
+ validation: [
132
+ {
133
+ rule: 'regex',
134
+ param: `^${'a'.repeat(MAX_CUSTOM_FIELD_REGEX_PATTERN_LENGTH)}$`,
135
+ message: 'pattern too large',
136
+ },
137
+ ],
138
+ },
139
+ },
140
+ ]
141
+
142
+ const result = validateValuesAgainstDefs({ code: 'a' }, defs as any)
143
+
144
+ expect(result.ok).toBe(false)
145
+ expect(result.fieldErrors['cf_code']).toBe('pattern too large')
146
+ })
147
+ })
@@ -1,4 +1,8 @@
1
1
  import { z } from 'zod'
2
+ import { testLinearRegex } from '../../lib/regex/linear'
3
+
4
+ export const MAX_CUSTOM_FIELD_REGEX_PATTERN_LENGTH = 500
5
+ export const MAX_CUSTOM_FIELD_REGEX_INPUT_LENGTH = 10_000
2
6
 
3
7
  // Supported rule types for custom fields validation
4
8
  export const VALIDATION_RULES = [
@@ -28,7 +32,11 @@ export const validationRuleSchema = z.discriminatedUnion('rule', [
28
32
  z.object({ rule: z.literal('gte'), param: z.number(), message: z.string().min(1) }),
29
33
  z.object({ rule: z.literal('eq'), param: z.any(), message: z.string().min(1) }),
30
34
  z.object({ rule: z.literal('ne'), param: z.any(), message: z.string().min(1) }),
31
- z.object({ rule: z.literal('regex'), param: z.string().min(1), message: z.string().min(1) }),
35
+ z.object({
36
+ rule: z.literal('regex'),
37
+ param: z.string().min(1),
38
+ message: z.string().min(1),
39
+ }),
32
40
  ])
33
41
 
34
42
  export type ValidationRule = z.infer<typeof validationRuleSchema>
@@ -83,13 +91,11 @@ function evalRule(rule: ValidationRule, value: any, kind: string): string | null
83
91
  return value !== (rule as any).param ? null : rule.message
84
92
  case 'regex':
85
93
  if (isEmpty(value)) return null
86
- try {
87
- const re = new RegExp((rule as any).param)
88
- return re.test(String(value)) ? null : rule.message
89
- } catch {
90
- // Invalid regex in definition: consider it failed safe and return message
91
- return rule.message
92
- }
94
+ const regexResult = testLinearRegex(String((rule as any).param), String(value), {
95
+ maxPatternLength: MAX_CUSTOM_FIELD_REGEX_PATTERN_LENGTH,
96
+ maxInputLength: MAX_CUSTOM_FIELD_REGEX_INPUT_LENGTH,
97
+ })
98
+ return regexResult.ok && regexResult.matched ? null : rule.message
93
99
  default:
94
100
  return null
95
101
  }
@@ -115,4 +121,3 @@ export function validateValuesAgainstDefs(
115
121
  }
116
122
  return { ok: Object.keys(errors).length === 0, fieldErrors: errors }
117
123
  }
118
-
@@ -173,4 +173,25 @@ describe('Injection loader — requiredModules gating (#1849)', () => {
173
173
  expect(ids).toContain(ALWAYS_AVAILABLE_WIDGET_ID)
174
174
  expect(ids).not.toContain(REQUIRES_AI_WIDGET_ID)
175
175
  })
176
+
177
+ it('loads wildcard spot entries without regex pattern execution', async () => {
178
+ invalidateInjectionWidgetCache()
179
+ registerCoreInjectionWidgets([
180
+ makeWidgetEntry('host', 'host/widgets/injection/always-available/widget.ts', async () => alwaysAvailableWidget),
181
+ ])
182
+ registerCoreInjectionTables([
183
+ {
184
+ moduleId: 'host',
185
+ table: {
186
+ 'data-table:host.*:search-trailing': [
187
+ { widgetId: ALWAYS_AVAILABLE_WIDGET_ID, priority: 50 },
188
+ ],
189
+ },
190
+ },
191
+ ])
192
+ registerEnabledModuleIds(['host'])
193
+
194
+ const loaded = await loadInjectionWidgetsForSpot(HOST_SPOT_ID)
195
+ expect(loaded.map((widget) => widget.metadata.id)).toEqual([ALWAYS_AVAILABLE_WIDGET_ID])
196
+ })
176
197
  })
@@ -1,4 +1,5 @@
1
1
  import type { ModuleInjectionWidgetEntry } from '../registry'
2
+ import { matchWildcardPattern } from '@open-mercato/shared/lib/patterns/wildcard'
2
3
  import type {
3
4
  InjectionAnyWidgetModule,
4
5
  InjectionDataWidgetModule,
@@ -452,8 +453,7 @@ async function getResolvedEntriesForSpot(spotId: InjectionSpotId): Promise<Table
452
453
  for (const [candidateSpotId, candidateEntries] of table.entries()) {
453
454
  if (candidateSpotId === spotId) continue
454
455
  if (!candidateSpotId.includes('*')) continue
455
- const pattern = new RegExp(`^${candidateSpotId.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*')}$`)
456
- if (!pattern.test(spotId)) continue
456
+ if (!matchWildcardPattern(spotId, candidateSpotId)) continue
457
457
  wildcardEntries.push(...candidateEntries)
458
458
  }
459
459