@open-mercato/core 0.6.7-develop.6652.1.3ab996caba → 0.6.7-develop.6660.1.90e1e2eef6

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 (57) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/business_rules/lib/value-resolver.js +7 -0
  3. package/dist/modules/business_rules/lib/value-resolver.js.map +2 -2
  4. package/dist/modules/currencies/commands/currencies.js +22 -8
  5. package/dist/modules/currencies/commands/currencies.js.map +2 -2
  6. package/dist/modules/currencies/commands/exchange-rates.js +22 -8
  7. package/dist/modules/currencies/commands/exchange-rates.js.map +2 -2
  8. package/dist/modules/currencies/commands/scope.js +22 -0
  9. package/dist/modules/currencies/commands/scope.js.map +7 -0
  10. package/dist/modules/customers/components/detail/ActivitiesSection.js +5 -4
  11. package/dist/modules/customers/components/detail/ActivitiesSection.js.map +2 -2
  12. package/dist/modules/customers/components/detail/ActivityHistorySection.js +5 -4
  13. package/dist/modules/customers/components/detail/ActivityHistorySection.js.map +2 -2
  14. package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js +6 -0
  15. package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js.map +2 -2
  16. package/dist/modules/dictionaries/api/[dictionaryId]/entries/reorder/route.js +3 -0
  17. package/dist/modules/dictionaries/api/[dictionaryId]/entries/reorder/route.js.map +2 -2
  18. package/dist/modules/dictionaries/api/[dictionaryId]/entries/route.js +6 -0
  19. package/dist/modules/dictionaries/api/[dictionaryId]/entries/route.js.map +2 -2
  20. package/dist/modules/dictionaries/api/[dictionaryId]/entries/set-default/route.js +3 -0
  21. package/dist/modules/dictionaries/api/[dictionaryId]/entries/set-default/route.js.map +2 -2
  22. package/dist/modules/dictionaries/components/DictionaryEntriesEditor.js +107 -92
  23. package/dist/modules/dictionaries/components/DictionaryEntriesEditor.js.map +2 -2
  24. package/dist/modules/feature_toggles/api/global/route.js +6 -0
  25. package/dist/modules/feature_toggles/api/global/route.js.map +2 -2
  26. package/dist/modules/feature_toggles/commands/global.js +9 -9
  27. package/dist/modules/feature_toggles/commands/global.js.map +2 -2
  28. package/dist/modules/query_index/lib/subscriber-scope.js +63 -10
  29. package/dist/modules/query_index/lib/subscriber-scope.js.map +2 -2
  30. package/dist/modules/query_index/subscribers/delete_one.js +20 -6
  31. package/dist/modules/query_index/subscribers/delete_one.js.map +2 -2
  32. package/dist/modules/query_index/subscribers/upsert_one.js +8 -3
  33. package/dist/modules/query_index/subscribers/upsert_one.js.map +2 -2
  34. package/dist/modules/workflows/api/definitions/[id]/customize/route.js +2 -0
  35. package/dist/modules/workflows/api/definitions/[id]/customize/route.js.map +2 -2
  36. package/dist/modules/workflows/api/definitions/[id]/reset-to-code/route.js +4 -0
  37. package/dist/modules/workflows/api/definitions/[id]/reset-to-code/route.js.map +2 -2
  38. package/package.json +7 -7
  39. package/src/modules/business_rules/lib/value-resolver.ts +16 -0
  40. package/src/modules/currencies/commands/currencies.ts +26 -8
  41. package/src/modules/currencies/commands/exchange-rates.ts +26 -8
  42. package/src/modules/currencies/commands/scope.ts +32 -0
  43. package/src/modules/customers/components/detail/ActivitiesSection.tsx +10 -6
  44. package/src/modules/customers/components/detail/ActivityHistorySection.tsx +6 -3
  45. package/src/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.ts +6 -0
  46. package/src/modules/dictionaries/api/[dictionaryId]/entries/reorder/route.ts +3 -0
  47. package/src/modules/dictionaries/api/[dictionaryId]/entries/route.ts +6 -0
  48. package/src/modules/dictionaries/api/[dictionaryId]/entries/set-default/route.ts +3 -0
  49. package/src/modules/dictionaries/components/DictionaryEntriesEditor.tsx +15 -4
  50. package/src/modules/feature_toggles/api/global/route.ts +7 -1
  51. package/src/modules/feature_toggles/commands/global.ts +9 -12
  52. package/src/modules/query_index/lib/subscriber-scope.ts +98 -14
  53. package/src/modules/query_index/subscribers/delete_one.ts +21 -10
  54. package/src/modules/query_index/subscribers/upsert_one.ts +8 -3
  55. package/src/modules/workflows/AGENTS.md +22 -0
  56. package/src/modules/workflows/api/definitions/[id]/customize/route.ts +9 -0
  57. package/src/modules/workflows/api/definitions/[id]/reset-to-code/route.ts +7 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/feature_toggles/api/global/route.ts"],
4
- "sourcesContent": ["import { makeCrudRoute, type CrudCtx } from '@open-mercato/shared/lib/crud/factory'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { FeatureToggle } from '../../data/entities'\nimport { E } from '#generated/entities.ids.generated'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { toggleTypeSchema, toggleCreateSchema, toggleUpdateSchema } from '../../data/validators'\nimport {\n featureTogglesTag,\n featureToggleListResponseSchema,\n featureToggleErrorSchema\n} from '../openapi'\n\nconst rawBodySchema = z.object({}).passthrough()\nconst listQuerySchema = z\n .object({\n page: z.coerce.number().min(1).default(1).describe('Page number for pagination'),\n pageSize: z.coerce.number().min(1).max(200).default(50).describe('Number of items per page (max 200)'),\n search: z.string().optional().describe('Case-insensitive search across identifier, name, description, and category'),\n type: toggleTypeSchema.optional().describe('Filter by toggle type (boolean, string, number, json)'),\n category: z.string().optional().describe('Filter by category (case-insensitive partial match)'),\n name: z.string().optional().describe('Filter by name (case-insensitive partial match)'),\n identifier: z.string().optional().describe('Filter by identifier (case-insensitive partial match)'),\n sortField: z.enum(['id', 'category', 'identifier', 'name', 'createdAt', 'updatedAt', 'type']).optional().describe('Field to sort by'),\n sortDir: z.enum(['asc', 'desc']).optional().describe('Sort direction (ascending or descending)'),\n })\n .passthrough()\n\ntype FeatureToggleListQuery = z.infer<typeof listQuerySchema>\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['feature_toggles.view'] },\n // Global feature toggles are platform-wide (no tenant_id); writing them is\n // restricted to super administrators via the dedicated global feature.\n POST: { requireAuth: true, requireFeatures: ['feature_toggles.global.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['feature_toggles.global.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['feature_toggles.global.manage'] },\n}\n\nconst listFields = [\n 'id',\n 'identifier',\n 'name',\n 'description',\n 'category',\n 'type',\n 'default_value',\n 'created_at',\n 'updated_at',\n]\n\nexport const transformFeatureToggleListItem = (item: Record<string, unknown>) => {\n if (!item) return item\n return {\n id: item.id,\n identifier: item.identifier,\n name: item.name,\n description: item.description ?? null,\n category: item.category ?? null,\n type: item.type,\n defaultValue: item.default_value,\n createdAt: item.created_at,\n updatedAt: item.updated_at,\n }\n}\n\nconst buildFilters = (query: FeatureToggleListQuery): Record<string, unknown> => {\n const filters: Record<string, unknown> = {}\n const search = query.search?.trim()\n if (search && search.length > 0) {\n const escaped = escapeLikePattern(search)\n const pattern = `%${escaped}%`\n filters.$or = [\n { identifier: { $ilike: pattern } },\n { name: { $ilike: pattern } },\n { description: { $ilike: pattern } },\n { category: { $ilike: pattern } },\n ]\n }\n const category = query.category?.trim()\n if (category && category.length > 0) {\n filters.category = { $ilike: `%${escapeLikePattern(category)}%` }\n }\n const name = query.name?.trim()\n if (name && name.length > 0) {\n filters.name = { $ilike: `%${escapeLikePattern(name)}%` }\n }\n const identifier = query.identifier?.trim()\n if (identifier && identifier.length > 0) {\n filters.identifier = { $ilike: `%${escapeLikePattern(identifier)}%` }\n }\n const type = query.type?.trim()\n if (type && type.length > 0) {\n filters.type = { $eq: query.type }\n }\n return filters\n}\n\n\nconst crud = makeCrudRoute({\n metadata: routeMetadata,\n orm: {\n entity: FeatureToggle,\n idField: 'id',\n orgField: null,\n tenantField: \"tenantId\",\n softDeleteField: 'deletedAt'\n },\n indexer: { entityType: E.feature_toggles.feature_toggle },\n list: {\n schema: listQuerySchema,\n entityId: E.feature_toggles.feature_toggle,\n fields: listFields,\n sortFieldMap: {\n id: 'id',\n category: 'category',\n identifier: 'identifier',\n name: 'name',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n type: 'type',\n },\n transformItem: transformFeatureToggleListItem,\n buildFilters: async (query) => buildFilters(query),\n },\n actions: {\n create: {\n commandId: 'feature_toggles.global.create',\n schema: rawBodySchema,\n response: ({ result }) => ({ id: result?.toggleId ?? result?.id ?? null }),\n status: 201,\n },\n update: {\n commandId: 'feature_toggles.global.update',\n schema: rawBodySchema,\n response: ({ result }) => ({ id: result?.toggleId ?? result?.id ?? null }),\n status: 200,\n },\n delete: {\n commandId: 'feature_toggles.global.delete',\n schema: rawBodySchema,\n response: ({ result }) => ({ id: result?.toggleId ?? result?.id ?? null }),\n status: 200,\n },\n }\n})\n\nexport const metadata = crud.metadata\nexport const GET = crud.GET\nexport const POST = crud.POST\nexport const PUT = crud.PUT\nexport const DELETE = crud.DELETE\n\nconst createResponseSchema = z.object({\n id: z.string().uuid(),\n})\n\nconst updateResponseSchema = z.object({\n id: z.string().uuid(),\n})\n\nconst deleteResponseSchema = z.object({\n id: z.string().uuid(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: featureTogglesTag,\n summary: 'Global feature toggle management',\n methods: {\n GET: {\n summary: 'List global feature toggles',\n description: 'Returns all global feature toggles with filtering and pagination. Requires superadmin role.',\n query: listQuerySchema,\n responses: [\n { status: 200, description: 'Feature toggles collection', schema: featureToggleListResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid query parameters', schema: featureToggleErrorSchema },\n { status: 401, description: 'Unauthorized', schema: featureToggleErrorSchema },\n { status: 403, description: 'Forbidden - superadmin role required', schema: featureToggleErrorSchema },\n ],\n },\n POST: {\n summary: 'Create global feature toggle',\n description: 'Creates a new global feature toggle. Requires superadmin role.',\n requestBody: {\n contentType: 'application/json',\n schema: toggleCreateSchema,\n },\n responses: [\n {\n status: 201,\n description: 'Feature toggle created',\n schema: createResponseSchema,\n },\n ],\n errors: [\n { status: 400, description: 'Invalid payload', schema: featureToggleErrorSchema },\n { status: 401, description: 'Unauthorized', schema: featureToggleErrorSchema },\n { status: 403, description: 'Forbidden - superadmin role required', schema: featureToggleErrorSchema },\n ],\n },\n PUT: {\n summary: 'Update global feature toggle',\n description: 'Updates an existing global feature toggle. Requires superadmin role.',\n requestBody: {\n contentType: 'application/json',\n schema: toggleUpdateSchema,\n },\n responses: [\n {\n status: 200,\n description: 'Feature toggle updated',\n schema: updateResponseSchema,\n },\n ],\n errors: [\n { status: 400, description: 'Invalid payload', schema: featureToggleErrorSchema },\n { status: 401, description: 'Unauthorized', schema: featureToggleErrorSchema },\n { status: 403, description: 'Forbidden - superadmin role required', schema: featureToggleErrorSchema },\n { status: 404, description: 'Feature toggle not found', schema: featureToggleErrorSchema },\n ],\n },\n DELETE: {\n summary: 'Delete global feature toggle',\n description: 'Soft deletes a global feature toggle by ID. Requires superadmin role.',\n query: z.object({ id: z.string().uuid().describe('Feature toggle identifier') }),\n responses: [\n { status: 200, description: 'Feature toggle deleted', schema: deleteResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid identifier', schema: featureToggleErrorSchema },\n { status: 401, description: 'Unauthorized', schema: featureToggleErrorSchema },\n { status: 403, description: 'Forbidden - superadmin role required', schema: featureToggleErrorSchema },\n { status: 404, description: 'Feature toggle not found', schema: featureToggleErrorSchema },\n ],\n },\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { makeCrudRoute, type CrudCtx } from '@open-mercato/shared/lib/crud/factory'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { FeatureToggle } from '../../data/entities'\nimport { E } from '#generated/entities.ids.generated'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { toggleTypeSchema, toggleCreateSchema, toggleUpdateSchema } from '../../data/validators'\nimport {\n featureTogglesTag,\n featureToggleListResponseSchema,\n featureToggleErrorSchema\n} from '../openapi'\n\nconst rawBodySchema = z.object({}).passthrough()\nconst listQuerySchema = z\n .object({\n page: z.coerce.number().min(1).default(1).describe('Page number for pagination'),\n pageSize: z.coerce.number().min(1).max(200).default(50).describe('Number of items per page (max 200)'),\n search: z.string().optional().describe('Case-insensitive search across identifier, name, description, and category'),\n type: toggleTypeSchema.optional().describe('Filter by toggle type (boolean, string, number, json)'),\n category: z.string().optional().describe('Filter by category (case-insensitive partial match)'),\n name: z.string().optional().describe('Filter by name (case-insensitive partial match)'),\n identifier: z.string().optional().describe('Filter by identifier (case-insensitive partial match)'),\n sortField: z.enum(['id', 'category', 'identifier', 'name', 'createdAt', 'updatedAt', 'type']).optional().describe('Field to sort by'),\n sortDir: z.enum(['asc', 'desc']).optional().describe('Sort direction (ascending or descending)'),\n })\n .passthrough()\n\ntype FeatureToggleListQuery = z.infer<typeof listQuerySchema>\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['feature_toggles.view'] },\n // Global feature toggles are platform-wide (no tenant_id); writing them is\n // restricted to super administrators via the dedicated global feature.\n POST: { requireAuth: true, requireFeatures: ['feature_toggles.global.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['feature_toggles.global.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['feature_toggles.global.manage'] },\n}\n\nconst listFields = [\n 'id',\n 'identifier',\n 'name',\n 'description',\n 'category',\n 'type',\n 'default_value',\n 'created_at',\n 'updated_at',\n]\n\nexport const transformFeatureToggleListItem = (item: Record<string, unknown>) => {\n if (!item) return item\n return {\n id: item.id,\n identifier: item.identifier,\n name: item.name,\n description: item.description ?? null,\n category: item.category ?? null,\n type: item.type,\n defaultValue: item.default_value,\n createdAt: item.created_at,\n updatedAt: item.updated_at,\n }\n}\n\nconst buildFilters = (query: FeatureToggleListQuery): Record<string, unknown> => {\n const filters: Record<string, unknown> = {}\n const search = query.search?.trim()\n if (search && search.length > 0) {\n const escaped = escapeLikePattern(search)\n const pattern = `%${escaped}%`\n filters.$or = [\n { identifier: { $ilike: pattern } },\n { name: { $ilike: pattern } },\n { description: { $ilike: pattern } },\n { category: { $ilike: pattern } },\n ]\n }\n const category = query.category?.trim()\n if (category && category.length > 0) {\n filters.category = { $ilike: `%${escapeLikePattern(category)}%` }\n }\n const name = query.name?.trim()\n if (name && name.length > 0) {\n filters.name = { $ilike: `%${escapeLikePattern(name)}%` }\n }\n const identifier = query.identifier?.trim()\n if (identifier && identifier.length > 0) {\n filters.identifier = { $ilike: `%${escapeLikePattern(identifier)}%` }\n }\n const type = query.type?.trim()\n if (type && type.length > 0) {\n filters.type = { $eq: query.type }\n }\n return filters\n}\n\n\nconst crud = makeCrudRoute({\n metadata: routeMetadata,\n orm: {\n entity: FeatureToggle,\n idField: 'id',\n orgField: null,\n // The query engine requires the caller's tenant context even when the\n // underlying entity is global; the list below explicitly disables its\n // automatic tenant predicate.\n tenantField: 'tenantId',\n softDeleteField: 'deletedAt'\n },\n indexer: { entityType: E.feature_toggles.feature_toggle },\n list: {\n schema: listQuerySchema,\n entityId: E.feature_toggles.feature_toggle,\n // FeatureToggle rows and their query-index projections are global\n // (null/null scope), so filtering them by the actor's tenant hides them.\n omitAutomaticTenantOrgScope: true,\n fields: listFields,\n sortFieldMap: {\n id: 'id',\n category: 'category',\n identifier: 'identifier',\n name: 'name',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n type: 'type',\n },\n transformItem: transformFeatureToggleListItem,\n buildFilters: async (query) => buildFilters(query),\n },\n actions: {\n create: {\n commandId: 'feature_toggles.global.create',\n schema: rawBodySchema,\n response: ({ result }) => ({ id: result?.toggleId ?? result?.id ?? null }),\n status: 201,\n },\n update: {\n commandId: 'feature_toggles.global.update',\n schema: rawBodySchema,\n response: ({ result }) => ({ id: result?.toggleId ?? result?.id ?? null }),\n status: 200,\n },\n delete: {\n commandId: 'feature_toggles.global.delete',\n schema: rawBodySchema,\n response: ({ result }) => ({ id: result?.toggleId ?? result?.id ?? null }),\n status: 200,\n },\n }\n})\n\nexport const metadata = crud.metadata\nexport const GET = crud.GET\nexport const POST = crud.POST\nexport const PUT = crud.PUT\nexport const DELETE = crud.DELETE\n\nconst createResponseSchema = z.object({\n id: z.string().uuid(),\n})\n\nconst updateResponseSchema = z.object({\n id: z.string().uuid(),\n})\n\nconst deleteResponseSchema = z.object({\n id: z.string().uuid(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: featureTogglesTag,\n summary: 'Global feature toggle management',\n methods: {\n GET: {\n summary: 'List global feature toggles',\n description: 'Returns all global feature toggles with filtering and pagination. Requires superadmin role.',\n query: listQuerySchema,\n responses: [\n { status: 200, description: 'Feature toggles collection', schema: featureToggleListResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid query parameters', schema: featureToggleErrorSchema },\n { status: 401, description: 'Unauthorized', schema: featureToggleErrorSchema },\n { status: 403, description: 'Forbidden - superadmin role required', schema: featureToggleErrorSchema },\n ],\n },\n POST: {\n summary: 'Create global feature toggle',\n description: 'Creates a new global feature toggle. Requires superadmin role.',\n requestBody: {\n contentType: 'application/json',\n schema: toggleCreateSchema,\n },\n responses: [\n {\n status: 201,\n description: 'Feature toggle created',\n schema: createResponseSchema,\n },\n ],\n errors: [\n { status: 400, description: 'Invalid payload', schema: featureToggleErrorSchema },\n { status: 401, description: 'Unauthorized', schema: featureToggleErrorSchema },\n { status: 403, description: 'Forbidden - superadmin role required', schema: featureToggleErrorSchema },\n ],\n },\n PUT: {\n summary: 'Update global feature toggle',\n description: 'Updates an existing global feature toggle. Requires superadmin role.',\n requestBody: {\n contentType: 'application/json',\n schema: toggleUpdateSchema,\n },\n responses: [\n {\n status: 200,\n description: 'Feature toggle updated',\n schema: updateResponseSchema,\n },\n ],\n errors: [\n { status: 400, description: 'Invalid payload', schema: featureToggleErrorSchema },\n { status: 401, description: 'Unauthorized', schema: featureToggleErrorSchema },\n { status: 403, description: 'Forbidden - superadmin role required', schema: featureToggleErrorSchema },\n { status: 404, description: 'Feature toggle not found', schema: featureToggleErrorSchema },\n ],\n },\n DELETE: {\n summary: 'Delete global feature toggle',\n description: 'Soft deletes a global feature toggle by ID. Requires superadmin role.',\n query: z.object({ id: z.string().uuid().describe('Feature toggle identifier') }),\n responses: [\n { status: 200, description: 'Feature toggle deleted', schema: deleteResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid identifier', schema: featureToggleErrorSchema },\n { status: 401, description: 'Unauthorized', schema: featureToggleErrorSchema },\n { status: 403, description: 'Forbidden - superadmin role required', schema: featureToggleErrorSchema },\n { status: 404, description: 'Feature toggle not found', schema: featureToggleErrorSchema },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -16,11 +16,11 @@ function assertGlobalToggleSuperAdmin(ctx) {
16
16
  }
17
17
  const featureToggleCrudIndexer = { entityType: E.feature_toggles.feature_toggle };
18
18
  const FEATURE_TOGGLE_LOCK_RESOURCE_KIND = "feature_toggles.feature_toggle";
19
- function featureToggleIdentifiers(toggle, ctx) {
19
+ function featureToggleIdentifiers(toggle) {
20
20
  return {
21
21
  id: toggle.id,
22
22
  organizationId: null,
23
- tenantId: ctx.auth?.tenantId ?? null
23
+ tenantId: null
24
24
  };
25
25
  }
26
26
  async function loadToggleSnapshot(em, id) {
@@ -70,7 +70,7 @@ const createToggleCommand = {
70
70
  dataEngine,
71
71
  action: "created",
72
72
  entity: toggle,
73
- identifiers: featureToggleIdentifiers(toggle, ctx),
73
+ identifiers: featureToggleIdentifiers(toggle),
74
74
  syncOrigin: ctx.syncOrigin,
75
75
  indexer: featureToggleCrudIndexer
76
76
  });
@@ -113,7 +113,7 @@ const createToggleCommand = {
113
113
  dataEngine,
114
114
  action: "deleted",
115
115
  entity: toggle,
116
- identifiers: featureToggleIdentifiers(toggle, ctx),
116
+ identifiers: featureToggleIdentifiers(toggle),
117
117
  syncOrigin: ctx.syncOrigin,
118
118
  indexer: featureToggleCrudIndexer
119
119
  });
@@ -152,7 +152,7 @@ const createToggleCommand = {
152
152
  dataEngine,
153
153
  action: "created",
154
154
  entity: toggle,
155
- identifiers: featureToggleIdentifiers(toggle, ctx),
155
+ identifiers: featureToggleIdentifiers(toggle),
156
156
  syncOrigin: ctx.syncOrigin,
157
157
  indexer: featureToggleCrudIndexer
158
158
  });
@@ -200,7 +200,7 @@ const updateToggleCommand = {
200
200
  dataEngine,
201
201
  action: "updated",
202
202
  entity: toggle,
203
- identifiers: featureToggleIdentifiers(toggle, ctx),
203
+ identifiers: featureToggleIdentifiers(toggle),
204
204
  syncOrigin: ctx.syncOrigin,
205
205
  indexer: featureToggleCrudIndexer
206
206
  });
@@ -279,7 +279,7 @@ const updateToggleCommand = {
279
279
  dataEngine,
280
280
  action: "updated",
281
281
  entity: toggle,
282
- identifiers: featureToggleIdentifiers(toggle, ctx),
282
+ identifiers: featureToggleIdentifiers(toggle),
283
283
  syncOrigin: ctx.syncOrigin,
284
284
  indexer: featureToggleCrudIndexer
285
285
  });
@@ -317,7 +317,7 @@ const deleteToggleCommand = {
317
317
  dataEngine,
318
318
  action: "deleted",
319
319
  entity: toggle,
320
- identifiers: featureToggleIdentifiers(toggle, ctx),
320
+ identifiers: featureToggleIdentifiers(toggle),
321
321
  syncOrigin: ctx.syncOrigin,
322
322
  indexer: featureToggleCrudIndexer
323
323
  });
@@ -387,7 +387,7 @@ const deleteToggleCommand = {
387
387
  dataEngine,
388
388
  action: "updated",
389
389
  entity: toggle,
390
- identifiers: featureToggleIdentifiers(toggle, ctx),
390
+ identifiers: featureToggleIdentifiers(toggle),
391
391
  syncOrigin: ctx.syncOrigin,
392
392
  indexer: featureToggleCrudIndexer
393
393
  });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/feature_toggles/commands/global.ts"],
4
- "sourcesContent": ["import type { CommandHandler } from '@open-mercato/shared/lib/commands'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { FeatureToggle, FeatureToggleOverride } from '../data/entities'\nimport { ToggleCreateInput, toggleCreateSchema, ToggleUpdateInput, toggleUpdateSchema } from '../data/validators'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { buildChanges, emitCrudSideEffects, emitCrudUndoSideEffects, requireId } from '@open-mercato/shared/lib/commands/helpers'\nimport { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'\nimport { resolveRedoSnapshot } from '@open-mercato/shared/lib/commands/redo'\nimport { FeatureTogglesService } from '../lib/feature-flag-check'\nimport { E } from '#generated/entities.ids.generated'\n\nfunction assertGlobalToggleSuperAdmin(ctx: { auth?: { [key: string]: unknown } | null; systemActor?: boolean }): void {\n // Trusted server-side callers (CLI seed-defaults/toggle-*, tenant setup) run\n // without an authenticated actor and opt in via `systemActor`. HTTP request\n // paths never set it and always carry a real `auth` actor, so an authenticated\n // but non-super-admin caller \u2014 the cross-tenant escalation vector (#2266) \u2014\n // stays denied.\n if (ctx.systemActor === true) return\n if (ctx.auth?.isSuperAdmin !== true) {\n throw new CrudHttpError(403, { error: 'Global feature toggles can only be managed by a super administrator.' })\n }\n}\n\ntype ToggleSnapshot = {\n id: string\n identifier: string\n name: string\n description: string | null\n category: string | null\n type: 'boolean' | 'string' | 'number' | 'json'\n defaultValue: any\n}\n\ntype OverrideSnapshot = {\n id: string\n toggleId: string\n tenantId: string\n value?: any\n}\n\ntype ToggleUndoPayload = {\n after?: ToggleSnapshot | null\n before?: ToggleSnapshot | null\n overrides?: OverrideSnapshot[]\n}\n\nconst featureToggleCrudIndexer = { entityType: E.feature_toggles.feature_toggle }\n\nconst FEATURE_TOGGLE_LOCK_RESOURCE_KIND = 'feature_toggles.feature_toggle'\n\nfunction featureToggleIdentifiers(\n toggle: FeatureToggle | ToggleSnapshot,\n ctx: { auth?: { tenantId?: string | null } | null },\n) {\n return {\n id: toggle.id,\n organizationId: null,\n tenantId: ctx.auth?.tenantId ?? null,\n }\n}\n\nasync function loadToggleSnapshot(em: EntityManager, id: string): Promise<ToggleSnapshot | null> {\n const toggle = await em.findOne(FeatureToggle, { id })\n if (!toggle) return null\n return {\n id: toggle.id,\n identifier: toggle.identifier,\n name: toggle.name,\n description: toggle.description ?? null,\n category: toggle.category ?? null,\n type: toggle.type ?? 'boolean',\n defaultValue: toggle.defaultValue ?? null,\n }\n}\n\nasync function loadOverrideSnapshots(em: EntityManager, toggleId: string): Promise<OverrideSnapshot[]> {\n const overrides = await em.find(FeatureToggleOverride, { toggle: toggleId })\n return overrides.map(o => ({\n id: o.id,\n toggleId: o.toggle.id,\n tenantId: o.tenantId,\n value: o.value,\n }))\n}\n\nconst createToggleCommand: CommandHandler<ToggleCreateInput, { toggleId: string }> = {\n id: 'feature_toggles.global.create',\n async execute(rawInput, ctx) {\n assertGlobalToggleSuperAdmin(ctx)\n const parsed = toggleCreateSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const existing = await em.findOne(FeatureToggle, { identifier: parsed.identifier })\n if (existing) {\n throw new CrudHttpError(400, { error: 'Feature toggle identifier already exists' })\n }\n const toggle = em.create(FeatureToggle, {\n identifier: parsed.identifier,\n name: parsed.name,\n description: parsed.description,\n category: parsed.category,\n type: parsed.type,\n defaultValue: parsed.defaultValue,\n })\n em.persist(toggle)\n await em.flush()\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: 'created',\n entity: toggle,\n identifiers: featureToggleIdentifiers(toggle, ctx),\n syncOrigin: ctx.syncOrigin,\n indexer: featureToggleCrudIndexer,\n })\n\n return { toggleId: toggle.id }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n return await loadToggleSnapshot(em, result.toggleId)\n },\n buildLog: async ({ result, ctx }) => {\n const { translate } = await resolveTranslations()\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const snapshot = await loadToggleSnapshot(em, result.toggleId)\n return {\n actionLabel: translate('feature_toggles.audit.toggles.create', 'Create toggle'),\n resourceKind: 'feature_toggles.global',\n resourceId: result.toggleId,\n snapshotAfter: snapshot ?? null,\n payload: {\n undo: {\n after: snapshot ?? null,\n } satisfies ToggleUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const toggleId = logEntry?.resourceId ?? null\n if (!toggleId) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const overrides = await em.find(FeatureToggleOverride, { toggle: toggleId })\n if (overrides.length > 0) {\n em.remove(overrides)\n }\n const toggle = await em.findOne(FeatureToggle, { id: toggleId })\n if (toggle) {\n em.remove(toggle)\n await em.flush()\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudUndoSideEffects({\n dataEngine,\n action: 'deleted',\n entity: toggle,\n identifiers: featureToggleIdentifiers(toggle, ctx),\n syncOrigin: ctx.syncOrigin,\n indexer: featureToggleCrudIndexer,\n })\n const featureTogglesService = ctx.container.resolve('featureTogglesService') as FeatureTogglesService\n await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier)\n }\n },\n redo: async ({ logEntry, ctx }) => {\n const after = resolveRedoSnapshot<ToggleSnapshot>(logEntry)\n if (!after) throw new CrudHttpError(400, { error: '[internal] redo snapshot unavailable for toggle create' })\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n let toggle = await em.findOne(FeatureToggle, { id: after.id })\n if (!toggle) {\n toggle = em.create(FeatureToggle, {\n id: after.id,\n identifier: after.identifier,\n name: after.name,\n description: after.description,\n category: after.category,\n type: after.type,\n defaultValue: after.defaultValue,\n })\n em.persist(toggle)\n } else {\n toggle.deletedAt = null\n toggle.identifier = after.identifier\n toggle.name = after.name\n toggle.description = after.description\n toggle.category = after.category\n toggle.type = after.type\n toggle.defaultValue = after.defaultValue\n }\n await em.flush()\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: 'created',\n entity: toggle,\n identifiers: featureToggleIdentifiers(toggle, ctx),\n syncOrigin: ctx.syncOrigin,\n indexer: featureToggleCrudIndexer,\n })\n const featureTogglesService = ctx.container.resolve('featureTogglesService') as FeatureTogglesService\n await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier)\n return { toggleId: toggle.id }\n },\n}\n\nconst updateToggleCommand: CommandHandler<ToggleUpdateInput, { toggleId: string }> = {\n id: 'feature_toggles.global.update',\n async prepare(rawInput, ctx) {\n const parsed = toggleUpdateSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager)\n const snapshot = await loadToggleSnapshot(em, parsed.id)\n return snapshot ? { before: snapshot } : {}\n },\n async execute(rawInput, ctx) {\n assertGlobalToggleSuperAdmin(ctx)\n const parsed = toggleUpdateSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const toggle = await em.findOne(FeatureToggle, { id: parsed.id, deletedAt: null })\n if (!toggle) throw new CrudHttpError(404, { error: 'Toggle not found' })\n enforceCommandOptimisticLock({\n resourceKind: FEATURE_TOGGLE_LOCK_RESOURCE_KIND,\n resourceId: toggle.id,\n current: toggle.updatedAt ?? null,\n request: ctx.request ?? null,\n })\n const previousIdentifier = toggle.identifier\n if (parsed.identifier && parsed.identifier !== toggle.identifier) {\n const existing = await em.findOne(FeatureToggle, { identifier: parsed.identifier })\n if (existing && existing.id !== toggle.id) {\n throw new CrudHttpError(400, { error: 'Feature toggle identifier already exists' })\n }\n }\n toggle.identifier = parsed.identifier ?? toggle.identifier\n toggle.name = parsed.name ?? toggle.name\n toggle.description = parsed.description ?? toggle.description\n toggle.category = parsed.category ?? toggle.category\n toggle.type = parsed.type ?? toggle.type\n toggle.defaultValue = parsed.defaultValue ?? toggle.defaultValue\n await em.flush()\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: 'updated',\n entity: toggle,\n identifiers: featureToggleIdentifiers(toggle, ctx),\n syncOrigin: ctx.syncOrigin,\n indexer: featureToggleCrudIndexer,\n })\n const featureTogglesService = ctx.container.resolve('featureTogglesService') as FeatureTogglesService\n if (previousIdentifier !== toggle.identifier) {\n await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(previousIdentifier)\n }\n await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier)\n return { toggleId: toggle.id }\n },\n buildLog: async ({ snapshots, ctx }) => {\n const { translate } = await resolveTranslations()\n const before = snapshots.before as ToggleSnapshot | undefined\n if (!before) return null\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const afterSnapshot = await loadToggleSnapshot(em, before.id)\n const changes =\n afterSnapshot && before\n ? buildChanges(\n before as unknown as Record<string, unknown>,\n afterSnapshot as unknown as Record<string, unknown>,\n [\n 'identifier',\n 'name',\n 'description',\n 'category',\n 'failMode',\n 'type',\n 'defaultValue',\n ]\n )\n : {}\n\n return {\n actionLabel: translate('feature_toggles.audit.toggles.update', 'Update toggle'),\n resourceKind: 'feature_toggles.global',\n resourceId: before.id,\n snapshotBefore: before,\n snapshotAfter: afterSnapshot ?? null,\n changes,\n payload: {\n undo: {\n before,\n after: afterSnapshot ?? null,\n } satisfies ToggleUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<ToggleUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n let toggle = await em.findOne(FeatureToggle, { id: before.id })\n if (!toggle) {\n toggle = em.create(FeatureToggle, {\n id: before.id,\n identifier: before.identifier,\n name: before.name,\n description: before.description,\n category: before.category,\n type: before.type,\n defaultValue: before.defaultValue,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(toggle)\n } else {\n toggle.identifier = before.identifier\n toggle.name = before.name\n toggle.description = before.description\n toggle.category = before.category\n toggle.type = before.type\n toggle.defaultValue = before.defaultValue\n toggle.deletedAt = null\n }\n await em.flush()\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudUndoSideEffects({\n dataEngine,\n action: 'updated',\n entity: toggle,\n identifiers: featureToggleIdentifiers(toggle, ctx),\n syncOrigin: ctx.syncOrigin,\n indexer: featureToggleCrudIndexer,\n })\n const featureTogglesService = ctx.container.resolve('featureTogglesService') as FeatureTogglesService\n await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier)\n }\n}\n\nconst deleteToggleCommand: CommandHandler<{ body?: Record<string, unknown>; query?: Record<string, unknown> }, { toggleId: string }> =\n{\n id: 'feature_toggles.global.delete',\n async prepare(input, ctx) {\n const id = requireId(input, 'Feature toggle id required')\n const em = (ctx.container.resolve('em') as EntityManager)\n const snapshot = await loadToggleSnapshot(em, id)\n const overrides = await loadOverrideSnapshots(em, id)\n return snapshot ? { before: snapshot, overrides } : {}\n },\n async execute(input, ctx) {\n assertGlobalToggleSuperAdmin(ctx)\n const id = requireId(input, 'Feature toggle id required')\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const toggle = await em.findOne(FeatureToggle, { id, deletedAt: null })\n if (!toggle) throw new CrudHttpError(404, { error: 'Feature toggle not found' })\n enforceCommandOptimisticLock({\n resourceKind: FEATURE_TOGGLE_LOCK_RESOURCE_KIND,\n resourceId: toggle.id,\n current: toggle.updatedAt ?? null,\n request: ctx.request ?? null,\n })\n const featureTogglesService = ctx.container.resolve('featureTogglesService') as FeatureTogglesService\n await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier)\n\n toggle.deletedAt = new Date()\n await em.flush()\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: 'deleted',\n entity: toggle,\n identifiers: featureToggleIdentifiers(toggle, ctx),\n syncOrigin: ctx.syncOrigin,\n indexer: featureToggleCrudIndexer,\n })\n\n return { toggleId: toggle.id }\n },\n buildLog: async ({ snapshots }) => {\n const before = snapshots.before as ToggleSnapshot | undefined\n const overrides = (snapshots as any).overrides as OverrideSnapshot[] | undefined\n\n if (!before) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('feature_toggles.audit.toggles.delete', 'Delete toggle'),\n resourceKind: 'feature_toggles.global',\n resourceId: before.id,\n snapshotBefore: before,\n payload: {\n undo: {\n before,\n overrides,\n } satisfies ToggleUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<ToggleUndoPayload>(logEntry)\n const before = payload?.before\n const overrides = payload?.overrides || []\n\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n let toggle = await em.findOne(FeatureToggle, { id: before.id })\n if (!toggle) {\n toggle = em.create(FeatureToggle, {\n id: before.id,\n identifier: before.identifier,\n name: before.name,\n description: before.description,\n category: before.category,\n type: before.type,\n defaultValue: before.defaultValue,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(toggle)\n\n for (const ov of overrides) {\n const override = em.create(FeatureToggleOverride, {\n id: ov.id,\n toggle: toggle,\n tenantId: ov.tenantId,\n value: ov.value ?? null,\n })\n em.persist(override)\n }\n } else {\n toggle.identifier = before.identifier\n toggle.name = before.name\n toggle.description = before.description\n toggle.category = before.category\n toggle.type = before.type\n toggle.defaultValue = before.defaultValue\n toggle.deletedAt = null\n }\n const featureTogglesService = ctx.container.resolve('featureTogglesService') as FeatureTogglesService\n await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier)\n await em.flush()\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudUndoSideEffects({\n dataEngine,\n action: 'updated',\n entity: toggle,\n identifiers: featureToggleIdentifiers(toggle, ctx),\n syncOrigin: ctx.syncOrigin,\n indexer: featureToggleCrudIndexer,\n })\n },\n}\n\nregisterCommand(createToggleCommand)\nregisterCommand(updateToggleCommand)\nregisterCommand(deleteToggleCommand)\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { CommandHandler } from '@open-mercato/shared/lib/commands'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { FeatureToggle, FeatureToggleOverride } from '../data/entities'\nimport { ToggleCreateInput, toggleCreateSchema, ToggleUpdateInput, toggleUpdateSchema } from '../data/validators'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { buildChanges, emitCrudSideEffects, emitCrudUndoSideEffects, requireId } from '@open-mercato/shared/lib/commands/helpers'\nimport { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'\nimport { resolveRedoSnapshot } from '@open-mercato/shared/lib/commands/redo'\nimport { FeatureTogglesService } from '../lib/feature-flag-check'\nimport { E } from '#generated/entities.ids.generated'\n\nfunction assertGlobalToggleSuperAdmin(ctx: { auth?: { [key: string]: unknown } | null; systemActor?: boolean }): void {\n // Trusted server-side callers (CLI seed-defaults/toggle-*, tenant setup) run\n // without an authenticated actor and opt in via `systemActor`. HTTP request\n // paths never set it and always carry a real `auth` actor, so an authenticated\n // but non-super-admin caller \u2014 the cross-tenant escalation vector (#2266) \u2014\n // stays denied.\n if (ctx.systemActor === true) return\n if (ctx.auth?.isSuperAdmin !== true) {\n throw new CrudHttpError(403, { error: 'Global feature toggles can only be managed by a super administrator.' })\n }\n}\n\ntype ToggleSnapshot = {\n id: string\n identifier: string\n name: string\n description: string | null\n category: string | null\n type: 'boolean' | 'string' | 'number' | 'json'\n defaultValue: any\n}\n\ntype OverrideSnapshot = {\n id: string\n toggleId: string\n tenantId: string\n value?: any\n}\n\ntype ToggleUndoPayload = {\n after?: ToggleSnapshot | null\n before?: ToggleSnapshot | null\n overrides?: OverrideSnapshot[]\n}\n\nconst featureToggleCrudIndexer = { entityType: E.feature_toggles.feature_toggle }\n\nconst FEATURE_TOGGLE_LOCK_RESOURCE_KIND = 'feature_toggles.feature_toggle'\n\nfunction featureToggleIdentifiers(toggle: FeatureToggle | ToggleSnapshot) {\n return {\n id: toggle.id,\n organizationId: null,\n tenantId: null,\n }\n}\n\nasync function loadToggleSnapshot(em: EntityManager, id: string): Promise<ToggleSnapshot | null> {\n const toggle = await em.findOne(FeatureToggle, { id })\n if (!toggle) return null\n return {\n id: toggle.id,\n identifier: toggle.identifier,\n name: toggle.name,\n description: toggle.description ?? null,\n category: toggle.category ?? null,\n type: toggle.type ?? 'boolean',\n defaultValue: toggle.defaultValue ?? null,\n }\n}\n\nasync function loadOverrideSnapshots(em: EntityManager, toggleId: string): Promise<OverrideSnapshot[]> {\n const overrides = await em.find(FeatureToggleOverride, { toggle: toggleId })\n return overrides.map(o => ({\n id: o.id,\n toggleId: o.toggle.id,\n tenantId: o.tenantId,\n value: o.value,\n }))\n}\n\nconst createToggleCommand: CommandHandler<ToggleCreateInput, { toggleId: string }> = {\n id: 'feature_toggles.global.create',\n async execute(rawInput, ctx) {\n assertGlobalToggleSuperAdmin(ctx)\n const parsed = toggleCreateSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const existing = await em.findOne(FeatureToggle, { identifier: parsed.identifier })\n if (existing) {\n throw new CrudHttpError(400, { error: 'Feature toggle identifier already exists' })\n }\n const toggle = em.create(FeatureToggle, {\n identifier: parsed.identifier,\n name: parsed.name,\n description: parsed.description,\n category: parsed.category,\n type: parsed.type,\n defaultValue: parsed.defaultValue,\n })\n em.persist(toggle)\n await em.flush()\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: 'created',\n entity: toggle,\n identifiers: featureToggleIdentifiers(toggle),\n syncOrigin: ctx.syncOrigin,\n indexer: featureToggleCrudIndexer,\n })\n\n return { toggleId: toggle.id }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n return await loadToggleSnapshot(em, result.toggleId)\n },\n buildLog: async ({ result, ctx }) => {\n const { translate } = await resolveTranslations()\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const snapshot = await loadToggleSnapshot(em, result.toggleId)\n return {\n actionLabel: translate('feature_toggles.audit.toggles.create', 'Create toggle'),\n resourceKind: 'feature_toggles.global',\n resourceId: result.toggleId,\n snapshotAfter: snapshot ?? null,\n payload: {\n undo: {\n after: snapshot ?? null,\n } satisfies ToggleUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const toggleId = logEntry?.resourceId ?? null\n if (!toggleId) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const overrides = await em.find(FeatureToggleOverride, { toggle: toggleId })\n if (overrides.length > 0) {\n em.remove(overrides)\n }\n const toggle = await em.findOne(FeatureToggle, { id: toggleId })\n if (toggle) {\n em.remove(toggle)\n await em.flush()\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudUndoSideEffects({\n dataEngine,\n action: 'deleted',\n entity: toggle,\n identifiers: featureToggleIdentifiers(toggle),\n syncOrigin: ctx.syncOrigin,\n indexer: featureToggleCrudIndexer,\n })\n const featureTogglesService = ctx.container.resolve('featureTogglesService') as FeatureTogglesService\n await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier)\n }\n },\n redo: async ({ logEntry, ctx }) => {\n const after = resolveRedoSnapshot<ToggleSnapshot>(logEntry)\n if (!after) throw new CrudHttpError(400, { error: '[internal] redo snapshot unavailable for toggle create' })\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n let toggle = await em.findOne(FeatureToggle, { id: after.id })\n if (!toggle) {\n toggle = em.create(FeatureToggle, {\n id: after.id,\n identifier: after.identifier,\n name: after.name,\n description: after.description,\n category: after.category,\n type: after.type,\n defaultValue: after.defaultValue,\n })\n em.persist(toggle)\n } else {\n toggle.deletedAt = null\n toggle.identifier = after.identifier\n toggle.name = after.name\n toggle.description = after.description\n toggle.category = after.category\n toggle.type = after.type\n toggle.defaultValue = after.defaultValue\n }\n await em.flush()\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: 'created',\n entity: toggle,\n identifiers: featureToggleIdentifiers(toggle),\n syncOrigin: ctx.syncOrigin,\n indexer: featureToggleCrudIndexer,\n })\n const featureTogglesService = ctx.container.resolve('featureTogglesService') as FeatureTogglesService\n await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier)\n return { toggleId: toggle.id }\n },\n}\n\nconst updateToggleCommand: CommandHandler<ToggleUpdateInput, { toggleId: string }> = {\n id: 'feature_toggles.global.update',\n async prepare(rawInput, ctx) {\n const parsed = toggleUpdateSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager)\n const snapshot = await loadToggleSnapshot(em, parsed.id)\n return snapshot ? { before: snapshot } : {}\n },\n async execute(rawInput, ctx) {\n assertGlobalToggleSuperAdmin(ctx)\n const parsed = toggleUpdateSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const toggle = await em.findOne(FeatureToggle, { id: parsed.id, deletedAt: null })\n if (!toggle) throw new CrudHttpError(404, { error: 'Toggle not found' })\n enforceCommandOptimisticLock({\n resourceKind: FEATURE_TOGGLE_LOCK_RESOURCE_KIND,\n resourceId: toggle.id,\n current: toggle.updatedAt ?? null,\n request: ctx.request ?? null,\n })\n const previousIdentifier = toggle.identifier\n if (parsed.identifier && parsed.identifier !== toggle.identifier) {\n const existing = await em.findOne(FeatureToggle, { identifier: parsed.identifier })\n if (existing && existing.id !== toggle.id) {\n throw new CrudHttpError(400, { error: 'Feature toggle identifier already exists' })\n }\n }\n toggle.identifier = parsed.identifier ?? toggle.identifier\n toggle.name = parsed.name ?? toggle.name\n toggle.description = parsed.description ?? toggle.description\n toggle.category = parsed.category ?? toggle.category\n toggle.type = parsed.type ?? toggle.type\n toggle.defaultValue = parsed.defaultValue ?? toggle.defaultValue\n await em.flush()\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: 'updated',\n entity: toggle,\n identifiers: featureToggleIdentifiers(toggle),\n syncOrigin: ctx.syncOrigin,\n indexer: featureToggleCrudIndexer,\n })\n const featureTogglesService = ctx.container.resolve('featureTogglesService') as FeatureTogglesService\n if (previousIdentifier !== toggle.identifier) {\n await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(previousIdentifier)\n }\n await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier)\n return { toggleId: toggle.id }\n },\n buildLog: async ({ snapshots, ctx }) => {\n const { translate } = await resolveTranslations()\n const before = snapshots.before as ToggleSnapshot | undefined\n if (!before) return null\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const afterSnapshot = await loadToggleSnapshot(em, before.id)\n const changes =\n afterSnapshot && before\n ? buildChanges(\n before as unknown as Record<string, unknown>,\n afterSnapshot as unknown as Record<string, unknown>,\n [\n 'identifier',\n 'name',\n 'description',\n 'category',\n 'failMode',\n 'type',\n 'defaultValue',\n ]\n )\n : {}\n\n return {\n actionLabel: translate('feature_toggles.audit.toggles.update', 'Update toggle'),\n resourceKind: 'feature_toggles.global',\n resourceId: before.id,\n snapshotBefore: before,\n snapshotAfter: afterSnapshot ?? null,\n changes,\n payload: {\n undo: {\n before,\n after: afterSnapshot ?? null,\n } satisfies ToggleUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<ToggleUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n let toggle = await em.findOne(FeatureToggle, { id: before.id })\n if (!toggle) {\n toggle = em.create(FeatureToggle, {\n id: before.id,\n identifier: before.identifier,\n name: before.name,\n description: before.description,\n category: before.category,\n type: before.type,\n defaultValue: before.defaultValue,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(toggle)\n } else {\n toggle.identifier = before.identifier\n toggle.name = before.name\n toggle.description = before.description\n toggle.category = before.category\n toggle.type = before.type\n toggle.defaultValue = before.defaultValue\n toggle.deletedAt = null\n }\n await em.flush()\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudUndoSideEffects({\n dataEngine,\n action: 'updated',\n entity: toggle,\n identifiers: featureToggleIdentifiers(toggle),\n syncOrigin: ctx.syncOrigin,\n indexer: featureToggleCrudIndexer,\n })\n const featureTogglesService = ctx.container.resolve('featureTogglesService') as FeatureTogglesService\n await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier)\n }\n}\n\nconst deleteToggleCommand: CommandHandler<{ body?: Record<string, unknown>; query?: Record<string, unknown> }, { toggleId: string }> =\n{\n id: 'feature_toggles.global.delete',\n async prepare(input, ctx) {\n const id = requireId(input, 'Feature toggle id required')\n const em = (ctx.container.resolve('em') as EntityManager)\n const snapshot = await loadToggleSnapshot(em, id)\n const overrides = await loadOverrideSnapshots(em, id)\n return snapshot ? { before: snapshot, overrides } : {}\n },\n async execute(input, ctx) {\n assertGlobalToggleSuperAdmin(ctx)\n const id = requireId(input, 'Feature toggle id required')\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const toggle = await em.findOne(FeatureToggle, { id, deletedAt: null })\n if (!toggle) throw new CrudHttpError(404, { error: 'Feature toggle not found' })\n enforceCommandOptimisticLock({\n resourceKind: FEATURE_TOGGLE_LOCK_RESOURCE_KIND,\n resourceId: toggle.id,\n current: toggle.updatedAt ?? null,\n request: ctx.request ?? null,\n })\n const featureTogglesService = ctx.container.resolve('featureTogglesService') as FeatureTogglesService\n await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier)\n\n toggle.deletedAt = new Date()\n await em.flush()\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: 'deleted',\n entity: toggle,\n identifiers: featureToggleIdentifiers(toggle),\n syncOrigin: ctx.syncOrigin,\n indexer: featureToggleCrudIndexer,\n })\n\n return { toggleId: toggle.id }\n },\n buildLog: async ({ snapshots }) => {\n const before = snapshots.before as ToggleSnapshot | undefined\n const overrides = (snapshots as any).overrides as OverrideSnapshot[] | undefined\n\n if (!before) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('feature_toggles.audit.toggles.delete', 'Delete toggle'),\n resourceKind: 'feature_toggles.global',\n resourceId: before.id,\n snapshotBefore: before,\n payload: {\n undo: {\n before,\n overrides,\n } satisfies ToggleUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<ToggleUndoPayload>(logEntry)\n const before = payload?.before\n const overrides = payload?.overrides || []\n\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n let toggle = await em.findOne(FeatureToggle, { id: before.id })\n if (!toggle) {\n toggle = em.create(FeatureToggle, {\n id: before.id,\n identifier: before.identifier,\n name: before.name,\n description: before.description,\n category: before.category,\n type: before.type,\n defaultValue: before.defaultValue,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(toggle)\n\n for (const ov of overrides) {\n const override = em.create(FeatureToggleOverride, {\n id: ov.id,\n toggle: toggle,\n tenantId: ov.tenantId,\n value: ov.value ?? null,\n })\n em.persist(override)\n }\n } else {\n toggle.identifier = before.identifier\n toggle.name = before.name\n toggle.description = before.description\n toggle.category = before.category\n toggle.type = before.type\n toggle.defaultValue = before.defaultValue\n toggle.deletedAt = null\n }\n const featureTogglesService = ctx.container.resolve('featureTogglesService') as FeatureTogglesService\n await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier)\n await em.flush()\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudUndoSideEffects({\n dataEngine,\n action: 'updated',\n entity: toggle,\n identifiers: featureToggleIdentifiers(toggle),\n syncOrigin: ctx.syncOrigin,\n indexer: featureToggleCrudIndexer,\n })\n },\n}\n\nregisterCommand(createToggleCommand)\nregisterCommand(updateToggleCommand)\nregisterCommand(deleteToggleCommand)\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,20 +1,49 @@
1
- import { resolveEntityTableName } from "@open-mercato/shared/lib/query/engine";
1
+ import { getEntityIds } from "@open-mercato/shared/lib/encryption/entityIds";
2
+ import { resolveRegisteredEntityTableName } from "@open-mercato/shared/lib/query/engine";
2
3
  class QueryIndexScopeError extends Error {
3
4
  constructor(message) {
4
5
  super(message);
5
6
  this.name = "QueryIndexScopeError";
6
7
  }
7
8
  }
8
- async function loadQueryIndexRowScope(em, entityType, recordId) {
9
+ function resolveQueryIndexSourceMetadata(em, entityType) {
10
+ const registeredEntityIds = Object.values(getEntityIds(false)).flatMap((moduleEntities) => Object.values(moduleEntities ?? {}));
11
+ if (!registeredEntityIds.includes(entityType)) {
12
+ throw new QueryIndexScopeError(`Query index entity type is not registered: ${entityType}`);
13
+ }
14
+ const table = resolveRegisteredEntityTableName(em, entityType);
15
+ if (!table) {
16
+ throw new QueryIndexScopeError(`Query index entity type has no registered ORM table: ${entityType}`);
17
+ }
18
+ const registry = em.getMetadata?.();
19
+ const allMetadataRaw = typeof registry?.getAll === "function" ? registry.getAll() : null;
20
+ const allMetadata = Array.isArray(allMetadataRaw) ? allMetadataRaw : allMetadataRaw instanceof Map ? Array.from(allMetadataRaw.values()) : Object.values(allMetadataRaw ?? {});
21
+ const metadata = allMetadata.find((candidate) => String(candidate.tableName ?? "") === table);
22
+ if (!metadata) {
23
+ throw new QueryIndexScopeError(`Query index entity metadata was not found for table: ${table}`);
24
+ }
25
+ return {
26
+ table,
27
+ organizationColumn: resolveScopeColumn(metadata, "organizationId", table),
28
+ tenantColumn: resolveScopeColumn(metadata, "tenantId", table)
29
+ };
30
+ }
31
+ async function loadQueryIndexRowScope(em, source, recordId) {
32
+ if (!source.organizationColumn && !source.tenantColumn) {
33
+ return { kind: "global" };
34
+ }
9
35
  const db = em.getKysely();
10
- const table = resolveEntityTableName(em, entityType);
11
- const row = await db.selectFrom(table).select(["organization_id", "tenant_id"]).where("id", "=", recordId).executeTakeFirst();
36
+ const columns = [source.organizationColumn, source.tenantColumn].filter((column) => column !== null);
37
+ const row = await db.selectFrom(source.table).select(columns).where("id", "=", recordId).executeTakeFirst();
12
38
  if (!row) {
13
- return null;
39
+ return { kind: "missing" };
14
40
  }
15
41
  return {
16
- organizationId: row.organization_id ?? null,
17
- tenantId: row.tenant_id ?? null
42
+ kind: "row",
43
+ scope: {
44
+ organizationId: source.organizationColumn ? row[source.organizationColumn] ?? null : null,
45
+ tenantId: source.tenantColumn ? row[source.tenantColumn] ?? null : null
46
+ }
18
47
  };
19
48
  }
20
49
  function resolveQueryIndexRecordScope(input) {
@@ -23,9 +52,20 @@ function resolveQueryIndexRecordScope(input) {
23
52
  payloadOrganizationId,
24
53
  hasPayloadTenantId,
25
54
  hasPayloadOrganizationId,
26
- rowScope
55
+ sourceScope
27
56
  } = input;
28
- if (!rowScope) {
57
+ if (sourceScope.kind === "global") {
58
+ if (!hasPayloadTenantId || !hasPayloadOrganizationId || payloadTenantId !== null || payloadOrganizationId !== null) {
59
+ throw new QueryIndexScopeError(
60
+ "Query index event for a global entity must explicitly provide tenantId and organizationId as null"
61
+ );
62
+ }
63
+ return {
64
+ tenantId: null,
65
+ organizationId: null
66
+ };
67
+ }
68
+ if (sourceScope.kind === "missing") {
29
69
  if (!hasPayloadTenantId || !hasPayloadOrganizationId) {
30
70
  throw new QueryIndexScopeError(
31
71
  "Query index event is missing tenantId/organizationId and source row scope could not be resolved"
@@ -36,6 +76,7 @@ function resolveQueryIndexRecordScope(input) {
36
76
  organizationId: payloadOrganizationId ?? null
37
77
  };
38
78
  }
79
+ const rowScope = sourceScope.scope;
39
80
  if (hasPayloadTenantId && !isSameScopeValue(payloadTenantId, rowScope.tenantId)) {
40
81
  throw new QueryIndexScopeError(
41
82
  `Query index event tenantId does not match source row scope (payload=${String(payloadTenantId ?? null)}, row=${String(rowScope.tenantId)})`
@@ -65,10 +106,22 @@ function resolveQueryIndexReindexScope(input) {
65
106
  function isSameScopeValue(left, right) {
66
107
  return (left ?? null) === right;
67
108
  }
109
+ function resolveScopeColumn(metadata, property, table) {
110
+ const scopeProperty = metadata.properties?.[property];
111
+ if (!scopeProperty) return null;
112
+ const fieldNames = scopeProperty.fieldNames;
113
+ if (!Array.isArray(fieldNames) || fieldNames.length !== 1 || typeof fieldNames[0] !== "string" || !fieldNames[0]) {
114
+ throw new QueryIndexScopeError(
115
+ `Query index ${property} metadata must map exactly one physical column for table: ${table}`
116
+ );
117
+ }
118
+ return fieldNames[0];
119
+ }
68
120
  export {
69
121
  QueryIndexScopeError,
70
122
  loadQueryIndexRowScope,
71
123
  resolveQueryIndexRecordScope,
72
- resolveQueryIndexReindexScope
124
+ resolveQueryIndexReindexScope,
125
+ resolveQueryIndexSourceMetadata
73
126
  };
74
127
  //# sourceMappingURL=subscriber-scope.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/query_index/lib/subscriber-scope.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { resolveEntityTableName } from '@open-mercato/shared/lib/query/engine'\n\nexport type QueryIndexScope = {\n tenantId: string | null\n organizationId: string | null\n}\n\nexport class QueryIndexScopeError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'QueryIndexScopeError'\n }\n}\n\ntype ResolveRecordScopeInput = {\n payloadTenantId: string | null | undefined\n payloadOrganizationId: string | null | undefined\n hasPayloadTenantId: boolean\n hasPayloadOrganizationId: boolean\n rowScope: QueryIndexScope | null\n}\n\ntype ResolveReindexScopeInput = {\n tenantId: string | null | undefined\n organizationId: string | null | undefined\n allowAllTenants?: boolean\n}\n\nexport async function loadQueryIndexRowScope(\n em: EntityManager,\n entityType: string,\n recordId: string\n): Promise<QueryIndexScope | null> {\n const db = em.getKysely<any>()\n const table = resolveEntityTableName(em, entityType)\n const row = await db\n .selectFrom(table as any)\n .select(['organization_id' as any, 'tenant_id' as any])\n .where('id' as any, '=', recordId)\n .executeTakeFirst() as { organization_id: string | null; tenant_id: string | null } | undefined\n\n if (!row) {\n return null\n }\n\n return {\n organizationId: row.organization_id ?? null,\n tenantId: row.tenant_id ?? null,\n }\n}\n\nexport function resolveQueryIndexRecordScope(input: ResolveRecordScopeInput): QueryIndexScope {\n const {\n payloadTenantId,\n payloadOrganizationId,\n hasPayloadTenantId,\n hasPayloadOrganizationId,\n rowScope,\n } = input\n\n if (!rowScope) {\n if (!hasPayloadTenantId || !hasPayloadOrganizationId) {\n throw new QueryIndexScopeError(\n 'Query index event is missing tenantId/organizationId and source row scope could not be resolved'\n )\n }\n\n return {\n tenantId: payloadTenantId ?? null,\n organizationId: payloadOrganizationId ?? null,\n }\n }\n\n if (hasPayloadTenantId && !isSameScopeValue(payloadTenantId, rowScope.tenantId)) {\n throw new QueryIndexScopeError(\n `Query index event tenantId does not match source row scope (payload=${String(payloadTenantId ?? null)}, row=${String(rowScope.tenantId)})`\n )\n }\n\n if (hasPayloadOrganizationId && !isSameScopeValue(payloadOrganizationId, rowScope.organizationId)) {\n throw new QueryIndexScopeError(\n `Query index event organizationId does not match source row scope (payload=${String(payloadOrganizationId ?? null)}, row=${String(rowScope.organizationId)})`\n )\n }\n\n return {\n tenantId: hasPayloadTenantId ? (payloadTenantId ?? null) : rowScope.tenantId,\n organizationId: hasPayloadOrganizationId ? (payloadOrganizationId ?? null) : rowScope.organizationId,\n }\n}\n\nexport function resolveQueryIndexReindexScope(input: ResolveReindexScopeInput): {\n tenantId: string | null | undefined\n organizationId: string | null | undefined\n} {\n if (input.tenantId === undefined && input.allowAllTenants !== true) {\n throw new QueryIndexScopeError(\n 'Query index reindex requires tenantId to be set explicitly; all-tenant reindex must opt in with allowAllTenants=true'\n )\n }\n\n return {\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n }\n}\n\nfunction isSameScopeValue(left: string | null | undefined, right: string | null): boolean {\n return (left ?? null) === right\n}\n"],
5
- "mappings": "AACA,SAAS,8BAA8B;AAOhC,MAAM,6BAA6B,MAAM;AAAA,EAC9C,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAgBA,eAAsB,uBACpB,IACA,YACA,UACiC;AACjC,QAAM,KAAK,GAAG,UAAe;AAC7B,QAAM,QAAQ,uBAAuB,IAAI,UAAU;AACnD,QAAM,MAAM,MAAM,GACf,WAAW,KAAY,EACvB,OAAO,CAAC,mBAA0B,WAAkB,CAAC,EACrD,MAAM,MAAa,KAAK,QAAQ,EAChC,iBAAiB;AAEpB,MAAI,CAAC,KAAK;AACR,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,gBAAgB,IAAI,mBAAmB;AAAA,IACvC,UAAU,IAAI,aAAa;AAAA,EAC7B;AACF;AAEO,SAAS,6BAA6B,OAAiD;AAC5F,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAEJ,MAAI,CAAC,UAAU;AACb,QAAI,CAAC,sBAAsB,CAAC,0BAA0B;AACpD,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,MACL,UAAU,mBAAmB;AAAA,MAC7B,gBAAgB,yBAAyB;AAAA,IAC3C;AAAA,EACF;AAEA,MAAI,sBAAsB,CAAC,iBAAiB,iBAAiB,SAAS,QAAQ,GAAG;AAC/E,UAAM,IAAI;AAAA,MACR,uEAAuE,OAAO,mBAAmB,IAAI,CAAC,SAAS,OAAO,SAAS,QAAQ,CAAC;AAAA,IAC1I;AAAA,EACF;AAEA,MAAI,4BAA4B,CAAC,iBAAiB,uBAAuB,SAAS,cAAc,GAAG;AACjG,UAAM,IAAI;AAAA,MACR,6EAA6E,OAAO,yBAAyB,IAAI,CAAC,SAAS,OAAO,SAAS,cAAc,CAAC;AAAA,IAC5J;AAAA,EACF;AAEA,SAAO;AAAA,IACL,UAAU,qBAAsB,mBAAmB,OAAQ,SAAS;AAAA,IACpE,gBAAgB,2BAA4B,yBAAyB,OAAQ,SAAS;AAAA,EACxF;AACF;AAEO,SAAS,8BAA8B,OAG5C;AACA,MAAI,MAAM,aAAa,UAAa,MAAM,oBAAoB,MAAM;AAClE,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,UAAU,MAAM;AAAA,IAChB,gBAAgB,MAAM;AAAA,EACxB;AACF;AAEA,SAAS,iBAAiB,MAAiC,OAA+B;AACxF,UAAQ,QAAQ,UAAU;AAC5B;",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { getEntityIds } from '@open-mercato/shared/lib/encryption/entityIds'\nimport { resolveRegisteredEntityTableName } from '@open-mercato/shared/lib/query/engine'\n\nexport type QueryIndexScope = {\n tenantId: string | null\n organizationId: string | null\n}\n\nexport type QueryIndexSourceMetadata = {\n table: string\n organizationColumn: string | null\n tenantColumn: string | null\n}\n\nexport type QueryIndexSourceScope =\n | { kind: 'global' }\n | { kind: 'missing' }\n | { kind: 'row'; scope: QueryIndexScope }\n\ntype QueryIndexEntityMetadata = {\n tableName?: unknown\n properties?: Record<string, { fieldNames?: unknown }>\n}\n\nexport class QueryIndexScopeError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'QueryIndexScopeError'\n }\n}\n\ntype ResolveRecordScopeInput = {\n payloadTenantId: string | null | undefined\n payloadOrganizationId: string | null | undefined\n hasPayloadTenantId: boolean\n hasPayloadOrganizationId: boolean\n sourceScope: QueryIndexSourceScope\n}\n\ntype ResolveReindexScopeInput = {\n tenantId: string | null | undefined\n organizationId: string | null | undefined\n allowAllTenants?: boolean\n}\n\nexport function resolveQueryIndexSourceMetadata(\n em: EntityManager,\n entityType: string,\n): QueryIndexSourceMetadata {\n const registeredEntityIds = Object.values(getEntityIds(false)).flatMap((moduleEntities) => Object.values(moduleEntities ?? {}))\n if (!registeredEntityIds.includes(entityType)) {\n throw new QueryIndexScopeError(`Query index entity type is not registered: ${entityType}`)\n }\n\n const table = resolveRegisteredEntityTableName(em, entityType as never)\n if (!table) {\n throw new QueryIndexScopeError(`Query index entity type has no registered ORM table: ${entityType}`)\n }\n\n const registry = em.getMetadata?.()\n const allMetadataRaw = typeof registry?.getAll === 'function' ? registry.getAll() : null\n const allMetadata: QueryIndexEntityMetadata[] = Array.isArray(allMetadataRaw)\n ? allMetadataRaw as QueryIndexEntityMetadata[]\n : allMetadataRaw instanceof Map\n ? Array.from(allMetadataRaw.values()) as QueryIndexEntityMetadata[]\n : Object.values(allMetadataRaw ?? {}) as QueryIndexEntityMetadata[]\n const metadata = allMetadata.find((candidate) => String(candidate.tableName ?? '') === table)\n if (!metadata) {\n throw new QueryIndexScopeError(`Query index entity metadata was not found for table: ${table}`)\n }\n\n return {\n table,\n organizationColumn: resolveScopeColumn(metadata, 'organizationId', table),\n tenantColumn: resolveScopeColumn(metadata, 'tenantId', table),\n }\n}\n\nexport async function loadQueryIndexRowScope(\n em: EntityManager,\n source: QueryIndexSourceMetadata,\n recordId: string,\n): Promise<QueryIndexSourceScope> {\n if (!source.organizationColumn && !source.tenantColumn) {\n return { kind: 'global' }\n }\n\n const db = em.getKysely<any>()\n const columns = [source.organizationColumn, source.tenantColumn].filter((column): column is string => column !== null)\n const row = await db\n .selectFrom(source.table as any)\n .select(columns as any)\n .where('id' as any, '=', recordId)\n .executeTakeFirst() as Record<string, string | null | undefined> | undefined\n\n if (!row) {\n return { kind: 'missing' }\n }\n\n return {\n kind: 'row',\n scope: {\n organizationId: source.organizationColumn ? row[source.organizationColumn] ?? null : null,\n tenantId: source.tenantColumn ? row[source.tenantColumn] ?? null : null,\n },\n }\n}\n\nexport function resolveQueryIndexRecordScope(input: ResolveRecordScopeInput): QueryIndexScope {\n const {\n payloadTenantId,\n payloadOrganizationId,\n hasPayloadTenantId,\n hasPayloadOrganizationId,\n sourceScope,\n } = input\n\n if (sourceScope.kind === 'global') {\n if (!hasPayloadTenantId || !hasPayloadOrganizationId || payloadTenantId !== null || payloadOrganizationId !== null) {\n throw new QueryIndexScopeError(\n 'Query index event for a global entity must explicitly provide tenantId and organizationId as null'\n )\n }\n\n return {\n tenantId: null,\n organizationId: null,\n }\n }\n\n if (sourceScope.kind === 'missing') {\n if (!hasPayloadTenantId || !hasPayloadOrganizationId) {\n throw new QueryIndexScopeError(\n 'Query index event is missing tenantId/organizationId and source row scope could not be resolved'\n )\n }\n\n return {\n tenantId: payloadTenantId ?? null,\n organizationId: payloadOrganizationId ?? null,\n }\n }\n\n const rowScope = sourceScope.scope\n\n if (hasPayloadTenantId && !isSameScopeValue(payloadTenantId, rowScope.tenantId)) {\n throw new QueryIndexScopeError(\n `Query index event tenantId does not match source row scope (payload=${String(payloadTenantId ?? null)}, row=${String(rowScope.tenantId)})`\n )\n }\n\n if (hasPayloadOrganizationId && !isSameScopeValue(payloadOrganizationId, rowScope.organizationId)) {\n throw new QueryIndexScopeError(\n `Query index event organizationId does not match source row scope (payload=${String(payloadOrganizationId ?? null)}, row=${String(rowScope.organizationId)})`\n )\n }\n\n return {\n tenantId: hasPayloadTenantId ? (payloadTenantId ?? null) : rowScope.tenantId,\n organizationId: hasPayloadOrganizationId ? (payloadOrganizationId ?? null) : rowScope.organizationId,\n }\n}\n\nexport function resolveQueryIndexReindexScope(input: ResolveReindexScopeInput): {\n tenantId: string | null | undefined\n organizationId: string | null | undefined\n} {\n if (input.tenantId === undefined && input.allowAllTenants !== true) {\n throw new QueryIndexScopeError(\n 'Query index reindex requires tenantId to be set explicitly; all-tenant reindex must opt in with allowAllTenants=true'\n )\n }\n\n return {\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n }\n}\n\nfunction isSameScopeValue(left: string | null | undefined, right: string | null): boolean {\n return (left ?? null) === right\n}\n\nfunction resolveScopeColumn(metadata: QueryIndexEntityMetadata, property: string, table: string): string | null {\n const scopeProperty = metadata.properties?.[property]\n if (!scopeProperty) return null\n const fieldNames = scopeProperty.fieldNames\n if (!Array.isArray(fieldNames) || fieldNames.length !== 1 || typeof fieldNames[0] !== 'string' || !fieldNames[0]) {\n throw new QueryIndexScopeError(\n `Query index ${property} metadata must map exactly one physical column for table: ${table}`\n )\n }\n return fieldNames[0]\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,9 +1,12 @@
1
1
  import { recordIndexerError } from "@open-mercato/shared/lib/indexers/error-log";
2
- import { resolveEntityTableName } from "@open-mercato/shared/lib/query/engine";
3
2
  import { sql } from "kysely";
4
3
  import { markDeleted } from "../lib/indexer.js";
5
4
  import { applyCoverageAdjustments, createCoverageAdjustments } from "../lib/coverage.js";
6
- import { loadQueryIndexRowScope, resolveQueryIndexRecordScope } from "../lib/subscriber-scope.js";
5
+ import {
6
+ loadQueryIndexRowScope,
7
+ resolveQueryIndexRecordScope,
8
+ resolveQueryIndexSourceMetadata
9
+ } from "../lib/subscriber-scope.js";
7
10
  const metadata = { event: "query_index.delete_one", persistent: false };
8
11
  const DELETE_COVERAGE_THROTTLE_MS = 5 * 60 * 1e3;
9
12
  const lastDeleteCoverageRefreshAt = /* @__PURE__ */ new Map();
@@ -28,13 +31,14 @@ async function handle(payload, ctx) {
28
31
  try {
29
32
  const hasPayloadOrganizationId = Object.prototype.hasOwnProperty.call(payload ?? {}, "organizationId");
30
33
  const hasPayloadTenantId = Object.prototype.hasOwnProperty.call(payload ?? {}, "tenantId");
31
- const rowScope = await loadQueryIndexRowScope(em, entityType, recordId).catch(() => null);
34
+ const source = resolveQueryIndexSourceMetadata(em, entityType);
35
+ const sourceScope = await loadQueryIndexRowScope(em, source, recordId);
32
36
  const resolvedScope = resolveQueryIndexRecordScope({
33
37
  payloadOrganizationId: payload?.organizationId,
34
38
  payloadTenantId: payload?.tenantId,
35
39
  hasPayloadOrganizationId,
36
40
  hasPayloadTenantId,
37
- rowScope
41
+ sourceScope
38
42
  });
39
43
  organizationId = resolvedScope.organizationId;
40
44
  tenantId = resolvedScope.tenantId;
@@ -43,8 +47,18 @@ async function handle(payload, ctx) {
43
47
  let baseCheckSucceeded = false;
44
48
  try {
45
49
  const db = em.getKysely();
46
- const table = resolveEntityTableName(em, entityType);
47
- const row = await db.selectFrom(table).select(["deleted_at"]).where("id", "=", recordId).where("organization_id", organizationId === null ? "is" : "=", organizationId).where(sql`tenant_id is not distinct from ${tenantId}`).executeTakeFirst();
50
+ let baseQuery = db.selectFrom(source.table).select(["deleted_at"]).where("id", "=", recordId);
51
+ if (source.organizationColumn) {
52
+ baseQuery = baseQuery.where(
53
+ source.organizationColumn,
54
+ organizationId === null ? "is" : "=",
55
+ organizationId
56
+ );
57
+ }
58
+ if (source.tenantColumn) {
59
+ baseQuery = baseQuery.where(sql`${sql.ref(source.tenantColumn)} is not distinct from ${tenantId}`);
60
+ }
61
+ const row = await baseQuery.executeTakeFirst();
48
62
  const baseMissing = !row;
49
63
  const baseDeleted = baseMissing || row && row.deleted_at != null;
50
64
  baseCheckSucceeded = true;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/query_index/subscribers/delete_one.ts"],
4
- "sourcesContent": ["import { recordIndexerError } from '@open-mercato/shared/lib/indexers/error-log'\nimport { resolveEntityTableName } from '@open-mercato/shared/lib/query/engine'\nimport { sql } from 'kysely'\nimport { markDeleted } from '../lib/indexer'\nimport { applyCoverageAdjustments, createCoverageAdjustments } from '../lib/coverage'\nimport { loadQueryIndexRowScope, resolveQueryIndexRecordScope } from '../lib/subscriber-scope'\n\nexport const metadata = { event: 'query_index.delete_one', persistent: false }\n\n// Mirrors `shouldTriggerCoverageRefresh()` in `@open-mercato/shared/lib/data/engine.ts`\n// as a small local throttle so per-record deletes stop firing an unconditional, immediate\n// full recount. Note: this Map and the shared engine's own throttle Map are independent,\n// so a rare race right after a 5-minute window resets on both could double-fire once \u2014\n// harmless (the recompute is idempotent) and far cheaper than today's per-delete cost.\nconst DELETE_COVERAGE_THROTTLE_MS = 5 * 60 * 1000\nconst lastDeleteCoverageRefreshAt = new Map<string, number>()\n\nfunction shouldAllowDeleteCoverageRefresh(entityType: string, tenantId: string | null): boolean {\n if (!entityType) return false\n const key = `${entityType}|${tenantId ?? '__null__'}`\n const now = Date.now()\n const last = lastDeleteCoverageRefreshAt.get(key) ?? 0\n if (now - last < DELETE_COVERAGE_THROTTLE_MS) return false\n lastDeleteCoverageRefreshAt.set(key, now)\n return true\n}\n\nexport default async function handle(payload: any, ctx: { resolve: <T=any>(name: string) => T }) {\n // Forked EntityManager \u2014 this awaited subscriber runs synchronously on the request\n // `em`; isolating it prevents our queries/writes from resetting the originating CRUD\n // write's UnitOfWork and dropping its pending changes. See upsert_one.ts for detail.\n const baseEm = ctx.resolve<any>('em')\n const em = typeof baseEm?.fork === 'function' ? baseEm.fork() : baseEm\n const entityType = String(payload?.entityType || '')\n const recordId = String(payload?.recordId || '')\n if (!entityType || !recordId) return\n let organizationId: string | null = payload?.organizationId ?? null\n let tenantId: string | null = payload?.tenantId ?? null\n const coverageDelayMs = typeof payload?.coverageDelayMs === 'number' ? payload.coverageDelayMs : undefined\n try {\n const hasPayloadOrganizationId = Object.prototype.hasOwnProperty.call(payload ?? {}, 'organizationId')\n const hasPayloadTenantId = Object.prototype.hasOwnProperty.call(payload ?? {}, 'tenantId')\n const rowScope = await loadQueryIndexRowScope(em, entityType, recordId).catch(() => null)\n const resolvedScope = resolveQueryIndexRecordScope({\n payloadOrganizationId: payload?.organizationId,\n payloadTenantId: payload?.tenantId,\n hasPayloadOrganizationId,\n hasPayloadTenantId,\n rowScope,\n })\n organizationId = resolvedScope.organizationId\n tenantId = resolvedScope.tenantId\n\n const { wasActive } = await markDeleted(em, { entityType, recordId, organizationId, tenantId })\n\n let baseDelta = 0\n let baseCheckSucceeded = false\n try {\n const db = (em as any).getKysely()\n const table = resolveEntityTableName(em, entityType)\n const row = await db\n .selectFrom(table as any)\n .select(['deleted_at' as any])\n .where('id' as any, '=', recordId)\n .where('organization_id' as any, organizationId === null ? 'is' : '=', organizationId as any)\n .where(sql`tenant_id is not distinct from ${tenantId}`)\n .executeTakeFirst() as { deleted_at: Date | null } | undefined\n const baseMissing = !row\n const baseDeleted = baseMissing || (row && row.deleted_at != null)\n baseCheckSucceeded = true\n if (baseDeleted) baseDelta = -1\n } catch {}\n if (!baseCheckSucceeded) baseDelta = -1\n\n const baseDeltaOverride =\n typeof payload?.coverageBaseDelta === 'number' ? payload.coverageBaseDelta : undefined\n const indexDeltaOverride =\n typeof payload?.coverageIndexDelta === 'number' ? payload.coverageIndexDelta : undefined\n let effectiveBaseDelta = baseDeltaOverride ?? baseDelta\n let effectiveIndexDelta = indexDeltaOverride ?? (wasActive ? -1 : 0)\n\n if (!Number.isFinite(effectiveBaseDelta)) effectiveBaseDelta = 0\n if (!Number.isFinite(effectiveIndexDelta)) effectiveIndexDelta = 0\n\n if (effectiveBaseDelta !== 0 || effectiveIndexDelta !== 0) {\n const adjustments = createCoverageAdjustments({\n entityType,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n baseDelta: effectiveBaseDelta,\n indexDelta: effectiveIndexDelta,\n })\n if (adjustments.length) {\n await applyCoverageAdjustments(em, adjustments)\n }\n }\n\n // The projection row + token removal above are synchronous (the data engine\n // awaits this subscriber) so list reads are consistent immediately. The coverage\n // recompute (a COUNT, run inline when delayMs is 0) and the fulltext delete are\n // secondary, so defer them fire-and-forget to keep write/bulk-delete latency bounded.\n const suppressCoverage = payload?.suppressCoverage === true\n const explicitDelayRequested = typeof payload?.coverageDelayMs === 'number'\n const shouldRefreshCoverage =\n !suppressCoverage &&\n (coverageDelayMs === undefined || coverageDelayMs >= 0) &&\n (explicitDelayRequested || shouldAllowDeleteCoverageRefresh(entityType, tenantId))\n const coverageRefreshDelay = coverageDelayMs ?? 0\n void (async () => {\n try {\n const bus = ctx.resolve<any>('eventBus')\n if (shouldRefreshCoverage) {\n await bus.emitEvent('query_index.coverage.refresh', {\n entityType,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n delayMs: coverageRefreshDelay,\n })\n }\n await bus.emitEvent('search.delete_record', { entityId: entityType, recordId, organizationId, tenantId })\n } catch (error) {\n await recordIndexerError(\n { em },\n {\n source: 'query_index',\n handler: 'event:query_index.delete_one:coverage_search',\n error,\n entityType,\n recordId,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n payload,\n },\n ).catch(() => {})\n }\n })()\n } catch (error) {\n await recordIndexerError(\n { em },\n {\n source: 'query_index',\n handler: 'event:query_index.delete_one',\n error,\n entityType,\n recordId,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n payload,\n },\n )\n throw error\n }\n}\n"],
5
- "mappings": "AAAA,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,WAAW;AACpB,SAAS,mBAAmB;AAC5B,SAAS,0BAA0B,iCAAiC;AACpE,SAAS,wBAAwB,oCAAoC;AAE9D,MAAM,WAAW,EAAE,OAAO,0BAA0B,YAAY,MAAM;AAO7E,MAAM,8BAA8B,IAAI,KAAK;AAC7C,MAAM,8BAA8B,oBAAI,IAAoB;AAE5D,SAAS,iCAAiC,YAAoB,UAAkC;AAC9F,MAAI,CAAC,WAAY,QAAO;AACxB,QAAM,MAAM,GAAG,UAAU,IAAI,YAAY,UAAU;AACnD,QAAM,MAAM,KAAK,IAAI;AACrB,QAAM,OAAO,4BAA4B,IAAI,GAAG,KAAK;AACrD,MAAI,MAAM,OAAO,4BAA6B,QAAO;AACrD,8BAA4B,IAAI,KAAK,GAAG;AACxC,SAAO;AACT;AAEA,eAAO,OAA8B,SAAc,KAA8C;AAI/F,QAAM,SAAS,IAAI,QAAa,IAAI;AACpC,QAAM,KAAK,OAAO,QAAQ,SAAS,aAAa,OAAO,KAAK,IAAI;AAChE,QAAM,aAAa,OAAO,SAAS,cAAc,EAAE;AACnD,QAAM,WAAW,OAAO,SAAS,YAAY,EAAE;AAC/C,MAAI,CAAC,cAAc,CAAC,SAAU;AAC9B,MAAI,iBAAgC,SAAS,kBAAkB;AAC/D,MAAI,WAA0B,SAAS,YAAY;AACnD,QAAM,kBAAkB,OAAO,SAAS,oBAAoB,WAAW,QAAQ,kBAAkB;AACjG,MAAI;AACF,UAAM,2BAA2B,OAAO,UAAU,eAAe,KAAK,WAAW,CAAC,GAAG,gBAAgB;AACrG,UAAM,qBAAqB,OAAO,UAAU,eAAe,KAAK,WAAW,CAAC,GAAG,UAAU;AACzF,UAAM,WAAW,MAAM,uBAAuB,IAAI,YAAY,QAAQ,EAAE,MAAM,MAAM,IAAI;AACxF,UAAM,gBAAgB,6BAA6B;AAAA,MACjD,uBAAuB,SAAS;AAAA,MAChC,iBAAiB,SAAS;AAAA,MAC1B;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AACD,qBAAiB,cAAc;AAC/B,eAAW,cAAc;AAEzB,UAAM,EAAE,UAAU,IAAI,MAAM,YAAY,IAAI,EAAE,YAAY,UAAU,gBAAgB,SAAS,CAAC;AAE9F,QAAI,YAAY;AAChB,QAAI,qBAAqB;AACzB,QAAI;AACF,YAAM,KAAM,GAAW,UAAU;AACjC,YAAM,QAAQ,uBAAuB,IAAI,UAAU;AACnD,YAAM,MAAM,MAAM,GACf,WAAW,KAAY,EACvB,OAAO,CAAC,YAAmB,CAAC,EAC5B,MAAM,MAAa,KAAK,QAAQ,EAChC,MAAM,mBAA0B,mBAAmB,OAAO,OAAO,KAAK,cAAqB,EAC3F,MAAM,qCAAqC,QAAQ,EAAE,EACrD,iBAAiB;AACpB,YAAM,cAAc,CAAC;AACrB,YAAM,cAAc,eAAgB,OAAO,IAAI,cAAc;AAC7D,2BAAqB;AACrB,UAAI,YAAa,aAAY;AAAA,IAC/B,QAAQ;AAAA,IAAC;AACT,QAAI,CAAC,mBAAoB,aAAY;AAErC,UAAM,oBACJ,OAAO,SAAS,sBAAsB,WAAW,QAAQ,oBAAoB;AAC/E,UAAM,qBACJ,OAAO,SAAS,uBAAuB,WAAW,QAAQ,qBAAqB;AACjF,QAAI,qBAAqB,qBAAqB;AAC9C,QAAI,sBAAsB,uBAAuB,YAAY,KAAK;AAElE,QAAI,CAAC,OAAO,SAAS,kBAAkB,EAAG,sBAAqB;AAC/D,QAAI,CAAC,OAAO,SAAS,mBAAmB,EAAG,uBAAsB;AAEjE,QAAI,uBAAuB,KAAK,wBAAwB,GAAG;AACzD,YAAM,cAAc,0BAA0B;AAAA,QAC5C;AAAA,QACA,UAAU,YAAY;AAAA,QACtB,gBAAgB,kBAAkB;AAAA,QAClC,WAAW;AAAA,QACX,YAAY;AAAA,MACd,CAAC;AACD,UAAI,YAAY,QAAQ;AACtB,cAAM,yBAAyB,IAAI,WAAW;AAAA,MAChD;AAAA,IACF;AAMA,UAAM,mBAAmB,SAAS,qBAAqB;AACvD,UAAM,yBAAyB,OAAO,SAAS,oBAAoB;AACnE,UAAM,wBACJ,CAAC,qBACA,oBAAoB,UAAa,mBAAmB,OACpD,0BAA0B,iCAAiC,YAAY,QAAQ;AAClF,UAAM,uBAAuB,mBAAmB;AAChD,UAAM,YAAY;AAChB,UAAI;AACF,cAAM,MAAM,IAAI,QAAa,UAAU;AACvC,YAAI,uBAAuB;AACzB,gBAAM,IAAI,UAAU,gCAAgC;AAAA,YAClD;AAAA,YACA,UAAU,YAAY;AAAA,YACtB,gBAAgB,kBAAkB;AAAA,YAClC,SAAS;AAAA,UACX,CAAC;AAAA,QACH;AACA,cAAM,IAAI,UAAU,wBAAwB,EAAE,UAAU,YAAY,UAAU,gBAAgB,SAAS,CAAC;AAAA,MAC1G,SAAS,OAAO;AACd,cAAM;AAAA,UACJ,EAAE,GAAG;AAAA,UACL;AAAA,YACE,QAAQ;AAAA,YACR,SAAS;AAAA,YACT;AAAA,YACA;AAAA,YACA;AAAA,YACA,UAAU,YAAY;AAAA,YACtB,gBAAgB,kBAAkB;AAAA,YAClC;AAAA,UACF;AAAA,QACF,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,MAClB;AAAA,IACF,GAAG;AAAA,EACL,SAAS,OAAO;AACd,UAAM;AAAA,MACJ,EAAE,GAAG;AAAA,MACL;AAAA,QACE,QAAQ;AAAA,QACR,SAAS;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,QACA,UAAU,YAAY;AAAA,QACtB,gBAAgB,kBAAkB;AAAA,QAClC;AAAA,MACF;AAAA,IACF;AACA,UAAM;AAAA,EACR;AACF;",
4
+ "sourcesContent": ["import { recordIndexerError } from '@open-mercato/shared/lib/indexers/error-log'\nimport { sql } from 'kysely'\nimport { markDeleted } from '../lib/indexer'\nimport { applyCoverageAdjustments, createCoverageAdjustments } from '../lib/coverage'\nimport {\n loadQueryIndexRowScope,\n resolveQueryIndexRecordScope,\n resolveQueryIndexSourceMetadata,\n} from '../lib/subscriber-scope'\n\nexport const metadata = { event: 'query_index.delete_one', persistent: false }\n\n// Mirrors `shouldTriggerCoverageRefresh()` in `@open-mercato/shared/lib/data/engine.ts`\n// as a small local throttle so per-record deletes stop firing an unconditional, immediate\n// full recount. Note: this Map and the shared engine's own throttle Map are independent,\n// so a rare race right after a 5-minute window resets on both could double-fire once \u2014\n// harmless (the recompute is idempotent) and far cheaper than today's per-delete cost.\nconst DELETE_COVERAGE_THROTTLE_MS = 5 * 60 * 1000\nconst lastDeleteCoverageRefreshAt = new Map<string, number>()\n\nfunction shouldAllowDeleteCoverageRefresh(entityType: string, tenantId: string | null): boolean {\n if (!entityType) return false\n const key = `${entityType}|${tenantId ?? '__null__'}`\n const now = Date.now()\n const last = lastDeleteCoverageRefreshAt.get(key) ?? 0\n if (now - last < DELETE_COVERAGE_THROTTLE_MS) return false\n lastDeleteCoverageRefreshAt.set(key, now)\n return true\n}\n\nexport default async function handle(payload: any, ctx: { resolve: <T=any>(name: string) => T }) {\n // Forked EntityManager \u2014 this awaited subscriber runs synchronously on the request\n // `em`; isolating it prevents our queries/writes from resetting the originating CRUD\n // write's UnitOfWork and dropping its pending changes. See upsert_one.ts for detail.\n const baseEm = ctx.resolve<any>('em')\n const em = typeof baseEm?.fork === 'function' ? baseEm.fork() : baseEm\n const entityType = String(payload?.entityType || '')\n const recordId = String(payload?.recordId || '')\n if (!entityType || !recordId) return\n let organizationId: string | null = payload?.organizationId ?? null\n let tenantId: string | null = payload?.tenantId ?? null\n const coverageDelayMs = typeof payload?.coverageDelayMs === 'number' ? payload.coverageDelayMs : undefined\n try {\n const hasPayloadOrganizationId = Object.prototype.hasOwnProperty.call(payload ?? {}, 'organizationId')\n const hasPayloadTenantId = Object.prototype.hasOwnProperty.call(payload ?? {}, 'tenantId')\n const source = resolveQueryIndexSourceMetadata(em, entityType)\n const sourceScope = await loadQueryIndexRowScope(em, source, recordId)\n const resolvedScope = resolveQueryIndexRecordScope({\n payloadOrganizationId: payload?.organizationId,\n payloadTenantId: payload?.tenantId,\n hasPayloadOrganizationId,\n hasPayloadTenantId,\n sourceScope,\n })\n organizationId = resolvedScope.organizationId\n tenantId = resolvedScope.tenantId\n\n const { wasActive } = await markDeleted(em, { entityType, recordId, organizationId, tenantId })\n\n let baseDelta = 0\n let baseCheckSucceeded = false\n try {\n const db = (em as any).getKysely()\n let baseQuery = db\n .selectFrom(source.table as any)\n .select(['deleted_at' as any])\n .where('id' as any, '=', recordId)\n if (source.organizationColumn) {\n baseQuery = baseQuery.where(\n source.organizationColumn as any,\n organizationId === null ? 'is' : '=',\n organizationId as any,\n )\n }\n if (source.tenantColumn) {\n baseQuery = baseQuery.where(sql`${sql.ref(source.tenantColumn)} is not distinct from ${tenantId}`)\n }\n const row = await baseQuery.executeTakeFirst() as { deleted_at: Date | null } | undefined\n const baseMissing = !row\n const baseDeleted = baseMissing || (row && row.deleted_at != null)\n baseCheckSucceeded = true\n if (baseDeleted) baseDelta = -1\n } catch {}\n if (!baseCheckSucceeded) baseDelta = -1\n\n const baseDeltaOverride =\n typeof payload?.coverageBaseDelta === 'number' ? payload.coverageBaseDelta : undefined\n const indexDeltaOverride =\n typeof payload?.coverageIndexDelta === 'number' ? payload.coverageIndexDelta : undefined\n let effectiveBaseDelta = baseDeltaOverride ?? baseDelta\n let effectiveIndexDelta = indexDeltaOverride ?? (wasActive ? -1 : 0)\n\n if (!Number.isFinite(effectiveBaseDelta)) effectiveBaseDelta = 0\n if (!Number.isFinite(effectiveIndexDelta)) effectiveIndexDelta = 0\n\n if (effectiveBaseDelta !== 0 || effectiveIndexDelta !== 0) {\n const adjustments = createCoverageAdjustments({\n entityType,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n baseDelta: effectiveBaseDelta,\n indexDelta: effectiveIndexDelta,\n })\n if (adjustments.length) {\n await applyCoverageAdjustments(em, adjustments)\n }\n }\n\n // The projection row + token removal above are synchronous (the data engine\n // awaits this subscriber) so list reads are consistent immediately. The coverage\n // recompute (a COUNT, run inline when delayMs is 0) and the fulltext delete are\n // secondary, so defer them fire-and-forget to keep write/bulk-delete latency bounded.\n const suppressCoverage = payload?.suppressCoverage === true\n const explicitDelayRequested = typeof payload?.coverageDelayMs === 'number'\n const shouldRefreshCoverage =\n !suppressCoverage &&\n (coverageDelayMs === undefined || coverageDelayMs >= 0) &&\n (explicitDelayRequested || shouldAllowDeleteCoverageRefresh(entityType, tenantId))\n const coverageRefreshDelay = coverageDelayMs ?? 0\n void (async () => {\n try {\n const bus = ctx.resolve<any>('eventBus')\n if (shouldRefreshCoverage) {\n await bus.emitEvent('query_index.coverage.refresh', {\n entityType,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n delayMs: coverageRefreshDelay,\n })\n }\n await bus.emitEvent('search.delete_record', { entityId: entityType, recordId, organizationId, tenantId })\n } catch (error) {\n await recordIndexerError(\n { em },\n {\n source: 'query_index',\n handler: 'event:query_index.delete_one:coverage_search',\n error,\n entityType,\n recordId,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n payload,\n },\n ).catch(() => {})\n }\n })()\n } catch (error) {\n await recordIndexerError(\n { em },\n {\n source: 'query_index',\n handler: 'event:query_index.delete_one',\n error,\n entityType,\n recordId,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n payload,\n },\n )\n throw error\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,11 @@
1
1
  import { recordIndexerError } from "@open-mercato/shared/lib/indexers/error-log";
2
2
  import { upsertIndexRow, reindexSearchTokensForRecord } from "../lib/indexer.js";
3
3
  import { applyCoverageAdjustments, createCoverageAdjustments } from "../lib/coverage.js";
4
- import { loadQueryIndexRowScope, resolveQueryIndexRecordScope } from "../lib/subscriber-scope.js";
4
+ import {
5
+ loadQueryIndexRowScope,
6
+ resolveQueryIndexRecordScope,
7
+ resolveQueryIndexSourceMetadata
8
+ } from "../lib/subscriber-scope.js";
5
9
  const metadata = { event: "query_index.upsert_one", persistent: false };
6
10
  async function handle(payload, ctx) {
7
11
  const baseEm = ctx.resolve("em");
@@ -16,13 +20,14 @@ async function handle(payload, ctx) {
16
20
  try {
17
21
  const hasPayloadOrganizationId = Object.prototype.hasOwnProperty.call(payload ?? {}, "organizationId");
18
22
  const hasPayloadTenantId = Object.prototype.hasOwnProperty.call(payload ?? {}, "tenantId");
19
- const rowScope = await loadQueryIndexRowScope(em, entityType, recordId).catch(() => null);
23
+ const source = resolveQueryIndexSourceMetadata(em, entityType);
24
+ const sourceScope = await loadQueryIndexRowScope(em, source, recordId);
20
25
  const resolvedScope = resolveQueryIndexRecordScope({
21
26
  payloadOrganizationId: payload?.organizationId,
22
27
  payloadTenantId: payload?.tenantId,
23
28
  hasPayloadOrganizationId,
24
29
  hasPayloadTenantId,
25
- rowScope
30
+ sourceScope
26
31
  });
27
32
  organizationId = resolvedScope.organizationId;
28
33
  tenantId = resolvedScope.tenantId;