@open-mercato/search 0.4.2-canary-c02407ff85
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/AGENTS.md +678 -0
- package/build.mjs +92 -0
- package/dist/di.js +157 -0
- package/dist/di.js.map +7 -0
- package/dist/fulltext/drivers/index.js +21 -0
- package/dist/fulltext/drivers/index.js.map +7 -0
- package/dist/fulltext/drivers/meilisearch/index.js +320 -0
- package/dist/fulltext/drivers/meilisearch/index.js.map +7 -0
- package/dist/fulltext/index.js +7 -0
- package/dist/fulltext/index.js.map +7 -0
- package/dist/fulltext/types.js +1 -0
- package/dist/fulltext/types.js.map +7 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +7 -0
- package/dist/indexer/index.js +8 -0
- package/dist/indexer/index.js.map +7 -0
- package/dist/indexer/search-indexer.js +848 -0
- package/dist/indexer/search-indexer.js.map +7 -0
- package/dist/indexer/subscribers/delete.js +41 -0
- package/dist/indexer/subscribers/delete.js.map +7 -0
- package/dist/lib/debug.js +34 -0
- package/dist/lib/debug.js.map +7 -0
- package/dist/lib/fallback-presenter.js +107 -0
- package/dist/lib/fallback-presenter.js.map +7 -0
- package/dist/lib/field-policy.js +75 -0
- package/dist/lib/field-policy.js.map +7 -0
- package/dist/lib/index.js +19 -0
- package/dist/lib/index.js.map +7 -0
- package/dist/lib/merger.js +93 -0
- package/dist/lib/merger.js.map +7 -0
- package/dist/lib/presenter-enricher.js +192 -0
- package/dist/lib/presenter-enricher.js.map +7 -0
- package/dist/modules/search/acl.js +14 -0
- package/dist/modules/search/acl.js.map +7 -0
- package/dist/modules/search/ai-tools.js +284 -0
- package/dist/modules/search/ai-tools.js.map +7 -0
- package/dist/modules/search/api/embeddings/reindex/cancel/route.js +65 -0
- package/dist/modules/search/api/embeddings/reindex/cancel/route.js.map +7 -0
- package/dist/modules/search/api/embeddings/reindex/route.js +165 -0
- package/dist/modules/search/api/embeddings/reindex/route.js.map +7 -0
- package/dist/modules/search/api/embeddings/route.js +246 -0
- package/dist/modules/search/api/embeddings/route.js.map +7 -0
- package/dist/modules/search/api/index/route.js +245 -0
- package/dist/modules/search/api/index/route.js.map +7 -0
- package/dist/modules/search/api/reindex/cancel/route.js +65 -0
- package/dist/modules/search/api/reindex/cancel/route.js.map +7 -0
- package/dist/modules/search/api/reindex/route.js +332 -0
- package/dist/modules/search/api/reindex/route.js.map +7 -0
- package/dist/modules/search/api/search/global/route.js +100 -0
- package/dist/modules/search/api/search/global/route.js.map +7 -0
- package/dist/modules/search/api/search/route.js +101 -0
- package/dist/modules/search/api/search/route.js.map +7 -0
- package/dist/modules/search/api/settings/fulltext/route.js +55 -0
- package/dist/modules/search/api/settings/fulltext/route.js.map +7 -0
- package/dist/modules/search/api/settings/global-search/route.js +80 -0
- package/dist/modules/search/api/settings/global-search/route.js.map +7 -0
- package/dist/modules/search/api/settings/route.js +118 -0
- package/dist/modules/search/api/settings/route.js.map +7 -0
- package/dist/modules/search/api/settings/vector-store/route.js +77 -0
- package/dist/modules/search/api/settings/vector-store/route.js.map +7 -0
- package/dist/modules/search/backend/config/search/page.js +10 -0
- package/dist/modules/search/backend/config/search/page.js.map +7 -0
- package/dist/modules/search/backend/config/search/page.meta.js +24 -0
- package/dist/modules/search/backend/config/search/page.meta.js.map +7 -0
- package/dist/modules/search/cli.js +698 -0
- package/dist/modules/search/cli.js.map +7 -0
- package/dist/modules/search/di.js +32 -0
- package/dist/modules/search/di.js.map +7 -0
- package/dist/modules/search/frontend/components/GlobalSearchDialog.js +357 -0
- package/dist/modules/search/frontend/components/GlobalSearchDialog.js.map +7 -0
- package/dist/modules/search/frontend/components/HybridSearchTable.js +343 -0
- package/dist/modules/search/frontend/components/HybridSearchTable.js.map +7 -0
- package/dist/modules/search/frontend/components/SearchSettingsPageClient.js +303 -0
- package/dist/modules/search/frontend/components/SearchSettingsPageClient.js.map +7 -0
- package/dist/modules/search/frontend/components/sections/FulltextSearchSection.js +360 -0
- package/dist/modules/search/frontend/components/sections/FulltextSearchSection.js.map +7 -0
- package/dist/modules/search/frontend/components/sections/GlobalSearchSection.js +101 -0
- package/dist/modules/search/frontend/components/sections/GlobalSearchSection.js.map +7 -0
- package/dist/modules/search/frontend/components/sections/VectorSearchSection.js +608 -0
- package/dist/modules/search/frontend/components/sections/VectorSearchSection.js.map +7 -0
- package/dist/modules/search/frontend/index.js +9 -0
- package/dist/modules/search/frontend/index.js.map +7 -0
- package/dist/modules/search/frontend/utils.js +41 -0
- package/dist/modules/search/frontend/utils.js.map +7 -0
- package/dist/modules/search/i18n/de.json +61 -0
- package/dist/modules/search/i18n/en.json +72 -0
- package/dist/modules/search/i18n/es.json +61 -0
- package/dist/modules/search/i18n/pl.json +61 -0
- package/dist/modules/search/index.js +11 -0
- package/dist/modules/search/index.js.map +7 -0
- package/dist/modules/search/lib/auto-indexing.js +29 -0
- package/dist/modules/search/lib/auto-indexing.js.map +7 -0
- package/dist/modules/search/lib/embedding-config.js +131 -0
- package/dist/modules/search/lib/embedding-config.js.map +7 -0
- package/dist/modules/search/lib/global-search-config.js +45 -0
- package/dist/modules/search/lib/global-search-config.js.map +7 -0
- package/dist/modules/search/lib/reindex-lock.js +99 -0
- package/dist/modules/search/lib/reindex-lock.js.map +7 -0
- package/dist/modules/search/subscribers/fulltext_upsert.js +64 -0
- package/dist/modules/search/subscribers/fulltext_upsert.js.map +7 -0
- package/dist/modules/search/subscribers/vector_delete.js +58 -0
- package/dist/modules/search/subscribers/vector_delete.js.map +7 -0
- package/dist/modules/search/subscribers/vector_purge.js +142 -0
- package/dist/modules/search/subscribers/vector_purge.js.map +7 -0
- package/dist/modules/search/subscribers/vector_upsert.js +58 -0
- package/dist/modules/search/subscribers/vector_upsert.js.map +7 -0
- package/dist/modules/search/workers/fulltext-index.worker.js +240 -0
- package/dist/modules/search/workers/fulltext-index.worker.js.map +7 -0
- package/dist/modules/search/workers/vector-index.worker.js +234 -0
- package/dist/modules/search/workers/vector-index.worker.js.map +7 -0
- package/dist/queue/fulltext-indexing.js +15 -0
- package/dist/queue/fulltext-indexing.js.map +7 -0
- package/dist/queue/index.js +3 -0
- package/dist/queue/index.js.map +7 -0
- package/dist/queue/vector-indexing.js +15 -0
- package/dist/queue/vector-indexing.js.map +7 -0
- package/dist/service.js +286 -0
- package/dist/service.js.map +7 -0
- package/dist/strategies/fulltext.strategy.js +116 -0
- package/dist/strategies/fulltext.strategy.js.map +7 -0
- package/dist/strategies/index.js +12 -0
- package/dist/strategies/index.js.map +7 -0
- package/dist/strategies/token.strategy.js +80 -0
- package/dist/strategies/token.strategy.js.map +7 -0
- package/dist/strategies/vector.strategy.js +137 -0
- package/dist/strategies/vector.strategy.js.map +7 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +7 -0
- package/dist/vector/drivers/chromadb/index.js +44 -0
- package/dist/vector/drivers/chromadb/index.js.map +7 -0
- package/dist/vector/drivers/index.js +9 -0
- package/dist/vector/drivers/index.js.map +7 -0
- package/dist/vector/drivers/pgvector/index.js +509 -0
- package/dist/vector/drivers/pgvector/index.js.map +7 -0
- package/dist/vector/drivers/qdrant/index.js +44 -0
- package/dist/vector/drivers/qdrant/index.js.map +7 -0
- package/dist/vector/index.js +4 -0
- package/dist/vector/index.js.map +7 -0
- package/dist/vector/lib/vector-logs.js +33 -0
- package/dist/vector/lib/vector-logs.js.map +7 -0
- package/dist/vector/services/checksum.js +20 -0
- package/dist/vector/services/checksum.js.map +7 -0
- package/dist/vector/services/embedding.js +222 -0
- package/dist/vector/services/embedding.js.map +7 -0
- package/dist/vector/services/index.js +4 -0
- package/dist/vector/services/index.js.map +7 -0
- package/dist/vector/services/vector-index.service.js +960 -0
- package/dist/vector/services/vector-index.service.js.map +7 -0
- package/dist/vector/types/pg.d.js +1 -0
- package/dist/vector/types/pg.d.js.map +7 -0
- package/dist/vector/types.js +75 -0
- package/dist/vector/types.js.map +7 -0
- package/jest.config.cjs +19 -0
- package/package.json +142 -0
- package/src/__tests__/queue.test.ts +148 -0
- package/src/__tests__/service.test.ts +345 -0
- package/src/__tests__/workers.test.ts +319 -0
- package/src/di.ts +291 -0
- package/src/fulltext/drivers/index.ts +41 -0
- package/src/fulltext/drivers/meilisearch/index.ts +410 -0
- package/src/fulltext/index.ts +13 -0
- package/src/fulltext/types.ts +115 -0
- package/src/index.ts +36 -0
- package/src/indexer/index.ts +13 -0
- package/src/indexer/search-indexer.ts +1141 -0
- package/src/indexer/subscribers/delete.ts +49 -0
- package/src/lib/debug.ts +46 -0
- package/src/lib/fallback-presenter.ts +106 -0
- package/src/lib/field-policy.ts +169 -0
- package/src/lib/index.ts +13 -0
- package/src/lib/merger.ts +159 -0
- package/src/lib/presenter-enricher.ts +323 -0
- package/src/modules/search/README.md +694 -0
- package/src/modules/search/acl.ts +10 -0
- package/src/modules/search/ai-tools.ts +467 -0
- package/src/modules/search/api/embeddings/reindex/cancel/route.ts +77 -0
- package/src/modules/search/api/embeddings/reindex/route.ts +197 -0
- package/src/modules/search/api/embeddings/route.ts +304 -0
- package/src/modules/search/api/index/route.ts +297 -0
- package/src/modules/search/api/reindex/cancel/route.ts +77 -0
- package/src/modules/search/api/reindex/route.ts +419 -0
- package/src/modules/search/api/search/global/route.ts +120 -0
- package/src/modules/search/api/search/route.ts +121 -0
- package/src/modules/search/api/settings/fulltext/route.ts +82 -0
- package/src/modules/search/api/settings/global-search/route.ts +91 -0
- package/src/modules/search/api/settings/route.ts +187 -0
- package/src/modules/search/api/settings/vector-store/route.ts +105 -0
- package/src/modules/search/backend/config/search/page.meta.ts +22 -0
- package/src/modules/search/backend/config/search/page.tsx +12 -0
- package/src/modules/search/cli.ts +818 -0
- package/src/modules/search/di.ts +50 -0
- package/src/modules/search/frontend/components/GlobalSearchDialog.tsx +436 -0
- package/src/modules/search/frontend/components/HybridSearchTable.tsx +418 -0
- package/src/modules/search/frontend/components/SearchSettingsPageClient.tsx +476 -0
- package/src/modules/search/frontend/components/sections/FulltextSearchSection.tsx +624 -0
- package/src/modules/search/frontend/components/sections/GlobalSearchSection.tsx +124 -0
- package/src/modules/search/frontend/components/sections/VectorSearchSection.tsx +943 -0
- package/src/modules/search/frontend/index.ts +3 -0
- package/src/modules/search/frontend/utils.ts +82 -0
- package/src/modules/search/i18n/de.json +61 -0
- package/src/modules/search/i18n/en.json +72 -0
- package/src/modules/search/i18n/es.json +61 -0
- package/src/modules/search/i18n/pl.json +61 -0
- package/src/modules/search/index.ts +9 -0
- package/src/modules/search/lib/auto-indexing.ts +35 -0
- package/src/modules/search/lib/embedding-config.ts +161 -0
- package/src/modules/search/lib/global-search-config.ts +69 -0
- package/src/modules/search/lib/reindex-lock.ts +201 -0
- package/src/modules/search/subscribers/fulltext_upsert.ts +83 -0
- package/src/modules/search/subscribers/vector_delete.ts +75 -0
- package/src/modules/search/subscribers/vector_purge.ts +161 -0
- package/src/modules/search/subscribers/vector_upsert.ts +75 -0
- package/src/modules/search/workers/fulltext-index.worker.ts +318 -0
- package/src/modules/search/workers/vector-index.worker.ts +292 -0
- package/src/queue/fulltext-indexing.ts +87 -0
- package/src/queue/index.ts +2 -0
- package/src/queue/vector-indexing.ts +66 -0
- package/src/service.ts +397 -0
- package/src/strategies/fulltext.strategy.ts +155 -0
- package/src/strategies/index.ts +17 -0
- package/src/strategies/token.strategy.ts +153 -0
- package/src/strategies/vector.strategy.ts +234 -0
- package/src/types.ts +38 -0
- package/src/vector/drivers/chromadb/index.ts +49 -0
- package/src/vector/drivers/index.ts +4 -0
- package/src/vector/drivers/pgvector/index.ts +627 -0
- package/src/vector/drivers/qdrant/index.ts +49 -0
- package/src/vector/index.ts +3 -0
- package/src/vector/lib/vector-logs.ts +46 -0
- package/src/vector/services/checksum.ts +18 -0
- package/src/vector/services/embedding.ts +275 -0
- package/src/vector/services/index.ts +3 -0
- package/src/vector/services/vector-index.service.ts +1234 -0
- package/src/vector/types/pg.d.ts +1 -0
- package/src/vector/types.ts +220 -0
- package/tsconfig.json +9 -0
- package/watch.mjs +6 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
3
|
+
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
4
|
+
import { recordIndexerLog } from "@open-mercato/shared/lib/indexers/status-log";
|
|
5
|
+
import { writeCoverageCounts } from "@open-mercato/core/modules/query_index/lib/coverage";
|
|
6
|
+
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
7
|
+
import { searchDebugWarn, searchError } from "../../../../lib/debug.js";
|
|
8
|
+
const metadata = {
|
|
9
|
+
GET: { requireAuth: true, requireFeatures: ["search.view"] },
|
|
10
|
+
DELETE: { requireAuth: true, requireFeatures: ["search.embeddings.manage"] }
|
|
11
|
+
};
|
|
12
|
+
function parseLimit(value) {
|
|
13
|
+
if (!value) return 50;
|
|
14
|
+
const parsed = Number.parseInt(value, 10);
|
|
15
|
+
if (Number.isNaN(parsed) || parsed <= 0) return 50;
|
|
16
|
+
return Math.min(parsed, 200);
|
|
17
|
+
}
|
|
18
|
+
function parseOffset(value) {
|
|
19
|
+
if (!value) return 0;
|
|
20
|
+
const parsed = Number.parseInt(value, 10);
|
|
21
|
+
if (Number.isNaN(parsed) || parsed < 0) return 0;
|
|
22
|
+
return parsed;
|
|
23
|
+
}
|
|
24
|
+
async function GET(req) {
|
|
25
|
+
const { t } = await resolveTranslations();
|
|
26
|
+
const auth = await getAuthFromRequest(req);
|
|
27
|
+
if (!auth?.tenantId) {
|
|
28
|
+
return NextResponse.json({ error: t("api.errors.unauthorized", "Unauthorized") }, { status: 401 });
|
|
29
|
+
}
|
|
30
|
+
const url = new URL(req.url);
|
|
31
|
+
const entityIdParam = url.searchParams.get("entityId");
|
|
32
|
+
const limit = parseLimit(url.searchParams.get("limit"));
|
|
33
|
+
const offset = parseOffset(url.searchParams.get("offset"));
|
|
34
|
+
const container = await createRequestContainer();
|
|
35
|
+
try {
|
|
36
|
+
let searchService;
|
|
37
|
+
try {
|
|
38
|
+
searchService = container.resolve("searchService");
|
|
39
|
+
} catch {
|
|
40
|
+
return NextResponse.json(
|
|
41
|
+
{ error: t("search.api.errors.serviceUnavailable", "Search service unavailable") },
|
|
42
|
+
{ status: 503 }
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
const strategies = searchService.getStrategies();
|
|
46
|
+
const vectorStrategy = strategies.find((s) => s.id === "vector");
|
|
47
|
+
if (!vectorStrategy) {
|
|
48
|
+
return NextResponse.json(
|
|
49
|
+
{ error: t("search.api.errors.vectorUnavailable", "Vector strategy not configured") },
|
|
50
|
+
{ status: 503 }
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
const isAvailable = await vectorStrategy.isAvailable();
|
|
54
|
+
if (!isAvailable) {
|
|
55
|
+
return NextResponse.json(
|
|
56
|
+
{ error: t("search.api.errors.vectorUnavailable", "Vector strategy not available") },
|
|
57
|
+
{ status: 503 }
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
const entries = await vectorStrategy.listEntries({
|
|
61
|
+
tenantId: auth.tenantId,
|
|
62
|
+
organizationId: auth.orgId ?? null,
|
|
63
|
+
entityId: entityIdParam ?? void 0,
|
|
64
|
+
limit,
|
|
65
|
+
offset
|
|
66
|
+
});
|
|
67
|
+
return NextResponse.json({ entries, limit, offset });
|
|
68
|
+
} catch (error) {
|
|
69
|
+
const err = error;
|
|
70
|
+
const status = typeof err?.status === "number" ? err.status : typeof err?.statusCode === "number" ? err.statusCode : 500;
|
|
71
|
+
searchError("search.index.list", "failed", {
|
|
72
|
+
error: error instanceof Error ? error.message : error,
|
|
73
|
+
stack: error instanceof Error ? error.stack : void 0
|
|
74
|
+
});
|
|
75
|
+
return NextResponse.json(
|
|
76
|
+
{ error: t("search.api.errors.indexFetchFailed", "Failed to fetch vector index. Please try again.") },
|
|
77
|
+
{ status: status >= 400 ? status : 500 }
|
|
78
|
+
);
|
|
79
|
+
} finally {
|
|
80
|
+
const disposable = container;
|
|
81
|
+
if (typeof disposable.dispose === "function") {
|
|
82
|
+
await disposable.dispose();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async function DELETE(req) {
|
|
87
|
+
const { t } = await resolveTranslations();
|
|
88
|
+
const auth = await getAuthFromRequest(req);
|
|
89
|
+
if (!auth?.tenantId) {
|
|
90
|
+
return NextResponse.json({ error: t("api.errors.unauthorized", "Unauthorized") }, { status: 401 });
|
|
91
|
+
}
|
|
92
|
+
const url = new URL(req.url);
|
|
93
|
+
const entityIdParam = url.searchParams.get("entityId");
|
|
94
|
+
const confirmAll = url.searchParams.get("confirmAll") === "true";
|
|
95
|
+
if (!entityIdParam && !confirmAll) {
|
|
96
|
+
return NextResponse.json(
|
|
97
|
+
{ error: t("search.api.errors.confirmAllRequired", "Purging all entities requires confirmAll=true parameter.") },
|
|
98
|
+
{ status: 400 }
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
const container = await createRequestContainer();
|
|
102
|
+
try {
|
|
103
|
+
let searchIndexer;
|
|
104
|
+
try {
|
|
105
|
+
searchIndexer = container.resolve("searchIndexer");
|
|
106
|
+
} catch {
|
|
107
|
+
return NextResponse.json(
|
|
108
|
+
{ error: t("search.api.errors.indexUnavailable", "Search indexer unavailable") },
|
|
109
|
+
{ status: 503 }
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
let em = null;
|
|
113
|
+
try {
|
|
114
|
+
em = container.resolve("em");
|
|
115
|
+
} catch {
|
|
116
|
+
}
|
|
117
|
+
let eventBus = null;
|
|
118
|
+
try {
|
|
119
|
+
eventBus = container.resolve("eventBus");
|
|
120
|
+
} catch {
|
|
121
|
+
eventBus = null;
|
|
122
|
+
}
|
|
123
|
+
const entityIds = entityIdParam ? [entityIdParam] : searchIndexer.listEnabledEntities();
|
|
124
|
+
const scopes = /* @__PURE__ */ new Set();
|
|
125
|
+
const registerScope = (org) => {
|
|
126
|
+
const key = org ?? "__null__";
|
|
127
|
+
if (!scopes.has(key)) scopes.add(key);
|
|
128
|
+
};
|
|
129
|
+
registerScope(null);
|
|
130
|
+
if (auth.orgId) registerScope(auth.orgId);
|
|
131
|
+
await recordIndexerLog(
|
|
132
|
+
{ em: em ?? void 0 },
|
|
133
|
+
{
|
|
134
|
+
source: "vector",
|
|
135
|
+
handler: "api:search.index.purge",
|
|
136
|
+
message: entityIdParam ? `Vector purge requested for ${entityIdParam}` : "Vector purge requested for all entities",
|
|
137
|
+
entityType: entityIdParam ?? null,
|
|
138
|
+
tenantId: auth.tenantId ?? null,
|
|
139
|
+
organizationId: auth.orgId ?? null,
|
|
140
|
+
details: { entityIds }
|
|
141
|
+
}
|
|
142
|
+
).catch(() => void 0);
|
|
143
|
+
for (const entityId of entityIds) {
|
|
144
|
+
await searchIndexer.purgeEntity({
|
|
145
|
+
entityId,
|
|
146
|
+
tenantId: auth.tenantId
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
if (em) {
|
|
150
|
+
try {
|
|
151
|
+
for (const entityId of entityIds) {
|
|
152
|
+
for (const scope of scopes) {
|
|
153
|
+
const orgValue = scope === "__null__" ? null : scope;
|
|
154
|
+
await writeCoverageCounts(
|
|
155
|
+
em,
|
|
156
|
+
{
|
|
157
|
+
entityType: entityId,
|
|
158
|
+
tenantId: auth.tenantId,
|
|
159
|
+
organizationId: orgValue,
|
|
160
|
+
withDeleted: false
|
|
161
|
+
},
|
|
162
|
+
{ vectorCount: 0 }
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
} catch (coverageError) {
|
|
167
|
+
searchDebugWarn("search.index.purge", "Failed to reset coverage after purge", {
|
|
168
|
+
error: coverageError instanceof Error ? coverageError.message : coverageError
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (eventBus) {
|
|
173
|
+
await Promise.all(
|
|
174
|
+
entityIds.flatMap(
|
|
175
|
+
(entityId) => Array.from(scopes).map((scope) => {
|
|
176
|
+
const orgValue = scope === "__null__" ? null : scope;
|
|
177
|
+
return eventBus.emitEvent(
|
|
178
|
+
"query_index.coverage.refresh",
|
|
179
|
+
{
|
|
180
|
+
entityType: entityId,
|
|
181
|
+
tenantId: auth.tenantId,
|
|
182
|
+
organizationId: orgValue,
|
|
183
|
+
delayMs: 0
|
|
184
|
+
}
|
|
185
|
+
).catch(() => void 0);
|
|
186
|
+
})
|
|
187
|
+
)
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
await recordIndexerLog(
|
|
191
|
+
{ em: em ?? void 0 },
|
|
192
|
+
{
|
|
193
|
+
source: "vector",
|
|
194
|
+
handler: "api:search.index.purge",
|
|
195
|
+
message: entityIdParam ? `Vector purge completed for ${entityIdParam}` : "Vector purge completed for all entities",
|
|
196
|
+
entityType: entityIdParam ?? null,
|
|
197
|
+
tenantId: auth.tenantId ?? null,
|
|
198
|
+
organizationId: auth.orgId ?? null,
|
|
199
|
+
details: { entityIds }
|
|
200
|
+
}
|
|
201
|
+
).catch(() => void 0);
|
|
202
|
+
return NextResponse.json({ ok: true });
|
|
203
|
+
} catch (error) {
|
|
204
|
+
const err = error;
|
|
205
|
+
const status = typeof err?.status === "number" ? err.status : typeof err?.statusCode === "number" ? err.statusCode : 500;
|
|
206
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
207
|
+
searchError("search.index.purge", "failed", {
|
|
208
|
+
error: errorMessage,
|
|
209
|
+
stack: error instanceof Error ? error.stack : void 0
|
|
210
|
+
});
|
|
211
|
+
let em = null;
|
|
212
|
+
try {
|
|
213
|
+
em = container.resolve("em");
|
|
214
|
+
} catch {
|
|
215
|
+
}
|
|
216
|
+
await recordIndexerLog(
|
|
217
|
+
{ em: em ?? void 0 },
|
|
218
|
+
{
|
|
219
|
+
source: "vector",
|
|
220
|
+
handler: "api:search.index.purge",
|
|
221
|
+
level: "warn",
|
|
222
|
+
message: entityIdParam ? `Vector purge failed for ${entityIdParam}` : "Vector purge failed for all entities",
|
|
223
|
+
entityType: entityIdParam ?? null,
|
|
224
|
+
tenantId: auth.tenantId ?? null,
|
|
225
|
+
organizationId: auth.orgId ?? null,
|
|
226
|
+
details: { error: errorMessage }
|
|
227
|
+
}
|
|
228
|
+
).catch(() => void 0);
|
|
229
|
+
return NextResponse.json(
|
|
230
|
+
{ error: t("search.api.errors.purgeFailed", "Vector index purge failed. Please try again.") },
|
|
231
|
+
{ status: status >= 400 ? status : 500 }
|
|
232
|
+
);
|
|
233
|
+
} finally {
|
|
234
|
+
const disposable = container;
|
|
235
|
+
if (typeof disposable.dispose === "function") {
|
|
236
|
+
await disposable.dispose();
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
export {
|
|
241
|
+
DELETE,
|
|
242
|
+
GET,
|
|
243
|
+
metadata
|
|
244
|
+
};
|
|
245
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/search/api/index/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport type { SearchIndexer } from '../../../../indexer/search-indexer'\nimport type { SearchService } from '../../../../service'\nimport { recordIndexerLog } from '@open-mercato/shared/lib/indexers/status-log'\nimport { writeCoverageCounts } from '@open-mercato/core/modules/query_index/lib/coverage'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { VectorSearchStrategy } from '../../../../strategies/vector.strategy'\nimport type { EntityId } from '@open-mercato/shared/modules/entities'\nimport { searchDebugWarn, searchError } from '../../../../lib/debug'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['search.view'] },\n DELETE: { requireAuth: true, requireFeatures: ['search.embeddings.manage'] },\n}\n\nfunction parseLimit(value: string | null): number {\n if (!value) return 50\n const parsed = Number.parseInt(value, 10)\n if (Number.isNaN(parsed) || parsed <= 0) return 50\n return Math.min(parsed, 200)\n}\n\nfunction parseOffset(value: string | null): number {\n if (!value) return 0\n const parsed = Number.parseInt(value, 10)\n if (Number.isNaN(parsed) || parsed < 0) return 0\n return parsed\n}\n\nexport async function GET(req: Request) {\n const { t } = await resolveTranslations()\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId) {\n return NextResponse.json({ error: t('api.errors.unauthorized', 'Unauthorized') }, { status: 401 })\n }\n\n const url = new URL(req.url)\n const entityIdParam = url.searchParams.get('entityId')\n const limit = parseLimit(url.searchParams.get('limit'))\n const offset = parseOffset(url.searchParams.get('offset'))\n\n const container = await createRequestContainer()\n try {\n // Get the vector strategy from search service\n let searchService: SearchService\n try {\n searchService = container.resolve('searchService') as SearchService\n } catch {\n return NextResponse.json(\n { error: t('search.api.errors.serviceUnavailable', 'Search service unavailable') },\n { status: 503 }\n )\n }\n\n // Access vector strategy for listing entries\n const strategies = searchService.getStrategies()\n const vectorStrategy = strategies.find((s) => s.id === 'vector') as VectorSearchStrategy | undefined\n\n if (!vectorStrategy) {\n return NextResponse.json(\n { error: t('search.api.errors.vectorUnavailable', 'Vector strategy not configured') },\n { status: 503 }\n )\n }\n\n const isAvailable = await vectorStrategy.isAvailable()\n if (!isAvailable) {\n return NextResponse.json(\n { error: t('search.api.errors.vectorUnavailable', 'Vector strategy not available') },\n { status: 503 }\n )\n }\n\n // List vector entries via the strategy\n const entries = await vectorStrategy.listEntries({\n tenantId: auth.tenantId,\n organizationId: auth.orgId ?? null,\n entityId: entityIdParam ?? undefined,\n limit,\n offset,\n })\n\n return NextResponse.json({ entries, limit, offset })\n } catch (error: unknown) {\n const err = error as { status?: number; statusCode?: number }\n const status = typeof err?.status === 'number'\n ? err.status\n : (typeof err?.statusCode === 'number' ? err.statusCode : 500)\n searchError('search.index.list', 'failed', {\n error: error instanceof Error ? error.message : error,\n stack: error instanceof Error ? error.stack : undefined,\n })\n return NextResponse.json(\n { error: t('search.api.errors.indexFetchFailed', 'Failed to fetch vector index. Please try again.') },\n { status: status >= 400 ? status : 500 }\n )\n } finally {\n const disposable = container as unknown as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') {\n await disposable.dispose()\n }\n }\n}\n\nexport async function DELETE(req: Request) {\n const { t } = await resolveTranslations()\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId) {\n return NextResponse.json({ error: t('api.errors.unauthorized', 'Unauthorized') }, { status: 401 })\n }\n\n const url = new URL(req.url)\n const entityIdParam = url.searchParams.get('entityId')\n const confirmAll = url.searchParams.get('confirmAll') === 'true'\n\n // Require explicit confirmation when purging ALL entities (dangerous operation)\n if (!entityIdParam && !confirmAll) {\n return NextResponse.json(\n { error: t('search.api.errors.confirmAllRequired', 'Purging all entities requires confirmAll=true parameter.') },\n { status: 400 }\n )\n }\n\n const container = await createRequestContainer()\n try {\n let searchIndexer: SearchIndexer\n try {\n searchIndexer = container.resolve('searchIndexer') as SearchIndexer\n } catch {\n return NextResponse.json(\n { error: t('search.api.errors.indexUnavailable', 'Search indexer unavailable') },\n { status: 503 }\n )\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let em: any = null\n try {\n em = container.resolve('em')\n } catch {\n // em not available\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let eventBus: { emitEvent(event: string, payload: any, options?: any): Promise<void> } | null = null\n try {\n eventBus = container.resolve('eventBus')\n } catch {\n eventBus = null\n }\n\n const entityIds = entityIdParam\n ? [entityIdParam]\n : searchIndexer.listEnabledEntities()\n\n const scopes = new Set<string>()\n const registerScope = (org: string | null) => {\n const key = org ?? '__null__'\n if (!scopes.has(key)) scopes.add(key)\n }\n registerScope(null)\n if (auth.orgId) registerScope(auth.orgId)\n\n await recordIndexerLog(\n { em: em ?? undefined },\n {\n source: 'vector',\n handler: 'api:search.index.purge',\n message: entityIdParam\n ? `Vector purge requested for ${entityIdParam}`\n : 'Vector purge requested for all entities',\n entityType: entityIdParam ?? null,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n details: { entityIds },\n },\n ).catch(() => undefined)\n\n // Purge each entity using SearchIndexer\n for (const entityId of entityIds) {\n await searchIndexer.purgeEntity({\n entityId: entityId as EntityId,\n tenantId: auth.tenantId,\n })\n }\n\n // Update coverage counts\n if (em) {\n try {\n for (const entityId of entityIds) {\n for (const scope of scopes) {\n const orgValue = scope === '__null__' ? null : scope\n await writeCoverageCounts(\n em,\n {\n entityType: entityId,\n tenantId: auth.tenantId,\n organizationId: orgValue,\n withDeleted: false,\n },\n { vectorCount: 0 },\n )\n }\n }\n } catch (coverageError) {\n searchDebugWarn('search.index.purge', 'Failed to reset coverage after purge', {\n error: coverageError instanceof Error ? coverageError.message : coverageError,\n })\n }\n }\n\n // Emit coverage refresh events\n if (eventBus) {\n await Promise.all(\n entityIds.flatMap((entityId) =>\n Array.from(scopes).map((scope) => {\n const orgValue = scope === '__null__' ? null : scope\n return eventBus!\n .emitEvent(\n 'query_index.coverage.refresh',\n {\n entityType: entityId,\n tenantId: auth.tenantId,\n organizationId: orgValue,\n delayMs: 0,\n },\n )\n .catch(() => undefined)\n }),\n ),\n )\n }\n\n await recordIndexerLog(\n { em: em ?? undefined },\n {\n source: 'vector',\n handler: 'api:search.index.purge',\n message: entityIdParam\n ? `Vector purge completed for ${entityIdParam}`\n : 'Vector purge completed for all entities',\n entityType: entityIdParam ?? null,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n details: { entityIds },\n },\n ).catch(() => undefined)\n\n return NextResponse.json({ ok: true })\n } catch (error: unknown) {\n const err = error as { status?: number; statusCode?: number }\n const status = typeof err?.status === 'number'\n ? err.status\n : (typeof err?.statusCode === 'number' ? err.statusCode : 500)\n const errorMessage = error instanceof Error ? error.message : String(error)\n searchError('search.index.purge', 'failed', {\n error: errorMessage,\n stack: error instanceof Error ? error.stack : undefined,\n })\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let em: any = null\n try {\n em = container.resolve('em')\n } catch {\n // em not available\n }\n\n await recordIndexerLog(\n { em: em ?? undefined },\n {\n source: 'vector',\n handler: 'api:search.index.purge',\n level: 'warn',\n message: entityIdParam\n ? `Vector purge failed for ${entityIdParam}`\n : 'Vector purge failed for all entities',\n entityType: entityIdParam ?? null,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n details: { error: errorMessage },\n },\n ).catch(() => undefined)\n\n return NextResponse.json(\n { error: t('search.api.errors.purgeFailed', 'Vector index purge failed. Please try again.') },\n { status: status >= 400 ? status : 500 }\n )\n } finally {\n const disposable = container as unknown as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') {\n await disposable.dispose()\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
3
|
+
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
4
|
+
import { clearReindexLock } from "../../../lib/reindex-lock.js";
|
|
5
|
+
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
6
|
+
import { recordIndexerLog } from "@open-mercato/shared/lib/indexers/status-log";
|
|
7
|
+
const metadata = {
|
|
8
|
+
POST: { requireAuth: true, requireFeatures: ["search.reindex"] }
|
|
9
|
+
};
|
|
10
|
+
async function POST(req) {
|
|
11
|
+
const { t } = await resolveTranslations();
|
|
12
|
+
const auth = await getAuthFromRequest(req);
|
|
13
|
+
if (!auth?.tenantId) {
|
|
14
|
+
return NextResponse.json({ error: t("api.errors.unauthorized", "Unauthorized") }, { status: 401 });
|
|
15
|
+
}
|
|
16
|
+
const container = await createRequestContainer();
|
|
17
|
+
const em = container.resolve("em");
|
|
18
|
+
const knex = em.getConnection().getKnex();
|
|
19
|
+
let queue;
|
|
20
|
+
try {
|
|
21
|
+
queue = container.resolve("fulltextIndexQueue");
|
|
22
|
+
} catch {
|
|
23
|
+
}
|
|
24
|
+
let jobsRemoved = 0;
|
|
25
|
+
if (queue) {
|
|
26
|
+
try {
|
|
27
|
+
const countsBefore = await queue.getJobCounts();
|
|
28
|
+
jobsRemoved = countsBefore.waiting + countsBefore.active;
|
|
29
|
+
await queue.clear();
|
|
30
|
+
} catch {
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
await clearReindexLock(knex, auth.tenantId, "fulltext", auth.orgId ?? null);
|
|
34
|
+
try {
|
|
35
|
+
const em2 = container.resolve("em");
|
|
36
|
+
await recordIndexerLog(
|
|
37
|
+
{ em: em2 },
|
|
38
|
+
{
|
|
39
|
+
source: "fulltext",
|
|
40
|
+
handler: "api:search.reindex.cancel",
|
|
41
|
+
message: `Cancelled fulltext reindex operation (${jobsRemoved} jobs removed)`,
|
|
42
|
+
tenantId: auth.tenantId,
|
|
43
|
+
organizationId: auth.orgId ?? null,
|
|
44
|
+
details: { jobsRemoved }
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
} catch {
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const disposable = container;
|
|
51
|
+
if (typeof disposable.dispose === "function") {
|
|
52
|
+
await disposable.dispose();
|
|
53
|
+
}
|
|
54
|
+
} catch {
|
|
55
|
+
}
|
|
56
|
+
return NextResponse.json({
|
|
57
|
+
ok: true,
|
|
58
|
+
jobsRemoved
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
export {
|
|
62
|
+
POST,
|
|
63
|
+
metadata
|
|
64
|
+
};
|
|
65
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/search/api/reindex/cancel/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport type { Queue } from '@open-mercato/queue'\nimport type { Knex } from 'knex'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { clearReindexLock } from '../../../lib/reindex-lock'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { recordIndexerLog } from '@open-mercato/shared/lib/indexers/status-log'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['search.reindex'] },\n}\n\nexport async function POST(req: Request) {\n const { t } = await resolveTranslations()\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId) {\n return NextResponse.json({ error: t('api.errors.unauthorized', 'Unauthorized') }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const knex = (em.getConnection() as unknown as { getKnex: () => Knex }).getKnex()\n\n let queue: Queue | undefined\n try {\n queue = container.resolve<Queue>('fulltextIndexQueue')\n } catch {\n // Queue not available - just clear the lock\n }\n\n let jobsRemoved = 0\n if (queue) {\n try {\n const countsBefore = await queue.getJobCounts()\n jobsRemoved = countsBefore.waiting + countsBefore.active\n await queue.clear()\n } catch {\n // Queue clear failed - continue to clear lock\n }\n }\n\n await clearReindexLock(knex, auth.tenantId, 'fulltext', auth.orgId ?? null)\n\n // Log the cancellation\n try {\n const em = container.resolve('em')\n await recordIndexerLog(\n { em },\n {\n source: 'fulltext',\n handler: 'api:search.reindex.cancel',\n message: `Cancelled fulltext reindex operation (${jobsRemoved} jobs removed)`,\n tenantId: auth.tenantId,\n organizationId: auth.orgId ?? null,\n details: { jobsRemoved },\n },\n )\n } catch {\n // Logging failure should not fail the cancel operation\n }\n\n try {\n const disposable = container as unknown as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') {\n await disposable.dispose()\n }\n } catch {\n // Ignore disposal errors\n }\n\n return NextResponse.json({\n ok: true,\n jobsRemoved,\n })\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AAInC,SAAS,wBAAwB;AACjC,SAAS,2BAA2B;AACpC,SAAS,wBAAwB;AAE1B,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,gBAAgB,EAAE;AACjE;AAEA,eAAsB,KAAK,KAAc;AACvC,QAAM,EAAE,EAAE,IAAI,MAAM,oBAAoB;AACxC,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,MAAM,UAAU;AACnB,WAAO,aAAa,KAAK,EAAE,OAAO,EAAE,2BAA2B,cAAc,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACnG;AAEA,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,KAAK,UAAU,QAAQ,IAAI;AACjC,QAAM,OAAQ,GAAG,cAAc,EAAyC,QAAQ;AAEhF,MAAI;AACJ,MAAI;AACF,YAAQ,UAAU,QAAe,oBAAoB;AAAA,EACvD,QAAQ;AAAA,EAER;AAEA,MAAI,cAAc;AAClB,MAAI,OAAO;AACT,QAAI;AACF,YAAM,eAAe,MAAM,MAAM,aAAa;AAC9C,oBAAc,aAAa,UAAU,aAAa;AAClD,YAAM,MAAM,MAAM;AAAA,IACpB,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,QAAM,iBAAiB,MAAM,KAAK,UAAU,YAAY,KAAK,SAAS,IAAI;AAG1E,MAAI;AACF,UAAMA,MAAK,UAAU,QAAQ,IAAI;AACjC,UAAM;AAAA,MACJ,EAAE,IAAAA,IAAG;AAAA,MACL;AAAA,QACE,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,SAAS,yCAAyC,WAAW;AAAA,QAC7D,UAAU,KAAK;AAAA,QACf,gBAAgB,KAAK,SAAS;AAAA,QAC9B,SAAS,EAAE,YAAY;AAAA,MACzB;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,MAAI;AACF,UAAM,aAAa;AACnB,QAAI,OAAO,WAAW,YAAY,YAAY;AAC5C,YAAM,WAAW,QAAQ;AAAA,IAC3B;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,SAAO,aAAa,KAAK;AAAA,IACvB,IAAI;AAAA,IACJ;AAAA,EACF,CAAC;AACH;",
|
|
6
|
+
"names": ["em"]
|
|
7
|
+
}
|