@open-mercato/shared 0.6.7-develop.6600.1.49a5df927f → 0.6.7-develop.6604.1.2c49816dff
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/crud/cache.js +3 -5
- package/dist/lib/crud/cache.js.map +2 -2
- package/dist/lib/crud/factory.js +4 -3
- package/dist/lib/crud/factory.js.map +2 -2
- package/dist/lib/crud/ids.js +11 -2
- package/dist/lib/crud/ids.js.map +2 -2
- package/dist/lib/version.js +1 -1
- package/dist/lib/version.js.map +1 -1
- package/package.json +2 -2
- package/src/lib/crud/__tests__/cache.test.ts +53 -1
- package/src/lib/crud/__tests__/ids.test.ts +31 -1
- package/src/lib/crud/cache.ts +8 -5
- package/src/lib/crud/factory.ts +4 -3
- package/src/lib/crud/ids.ts +22 -1
package/dist/lib/crud/cache.js
CHANGED
|
@@ -144,11 +144,9 @@ async function invalidateCrudCache(container, resource, identifiers, fallbackTen
|
|
|
144
144
|
if (recordId) {
|
|
145
145
|
tags.add(buildRecordTag(key, tenantId, recordId));
|
|
146
146
|
}
|
|
147
|
-
const organizationIds = [];
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
if (!organizationIds.length) organizationIds.push(null);
|
|
147
|
+
const organizationIds = [null];
|
|
148
|
+
const recordOrganizationId = identifiers.organizationId ?? null;
|
|
149
|
+
if (recordOrganizationId) organizationIds.push(recordOrganizationId);
|
|
152
150
|
for (const tag of buildCollectionTags(key, tenantId, organizationIds)) {
|
|
153
151
|
tags.add(tag);
|
|
154
152
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/crud/cache.ts"],
|
|
4
|
-
"sourcesContent": ["import type { AwilixContainer } from 'awilix'\nimport { runWithCacheTenant, type CacheStrategy } from '@open-mercato/cache'\nimport { parseBooleanToken } from '../boolean'\nimport { createLogger } from '../logger'\n\nconst logger = createLogger('shared').child({ component: 'crud-cache' })\n\nexport type CrudCacheIdentifiers = {\n id?: string | null\n organizationId?: string | null\n tenantId?: string | null\n}\n\nlet crudCacheEnabledFlag: boolean | null = null\nexport function isCrudCacheEnabled(): boolean {\n if (crudCacheEnabledFlag !== null) return crudCacheEnabledFlag\n crudCacheEnabledFlag = parseBooleanToken(process.env.ENABLE_CRUD_API_CACHE ?? '') === true\n return crudCacheEnabledFlag\n}\n\nlet crudCacheDebugFlag: boolean | null = null\nexport function isCrudCacheDebugEnabled(): boolean {\n if (crudCacheDebugFlag !== null) return crudCacheDebugFlag\n crudCacheDebugFlag = parseBooleanToken(process.env.QUERY_ENGINE_DEBUG_SQL ?? '') === true\n return crudCacheDebugFlag\n}\n\nexport function debugCrudCache(event: string, context: Record<string, unknown>) {\n if (!isCrudCacheDebugEnabled()) return\n try {\n logger.debug(event, context)\n } catch {}\n}\n\nexport function resolveCrudCache(container: AwilixContainer): CacheStrategy | null {\n try {\n const cache = (container.resolve('cache') as CacheStrategy)\n if (cache && typeof cache.get === 'function' && typeof cache.set === 'function') {\n return cache\n }\n } catch {}\n return null\n}\n\nexport function normalizeTagSegment(value: string | null | undefined): string {\n if (value === null || value === undefined || value === '') return 'null'\n return value.toString().trim().replace(/[^a-zA-Z0-9._-]/g, '-')\n}\n\nexport function canonicalizeResourceTag(value: string | null | undefined): string | null {\n if (value === null || value === undefined) return null\n const trimmed = String(value).trim()\n if (!trimmed.length) return null\n const withSeparators = trimmed\n .replace(/::/g, '.')\n .replace(/[/\\\\]+/g, '.')\n .replace(/[\\s]+/g, '.')\n .replace(/_/g, '.')\n .replace(/-+/g, '.')\n const withCamelBreaks = withSeparators.replace(/([a-z0-9])([A-Z])/g, '$1.$2')\n const collapsed = withCamelBreaks.replace(/\\.{2,}/g, '.').replace(/(?:^\\.+|\\.+$)/g, '')\n const lowered = collapsed.toLowerCase()\n return lowered.length ? lowered : null\n}\n\nexport function buildRecordTag(resource: string, tenantId: string | null, recordId: string): string {\n return [\n 'crud',\n normalizeTagSegment(resource),\n 'tenant',\n normalizeTagSegment(tenantId),\n 'record',\n normalizeTagSegment(recordId),\n ].join(':')\n}\n\nexport function buildCollectionTags(\n resource: string,\n tenantId: string | null,\n organizationIds: Array<string | null>\n): string[] {\n const normalizedResource = normalizeTagSegment(resource)\n const normalizedTenant = normalizeTagSegment(tenantId)\n const tags = new Set<string>()\n if (!organizationIds.length) {\n tags.add(['crud', normalizedResource, 'tenant', normalizedTenant, 'org', 'null', 'collection'].join(':'))\n return Array.from(tags)\n }\n for (const orgId of organizationIds) {\n tags.add(['crud', normalizedResource, 'tenant', normalizedTenant, 'org', normalizeTagSegment(orgId), 'collection'].join(':'))\n }\n return Array.from(tags)\n}\n\nexport function normalizeIdentifierValue(value: unknown): string | null {\n if (value === null || value === undefined) return null\n if (typeof value === 'string') return value\n if (typeof value === 'number' || typeof value === 'bigint') return String(value)\n if (typeof value === 'object') {\n if (value instanceof Date) return value.toISOString()\n if (value && typeof (value as { id?: unknown }).id !== 'undefined') {\n return normalizeIdentifierValue((value as { id?: unknown }).id)\n }\n }\n return String(value)\n}\n\nexport function pickFirstIdentifier(...values: Array<unknown>): string | null {\n for (const value of values) {\n const normalized = normalizeIdentifierValue(value)\n if (normalized) return normalized\n }\n return null\n}\n\nconst IRREGULAR_PLURALS: Record<string, string> = {\n people: 'person',\n children: 'child',\n mice: 'mouse',\n men: 'man',\n women: 'woman',\n geese: 'goose',\n feet: 'foot',\n teeth: 'tooth',\n oxen: 'ox',\n}\n\nfunction singularizeSegment(segment: string): string {\n const lower = segment.toLowerCase()\n const irregular = IRREGULAR_PLURALS[lower]\n if (irregular) return irregular\n if (lower.endsWith('ies') && lower.length > 3) return lower.slice(0, -3) + 'y'\n if (lower.endsWith('ses') && lower.length > 3) return lower.slice(0, -2)\n if (\n (lower.endsWith('xes') ||\n lower.endsWith('zes') ||\n lower.endsWith('ches') ||\n lower.endsWith('shes')) &&\n lower.length > 3\n ) {\n return lower.slice(0, -2)\n }\n if (lower.endsWith('s') && !lower.endsWith('ss') && lower.length > 1) return lower.slice(0, -1)\n return lower\n}\n\nfunction singularizeResourceSegment(segment: string): string {\n return segment\n .split('-')\n .map((part) => singularizeSegment(part))\n .join('-')\n}\n\nexport function deriveResourceFromCommandId(commandId: string | undefined | null): string | null {\n if (!commandId || typeof commandId !== 'string') return null\n const parts = commandId.split('.')\n if (parts.length < 2) return null\n const modulePart = parts[0]\n const entityPart = singularizeResourceSegment(parts[1])\n if (!modulePart || !entityPart) return null\n return `${modulePart}.${entityPart}`\n}\n\nexport function expandResourceAliases(resource: string, aliases?: string[]): string[] {\n const set = new Set<string>()\n const inputs = [resource, ...(aliases ?? [])]\n for (const candidate of inputs) {\n const canonical = canonicalizeResourceTag(candidate)\n if (canonical) set.add(canonical)\n }\n if (!set.size) return []\n return Array.from(set)\n}\n\nexport async function invalidateCrudCache(\n container: AwilixContainer,\n resource: string,\n identifiers: CrudCacheIdentifiers,\n fallbackTenant: string | null,\n reason: string,\n aliases?: string[]\n): Promise<void> {\n if (!isCrudCacheEnabled()) return\n const cache = resolveCrudCache(container)\n if (!cache || typeof cache.deleteByTags !== 'function') return\n const resources = expandResourceAliases(resource, aliases)\n const tenantId = identifiers.tenantId ?? fallbackTenant ?? null\n const recordId = normalizeIdentifierValue(identifiers.id)\n const tags = new Set<string>()\n for (const key of resources) {\n if (recordId) {\n tags.add(buildRecordTag(key, tenantId, recordId))\n }\n const organizationIds: Array<string | null> = []\n
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import type { AwilixContainer } from 'awilix'\nimport { runWithCacheTenant, type CacheStrategy } from '@open-mercato/cache'\nimport { parseBooleanToken } from '../boolean'\nimport { createLogger } from '../logger'\n\nconst logger = createLogger('shared').child({ component: 'crud-cache' })\n\nexport type CrudCacheIdentifiers = {\n id?: string | null\n organizationId?: string | null\n tenantId?: string | null\n}\n\nlet crudCacheEnabledFlag: boolean | null = null\nexport function isCrudCacheEnabled(): boolean {\n if (crudCacheEnabledFlag !== null) return crudCacheEnabledFlag\n crudCacheEnabledFlag = parseBooleanToken(process.env.ENABLE_CRUD_API_CACHE ?? '') === true\n return crudCacheEnabledFlag\n}\n\nlet crudCacheDebugFlag: boolean | null = null\nexport function isCrudCacheDebugEnabled(): boolean {\n if (crudCacheDebugFlag !== null) return crudCacheDebugFlag\n crudCacheDebugFlag = parseBooleanToken(process.env.QUERY_ENGINE_DEBUG_SQL ?? '') === true\n return crudCacheDebugFlag\n}\n\nexport function debugCrudCache(event: string, context: Record<string, unknown>) {\n if (!isCrudCacheDebugEnabled()) return\n try {\n logger.debug(event, context)\n } catch {}\n}\n\nexport function resolveCrudCache(container: AwilixContainer): CacheStrategy | null {\n try {\n const cache = (container.resolve('cache') as CacheStrategy)\n if (cache && typeof cache.get === 'function' && typeof cache.set === 'function') {\n return cache\n }\n } catch {}\n return null\n}\n\nexport function normalizeTagSegment(value: string | null | undefined): string {\n if (value === null || value === undefined || value === '') return 'null'\n return value.toString().trim().replace(/[^a-zA-Z0-9._-]/g, '-')\n}\n\nexport function canonicalizeResourceTag(value: string | null | undefined): string | null {\n if (value === null || value === undefined) return null\n const trimmed = String(value).trim()\n if (!trimmed.length) return null\n const withSeparators = trimmed\n .replace(/::/g, '.')\n .replace(/[/\\\\]+/g, '.')\n .replace(/[\\s]+/g, '.')\n .replace(/_/g, '.')\n .replace(/-+/g, '.')\n const withCamelBreaks = withSeparators.replace(/([a-z0-9])([A-Z])/g, '$1.$2')\n const collapsed = withCamelBreaks.replace(/\\.{2,}/g, '.').replace(/(?:^\\.+|\\.+$)/g, '')\n const lowered = collapsed.toLowerCase()\n return lowered.length ? lowered : null\n}\n\nexport function buildRecordTag(resource: string, tenantId: string | null, recordId: string): string {\n return [\n 'crud',\n normalizeTagSegment(resource),\n 'tenant',\n normalizeTagSegment(tenantId),\n 'record',\n normalizeTagSegment(recordId),\n ].join(':')\n}\n\nexport function buildCollectionTags(\n resource: string,\n tenantId: string | null,\n organizationIds: Array<string | null>\n): string[] {\n const normalizedResource = normalizeTagSegment(resource)\n const normalizedTenant = normalizeTagSegment(tenantId)\n const tags = new Set<string>()\n if (!organizationIds.length) {\n tags.add(['crud', normalizedResource, 'tenant', normalizedTenant, 'org', 'null', 'collection'].join(':'))\n return Array.from(tags)\n }\n for (const orgId of organizationIds) {\n tags.add(['crud', normalizedResource, 'tenant', normalizedTenant, 'org', normalizeTagSegment(orgId), 'collection'].join(':'))\n }\n return Array.from(tags)\n}\n\nexport function normalizeIdentifierValue(value: unknown): string | null {\n if (value === null || value === undefined) return null\n if (typeof value === 'string') return value\n if (typeof value === 'number' || typeof value === 'bigint') return String(value)\n if (typeof value === 'object') {\n if (value instanceof Date) return value.toISOString()\n if (value && typeof (value as { id?: unknown }).id !== 'undefined') {\n return normalizeIdentifierValue((value as { id?: unknown }).id)\n }\n }\n return String(value)\n}\n\nexport function pickFirstIdentifier(...values: Array<unknown>): string | null {\n for (const value of values) {\n const normalized = normalizeIdentifierValue(value)\n if (normalized) return normalized\n }\n return null\n}\n\nconst IRREGULAR_PLURALS: Record<string, string> = {\n people: 'person',\n children: 'child',\n mice: 'mouse',\n men: 'man',\n women: 'woman',\n geese: 'goose',\n feet: 'foot',\n teeth: 'tooth',\n oxen: 'ox',\n}\n\nfunction singularizeSegment(segment: string): string {\n const lower = segment.toLowerCase()\n const irregular = IRREGULAR_PLURALS[lower]\n if (irregular) return irregular\n if (lower.endsWith('ies') && lower.length > 3) return lower.slice(0, -3) + 'y'\n if (lower.endsWith('ses') && lower.length > 3) return lower.slice(0, -2)\n if (\n (lower.endsWith('xes') ||\n lower.endsWith('zes') ||\n lower.endsWith('ches') ||\n lower.endsWith('shes')) &&\n lower.length > 3\n ) {\n return lower.slice(0, -2)\n }\n if (lower.endsWith('s') && !lower.endsWith('ss') && lower.length > 1) return lower.slice(0, -1)\n return lower\n}\n\nfunction singularizeResourceSegment(segment: string): string {\n return segment\n .split('-')\n .map((part) => singularizeSegment(part))\n .join('-')\n}\n\nexport function deriveResourceFromCommandId(commandId: string | undefined | null): string | null {\n if (!commandId || typeof commandId !== 'string') return null\n const parts = commandId.split('.')\n if (parts.length < 2) return null\n const modulePart = parts[0]\n const entityPart = singularizeResourceSegment(parts[1])\n if (!modulePart || !entityPart) return null\n return `${modulePart}.${entityPart}`\n}\n\nexport function expandResourceAliases(resource: string, aliases?: string[]): string[] {\n const set = new Set<string>()\n const inputs = [resource, ...(aliases ?? [])]\n for (const candidate of inputs) {\n const canonical = canonicalizeResourceTag(candidate)\n if (canonical) set.add(canonical)\n }\n if (!set.size) return []\n return Array.from(set)\n}\n\nexport async function invalidateCrudCache(\n container: AwilixContainer,\n resource: string,\n identifiers: CrudCacheIdentifiers,\n fallbackTenant: string | null,\n reason: string,\n aliases?: string[]\n): Promise<void> {\n if (!isCrudCacheEnabled()) return\n const cache = resolveCrudCache(container)\n if (!cache || typeof cache.deleteByTags !== 'function') return\n const resources = expandResourceAliases(resource, aliases)\n const tenantId = identifiers.tenantId ?? fallbackTenant ?? null\n const recordId = normalizeIdentifierValue(identifiers.id)\n const tags = new Set<string>()\n for (const key of resources) {\n if (recordId) {\n tags.add(buildRecordTag(key, tenantId, recordId))\n }\n // Always flush the tenant-level (org:null) collection tag alongside the\n // write's own org. Tenant-scoped entities (orgField: null \u2014 e.g. auth roles)\n // cache their collections under org:null, but the command bus resolves a\n // write's organizationId from the actor's auth context, so the org-specific\n // tag alone never matches those entries and they only expire on TTL (#2919).\n const organizationIds: Array<string | null> = [null]\n const recordOrganizationId = identifiers.organizationId ?? null\n if (recordOrganizationId) organizationIds.push(recordOrganizationId)\n for (const tag of buildCollectionTags(key, tenantId, organizationIds)) {\n tags.add(tag)\n }\n }\n if (!tags.size) return\n const tagList = Array.from(tags)\n debugCrudCache('invalidate', {\n resource,\n aliases: resources,\n reason,\n tenantId: tenantId ?? 'null',\n tags: tagList,\n action: 'clearing',\n })\n const deleted = await runWithCacheTenant(tenantId, () => cache.deleteByTags(tagList))\n debugCrudCache('invalidate', {\n resource,\n reason,\n tenantId: tenantId ?? 'null',\n tags: tagList,\n action: 'cleared',\n deleted,\n })\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/lib/crud/factory.js
CHANGED
|
@@ -45,7 +45,7 @@ import { createProfiler, shouldEnableProfiler } from "@open-mercato/shared/lib/p
|
|
|
45
45
|
import { getTranslationOverlayPlugin } from "@open-mercato/shared/lib/localization/overlay-plugin";
|
|
46
46
|
import { applyResponseEnrichers, applyResponseEnricherToRecord, resolveListCacheEnricherPlan } from "./enricher-runner.js";
|
|
47
47
|
import { runApiInterceptorsAfter, runApiInterceptorsBefore } from "./interceptor-runner.js";
|
|
48
|
-
import { mergeIdFilter, parseIdsParam } from "./ids.js";
|
|
48
|
+
import { mergeIdFilter, parseIdsParam, isIdsParamProvided } from "./ids.js";
|
|
49
49
|
import { mergeAdvancedFilters } from "./advanced-filter-integration.js";
|
|
50
50
|
import { parseExtensionHeaders } from "../umes/extension-headers.js";
|
|
51
51
|
import { createGenericOptimisticLockReader } from "./optimistic-lock.js";
|
|
@@ -975,6 +975,7 @@ function makeCrudRoute(opts) {
|
|
|
975
975
|
...interceptorRequest.query ?? {}
|
|
976
976
|
};
|
|
977
977
|
const parsedIds = parseIdsParam(queryParams.ids);
|
|
978
|
+
const idsParamProvided = isIdsParamProvided(queryParams.ids);
|
|
978
979
|
await opts.hooks?.beforeList?.(validated, ctx);
|
|
979
980
|
profiler.mark("before_list_hook");
|
|
980
981
|
const availableFormats = resolveAvailableExportFormats(opts.list);
|
|
@@ -1155,7 +1156,7 @@ function makeCrudRoute(opts) {
|
|
|
1155
1156
|
const page = exportRequested ? { page: 1, pageSize: exportPageSize } : { page: requestedPage, pageSize: requestedPageSize };
|
|
1156
1157
|
const baseFilters = exportFullRequested ? {} : opts.list.buildFilters ? await opts.list.buildFilters(validated, ctx) : {};
|
|
1157
1158
|
const filters = exportFullRequested ? baseFilters : mergeAdvancedFilters(baseFilters, validated);
|
|
1158
|
-
const mergedFilters = exportFullRequested ? filters : mergeIdFilter(filters, parsedIds);
|
|
1159
|
+
const mergedFilters = exportFullRequested ? filters : mergeIdFilter(filters, parsedIds, { idsParamProvided });
|
|
1159
1160
|
const withDeleted = parseBooleanToken(queryParams.withDeleted) === true;
|
|
1160
1161
|
profiler.mark("filters_ready", { withDeleted });
|
|
1161
1162
|
if (ormCfg.orgField && ctx.organizationIds && ctx.organizationIds.length === 0 && !opts.list?.omitAutomaticTenantOrgScope) {
|
|
@@ -1412,7 +1413,7 @@ function makeCrudRoute(opts) {
|
|
|
1412
1413
|
}
|
|
1413
1414
|
const fallbackBaseFilters = exportFullRequested ? {} : opts.list.buildFilters ? await opts.list.buildFilters(validated, ctx) : {};
|
|
1414
1415
|
const fallbackFilters = exportFullRequested ? fallbackBaseFilters : mergeAdvancedFilters(fallbackBaseFilters, validated);
|
|
1415
|
-
const mergedFallbackFilters = exportFullRequested ? fallbackFilters : mergeIdFilter(fallbackFilters, parsedIds);
|
|
1416
|
+
const mergedFallbackFilters = exportFullRequested ? fallbackFilters : mergeIdFilter(fallbackFilters, parsedIds, { idsParamProvided });
|
|
1416
1417
|
const ormFilters = translateFiltersForOrm(
|
|
1417
1418
|
mergedFallbackFilters,
|
|
1418
1419
|
em,
|