@open-mercato/shared 0.9.1-develop.7574.1.837288eec1 → 0.10.1-develop.7576.1.21d5b50435

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.
Files changed (125) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +4 -4
  3. package/dist/lib/auth/server.js +1 -1
  4. package/dist/lib/auth/server.js.map +2 -2
  5. package/dist/lib/bootstrap/dynamicLoader.js +6 -46
  6. package/dist/lib/bootstrap/dynamicLoader.js.map +2 -2
  7. package/dist/lib/bootstrap/factory.js +3 -4
  8. package/dist/lib/bootstrap/factory.js.map +2 -2
  9. package/dist/lib/catalog-visibility/index.js +0 -4
  10. package/dist/lib/catalog-visibility/index.js.map +2 -2
  11. package/dist/lib/commands/command-bus.js +13 -45
  12. package/dist/lib/commands/command-bus.js.map +2 -2
  13. package/dist/lib/commands/types.js.map +2 -2
  14. package/dist/lib/crud/exporters.js +1 -1
  15. package/dist/lib/crud/exporters.js.map +2 -2
  16. package/dist/lib/crud/ids.js +1 -13
  17. package/dist/lib/crud/ids.js.map +2 -2
  18. package/dist/lib/db/pg-errors.js +0 -14
  19. package/dist/lib/db/pg-errors.js.map +2 -2
  20. package/dist/lib/localization/index.js +1 -9
  21. package/dist/lib/localization/index.js.map +2 -2
  22. package/dist/lib/localization/translatable-fields.js +0 -26
  23. package/dist/lib/localization/translatable-fields.js.map +2 -2
  24. package/dist/lib/number.js +0 -6
  25. package/dist/lib/number.js.map +2 -2
  26. package/dist/lib/openapi/generator.js +67 -84
  27. package/dist/lib/openapi/generator.js.map +2 -2
  28. package/dist/lib/query/engine.js +10 -45
  29. package/dist/lib/query/engine.js.map +2 -2
  30. package/dist/lib/query/join-utils.js +1 -1
  31. package/dist/lib/query/join-utils.js.map +2 -2
  32. package/dist/lib/query/types.js.map +2 -2
  33. package/dist/lib/url.js +0 -24
  34. package/dist/lib/url.js.map +2 -2
  35. package/dist/lib/version.js +1 -1
  36. package/dist/lib/version.js.map +1 -1
  37. package/dist/lib/webhooks/body.js +3 -7
  38. package/dist/lib/webhooks/body.js.map +2 -2
  39. package/dist/lib/webhooks/index.js +0 -2
  40. package/dist/lib/webhooks/index.js.map +2 -2
  41. package/dist/modules/integrations/types.js.map +2 -2
  42. package/dist/modules/notifications/types.js +0 -4
  43. package/dist/modules/notifications/types.js.map +3 -3
  44. package/dist/modules/payment_gateways/types.js +1 -14
  45. package/dist/modules/payment_gateways/types.js.map +2 -2
  46. package/dist/modules/registry.js.map +2 -2
  47. package/dist/modules/search.js.map +2 -2
  48. package/dist/modules/widgets/injection-loader.js +3 -14
  49. package/dist/modules/widgets/injection-loader.js.map +2 -2
  50. package/package.json +3 -10
  51. package/src/lib/__tests__/number.test.ts +1 -24
  52. package/src/lib/auth/__tests__/server.test.ts +0 -14
  53. package/src/lib/auth/server.ts +1 -1
  54. package/src/lib/bootstrap/__tests__/dynamicLoader.appDi.test.ts +2 -2
  55. package/src/lib/bootstrap/__tests__/dynamicLoader.appModuleOverrides.test.ts +2 -2
  56. package/src/lib/bootstrap/__tests__/dynamicLoader.cacheRecovery.test.ts +2 -2
  57. package/src/lib/bootstrap/__tests__/dynamicLoader.commandInterceptors.test.ts +2 -2
  58. package/src/lib/bootstrap/__tests__/dynamicLoader.tsconfig.test.ts +2 -2
  59. package/src/lib/bootstrap/__tests__/factory.test.ts +6 -18
  60. package/src/lib/bootstrap/dynamicLoader.ts +5 -63
  61. package/src/lib/bootstrap/factory.ts +3 -8
  62. package/src/lib/bootstrap/types.ts +1 -5
  63. package/src/lib/catalog-visibility/index.ts +0 -2
  64. package/src/lib/commands/__tests__/command-bus.test.ts +1 -100
  65. package/src/lib/commands/command-bus.ts +10 -42
  66. package/src/lib/commands/command-interceptor.ts +0 -4
  67. package/src/lib/commands/types.ts +0 -14
  68. package/src/lib/crud/__tests__/exporters.test.ts +0 -18
  69. package/src/lib/crud/__tests__/ids.test.ts +1 -34
  70. package/src/lib/crud/exporters.ts +1 -1
  71. package/src/lib/crud/ids.ts +0 -24
  72. package/src/lib/db/__tests__/pg-errors.test.ts +1 -68
  73. package/src/lib/db/pg-errors.ts +0 -35
  74. package/src/lib/encryption/__tests__/tenantDataEncryptionService.test.ts +0 -23
  75. package/src/lib/localization/__tests__/translatable-fields.test.ts +0 -17
  76. package/src/lib/localization/index.ts +1 -8
  77. package/src/lib/localization/translatable-fields.ts +0 -46
  78. package/src/lib/number.ts +0 -13
  79. package/src/lib/openapi/generator.ts +77 -106
  80. package/src/lib/query/engine.ts +8 -58
  81. package/src/lib/query/join-utils.ts +1 -1
  82. package/src/lib/query/types.ts +1 -13
  83. package/src/lib/url.ts +0 -30
  84. package/src/lib/webhooks/__tests__/body.test.ts +0 -48
  85. package/src/lib/webhooks/body.ts +4 -11
  86. package/src/lib/webhooks/index.ts +0 -1
  87. package/src/modules/integrations/types.ts +0 -6
  88. package/src/modules/notifications/types.ts +0 -7
  89. package/src/modules/payment_gateways/__tests__/types.test.ts +0 -44
  90. package/src/modules/payment_gateways/types.ts +6 -52
  91. package/src/modules/registry.ts +0 -5
  92. package/src/modules/search.ts +0 -39
  93. package/src/modules/widgets/injection-loader.ts +3 -33
  94. package/dist/lib/bootstrap/next-subpath-resolve-hook-worker.js +0 -6
  95. package/dist/lib/bootstrap/next-subpath-resolve-hook-worker.js.map +0 -7
  96. package/dist/lib/bootstrap/next-subpath-resolve-hook.js +0 -62
  97. package/dist/lib/bootstrap/next-subpath-resolve-hook.js.map +0 -7
  98. package/dist/lib/catalog-visibility/canonicalizeEffectiveScope.js +0 -33
  99. package/dist/lib/catalog-visibility/canonicalizeEffectiveScope.js.map +0 -7
  100. package/dist/lib/catalog-visibility/hashEffectiveScope.js +0 -9
  101. package/dist/lib/catalog-visibility/hashEffectiveScope.js.map +0 -7
  102. package/dist/lib/db/advisoryLock.js +0 -172
  103. package/dist/lib/db/advisoryLock.js.map +0 -7
  104. package/dist/lib/query/overlap.js +0 -58
  105. package/dist/lib/query/overlap.js.map +0 -7
  106. package/dist/modules/notifications/safeHref.js +0 -20
  107. package/dist/modules/notifications/safeHref.js.map +0 -7
  108. package/src/lib/__tests__/url.cross-site.test.ts +0 -140
  109. package/src/lib/bootstrap/__tests__/dynamicLoader.externalSubpath.test.ts +0 -141
  110. package/src/lib/bootstrap/__tests__/next-subpath-resolve-hook.test.ts +0 -174
  111. package/src/lib/bootstrap/next-subpath-resolve-hook-worker.ts +0 -3
  112. package/src/lib/bootstrap/next-subpath-resolve-hook.ts +0 -130
  113. package/src/lib/catalog-visibility/__tests__/hashEffectiveScope.test.ts +0 -121
  114. package/src/lib/catalog-visibility/canonicalizeEffectiveScope.ts +0 -41
  115. package/src/lib/catalog-visibility/hashEffectiveScope.ts +0 -8
  116. package/src/lib/db/__tests__/advisoryLock.integration.test.ts +0 -343
  117. package/src/lib/db/__tests__/advisoryLock.test.ts +0 -564
  118. package/src/lib/db/advisoryLock.ts +0 -271
  119. package/src/lib/openapi/__tests__/optional-path-params.test.ts +0 -102
  120. package/src/lib/query/__tests__/empty-ids-filter.pg.test.ts +0 -89
  121. package/src/lib/query/__tests__/engine.empty-in-filter.test.ts +0 -79
  122. package/src/lib/query/__tests__/overlap.test.ts +0 -219
  123. package/src/lib/query/overlap.ts +0 -106
  124. package/src/modules/notifications/safeHref.ts +0 -19
  125. package/src/modules/widgets/__tests__/injection-loader.partitioned-registration.test.ts +0 -78
@@ -38,13 +38,6 @@ import { resolveListCountCap } from './count-cap'
38
38
  import { mapWithConcurrency } from './bounded-decrypt'
39
39
  import { parseNumberWithDefault } from '../number'
40
40
  import { createLogger } from '../logger'
41
- import {
42
- buildArrayColumnNoOverlapPredicate,
43
- buildArrayColumnOverlapPredicate,
44
- buildJsonbNoOverlapPredicate,
45
- buildJsonbOverlapPredicate,
46
- buildScalarOverlapPredicate,
47
- } from './overlap'
48
41
 
49
42
  const logger = createLogger('shared').child({ component: 'query' })
50
43
 
@@ -1223,11 +1216,7 @@ export class BasicQueryEngine implements QueryEngine {
1223
1216
  })
1224
1217
  continue
1225
1218
  }
1226
- q = f.op === 'overlap'
1227
- ? q.where(buildScalarOverlapPredicate(expr, f.value))
1228
- : f.op === 'noverlap'
1229
- ? q.where(sql<boolean>`false`)
1230
- : this.applyColumnOp(q, expr, f.op, f.value)
1219
+ q = this.applyColumnOp(q, expr, f.op, f.value)
1231
1220
  }
1232
1221
 
1233
1222
  // OR groups are applied here, after the cf:* value expressions exist, so a
@@ -1274,10 +1263,7 @@ export class BasicQueryEngine implements QueryEngine {
1274
1263
  value: rf.value,
1275
1264
  })
1276
1265
  }
1277
- if (rf.op === 'noverlap') return sql<boolean>`false`
1278
- return rf.op === 'overlap'
1279
- ? buildScalarOverlapPredicate(cfValueExprByKey[rf.key], rf.value)
1280
- : this.buildColumnOpExpression(eb, cfValueExprByKey[rf.key], rf.op, rf.value)
1266
+ return this.buildColumnOpExpression(eb, cfValueExprByKey[rf.key], rf.op, rf.value)
1281
1267
  }
1282
1268
  return this.buildIndexDocOpExpression(eb, {
1283
1269
  entity: String(entity),
@@ -1603,22 +1589,10 @@ export class BasicQueryEngine implements QueryEngine {
1603
1589
  return builder.where(column as any, '<', value as any)
1604
1590
  case 'lte':
1605
1591
  return builder.where(column as any, '<=', value as any)
1606
- case 'in': {
1607
- // An empty list ("no id survived validation", #7502) compiles to invalid
1608
- // SQL (`in ()`) under Kysely/Postgres — short-circuit to a predicate that
1609
- // never matches instead of letting the driver reject the query.
1610
- const inValues = Array.isArray(value) ? value : [value]
1611
- return inValues.length === 0
1612
- ? builder.where(sql<boolean>`false`)
1613
- : builder.where(column as any, 'in', inValues)
1614
- }
1615
- case 'nin': {
1616
- // Mirrors 'in' above: empty means "exclude nothing".
1617
- const ninValues = Array.isArray(value) ? value : [value]
1618
- return ninValues.length === 0
1619
- ? builder.where(sql<boolean>`true`)
1620
- : builder.where(column as any, 'not in', ninValues)
1621
- }
1592
+ case 'in':
1593
+ return builder.where(column as any, 'in', Array.isArray(value) ? value : [value])
1594
+ case 'nin':
1595
+ return builder.where(column as any, 'not in', Array.isArray(value) ? value : [value])
1622
1596
  case 'like':
1623
1597
  return builder.where(column as any, 'like', value as any)
1624
1598
  case 'ilike':
@@ -1627,10 +1601,6 @@ export class BasicQueryEngine implements QueryEngine {
1627
1601
  return value
1628
1602
  ? builder.where(column as any, 'is not', null)
1629
1603
  : builder.where(column as any, 'is', null)
1630
- case 'overlap':
1631
- return builder.where(buildArrayColumnOverlapPredicate(column, value))
1632
- case 'noverlap':
1633
- return builder.where(buildArrayColumnNoOverlapPredicate(column, value))
1634
1604
  default:
1635
1605
  return builder
1636
1606
  }
@@ -1764,11 +1734,6 @@ export class BasicQueryEngine implements QueryEngine {
1764
1734
  case 'exists':
1765
1735
  predicate = sql<boolean>`${caseExpr} is not null`
1766
1736
  break
1767
- case 'overlap':
1768
- predicate = buildScalarOverlapPredicate(caseExpr, value)
1769
- break
1770
- case 'noverlap':
1771
- return sql<boolean>`false`
1772
1737
  default:
1773
1738
  // Mirrors buildColumnOpExpression's unknown-op fallback: a neutral
1774
1739
  // predicate, so full and count shapes drop the same leaves.
@@ -1786,20 +1751,11 @@ export class BasicQueryEngine implements QueryEngine {
1786
1751
  case 'gte': return eb(column, '>=', value)
1787
1752
  case 'lt': return eb(column, '<', value)
1788
1753
  case 'lte': return eb(column, '<=', value)
1789
- case 'in': {
1790
- // Mirrors the guard in applyColumnOp above (#7502).
1791
- const inValues = Array.isArray(value) ? value : [value]
1792
- return inValues.length === 0 ? eb.val(false) : eb(column, 'in', inValues)
1793
- }
1794
- case 'nin': {
1795
- const ninValues = Array.isArray(value) ? value : [value]
1796
- return ninValues.length === 0 ? eb.val(true) : eb(column, 'not in', ninValues)
1797
- }
1754
+ case 'in': return eb(column, 'in', Array.isArray(value) ? value : [value])
1755
+ case 'nin': return eb(column, 'not in', Array.isArray(value) ? value : [value])
1798
1756
  case 'like': return eb(column, 'like', value)
1799
1757
  case 'ilike': return eb(column, 'ilike', value)
1800
1758
  case 'exists': return value ? eb(column, 'is not', null) : eb(column, 'is', null)
1801
- case 'overlap': return buildArrayColumnOverlapPredicate(column, value)
1802
- case 'noverlap': return buildArrayColumnNoOverlapPredicate(column, value)
1803
1759
  default: return eb.val(true)
1804
1760
  }
1805
1761
  }
@@ -2017,12 +1973,6 @@ export class BasicQueryEngine implements QueryEngine {
2017
1973
  ? sub.where(sql<boolean>`${textExpr} is not null`)
2018
1974
  : sub.where(sql<boolean>`${textExpr} is null`)
2019
1975
  break
2020
- case 'overlap':
2021
- sub = sub.where(buildJsonbOverlapPredicate(`${alias}.doc`, opts.field, opts.value))
2022
- break
2023
- case 'noverlap':
2024
- sub = sub.where(buildJsonbNoOverlapPredicate(`${alias}.doc`, opts.field, opts.value))
2025
- break
2026
1976
  default:
2027
1977
  break
2028
1978
  }
@@ -7,7 +7,7 @@ type AnyBuilder = any
7
7
 
8
8
  export type NormalizedFilter = { field: string; op: FilterOp; value?: unknown; orGroup?: string; qualified?: string | null }
9
9
 
10
- const VALID_OPS = ['eq', 'ne', 'gt', 'gte', 'lt', 'lte', 'in', 'nin', 'like', 'ilike', 'exists', 'overlap', 'noverlap'] as const
10
+ const VALID_OPS = ['eq', 'ne', 'gt', 'gte', 'lt', 'lte', 'in', 'nin', 'like', 'ilike', 'exists'] as const
11
11
 
12
12
  const normalizeField = (key: string) => (key.startsWith('cf_') ? `cf:${key.slice(3)}` : key)
13
13
 
@@ -2,17 +2,7 @@ import type { EntityId } from '@open-mercato/shared/modules/entities'
2
2
  import type { Profiler } from '../profiler'
3
3
  import type { ResolvedCustomFieldDefinitions } from '../crud/custom-field-definition-index'
4
4
 
5
- /**
6
- * `overlap` (additive, 2026-10-05): "has any of" over a multi-valued field. An index-document
7
- * string array compiles to jsonb `?|`, a `cf:*` key to any-value-in-set, and an array-typed base
8
- * column to `&&`. An empty value list matches nothing.
9
- *
10
- * `noverlap` (additive, 2026-10-05): "has none of" — the negation of `overlap` on an index-document
11
- * string array or an array-typed base column. Only a present array can match (a missing/null key or
12
- * NULL column never does); an empty value list excludes nothing. Unsupported on `cf:*` keys, where
13
- * it compiles to `false`.
14
- */
15
- export type FilterOp = 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'nin' | 'like' | 'ilike' | 'exists' | 'overlap' | 'noverlap'
5
+ export type FilterOp = 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'nin' | 'like' | 'ilike' | 'exists'
16
6
 
17
7
  export enum SortDir {
18
8
  Asc = 'asc',
@@ -44,8 +34,6 @@ export type WhereOps<T> = {
44
34
  $like?: T extends string ? string : never
45
35
  $ilike?: T extends string ? string : never
46
36
  $exists?: boolean
47
- $overlap?: T extends readonly (infer Element)[] ? Element[] : T[]
48
- $noverlap?: T extends readonly (infer Element)[] ? Element[] : T[]
49
37
  }
50
38
 
51
39
  // A field filter can be a direct value (equals) or ops object
package/src/lib/url.ts CHANGED
@@ -267,36 +267,6 @@ export function toSecurityEmailUrl(input: RequestInput, path: string, env: EnvLi
267
267
  return `${base}/${path.replace(/^\/+/, '')}`
268
268
  }
269
269
 
270
- export function isCrossSiteBrowserRequest(req: Request, env: EnvLike = process.env): boolean {
271
- const rawFetchSite = req.headers.get('sec-fetch-site')
272
- const fetchSite = rawFetchSite?.trim().toLowerCase() ?? null
273
- if (fetchSite === 'cross-site') return true
274
- if (fetchSite === 'same-origin' || fetchSite === 'none') return false
275
-
276
- const rawOrigin = req.headers.get('origin')
277
- if (rawOrigin === null) return rawFetchSite !== null
278
- const origin = normalizeOrigin(rawOrigin)
279
- if (!origin) return true
280
-
281
- const allowedOrigins = readAllowedOrigins(env)
282
- if (allowedOrigins.has(origin)) return false
283
- if (shouldAllowLoopbackOrigin(origin, allowedOrigins, env)) return false
284
-
285
- const forwardedProto = readFirstHeaderValue(req.headers.get('x-forwarded-proto'))
286
- const protocol = forwardedProto
287
- ? (forwardedProto.endsWith(':') ? forwardedProto : `${forwardedProto}:`)
288
- : new URL(origin).protocol
289
- const host = req.headers.get('host')
290
- const forwardedHost = req.headers.get('x-forwarded-host')
291
- if (originFromHost(protocol, forwardedHost) === origin) return false
292
- if (originFromHost(protocol, host) === origin) return false
293
-
294
- if (env.NODE_ENV !== 'test') {
295
- logger.warn('Cross-site request rejected', { origin, host, forwardedHost })
296
- }
297
- return true
298
- }
299
-
300
270
  export function mapSecurityEmailUrlError(
301
271
  error: unknown,
302
272
  mapping: SecurityEmailUrlErrorMapping,
@@ -2,7 +2,6 @@
2
2
  import {
3
3
  DEFAULT_WEBHOOK_BODY_LIMIT_BYTES,
4
4
  readBoundedRequestBody,
5
- readBoundedRequestBytes,
6
5
  resolveWebhookBodyLimitBytes,
7
6
  WebhookBodyTooLargeError,
8
7
  } from '../body'
@@ -78,53 +77,6 @@ describe('readBoundedRequestBody', () => {
78
77
  })
79
78
  })
80
79
 
81
- describe('readBoundedRequestBytes', () => {
82
- it('preserves exact bytes including invalid UTF-8', async () => {
83
- const bytes = new Uint8Array([0xff, 0xfe, 0x41, 0xc3, 0x28])
84
- const { request } = makeStreamingRequest([bytes.slice(0, 2), bytes.slice(2)])
85
-
86
- const result = await readBoundedRequestBytes(request, { maxBytes: bytes.byteLength })
87
-
88
- expect(Array.from(result)).toEqual(Array.from(bytes))
89
- expect(new TextDecoder().decode(result)).toContain('\uFFFD')
90
- })
91
-
92
- it('returns an empty array for a missing body', async () => {
93
- const request = new Request('http://localhost/webhook', { method: 'POST' })
94
-
95
- expect((await readBoundedRequestBytes(request)).byteLength).toBe(0)
96
- await expect(readBoundedRequestBody(request)).resolves.toBe('')
97
- })
98
-
99
- it('enforces the declared and streamed limits and cancels the stream', async () => {
100
- const declared = new Request('http://localhost/webhook', {
101
- method: 'POST',
102
- headers: { 'content-length': '6' },
103
- body: 'ok',
104
- })
105
- await expect(readBoundedRequestBytes(declared, { maxBytes: 5 })).rejects.toBeInstanceOf(
106
- WebhookBodyTooLargeError,
107
- )
108
-
109
- const { request, cancel } = makeStreamingRequest([
110
- new Uint8Array([1, 2, 3]),
111
- new Uint8Array([4, 5, 6]),
112
- new Uint8Array([7, 8, 9]),
113
- ])
114
- await expect(readBoundedRequestBytes(request, { maxBytes: 5 })).rejects.toBeInstanceOf(
115
- WebhookBodyTooLargeError,
116
- )
117
- expect(cancel).toHaveBeenCalled()
118
- })
119
-
120
- it('keeps the string reader decoding the same bytes', async () => {
121
- const bytes = new TextEncoder().encode('zażółć')
122
- const { request } = makeStreamingRequest([bytes])
123
-
124
- await expect(readBoundedRequestBody(request)).resolves.toBe('zażółć')
125
- })
126
- })
127
-
128
80
  describe('resolveWebhookBodyLimitBytes', () => {
129
81
  it('uses the documented default for missing or invalid configuration', () => {
130
82
  expect(resolveWebhookBodyLimitBytes(undefined)).toBe(DEFAULT_WEBHOOK_BODY_LIMIT_BYTES)
@@ -23,10 +23,10 @@ export function resolveWebhookBodyLimitBytes(
23
23
  return Number.isSafeInteger(parsed) ? parsed : fallback
24
24
  }
25
25
 
26
- export async function readBoundedRequestBytes(
26
+ export async function readBoundedRequestBody(
27
27
  request: Request,
28
28
  options?: { maxBytes?: number },
29
- ): Promise<Uint8Array> {
29
+ ): Promise<string> {
30
30
  const configuredLimit = options?.maxBytes ?? resolveWebhookBodyLimitBytes()
31
31
  const maxBytes = Number.isSafeInteger(configuredLimit) && configuredLimit > 0
32
32
  ? configuredLimit
@@ -39,7 +39,7 @@ export async function readBoundedRequestBytes(
39
39
  }
40
40
  }
41
41
 
42
- if (!request.body) return new Uint8Array(0)
42
+ if (!request.body) return ''
43
43
 
44
44
  const reader = request.body.getReader()
45
45
  const chunks: Uint8Array[] = []
@@ -66,12 +66,5 @@ export async function readBoundedRequestBytes(
66
66
  body.set(chunk, offset)
67
67
  offset += chunk.byteLength
68
68
  }
69
- return body
70
- }
71
-
72
- export async function readBoundedRequestBody(
73
- request: Request,
74
- options?: { maxBytes?: number },
75
- ): Promise<string> {
76
- return new TextDecoder().decode(await readBoundedRequestBytes(request, options))
69
+ return new TextDecoder().decode(body)
77
70
  }
@@ -4,7 +4,6 @@ export { generateWebhookSecret, parseWebhookSecret, isValidWebhookSecret } from
4
4
  export {
5
5
  DEFAULT_WEBHOOK_BODY_LIMIT_BYTES,
6
6
  readBoundedRequestBody,
7
- readBoundedRequestBytes,
8
7
  resolveWebhookBodyLimitBytes,
9
8
  WebhookBodyTooLargeError,
10
9
  } from './body'
@@ -157,12 +157,6 @@ export interface IntegrationDetailPageConfig {
157
157
  * Provider modules can replace these with injected tabs using the same spot.
158
158
  */
159
159
  hiddenTabs?: IntegrationDetailBuiltInTab[]
160
- /**
161
- * Detail-page tab that holds the provider's Connect / Reconnect controls (a built-in tab id or
162
- * an injected tab id). The OAuth reauth banner links to `?tab=<connectTabId>` while that tab is
163
- * visible and the viewer holds `integrations.credentials.manage`.
164
- */
165
- connectTabId?: string
166
160
  }
167
161
 
168
162
  export type IntegrationDetailBuiltInTab =
@@ -3,13 +3,6 @@ import type { ComponentType } from 'react'
3
3
  export type NotificationStatus = 'unread' | 'read' | 'actioned' | 'dismissed'
4
4
  export type NotificationSeverity = 'info' | 'warning' | 'success' | 'error'
5
5
 
6
- /**
7
- * `code` of the 409 body `POST /api/notifications/:id/action` answers with when the notification's
8
- * action has already been executed. Lets a client tell that refusal apart from a 409 raised by
9
- * the action's own command.
10
- */
11
- export const NOTIFICATION_ACTION_ALREADY_EXECUTED_ERROR_CODE = 'notification_action_already_executed'
12
-
13
6
  export type NotificationAction = {
14
7
  id: string
15
8
  label: string
@@ -3,9 +3,6 @@ import {
3
3
  getWebhookHandler,
4
4
  registerWebhookHandler,
5
5
  type VerifyWebhookInput,
6
- type WebhookLocatorContext,
7
- type WebhookResponseOutcome,
8
- WebhookVerificationUnavailableError,
9
6
  type WebhookEvent,
10
7
  } from '../types'
11
8
 
@@ -37,45 +34,4 @@ describe('payment gateway webhook registration', () => {
37
34
  expect(getWebhookHandler('invalid')).toBeUndefined()
38
35
  },
39
36
  )
40
-
41
- it('stays compatible with one-argument locator hints', () => {
42
- const readSessionIdHint = (payload: Record<string, unknown> | null) => (payload ? 'sess' : null)
43
- registerWebhookHandler('legacy', handler, { readSessionIdHint })
44
-
45
- expect(getWebhookHandler('legacy')?.readSessionIdHint?.({})).toBe('sess')
46
- expect(getWebhookHandler('legacy')?.rawBody).toBeUndefined()
47
- expect(getWebhookHandler('legacy')?.formatResponse).toBeUndefined()
48
- })
49
-
50
- it('passes through the optional extension hooks', () => {
51
- const readSessionIdHint = (_payload: Record<string, unknown> | null, context?: WebhookLocatorContext) =>
52
- context ? String(context.headers['x-id']) : null
53
- const readPaymentIdHint = () => 'pay'
54
- const formatResponse = (outcome: WebhookResponseOutcome) => ({ status: 200, body: outcome })
55
- registerWebhookHandler('extended', handler, {
56
- readSessionIdHint,
57
- readPaymentIdHint,
58
- rawBody: 'bytes',
59
- formatResponse,
60
- })
61
-
62
- const registration = getWebhookHandler('extended')
63
- expect(registration?.rawBody).toBe('bytes')
64
- expect(registration?.readPaymentIdHint).toBe(readPaymentIdHint)
65
- expect(registration?.formatResponse).toBe(formatResponse)
66
- expect(registration?.readSessionIdHint?.(null, { rawBody: '', headers: { 'x-id': 'abc' } })).toBe('abc')
67
- })
68
-
69
- it('rejects an unknown rawBody mode', () => {
70
- expect(() =>
71
- registerWebhookHandler('bad', handler, { rawBody: 'stream' as unknown as 'text' }),
72
- ).toThrow('[internal] Payment gateway webhook rawBody must be')
73
- expect(getWebhookHandler('bad')).toBeUndefined()
74
- })
75
-
76
- it('exposes a typed verification-unavailable error', () => {
77
- const error = new WebhookVerificationUnavailableError()
78
- expect(error).toBeInstanceOf(Error)
79
- expect(error.name).toBe('WebhookVerificationUnavailableError')
80
- })
81
37
  })
@@ -237,45 +237,6 @@ export interface VerifyWebhookInput {
237
237
  rawBody: string | Buffer
238
238
  headers: Record<string, string | string[] | undefined>
239
239
  credentials: Record<string, unknown>
240
- /** Platform-resolved payment the webhook is expected to concern, when a locator found one. */
241
- candidate?: WebhookCandidateSnapshot
242
- }
243
-
244
- /** Raw request material handed to webhook locator hints. */
245
- export interface WebhookLocatorContext {
246
- rawBody: string | Buffer
247
- headers: Record<string, string | string[] | undefined>
248
- }
249
-
250
- export interface WebhookCandidateSnapshot {
251
- transactionId: string
252
- paymentId: string
253
- providerSessionId: string | null
254
- amount: string
255
- currencyCode: string
256
- }
257
-
258
- export type WebhookResponseOutcome =
259
- | 'accepted'
260
- | 'no_candidate'
261
- | 'verification_failed'
262
- | 'verification_unavailable'
263
- | 'processing_failed'
264
- | 'payload_too_large'
265
- | 'rate_limited'
266
-
267
- export interface WebhookHttpResponse {
268
- status: number
269
- body: string | Record<string, unknown>
270
- contentType?: string
271
- }
272
-
273
- /** Thrown by a webhook handler when verification cannot be performed (for example a provider outage). */
274
- export class WebhookVerificationUnavailableError extends Error {
275
- constructor(message = 'Webhook verification is unavailable') {
276
- super(message)
277
- this.name = 'WebhookVerificationUnavailableError'
278
- }
279
240
  }
280
241
 
281
242
  export interface WebhookEvent {
@@ -291,15 +252,10 @@ export interface WebhookEvent {
291
252
  export interface WebhookHandlerRegistration {
292
253
  handler: (input: VerifyWebhookInput) => Promise<WebhookEvent>
293
254
  queue?: string
294
- readSessionIdHint?: (payload: Record<string, unknown> | null, context?: WebhookLocatorContext) => string | null
295
- readPaymentIdHint?: (payload: Record<string, unknown> | null, context?: WebhookLocatorContext) => string | null
255
+ readSessionIdHint?: (payload: Record<string, unknown> | null) => string | null
296
256
  maxBodyBytes?: number
297
- rawBody?: WebhookRawBodyMode
298
- formatResponse?: (outcome: WebhookResponseOutcome) => WebhookHttpResponse
299
257
  }
300
258
 
301
- export type WebhookRawBodyMode = 'text' | 'bytes'
302
-
303
259
  // ── Adapter Registry Options ────────────────────────────────────────────────
304
260
 
305
261
  export interface RegisterAdapterOptions {
@@ -389,11 +345,12 @@ export function clearGatewayAdapters(): void {
389
345
  export function registerWebhookHandler(
390
346
  providerKey: string,
391
347
  handler: (input: VerifyWebhookInput) => Promise<WebhookEvent>,
392
- options?: Omit<WebhookHandlerRegistration, 'handler'>,
348
+ options?: {
349
+ queue?: string
350
+ readSessionIdHint?: (payload: Record<string, unknown> | null) => string | null
351
+ maxBodyBytes?: number
352
+ },
393
353
  ): () => void {
394
- if (options?.rawBody !== undefined && options.rawBody !== 'text' && options.rawBody !== 'bytes') {
395
- throw new Error("[internal] Payment gateway webhook rawBody must be 'text' or 'bytes'")
396
- }
397
354
  if (
398
355
  options?.maxBodyBytes !== undefined
399
356
  && (!Number.isSafeInteger(options.maxBodyBytes)
@@ -409,10 +366,7 @@ export function registerWebhookHandler(
409
366
  handler,
410
367
  queue: options?.queue,
411
368
  readSessionIdHint: options?.readSessionIdHint,
412
- readPaymentIdHint: options?.readPaymentIdHint,
413
369
  maxBodyBytes: options?.maxBodyBytes,
414
- rawBody: options?.rawBody,
415
- formatResponse: options?.formatResponse,
416
370
  })
417
371
  return () => {
418
372
  webhookHandlerRegistry.delete(providerKey)
@@ -178,11 +178,6 @@ export type ApiRouteManifestEntry = {
178
178
  export type ModuleCli = {
179
179
  command: string
180
180
  run: (argv: string[]) => Promise<void> | void
181
- // Usage text printed by the `mercato` dispatcher when the command is invoked with --help/-h.
182
- help?: string
183
- // Set when the command parses --help/-h itself; the dispatcher then forwards the flag
184
- // instead of intercepting it, so the command can print its own richer usage text.
185
- handlesHelp?: boolean
186
181
  }
187
182
 
188
183
  export type ModuleSubscriber = {
@@ -94,39 +94,6 @@ export type SearchOptions = {
94
94
  offset?: number
95
95
  /** How to combine results: 'or' merges all, 'and' requires match in all strategies */
96
96
  combineMode?: 'or' | 'and'
97
- /**
98
- * Restricts results to records whose query-index row (`entity_indexes`) matches this predicate,
99
- * evaluated inside the strategy's ranking query so a restrictive filter never starves the result
100
- * set (additive, 2026-10-06). Only strategies with `supportsIndexDocFilter: true` receive a
101
- * filtered search; any other strategy returns no results for it rather than unfiltered ones.
102
- */
103
- indexDocFilter?: SearchIndexDocFilter
104
- }
105
-
106
- /**
107
- * One condition over a record's query-index document (`entity_indexes.doc`), with the query
108
- * engine's index-document semantics:
109
- * - `exists`: the key is present and not JSON `null`;
110
- * - `eq`: the key's text value equals `value` (booleans and numbers compare by their JSON text);
111
- * - `overlap`: the key holds an array sharing at least one entry with `values`; empty `values`
112
- * matches nothing;
113
- * - `noverlap`: the key holds an array sharing no entry with `values`; a missing key never matches;
114
- * - `recordIdNotIn`: the record id is none of `values`.
115
- */
116
- export type SearchIndexDocCondition =
117
- | { op: 'exists'; key: string }
118
- | { op: 'eq'; key: string; value: string | number | boolean }
119
- | { op: 'overlap'; key: string; values: string[] }
120
- | { op: 'noverlap'; key: string; values: string[] }
121
- | { op: 'recordIdNotIn'; values: string[] }
122
-
123
- /**
124
- * A predicate over a record's `entity_indexes` row in disjunctive normal form: the record matches
125
- * when its index row is not deleted and every condition of at least one `anyOf` branch holds.
126
- * An empty `anyOf` matches nothing; an empty branch matches every indexed record.
127
- */
128
- export type SearchIndexDocFilter = {
129
- anyOf: SearchIndexDocCondition[][]
130
97
  }
131
98
 
132
99
  // =============================================================================
@@ -177,12 +144,6 @@ export interface SearchStrategy {
177
144
  /** Priority for result merging (higher = more prominent in results) */
178
145
  readonly priority: number
179
146
 
180
- /**
181
- * Whether `search()` applies `SearchOptions.indexDocFilter` inside its ranking query. Absent or
182
- * `false` means the strategy is skipped for filtered searches (fail closed).
183
- */
184
- readonly supportsIndexDocFilter?: boolean
185
-
186
147
  /** Check if strategy is available and configured */
187
148
  isAvailable(): Promise<boolean>
188
149
 
@@ -141,41 +141,11 @@ function notifyInjectionRegistryChanged() {
141
141
  }))
142
142
  }
143
143
 
144
- export type RegisterCoreInjectionWidgetsOptions = {
145
- /**
146
- * `replace` (default) publishes `entries` as the whole registry.
147
- *
148
- * `merge` adds only the entries the registry does not already carry, keeping whatever a
149
- * previous bootstrap published. A process may run more than one bootstrap partition (the
150
- * API-only one and the full page one share a Node process in a mixed web host), and the
151
- * partition that registers second must not shrink the registry the first one populated.
152
- */
153
- mode?: 'replace' | 'merge'
154
- }
155
-
156
- export function registerCoreInjectionWidgets(
157
- entries: ModuleInjectionWidgetEntry[],
158
- options: RegisterCoreInjectionWidgetsOptions = {},
159
- ) {
160
- if (_coreInjectionWidgetEntries !== null && process.env.NODE_ENV === 'development' && options.mode !== 'merge') {
144
+ export function registerCoreInjectionWidgets(entries: ModuleInjectionWidgetEntry[]) {
145
+ if (_coreInjectionWidgetEntries !== null && process.env.NODE_ENV === 'development') {
161
146
  logger.debug('Core injection widgets re-registered (this may occur during HMR)')
162
147
  }
163
- const incoming = applyInjectionWidgetOverridesToEntries(entries)
164
- if (options.mode !== 'merge') {
165
- _coreInjectionWidgetEntries = incoming
166
- writeGlobalInjectionWidgets(incoming)
167
- notifyInjectionRegistryChanged()
168
- return
169
- }
170
-
171
- const existing = readGlobalInjectionWidgets() ?? _coreInjectionWidgetEntries ?? []
172
- const known = new Set(existing.map((entry) => entry.key))
173
- const added = incoming.filter((entry) => !known.has(entry.key))
174
- // Nothing to contribute: leave the registry exactly as it was. Publishing an empty array here
175
- // would turn "never registered" into "registered with no widgets", which downgrades the loud
176
- // bootstrap error into silently missing contributions.
177
- if (!added.length) return
178
- const finalEntries = [...existing, ...added]
148
+ const finalEntries = applyInjectionWidgetOverridesToEntries(entries)
179
149
  _coreInjectionWidgetEntries = finalEntries
180
150
  writeGlobalInjectionWidgets(finalEntries)
181
151
  notifyInjectionRegistryChanged()
@@ -1,6 +0,0 @@
1
- import { resolveNextSubpathAsync } from "./next-subpath-resolve-hook.js";
2
- const resolve = resolveNextSubpathAsync;
3
- export {
4
- resolve
5
- };
6
- //# sourceMappingURL=next-subpath-resolve-hook-worker.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../src/lib/bootstrap/next-subpath-resolve-hook-worker.ts"],
4
- "sourcesContent": ["import { resolveNextSubpathAsync } from './next-subpath-resolve-hook'\n\nexport const resolve = resolveNextSubpathAsync\n"],
5
- "mappings": "AAAA,SAAS,+BAA+B;AAEjC,MAAM,UAAU;",
6
- "names": []
7
- }