@open-mercato/shared 0.6.8-develop.7099.1.7ba2771d06 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +1 -4
  3. package/dist/lib/auth/jwt.js +0 -6
  4. package/dist/lib/auth/jwt.js.map +2 -2
  5. package/dist/lib/auth/organizationAccess.js +4 -7
  6. package/dist/lib/auth/organizationAccess.js.map +2 -2
  7. package/dist/lib/auth/server.js +7 -38
  8. package/dist/lib/auth/server.js.map +2 -2
  9. package/dist/lib/commands/command-bus.js +1 -6
  10. package/dist/lib/commands/command-bus.js.map +2 -2
  11. package/dist/lib/crud/factory.js +8 -24
  12. package/dist/lib/crud/factory.js.map +2 -2
  13. package/dist/lib/data/engine.js +2 -8
  14. package/dist/lib/data/engine.js.map +2 -2
  15. package/dist/lib/location/countries.js +0 -12
  16. package/dist/lib/location/countries.js.map +2 -2
  17. package/dist/lib/openapi/crud.js +1 -4
  18. package/dist/lib/openapi/crud.js.map +2 -2
  19. package/dist/lib/query/engine.js +34 -270
  20. package/dist/lib/query/engine.js.map +3 -3
  21. package/dist/lib/query/types.js.map +1 -1
  22. package/dist/lib/search/config.js +0 -1
  23. package/dist/lib/search/config.js.map +2 -2
  24. package/dist/lib/version.js +1 -1
  25. package/dist/lib/version.js.map +1 -1
  26. package/dist/modules/events/factory.js +15 -69
  27. package/dist/modules/events/factory.js.map +2 -2
  28. package/dist/modules/registry.js +0 -15
  29. package/dist/modules/registry.js.map +2 -2
  30. package/dist/modules/widgets/component-registry.js.map +2 -2
  31. package/package.json +3 -10
  32. package/src/lib/auth/__tests__/jwt.test.ts +0 -13
  33. package/src/lib/auth/__tests__/organizationAccess.test.ts +1 -36
  34. package/src/lib/auth/__tests__/server.apiKeyCache.test.ts +0 -324
  35. package/src/lib/auth/__tests__/server.test.ts +0 -104
  36. package/src/lib/auth/jwt.ts +0 -17
  37. package/src/lib/auth/organizationAccess.ts +3 -11
  38. package/src/lib/auth/server.ts +8 -78
  39. package/src/lib/commands/__tests__/command-bus.test.ts +0 -31
  40. package/src/lib/commands/command-bus.ts +1 -8
  41. package/src/lib/crud/__tests__/crud-factory.test.ts +0 -165
  42. package/src/lib/crud/factory.ts +7 -33
  43. package/src/lib/data/__tests__/engine.event-validation.test.ts +1 -9
  44. package/src/lib/data/engine.ts +1 -7
  45. package/src/lib/location/__tests__/countries.test.ts +0 -15
  46. package/src/lib/location/countries.ts +0 -17
  47. package/src/lib/openapi/crud.ts +0 -3
  48. package/src/lib/query/__tests__/engine.count-distinct.test.ts +15 -162
  49. package/src/lib/query/__tests__/engine.scope-and-or.test.ts +1 -11
  50. package/src/lib/query/__tests__/engine.test.ts +7 -445
  51. package/src/lib/query/engine.ts +54 -434
  52. package/src/lib/query/types.ts +0 -15
  53. package/src/lib/search/config.ts +0 -10
  54. package/src/modules/events/factory.ts +19 -111
  55. package/src/modules/events/types.ts +0 -17
  56. package/src/modules/registry.ts +0 -40
  57. package/src/modules/widgets/component-registry.ts +0 -14
  58. package/dist/lib/auth/mfaPendingAccess.js +0 -42
  59. package/dist/lib/auth/mfaPendingAccess.js.map +0 -7
  60. package/dist/lib/auth/principal-service.js +0 -1
  61. package/dist/lib/auth/principal-service.js.map +0 -7
  62. package/dist/lib/html/htmlToPlainText.js +0 -16
  63. package/dist/lib/html/htmlToPlainText.js.map +0 -7
  64. package/dist/lib/query/count-cap.js +0 -11
  65. package/dist/lib/query/count-cap.js.map +0 -7
  66. package/dist/lib/queue/dispatchOrigin.js +0 -20
  67. package/dist/lib/queue/dispatchOrigin.js.map +0 -7
  68. package/dist/lib/search/entityAccess.js +0 -44
  69. package/dist/lib/search/entityAccess.js.map +0 -7
  70. package/src/lib/auth/__tests__/mfaPendingAccess.test.ts +0 -69
  71. package/src/lib/auth/__tests__/principalServiceExport.test.ts +0 -67
  72. package/src/lib/auth/mfaPendingAccess.ts +0 -70
  73. package/src/lib/auth/principal-service.ts +0 -110
  74. package/src/lib/html/__tests__/htmlToPlainText.test.ts +0 -59
  75. package/src/lib/html/htmlToPlainText.ts +0 -17
  76. package/src/lib/query/__tests__/count-cap-plan.test.ts +0 -240
  77. package/src/lib/query/__tests__/count-cap.test.ts +0 -41
  78. package/src/lib/query/count-cap.ts +0 -19
  79. package/src/lib/queue/dispatchOrigin.ts +0 -35
  80. package/src/lib/search/entityAccess.ts +0 -132
  81. package/src/modules/events/__tests__/factory.test.ts +0 -88
@@ -1,13 +1,6 @@
1
- import { BasicQueryEngine, clearColumnExistsCache } from '../engine'
1
+ import { BasicQueryEngine } from '../engine'
2
2
  import { registerModules } from '../../i18n/server'
3
3
 
4
- // The column-existence answer is memoized on the module, not the instance (#5605), so
5
- // the per-test fake schemas below would otherwise inherit whatever the first test
6
- // probed for the same table names.
7
- beforeEach(() => {
8
- clearColumnExistsCache()
9
- })
10
-
11
4
  // One entity extension on auth:user so includeExtensions exercises the joined-aggregate path.
12
5
  registerModules([
13
6
  { id: 'auth', entityExtensions: [{ base: 'auth:user', extension: 'my_module:user_profile', join: { baseKey: 'id', extensionKey: 'user_id' } }] },
@@ -146,22 +139,6 @@ function createFakeKysely(selectsSink: any[], overrides?: FakeData) {
146
139
  }
147
140
 
148
141
  function builderFor(tableArg: any): any {
149
- if (tableArg && typeof tableArg === 'object' && tableArg._ops) {
150
- // selectFrom(subquery.as(alias)) — the capped-count probe shape.
151
- const ops = {
152
- table: '__subquery__',
153
- alias: tableArg._ops.alias ?? null,
154
- subquery: tableArg._ops,
155
- wheres: [] as any[],
156
- joins: [] as any[],
157
- selects: [] as any[],
158
- orderBys: [] as any[],
159
- groups: [] as any[],
160
- limits: 0,
161
- offsets: 0,
162
- }
163
- return makeBuilder(ops, true)
164
- }
165
142
  const parsed = parseTableSpec(tableArg)
166
143
  const ops = {
167
144
  table: parsed.table,
@@ -196,28 +173,8 @@ function findCountSql(selectsSink: any[]): string {
196
173
  return rawSqlText(countExprs[countExprs.length - 1]).toLowerCase()
197
174
  }
198
175
 
199
- // The count query is rebuilt from scope + filters only (#4552 Phase 2): cf
200
- // filters compile to correlated EXISTS semi-joins, projection joins are
201
- // dropped, so the count never needs DISTINCT or GROUP BY — completing the
202
- // direction #2227 started — and the LIMIT cap+1 probe actually bounds the scan.
203
- describe('BasicQueryEngine — rebuilt list COUNT query (#4552 Phase 2)', () => {
204
- const originalCap = process.env.OM_LIST_COUNT_CAP
205
- afterEach(() => {
206
- if (originalCap === undefined) delete process.env.OM_LIST_COUNT_CAP
207
- else process.env.OM_LIST_COUNT_CAP = originalCap
208
- })
209
-
210
- // The count-shape builder is the base-table call carrying the probe LIMIT
211
- // (cap + 1); with the cap disabled it is the base call whose recorded select
212
- // is the bare count aggregate.
213
- function findCountShapeCall(fakeDb: any, table: string) {
214
- const baseCalls = fakeDb._calls.filter((b: any) => b._ops.table === table)
215
- const probed = baseCalls.find((b: any) => b._ops.limits === 10_001)
216
- if (probed) return probed
217
- return baseCalls.find((b: any) => b._ops.selects.some((s: any) => aliasName(s) === 'count'))
218
- }
219
-
220
- test('uses count(*) with no DISTINCT and no GROUP BY when nothing joins', async () => {
176
+ describe('BasicQueryEngine list COUNT query (issue #2227)', () => {
177
+ test('uses count(*) and no group-by when no joins can multiply base rows', async () => {
221
178
  const selects: any[] = []
222
179
  const fakeDb = createFakeKysely(selects)
223
180
  const engine = new BasicQueryEngine({} as any, () => fakeDb as any)
@@ -227,11 +184,11 @@ describe('BasicQueryEngine — rebuilt list COUNT query (#4552 Phase 2)', () =>
227
184
  expect(countSql).toContain('count(*)')
228
185
  expect(countSql).not.toContain('distinct')
229
186
 
230
- const countCall = findCountShapeCall(fakeDb, 'scheduled_jobs')
231
- expect(countCall._ops.groups.length).toBe(0)
187
+ const baseCall = fakeDb._calls.find((b: any) => b._ops.table === 'scheduled_jobs')
188
+ expect(baseCall._ops.groups.length).toBe(0)
232
189
  })
233
190
 
234
- test('drops extension projection joins from the count: count(*) over the bare base table', async () => {
191
+ test('keeps count(distinct base.id) with group-by when extensions are joined', async () => {
235
192
  const selects: any[] = []
236
193
  const fakeDb = createFakeKysely(selects)
237
194
  const engine = new BasicQueryEngine({} as any, () => fakeDb as any)
@@ -244,20 +201,14 @@ describe('BasicQueryEngine — rebuilt list COUNT query (#4552 Phase 2)', () =>
244
201
  })
245
202
 
246
203
  const countSql = findCountSql(selects)
247
- expect(countSql).toContain('count(*)')
248
- expect(countSql).not.toContain('distinct')
204
+ expect(countSql).toContain('count(distinct')
205
+ expect(countSql).not.toContain('count(*)')
249
206
 
250
- // The display query keeps the extension join + GROUP BY …
251
- const dataCall = fakeDb._calls.find((b: any) => b._ops.table === 'users' && b._ops.joins.length > 0)
252
- expect(dataCall._ops.groups.length).toBeGreaterThan(0)
253
- // … while the count shape carries neither: no join can multiply base rows,
254
- // so no barrier sits between the probe LIMIT and the scan.
255
- const countCall = findCountShapeCall(fakeDb, 'users')
256
- expect(countCall._ops.joins.length).toBe(0)
257
- expect(countCall._ops.groups.length).toBe(0)
207
+ const baseCall = fakeDb._calls.find((b: any) => b._ops.table === 'users')
208
+ expect(baseCall._ops.groups.length).toBeGreaterThan(0)
258
209
  })
259
210
 
260
- test('explicit relation joins never reach the count: count(*), join filters stay EXISTS', async () => {
211
+ test('keeps count(distinct base.id) without group-by when an explicit relation join is configured', async () => {
261
212
  const selects: any[] = []
262
213
  const fakeDb = createFakeKysely(selects)
263
214
  const engine = new BasicQueryEngine({} as any, () => fakeDb as any)
@@ -265,112 +216,14 @@ describe('BasicQueryEngine — rebuilt list COUNT query (#4552 Phase 2)', () =>
265
216
  tenantId: 't1',
266
217
  fields: ['id'],
267
218
  joins: [{ alias: 'owner', table: 'users', from: { field: 'owner_id' }, to: { field: 'id' } }],
268
- filters: { 'owner.email': { $eq: 'a@b.c' } },
269
- page: { page: 1, pageSize: 20 },
270
- })
271
-
272
- const countSql = findCountSql(selects)
273
- expect(countSql).toContain('count(*)')
274
- expect(countSql).not.toContain('distinct')
275
-
276
- const countCall = findCountShapeCall(fakeDb, 'scheduled_jobs')
277
- expect(countCall._ops.groups.length).toBe(0)
278
- expect(countCall._ops.joins.length).toBe(0)
279
- // The join filter is a semi-join on the count shape, not a join.
280
- expect(countCall._ops.wheres.some((w: any) => Array.isArray(w) && w[0] === 'exists')).toBe(true)
281
- })
282
-
283
- test('cf filters compile to EXISTS over custom_field_values on the count shape', async () => {
284
- const selects: any[] = []
285
- const fakeDb = createFakeKysely(selects, {
286
- custom_field_defs: [
287
- { key: 'color', entity_id: 'scheduler:scheduled_job', is_active: true, tenant_id: null, kind: 'text', config_json: '{}' },
288
- ],
289
- })
290
- const engine = new BasicQueryEngine({} as any, () => fakeDb as any)
291
- await engine.query('scheduler:scheduled_job', {
292
- tenantId: 't1',
293
- fields: ['id'],
294
- filters: { cf_color: { $eq: 'red' } },
295
- page: { page: 1, pageSize: 20 },
296
- })
297
-
298
- const countSql = findCountSql(selects)
299
- expect(countSql).toContain('count(*)')
300
- expect(countSql).not.toContain('distinct')
301
-
302
- const countCall = findCountShapeCall(fakeDb, 'scheduled_jobs')
303
- expect(countCall._ops.groups.length).toBe(0)
304
- // No custom_field_values join on the count shape — the filter is an EXISTS
305
- // whose subquery selects from custom_field_values.
306
- expect(countCall._ops.joins.length).toBe(0)
307
- const existsEntries = countCall._ops.wheres.filter((w: any) => Array.isArray(w) && w[0] === 'exists')
308
- expect(existsEntries.length).toBeGreaterThan(0)
309
- const existsSub = existsEntries[0][1]
310
- expect(existsSub._ops.table).toBe('custom_field_values')
311
- // The display query still resolves the same filter through its value join.
312
- const dataCall = fakeDb._calls.find((b: any) =>
313
- b._ops.table === 'scheduled_jobs' &&
314
- b._ops.joins.some((j: any) => Object.values(j.aliasObj).includes('custom_field_values')))
315
- expect(dataCall).toBeTruthy()
316
- })
317
-
318
- test('a cf leaf inside $or compiles to EXISTS on the count shape instead of being dropped (#5039)', async () => {
319
- const selects: any[] = []
320
- const fakeDb = createFakeKysely(selects, {
321
- custom_field_defs: [
322
- { key: 'color', entity_id: 'scheduler:scheduled_job', is_active: true, tenant_id: null, kind: 'text', config_json: '{}' },
323
- ],
324
- 'information_schema.columns': [
325
- { table_name: 'scheduled_jobs', column_name: 'id' },
326
- { table_name: 'scheduled_jobs', column_name: 'tenant_id' },
327
- { table_name: 'scheduled_jobs', column_name: 'status' },
328
- ],
329
- })
330
- const engine = new BasicQueryEngine({} as any, () => fakeDb as any)
331
- await engine.query('scheduler:scheduled_job', {
332
- tenantId: 't1',
333
- fields: ['id'],
334
- filters: { $or: [{ status: { $eq: 'closed' } }, { cf_color: { $eq: 'red' } }] },
335
219
  page: { page: 1, pageSize: 20 },
336
220
  })
337
221
 
338
- const countCall = findCountShapeCall(fakeDb, 'scheduled_jobs')
339
- expect(countCall._ops.joins.length).toBe(0)
340
- // The whole disjunction lands in one OR where-entry whose parts include an
341
- // EXISTS (the cf leaf) — dropping it would narrow the OR and undercount.
342
- const orEntries = countCall._ops.wheres.filter((w: any) => Array.isArray(w) && w[0] === 'or')
343
- expect(orEntries.length).toBeGreaterThan(0)
344
- const hasExistsPart = orEntries.some((entry: any) =>
345
- (entry[1] ?? []).some((part: any) => part?.kind === 'exists' || (part?.kind === 'and' && part.parts?.some((p: any) => p?.kind === 'exists'))))
346
- expect(hasExistsPart).toBe(true)
347
- })
348
-
349
- test('the probe LIMIT is cap + 1 on the row-producing inner query, counted by an outer aggregate', async () => {
350
- process.env.OM_LIST_COUNT_CAP = '100'
351
- const selects: any[] = []
352
- const fakeDb = createFakeKysely(selects)
353
- const engine = new BasicQueryEngine({} as any, () => fakeDb as any)
354
- await engine.query('scheduler:scheduled_job', { tenantId: 't1', fields: ['id'], page: { page: 1, pageSize: 20 } })
355
-
356
- const outer = fakeDb._calls.find((b: any) => b._ops.table === '__subquery__')
357
- expect(outer).toBeTruthy()
358
- expect(outer._ops.subquery.limits).toBe(101)
359
- expect(outer._ops.subquery.groups.length).toBe(0)
360
222
  const countSql = findCountSql(selects)
361
- expect(countSql).toContain('count(*)')
362
- })
223
+ expect(countSql).toContain('count(distinct')
224
+ expect(countSql).not.toContain('count(*)')
363
225
 
364
- test('OM_LIST_COUNT_CAP=0 disables the probe: a direct unbounded count(*)', async () => {
365
- process.env.OM_LIST_COUNT_CAP = '0'
366
- const selects: any[] = []
367
- const fakeDb = createFakeKysely(selects)
368
- const engine = new BasicQueryEngine({} as any, () => fakeDb as any)
369
- const result = await engine.query('scheduler:scheduled_job', { tenantId: 't1', fields: ['id'], page: { page: 1, pageSize: 20 } })
370
-
371
- expect(fakeDb._calls.some((b: any) => b._ops.table === '__subquery__')).toBe(false)
372
- const countSql = findCountSql(selects)
373
- expect(countSql).toContain('count(*)')
374
- expect(result.meta?.listCountCapWarning).toBeUndefined()
226
+ const baseCall = fakeDb._calls.find((b: any) => b._ops.table === 'scheduled_jobs')
227
+ expect(baseCall._ops.groups.length).toBe(0)
375
228
  })
376
229
  })
@@ -1,16 +1,6 @@
1
- import { BasicQueryEngine, clearColumnExistsCache } from '../engine'
1
+ import { BasicQueryEngine } from '../engine'
2
2
  import { normalizeFilters } from '../join-utils'
3
3
 
4
- // The column-existence answer is memoized on the module, not the instance (#5605), and
5
- // the fixtures below declare mutually contradictory `information_schema.columns` shapes
6
- // for the same `scheduled_jobs` table. Without this clear, whichever test ran first
7
- // decides the scoping every later test sees — the automatic tenant/organization guard
8
- // would silently drop out of a query and the assertion for it would still be reading a
9
- // stale `false`.
10
- beforeEach(() => {
11
- clearColumnExistsCache()
12
- })
13
-
14
4
  type FakeData = Record<string, any[]>
15
5
 
16
6
  function cloneRows(rows: any[] | undefined): any[] {