@pagenary/publisher 2026.6.2 → 2026.6.4
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/package.json +1 -1
- package/scripts/build-tenants.js +66 -4
- package/site/index.html +19 -5
- package/site/manifest.js +9 -9
- package/site/robots.txt +1 -1
- package/site/vendor/fortemi-aiwg-index.d.ts +31 -1
- package/site/vendor/fortemi-aiwg-index.js +1 -1
- package/src/index.html +17 -3
- package/src/vendor/fortemi-aiwg-index.d.ts +31 -1
- package/src/vendor/fortemi-aiwg-index.js +180 -12
package/package.json
CHANGED
package/scripts/build-tenants.js
CHANGED
|
@@ -10,7 +10,7 @@ import { generateSeoArtifacts, resolveBaseUrl, resolveOgImage } from './lib/seo-
|
|
|
10
10
|
import { generateCollections } from './lib/collections-generator.js';
|
|
11
11
|
import { parseFrontmatter } from './lib/frontmatter.js';
|
|
12
12
|
import { generateSearchIndex } from './lib/search-index-generator.js';
|
|
13
|
-
import { fileURLToPath } from 'node:url';
|
|
13
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
14
14
|
|
|
15
15
|
const root = process.cwd();
|
|
16
16
|
// The package's own directory (this file lives at <pkg>/scripts/build-tenants.js).
|
|
@@ -959,6 +959,59 @@ async function applyBranding(distDir, config, tenantId) {
|
|
|
959
959
|
}
|
|
960
960
|
}
|
|
961
961
|
|
|
962
|
+
/**
|
|
963
|
+
* Replace the shell's `__PAGENARY_TENANT__` base-resolution placeholder with the
|
|
964
|
+
* real tenant id. Runs for every tenant so the runtime `<base href>` bootstrap
|
|
965
|
+
* can resolve asset/module URLs against the tenant root under subpath mounts
|
|
966
|
+
* (e.g. /tenant/) while domain-root deploys fall back to "/".
|
|
967
|
+
* @param {string} distDir - Tenant output directory
|
|
968
|
+
* @param {string} tenantId - Tenant identifier
|
|
969
|
+
*/
|
|
970
|
+
async function injectTenantBase(distDir, tenantId) {
|
|
971
|
+
const indexPath = path.join(distDir, 'index.html');
|
|
972
|
+
if (!(await pathExists(indexPath))) return;
|
|
973
|
+
const html = await fsp.readFile(indexPath, 'utf8');
|
|
974
|
+
if (!html.includes('__PAGENARY_TENANT__')) return;
|
|
975
|
+
await fsp.writeFile(indexPath, html.split('__PAGENARY_TENANT__').join(tenantId), 'utf8');
|
|
976
|
+
console.log(` ↳ wired tenant base for ${tenantId}`);
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
/**
|
|
980
|
+
* Set the shell <title> to the default page's metadata title for SEO (#28).
|
|
981
|
+
* Reads the generated manifest.js for DEFAULT_SECTION and its (metadata-derived)
|
|
982
|
+
* title, producing "<page title> · <brand>" to mirror the runtime seo.js format.
|
|
983
|
+
* Generic brand is used only as a fallback when no default title is available,
|
|
984
|
+
* so the crawler-visible root URL gets a specific, descriptive title.
|
|
985
|
+
* @param {string} distDir - Tenant output directory
|
|
986
|
+
* @param {object} config - Tenant config (for the brand title)
|
|
987
|
+
*/
|
|
988
|
+
async function applyDefaultPageTitle(distDir, config) {
|
|
989
|
+
const indexPath = path.join(distDir, 'index.html');
|
|
990
|
+
const manifestPath = path.join(distDir, 'manifest.js');
|
|
991
|
+
if (!(await pathExists(indexPath)) || !(await pathExists(manifestPath))) return;
|
|
992
|
+
|
|
993
|
+
let defaultTitle = null;
|
|
994
|
+
try {
|
|
995
|
+
// Cache-bust so incremental rebuilds re-read the freshly written manifest.
|
|
996
|
+
const mod = await import(`${pathToFileURL(manifestPath).href}?t=${Date.now()}`);
|
|
997
|
+
const id = mod.DEFAULT_SECTION;
|
|
998
|
+
const entry = id && typeof mod.findSection === 'function' ? mod.findSection(id) : null;
|
|
999
|
+
if (entry && entry.title) defaultTitle = entry.title;
|
|
1000
|
+
} catch {
|
|
1001
|
+
// Manifest not importable — keep the existing (branded/generic) title.
|
|
1002
|
+
}
|
|
1003
|
+
if (!defaultTitle) return;
|
|
1004
|
+
|
|
1005
|
+
const brand = config.title || null;
|
|
1006
|
+
const shellTitle = brand ? `${defaultTitle} · ${brand}` : defaultTitle;
|
|
1007
|
+
let html = await fsp.readFile(indexPath, 'utf8');
|
|
1008
|
+
const replaced = html.replace(/<title>[^<]*<\/title>/, `<title>${escapeHtml(shellTitle)}</title>`);
|
|
1009
|
+
if (replaced !== html) {
|
|
1010
|
+
await fsp.writeFile(indexPath, replaced, 'utf8');
|
|
1011
|
+
console.log(` ↳ default page title: ${shellTitle}`);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
|
|
962
1015
|
function hexToRgb(hex) {
|
|
963
1016
|
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
964
1017
|
if (!result) return null;
|
|
@@ -2576,14 +2629,14 @@ async function materializeScannedSections(sections, context) {
|
|
|
2576
2629
|
title,
|
|
2577
2630
|
summary,
|
|
2578
2631
|
...metadata,
|
|
2579
|
-
module:
|
|
2632
|
+
module: `./sections/${outFile}`,
|
|
2580
2633
|
subsections: processedSubsections
|
|
2581
2634
|
};
|
|
2582
2635
|
if (type) entry.type = type;
|
|
2583
2636
|
if (collapsed) entry.collapsed = true;
|
|
2584
2637
|
processed.push(entry);
|
|
2585
2638
|
} else {
|
|
2586
|
-
const entry = { id, title, summary, ...metadata, module:
|
|
2639
|
+
const entry = { id, title, summary, ...metadata, module: `./sections/${outFile}` };
|
|
2587
2640
|
if (type) entry.type = type;
|
|
2588
2641
|
if (collapsed) entry.collapsed = true;
|
|
2589
2642
|
processed.push(entry);
|
|
@@ -2896,7 +2949,7 @@ async function materializeSectionModule(entry, context) {
|
|
|
2896
2949
|
return null;
|
|
2897
2950
|
}
|
|
2898
2951
|
|
|
2899
|
-
return
|
|
2952
|
+
return `./sections/${outFile}`;
|
|
2900
2953
|
}
|
|
2901
2954
|
|
|
2902
2955
|
function buildManifestModuleSource(manifestEntries, defaultSection, siteConfig = {}, exportConfig = {}) {
|
|
@@ -3313,6 +3366,15 @@ async function buildTenant(tenant, targetOverride, cacheDir, buildOptions) {
|
|
|
3313
3366
|
await applyWelcome(distDir, config, tenantId);
|
|
3314
3367
|
}
|
|
3315
3368
|
|
|
3369
|
+
// Inject the tenant id into the shell's base-resolution bootstrap for EVERY
|
|
3370
|
+
// tenant (regardless of branding config) so subpath mounts resolve assets
|
|
3371
|
+
// against the tenant root. Domain-root deploys fall back to "/".
|
|
3372
|
+
await injectTenantBase(distDir, tenantId);
|
|
3373
|
+
|
|
3374
|
+
// Set the shell <title> from the default page's metadata title (SEO, #28),
|
|
3375
|
+
// falling back to the generic brand only when no default title exists.
|
|
3376
|
+
await applyDefaultPageTitle(distDir, config);
|
|
3377
|
+
|
|
3316
3378
|
// Copy static assets from .public/ directory
|
|
3317
3379
|
await copyPublicAssets(sourceDir, distDir, tenantId);
|
|
3318
3380
|
|
package/site/index.html
CHANGED
|
@@ -3,11 +3,25 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
6
|
-
<title>Pagenary Docs</title>
|
|
6
|
+
<title>Welcome · Pagenary Docs</title>
|
|
7
7
|
<meta name="description" content="Pagenary developer documentation — building, configuring, deploying, and extending the multi-tenant documentation publisher, published with Pagenary itself." />
|
|
8
|
-
<
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
<script>
|
|
9
|
+
// Resolve all asset/module URLs against the tenant root in both deploy
|
|
10
|
+
// modes: domain-per-tenant at root (production, with SPA fallback) and a
|
|
11
|
+
// subpath mount such as /<tenant>/ (the dev preview server). The tenant id
|
|
12
|
+
// is injected at build time. On a domain root the path never starts with
|
|
13
|
+
// "/<tenant>/", so the base falls back to "/". For the un-replaced default
|
|
14
|
+
// build the placeholder starts with "_", which also yields "/".
|
|
15
|
+
(function () {
|
|
16
|
+
var t = "pagenary";
|
|
17
|
+
var p = location.pathname;
|
|
18
|
+
var base = t && p.indexOf("/" + t + "/") === 0 ? "/" + t + "/" : "/";
|
|
19
|
+
document.write('<base href="' + base + '">');
|
|
20
|
+
})();
|
|
21
|
+
</script>
|
|
22
|
+
<link rel="icon" type="image/png" href="./favicon.png" />
|
|
23
|
+
<link rel="stylesheet" href="./styles.css" />
|
|
24
|
+
<meta name="x-build" content="2026-06-15T21:21:38.022Z" />
|
|
11
25
|
</head>
|
|
12
26
|
<body>
|
|
13
27
|
<a class="skip-link" href="#app">Skip to content</a>
|
|
@@ -52,6 +66,6 @@
|
|
|
52
66
|
<ul id="commandList" class="cmd-list" role="listbox"></ul>
|
|
53
67
|
</div>
|
|
54
68
|
</div>
|
|
55
|
-
<script type="module" src="
|
|
69
|
+
<script type="module" src="./app.js"></script>
|
|
56
70
|
</body>
|
|
57
71
|
</html>
|
package/site/manifest.js
CHANGED
|
@@ -3,7 +3,7 @@ export const MANIFEST = [
|
|
|
3
3
|
"id": "welcome",
|
|
4
4
|
"title": "Welcome",
|
|
5
5
|
"summary": "What Pagenary is and how this dogfooded portal is built.",
|
|
6
|
-
"module": "
|
|
6
|
+
"module": "./sections/welcome.js"
|
|
7
7
|
},
|
|
8
8
|
{
|
|
9
9
|
"id": "getting-started",
|
|
@@ -14,7 +14,7 @@ export const MANIFEST = [
|
|
|
14
14
|
"id": "quickstart",
|
|
15
15
|
"title": "Quickstart",
|
|
16
16
|
"summary": "Install, build the default bundle, and serve it locally.",
|
|
17
|
-
"module": "
|
|
17
|
+
"module": "./sections/quickstart.js"
|
|
18
18
|
}
|
|
19
19
|
]
|
|
20
20
|
},
|
|
@@ -27,19 +27,19 @@ export const MANIFEST = [
|
|
|
27
27
|
"id": "developer-guide",
|
|
28
28
|
"title": "Developer Guide",
|
|
29
29
|
"summary": "Project layout, scripts, and the content authoring workflow.",
|
|
30
|
-
"module": "
|
|
30
|
+
"module": "./sections/developer-guide.js"
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
33
|
"id": "tenant-config",
|
|
34
34
|
"title": "Tenant Configuration",
|
|
35
35
|
"summary": "Every config.json option: branding, theming, SEO, and export.",
|
|
36
|
-
"module": "
|
|
36
|
+
"module": "./sections/tenant-config.js"
|
|
37
37
|
},
|
|
38
38
|
{
|
|
39
39
|
"id": "extending",
|
|
40
40
|
"title": "Extending",
|
|
41
41
|
"summary": "Add section templates, content types, and build behaviors.",
|
|
42
|
-
"module": "
|
|
42
|
+
"module": "./sections/extending.js"
|
|
43
43
|
}
|
|
44
44
|
]
|
|
45
45
|
},
|
|
@@ -52,25 +52,25 @@ export const MANIFEST = [
|
|
|
52
52
|
"id": "architecture",
|
|
53
53
|
"title": "Architecture",
|
|
54
54
|
"summary": "The static SPA pattern, build pipeline, and tenant content model.",
|
|
55
|
-
"module": "
|
|
55
|
+
"module": "./sections/architecture.js"
|
|
56
56
|
},
|
|
57
57
|
{
|
|
58
58
|
"id": "api",
|
|
59
59
|
"title": "API Reference",
|
|
60
60
|
"summary": "Module-level documentation for the publisher internals.",
|
|
61
|
-
"module": "
|
|
61
|
+
"module": "./sections/api.js"
|
|
62
62
|
},
|
|
63
63
|
{
|
|
64
64
|
"id": "deployment",
|
|
65
65
|
"title": "Deployment",
|
|
66
66
|
"summary": "Hosting the static output and multi-tenant domain routing.",
|
|
67
|
-
"module": "
|
|
67
|
+
"module": "./sections/deployment.js"
|
|
68
68
|
},
|
|
69
69
|
{
|
|
70
70
|
"id": "seo-strategy",
|
|
71
71
|
"title": "SEO Strategy",
|
|
72
72
|
"summary": "Metadata, hash-routing considerations, and discoverability.",
|
|
73
|
-
"module": "
|
|
73
|
+
"module": "./sections/seo-strategy.js"
|
|
74
74
|
}
|
|
75
75
|
]
|
|
76
76
|
}
|
package/site/robots.txt
CHANGED
|
@@ -51,6 +51,11 @@ interface AiwgFortemiChunkPartRef {
|
|
|
51
51
|
offset: number;
|
|
52
52
|
count: number;
|
|
53
53
|
}
|
|
54
|
+
declare const AIWG_SCAN_REQUIRED_FIELDS: Array<keyof AiwgFortemiRecord>;
|
|
55
|
+
type AiwgFortemiProjectedRecord = Pick<AiwgFortemiRecord, 'schema_version' | 'id' | 'type' | 'title' | 'text' | 'facets' | 'tags' | 'concepts' | 'privacy'> & Partial<AiwgFortemiRecord>;
|
|
56
|
+
interface AiwgFortemiChunkDetailRef {
|
|
57
|
+
href: string;
|
|
58
|
+
}
|
|
54
59
|
interface AiwgFortemiChunkManifest {
|
|
55
60
|
schema_version: 'aiwg.fortemi.index.chunk-manifest.v1';
|
|
56
61
|
generated_at: string;
|
|
@@ -58,6 +63,8 @@ interface AiwgFortemiChunkManifest {
|
|
|
58
63
|
total: number;
|
|
59
64
|
part_size: number;
|
|
60
65
|
facets?: Record<string, Record<string, number>>;
|
|
66
|
+
projection?: Array<keyof AiwgFortemiRecord>;
|
|
67
|
+
detail?: AiwgFortemiChunkDetailRef;
|
|
61
68
|
parts: AiwgFortemiChunkPartRef[];
|
|
62
69
|
}
|
|
63
70
|
interface AiwgFortemiChunkPart {
|
|
@@ -113,8 +120,11 @@ interface AiwgIndexQueryResult {
|
|
|
113
120
|
rankedItems?: AiwgIndexQueryRankedItem[];
|
|
114
121
|
}
|
|
115
122
|
type AiwgChunkedIndexLoader = (part: AiwgFortemiChunkPartRef, manifest: AiwgFortemiChunkManifest) => Promise<unknown>;
|
|
123
|
+
type AiwgChunkedIndexDetailLoader = (id: string, manifest: AiwgFortemiChunkManifest) => Promise<unknown>;
|
|
116
124
|
interface AiwgChunkedIndexLoadOptions {
|
|
117
125
|
maxCachedParts?: number;
|
|
126
|
+
detailLoader?: AiwgChunkedIndexDetailLoader;
|
|
127
|
+
maxCachedDetails?: number;
|
|
118
128
|
}
|
|
119
129
|
type AiwgChunkedIndexProgressPhase = 'part' | 'query';
|
|
120
130
|
interface AiwgChunkedIndexProgress {
|
|
@@ -175,6 +185,7 @@ interface AiwgIndexController {
|
|
|
175
185
|
getSnapshot(): AiwgIndexControllerSnapshot;
|
|
176
186
|
query(query?: string, options?: AiwgIndexQueryOptions): AiwgIndexQueryResult;
|
|
177
187
|
queryChunked(query?: string, options?: AiwgChunkedIndexQueryOptions): Promise<AiwgChunkedIndexQueryResult>;
|
|
188
|
+
getRecord(id: string): Promise<AiwgFortemiRecord>;
|
|
178
189
|
clearChunkCache(): void;
|
|
179
190
|
toCommunityGraph(options?: AiwgIndexGraphOptions): ReturnType<typeof aiwgFortemiIndexToCommunityGraph>;
|
|
180
191
|
setReviewDecision(input: AiwgReviewInput): AiwgReviewDecision;
|
|
@@ -189,7 +200,26 @@ declare function assertAiwgFortemiChunkManifest(value: unknown): AiwgFortemiChun
|
|
|
189
200
|
declare function validateAiwgFortemiChunkPart(value: unknown, partRef?: AiwgFortemiChunkPartRef, manifest?: AiwgFortemiChunkManifest): AiwgChunkedIndexValidationResult;
|
|
190
201
|
declare function assertAiwgFortemiChunkPart(value: unknown, partRef?: AiwgFortemiChunkPartRef, manifest?: AiwgFortemiChunkManifest): AiwgFortemiChunkPart;
|
|
191
202
|
declare function createAiwgFetchChunkLoader(baseUrl?: string | URL): AiwgChunkedIndexLoader;
|
|
203
|
+
declare function createAiwgFetchDetailLoader(baseUrl?: string | URL): AiwgChunkedIndexDetailLoader;
|
|
192
204
|
declare function getAiwgFortemiFacets(items: AiwgFortemiRecord[]): Record<string, Record<string, number>>;
|
|
205
|
+
interface AiwgChunkedIndexBuildOptions {
|
|
206
|
+
partSize?: number;
|
|
207
|
+
projection?: Array<keyof AiwgFortemiRecord>;
|
|
208
|
+
detailHref?: string;
|
|
209
|
+
generatedAt?: string;
|
|
210
|
+
}
|
|
211
|
+
interface AiwgChunkedIndexBuildResult {
|
|
212
|
+
manifest: AiwgFortemiChunkManifest;
|
|
213
|
+
parts: Array<{
|
|
214
|
+
href: string;
|
|
215
|
+
part: AiwgFortemiChunkPart;
|
|
216
|
+
}>;
|
|
217
|
+
details: Array<{
|
|
218
|
+
id: string;
|
|
219
|
+
record: AiwgFortemiRecord;
|
|
220
|
+
}>;
|
|
221
|
+
}
|
|
222
|
+
declare function buildAiwgChunkedIndex(index: AiwgFortemiIndexExport, options?: AiwgChunkedIndexBuildOptions): AiwgChunkedIndexBuildResult;
|
|
193
223
|
declare function queryAiwgFortemiIndex(index: AiwgFortemiIndexExport, query?: string, options?: AiwgIndexQueryOptions): AiwgIndexQueryResult;
|
|
194
224
|
declare function createAiwgReviewDecisionExport(source: AiwgFortemiIndexExport, decisions: AiwgReviewDecision[], generatedAt?: string): AiwgReviewDecisionExport;
|
|
195
225
|
declare function createAiwgIndexController(initialIndex?: AiwgFortemiIndexExport): AiwgIndexController;
|
|
@@ -209,4 +239,4 @@ declare function aiwgFortemiIndexToCommunityGraph(index: AiwgFortemiIndexExport,
|
|
|
209
239
|
}[];
|
|
210
240
|
};
|
|
211
241
|
|
|
212
|
-
export { type AiwgChunkedIndexLoadOptions, type AiwgChunkedIndexLoader, type AiwgChunkedIndexProgress, type AiwgChunkedIndexProgressPhase, type AiwgChunkedIndexQueryOptions, type AiwgChunkedIndexQueryResult, type AiwgChunkedIndexValidationResult, type AiwgFortemiChunkManifest, type AiwgFortemiChunkPart, type AiwgFortemiChunkPartRef, type AiwgFortemiIndexExport, type AiwgFortemiProvenance, type AiwgFortemiRecord, type AiwgFortemiRecordSource, type AiwgFortemiRecordType, type AiwgFortemiRelationship, type AiwgIndexController, type AiwgIndexControllerListener, type AiwgIndexControllerSnapshot, type AiwgIndexGraphOptions, type AiwgIndexQueryMatch, type AiwgIndexQueryOptions, type AiwgIndexQueryRankedItem, type AiwgIndexQueryResult, type AiwgIndexQueryWeights, type AiwgIndexValidationResult, type AiwgPrivacyClassification, type AiwgProvenanceConfidence, type AiwgReviewAction, type AiwgReviewDecision, type AiwgReviewDecisionExport, type AiwgReviewInput, aiwgFortemiIndexToCommunityGraph, assertAiwgFortemiChunkManifest, assertAiwgFortemiChunkPart, assertAiwgFortemiIndexExport, createAiwgFetchChunkLoader, createAiwgIndexController, createAiwgReviewDecisionExport, getAiwgFortemiFacets, queryAiwgFortemiIndex, validateAiwgFortemiChunkManifest, validateAiwgFortemiChunkPart, validateAiwgFortemiIndexExport };
|
|
242
|
+
export { AIWG_SCAN_REQUIRED_FIELDS, type AiwgChunkedIndexBuildOptions, type AiwgChunkedIndexBuildResult, type AiwgChunkedIndexDetailLoader, type AiwgChunkedIndexLoadOptions, type AiwgChunkedIndexLoader, type AiwgChunkedIndexProgress, type AiwgChunkedIndexProgressPhase, type AiwgChunkedIndexQueryOptions, type AiwgChunkedIndexQueryResult, type AiwgChunkedIndexValidationResult, type AiwgFortemiChunkDetailRef, type AiwgFortemiChunkManifest, type AiwgFortemiChunkPart, type AiwgFortemiChunkPartRef, type AiwgFortemiIndexExport, type AiwgFortemiProjectedRecord, type AiwgFortemiProvenance, type AiwgFortemiRecord, type AiwgFortemiRecordSource, type AiwgFortemiRecordType, type AiwgFortemiRelationship, type AiwgIndexController, type AiwgIndexControllerListener, type AiwgIndexControllerSnapshot, type AiwgIndexGraphOptions, type AiwgIndexQueryMatch, type AiwgIndexQueryOptions, type AiwgIndexQueryRankedItem, type AiwgIndexQueryResult, type AiwgIndexQueryWeights, type AiwgIndexValidationResult, type AiwgPrivacyClassification, type AiwgProvenanceConfidence, type AiwgReviewAction, type AiwgReviewDecision, type AiwgReviewDecisionExport, type AiwgReviewInput, aiwgFortemiIndexToCommunityGraph, assertAiwgFortemiChunkManifest, assertAiwgFortemiChunkPart, assertAiwgFortemiIndexExport, buildAiwgChunkedIndex, createAiwgFetchChunkLoader, createAiwgFetchDetailLoader, createAiwgIndexController, createAiwgReviewDecisionExport, getAiwgFortemiFacets, queryAiwgFortemiIndex, validateAiwgFortemiChunkManifest, validateAiwgFortemiChunkPart, validateAiwgFortemiIndexExport };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var e=["schema_version","id","type","source","title","text","facets","tags","concepts","relationships","provenance","privacy","updated_at"],t=new Set(["crm.contact","crm.organization","crm.event","crm.interaction","aiwg.artifact","docs.page"]),r={title:4,tag:3,concept:2,text:1};function n(e){return"string"==typeof e&&e.length>0}function s(e,t,r){e[t]??={},e[t][r]=(e[t][r]??0)+1}function i(e){return Number.isInteger(e)&&"number"==typeof e&&e>=0}function a(e){return Number.isInteger(e)&&"number"==typeof e&&e>0}function o(r){const s=[],i={},a=r;"aiwg.fortemi.index.export.v1"!==a?.schema_version&&s.push("schema_version must be aiwg.fortemi.index.export.v1"),n(a?.generated_at)||s.push("generated_at is required"),n(a?.source?.repo)||s.push("source.repo is required"),n(a?.source?.privacy)||s.push("source.privacy is required"),Array.isArray(a?.items)||s.push("items must be an array");const o=new Set;let c="";for(const[r,u]of(a.items??[]).entries()){for(const t of e)t in u||s.push("items["+r+"]."+t+" is required");"aiwg.fortemi.index.record.v1"!==u.schema_version&&s.push("items["+r+"].schema_version must be aiwg.fortemi.index.record.v1"),n(u.id)||s.push("items["+r+"].id is required"),n(u.id)&&o.has(u.id)&&s.push("duplicate id: "+u.id),n(u.id)&&o.add(u.id),c&&n(u.id)&&c.localeCompare(u.id)>0&&s.push("items must be sorted by id: "+c+" before "+u.id),n(u.id)&&(c=u.id),t.has(u.type)?i[u.type]=(i[u.type]??0)+1:s.push("items["+r+"].type is invalid"),n(u.source?.path)||s.push("items["+r+"].source.path is required"),n(u.source?.repo_relative_path)||s.push("items["+r+"].source.repo_relative_path is required"),n(u.source?.locator)||s.push("items["+r+"].source.locator is required"),Array.isArray(u.tags)||s.push("items["+r+"].tags must be an array"),Array.isArray(u.concepts)||s.push("items["+r+"].concepts must be an array"),Array.isArray(u.relationships)||s.push("items["+r+"].relationships must be an array"),Array.isArray(u.provenance)&&0!==u.provenance.length||s.push("items["+r+"].provenance must be a non-empty array"),u.privacy&&"boolean"==typeof u.privacy.pii&&n(u.privacy.classification)||s.push("items["+r+"].privacy requires classification and pii")}return{valid:0===s.length,errors:s,counts:i}}function c(e){const t=o(e);if(!t.valid)throw new Error("Invalid AIWG Fortemi index export:\n"+t.errors.join("\n"));return e}function u(e){const t=[],r=e;"aiwg.fortemi.index.chunk-manifest.v1"!==r?.schema_version&&t.push("schema_version must be aiwg.fortemi.index.chunk-manifest.v1"),n(r?.generated_at)||t.push("generated_at is required"),n(r?.source?.repo)||t.push("source.repo is required"),n(r?.source?.privacy)||t.push("source.privacy is required"),i(r?.total)||t.push("total must be a non-negative integer"),a(r?.part_size)||t.push("part_size must be a positive integer"),void 0===r.facets||function(e){return!(!e||"object"!=typeof e||Array.isArray(e))&&Object.values(e).every(e=>!!e&&"object"==typeof e&&!Array.isArray(e)&&Object.values(e).every(e=>i(e)))}(r.facets)||t.push("facets must be a nested string-to-number count object"),Array.isArray(r?.parts)||t.push("parts must be an array");let s=0;const o=Array.isArray(r?.parts)?r.parts:[];for(const[e,r]of o.entries())n(r.href)||t.push("parts["+e+"].href is required"),i(r.offset)||t.push("parts["+e+"].offset must be a non-negative integer"),i(r.count)||t.push("parts["+e+"].count must be a non-negative integer"),i(r.offset)&&r.offset!==s&&t.push("parts["+e+"].offset must be "+s),i(r.count)&&(s+=r.count);return i(r?.total)&&s!==r.total&&t.push("parts counts must add up to total"),{valid:0===t.length,errors:t}}function f(e){const t=u(e);if(!t.valid)throw new Error("Invalid AIWG Fortemi chunk manifest:\n"+t.errors.join("\n"));return e}function p(e,t,r){const n=[],s=e;if("aiwg.fortemi.index.chunk.v1"!==s?.schema_version&&n.push("schema_version must be aiwg.fortemi.index.chunk.v1"),"aiwg.fortemi.index.chunk-manifest.v1"!==s?.manifest_schema_version&&n.push("manifest_schema_version must be aiwg.fortemi.index.chunk-manifest.v1"),i(s?.offset)||n.push("offset must be a non-negative integer"),Array.isArray(s?.items)||n.push("items must be an array"),t&&i(s?.offset)&&s.offset!==t.offset&&n.push("offset must match manifest part offset "+t.offset),t&&Array.isArray(s?.items)&&s.items.length!==t.count&&n.push("items length must match manifest part count "+t.count),Array.isArray(s?.items)){const e=o({schema_version:"aiwg.fortemi.index.export.v1",generated_at:r?.generated_at??"1970-01-01T00:00:00.000Z",source:r?.source??{repo:"chunk",privacy:"public"},items:s.items});n.push(...e.errors.map(e=>"items."+e))}return{valid:0===n.length,errors:n}}function m(e,t,r){const n=p(e,t,r);if(!n.valid)throw new Error("Invalid AIWG Fortemi chunk part:\n"+n.errors.join("\n"));return e}function h(e){return async t=>{const r=e?new URL(t.href,e).toString():t.href,n=await fetch(r);if(!n.ok)throw new Error("Failed to fetch AIWG index chunk "+r+": "+n.status);return n.json()}}function l(e){const t={};for(const r of e){s(t,"type",r.type),s(t,"privacy",r.privacy.classification);for(const e of r.tags)s(t,"tag",e);for(const e of r.concepts)s(t,"concept",e);for(const[e,n]of Object.entries(r.facets))for(const r of n)s(t,e,r)}return t}function d(e,t){if(!t||0===t.length)return!0;const r=new Set(e);return t.every(e=>r.has(e))}function g(e,t){if(!t)return[];const r=[];e.title.toLowerCase().includes(t)&&r.push({field:"title",value:e.title}),e.text.toLowerCase().includes(t)&&r.push({field:"text",value:e.text});for(const n of e.tags)n.toLowerCase().includes(t)&&r.push({field:"tag",value:n});for(const n of e.concepts)n.toLowerCase().includes(t)&&r.push({field:"concept",value:n});return r}function y(e,t){return e.reduce((e,r)=>e+t[r.field],0)}function v(e,t,r,n){const s=t.find(e=>"text"===e.field),i=t.find(e=>"title"===e.field),a=s??i??t[0];return function(e,t,r){const n=Math.max(20,r);if(!e)return"";if(!t)return e.length>n?`${e.slice(0,n).trimEnd()}...`:e;const s=e.toLowerCase().indexOf(t);if(s<0)return e.length>n?`${e.slice(0,n).trimEnd()}...`:e;const i=Math.max(0,Math.floor((n-t.length)/2)),a=Math.max(0,s-i),o=Math.min(e.length,a+n),c=a>0?"...":"",u=o<e.length?"...":"";return`${c}${e.slice(a,o).trim()}${u}`}(a?.value??e.text,r,n)}function w(e,t,n,s=0){const i={...r,...n.weights};return e.map((e,r)=>({item:e,ordinal:s+r,matches:g(e,t)})).filter(({item:e,matches:r})=>!(t&&0===r.length||n.types&&!n.types.includes(e.type)||n.privacy&&!n.privacy.includes(e.privacy.classification)||!d(e.tags,n.tags)||!d(e.concepts,n.concepts)||!function(e,t){return!t||Object.entries(t).every(([t,r])=>d(e.facets[t]??[],r))}(e,n.facets)||n.relationshipTargetId&&!e.relationships.some(e=>e.target_id===n.relationshipTargetId))).map(({item:e,ordinal:t,matches:r})=>({item:e,ordinal:t,rank:y(r,i),matches:r}))}function x(e,t,r){const n=function(e,t){return[...e].sort((e,r)=>t&&r.rank-e.rank||e.ordinal-r.ordinal)}(e,r.rank),s=r.offset??0,i=r.limit??n.length,a=n.slice(s,s+i),o={items:a.map(e=>e.item),total:n.length,facets:l(n.map(e=>e.item))};if(r.rank||r.snippets||r.includeMatches){const e=r.snippetLength??160;o.rankedItems=a.map(n=>({item:n.item,rank:n.rank,...r.snippets?{snippet:v(n.item,n.matches,t,e)}:{},...r.includeMatches?{matches:n.matches}:{}}))}return o}function _(e,t="",r={}){const n=t.trim().toLowerCase();return x(w(e.items,n,r),n,r)}async function b(e,t){const r=function(e){return`${e.offset}:${e.href}`}(t),n=e.partCache.get(r);if(n)return e.partCache.delete(r),e.partCache.set(r,n),{part:n,fetched:!1};const s=m(await e.loader(t,e.manifest),t,e.manifest);for(e.partCache.set(r,s);e.partCache.size>e.maxCachedParts;){const t=e.partCache.keys().next().value;if(void 0===t)break;e.partCache.delete(t)}return{part:s,fetched:!0}}function C(e,t,r=(new Date).toISOString()){return{schema_version:"aiwg.fortemi.review-decisions.v1",generated_at:r,source_export_schema_version:e.schema_version,decisions:[...t].sort((e,t)=>e.item_id.localeCompare(t.item_id))}}function A(e){let t=e??null,r=null,n=null,s=null,i=[];const o=new Set,u=()=>({index:t,chunked:r?{manifest:r.manifest,cachedParts:r.partCache.size,maxCachedParts:r.maxCachedParts}:null,data:n,error:s,reviewDecisions:[...i]}),p=()=>{const e=u();for(const t of o)t(e)},m=()=>{if(!t)throw new Error("No AIWG index export loaded");return t};return{loadIndex(e){try{const a=c(e);return t=a,r=null,n=null,i=[],s=null,p(),a}catch(e){throw s=e instanceof Error?e:new Error(String(e)),p(),s}},loadChunkedIndex(e,o,c={}){try{const m=f(e);return t=null,r={manifest:m,loader:o,maxCachedParts:(u=c.maxCachedParts,a(u)?u:3),partCache:new Map},n=null,i=[],s=null,p(),m}catch(e){throw s=e instanceof Error?e:new Error(String(e)),p(),s}var u},getIndex:()=>t,getChunkedManifest:()=>r?.manifest??null,getSnapshot:()=>u(),query(e="",t){const r=_(m(),e,t);return n=r,s=null,p(),r},async queryChunked(e="",t){if(!r)throw new Error("No AIWG chunked index manifest loaded");try{const i=await async function(e,t="",r={}){const n=t.trim().toLowerCase();let s=0,i=0;if(function(e,t){return!(""!==e.trim()||t.rank||t.snippets||t.includeMatches||t.types||t.facets||t.tags||t.concepts||t.privacy||t.relationshipTargetId)}(t,r)){const t=r.offset??0,n=r.limit??e.manifest.total,a=function(e,t,r){const n=t+r;return e.parts.filter(e=>e.count>0&&e.offset<n&&e.offset+e.count>t)}(e.manifest,t,n),o=[];for(const c of a){const u=await b(e,c);u.fetched&&(i+=1),s+=1,r.onProgress?.({phase:"part",done:s,total:a.length,href:c.href});const f=Math.max(0,t-c.offset),p=Math.min(u.part.items.length,t+n-c.offset);o.push(...u.part.items.slice(f,p))}return{items:o,total:e.manifest.total,facets:e.manifest.facets??{},manifestTotal:e.manifest.total,scannedParts:s,fetchedParts:i,complete:!0}}const a=[];for(const t of e.manifest.parts){const o=await b(e,t);o.fetched&&(i+=1),s+=1,r.onProgress?.({phase:"part",done:s,total:e.manifest.parts.length,href:t.href}),a.push(...w(o.part.items,n,r,t.offset)),r.onProgress?.({phase:"query",done:s,total:e.manifest.parts.length,href:t.href})}return{...x(a,n,r),manifestTotal:e.manifest.total,scannedParts:s,fetchedParts:i,complete:!0}}(r,e,t);return n=i,s=null,p(),i}catch(e){throw s=e instanceof Error?e:new Error(String(e)),p(),s}},clearChunkCache(){r?.partCache.clear(),s=null,p()},toCommunityGraph:e=>k(m(),e),setReviewDecision(e){const t={...e,updated_at:(new Date).toISOString()};return i=[...i.filter(e=>e.item_id!==t.item_id),t].sort((e,t)=>e.item_id.localeCompare(t.item_id)),s=null,p(),t},clearReviewDecision(e){i=i.filter(t=>t.item_id!==e),s=null,p()},createReviewDecisionExport:e=>C(m(),i,e),subscribe:e=>(o.add(e),()=>{o.delete(e)})}}function k(e,t={}){const r=new Set(e.items.map(e=>e.id)),n=t.relationshipWeights??{},s=new Map;for(const i of e.items)for(const e of i.relationships){if(!r.has(e.target_id)&&!t.includeDanglingRelationships)continue;const a=e.type,o=n[a]??1,c=`${i.id}\0${e.target_id}\0${a}`,u=s.get(c);u?u.weight+=o:s.set(c,{source:i.id,target:e.target_id,kind:a,weight:o})}const i=new Map;for(const r of e.items){const e=I(r,t);for(const t of e){const e=i.get(t)??[];e.push(r.id),i.set(t,e)}}return{nodes:e.items.map(e=>({id:e.id})),edges:Array.from(s.values()).sort((e,t)=>e.source.localeCompare(t.source)||e.target.localeCompare(t.target)||e.kind.localeCompare(t.kind)),communities:Array.from(i.entries()).map(([e,t])=>({id:e,nodes:[...new Set(t)].sort()})).sort((e,t)=>e.id.localeCompare(t.id))}}function I(e,t){if(t.communityFacet){const r=e.facets[t.communityFacet]??[];if(r.length>0)return r.map(e=>`${t.communityFacet}:${e}`)}if(t.communityTagPrefix){const r=t.communityTagPrefix,n=e.tags.filter(e=>e.startsWith(r));if(n.length>0)return n}return e.concepts.length>0?e.concepts.map(e=>`concept:${e}`):[`type:${e.type}`]}export{k as aiwgFortemiIndexToCommunityGraph,f as assertAiwgFortemiChunkManifest,m as assertAiwgFortemiChunkPart,c as assertAiwgFortemiIndexExport,h as createAiwgFetchChunkLoader,A as createAiwgIndexController,C as createAiwgReviewDecisionExport,l as getAiwgFortemiFacets,_ as queryAiwgFortemiIndex,u as validateAiwgFortemiChunkManifest,p as validateAiwgFortemiChunkPart,o as validateAiwgFortemiIndexExport};
|
|
1
|
+
var e=["schema_version","id","type","title","text","facets","tags","concepts","privacy"],t=["schema_version","id","type","source","title","text","facets","tags","concepts","relationships","provenance","privacy","updated_at"],r=new Set(["crm.contact","crm.organization","crm.event","crm.interaction","aiwg.artifact","docs.page"]),i={title:4,tag:3,concept:2,text:1};function s(e){return"string"==typeof e&&e.length>0}function n(e,t,r){e[t]??={},e[t][r]=(e[t][r]??0)+1}function a(e){return Number.isInteger(e)&&"number"==typeof e&&e>=0}function o(e){return Number.isInteger(e)&&"number"==typeof e&&e>0}function c(e){const i=[],n={},a=e;"aiwg.fortemi.index.export.v1"!==a?.schema_version&&i.push("schema_version must be aiwg.fortemi.index.export.v1"),s(a?.generated_at)||i.push("generated_at is required"),s(a?.source?.repo)||i.push("source.repo is required"),s(a?.source?.privacy)||i.push("source.privacy is required"),Array.isArray(a?.items)||i.push("items must be an array");const o=new Set;let c="";for(const[e,u]of(a.items??[]).entries()){for(const r of t)r in u||i.push("items["+e+"]."+r+" is required");"aiwg.fortemi.index.record.v1"!==u.schema_version&&i.push("items["+e+"].schema_version must be aiwg.fortemi.index.record.v1"),s(u.id)||i.push("items["+e+"].id is required"),s(u.id)&&o.has(u.id)&&i.push("duplicate id: "+u.id),s(u.id)&&o.add(u.id),c&&s(u.id)&&c.localeCompare(u.id)>0&&i.push("items must be sorted by id: "+c+" before "+u.id),s(u.id)&&(c=u.id),r.has(u.type)?n[u.type]=(n[u.type]??0)+1:i.push("items["+e+"].type is invalid"),s(u.source?.path)||i.push("items["+e+"].source.path is required"),s(u.source?.repo_relative_path)||i.push("items["+e+"].source.repo_relative_path is required"),s(u.source?.locator)||i.push("items["+e+"].source.locator is required"),Array.isArray(u.tags)||i.push("items["+e+"].tags must be an array"),Array.isArray(u.concepts)||i.push("items["+e+"].concepts must be an array"),Array.isArray(u.relationships)||i.push("items["+e+"].relationships must be an array"),Array.isArray(u.provenance)&&0!==u.provenance.length||i.push("items["+e+"].provenance must be a non-empty array"),u.privacy&&"boolean"==typeof u.privacy.pii&&s(u.privacy.classification)||i.push("items["+e+"].privacy requires classification and pii")}return{valid:0===i.length,errors:i,counts:n}}function u(e){const t=c(e);if(!t.valid)throw new Error("Invalid AIWG Fortemi index export:\n"+t.errors.join("\n"));return e}function f(t){const r=[],i=t;if("aiwg.fortemi.index.chunk-manifest.v1"!==i?.schema_version&&r.push("schema_version must be aiwg.fortemi.index.chunk-manifest.v1"),s(i?.generated_at)||r.push("generated_at is required"),s(i?.source?.repo)||r.push("source.repo is required"),s(i?.source?.privacy)||r.push("source.privacy is required"),a(i?.total)||r.push("total must be a non-negative integer"),o(i?.part_size)||r.push("part_size must be a positive integer"),void 0===i.facets||function(e){return!(!e||"object"!=typeof e||Array.isArray(e))&&Object.values(e).every(e=>!!e&&"object"==typeof e&&!Array.isArray(e)&&Object.values(e).every(e=>a(e)))}(i.facets)||r.push("facets must be a nested string-to-number count object"),void 0!==i.projection)if(Array.isArray(i.projection)&&i.projection.every(e=>"string"==typeof e)){const t=new Set(i.projection);for(const i of e)t.has(i)||r.push("projection must include scan-required field "+i)}else r.push("projection must be an array of field names");void 0!==i.detail&&(s(i.detail.href)?i.detail.href.includes("{id}")||r.push("detail.href must contain the {id} placeholder"):r.push("detail.href is required")),Array.isArray(i?.parts)||r.push("parts must be an array");let n=0;const c=Array.isArray(i?.parts)?i.parts:[];for(const[e,t]of c.entries())s(t.href)||r.push("parts["+e+"].href is required"),a(t.offset)||r.push("parts["+e+"].offset must be a non-negative integer"),a(t.count)||r.push("parts["+e+"].count must be a non-negative integer"),a(t.offset)&&t.offset!==n&&r.push("parts["+e+"].offset must be "+n),a(t.count)&&(n+=t.count);return a(i?.total)&&n!==i.total&&r.push("parts counts must add up to total"),{valid:0===r.length,errors:r}}function d(e){const t=f(e);if(!t.valid)throw new Error("Invalid AIWG Fortemi chunk manifest:\n"+t.errors.join("\n"));return e}function p(e,t,i){const n=[],o=e;if("aiwg.fortemi.index.chunk.v1"!==o?.schema_version&&n.push("schema_version must be aiwg.fortemi.index.chunk.v1"),"aiwg.fortemi.index.chunk-manifest.v1"!==o?.manifest_schema_version&&n.push("manifest_schema_version must be aiwg.fortemi.index.chunk-manifest.v1"),a(o?.offset)||n.push("offset must be a non-negative integer"),Array.isArray(o?.items)||n.push("items must be an array"),t&&a(o?.offset)&&o.offset!==t.offset&&n.push("offset must match manifest part offset "+t.offset),t&&Array.isArray(o?.items)&&o.items.length!==t.count&&n.push("items length must match manifest part count "+t.count),Array.isArray(o?.items))if(i?.projection)n.push(...function(e){const t=[],i=new Set;let n="";for(const[a,o]of e.entries())"aiwg.fortemi.index.record.v1"!==o.schema_version&&t.push("items["+a+"].schema_version must be aiwg.fortemi.index.record.v1"),s(o.id)||t.push("items["+a+"].id is required"),s(o.id)&&i.has(o.id)&&t.push("duplicate id: "+o.id),s(o.id)&&i.add(o.id),n&&s(o.id)&&n.localeCompare(o.id)>0&&t.push("items must be sorted by id: "+n+" before "+o.id),s(o.id)&&(n=o.id),o.type&&r.has(o.type)||t.push("items["+a+"].type is invalid"),s(o.title)||t.push("items["+a+"].title is required"),"string"!=typeof o.text&&t.push("items["+a+"].text is required"),o.facets&&"object"==typeof o.facets&&!Array.isArray(o.facets)||t.push("items["+a+"].facets must be an object"),Array.isArray(o.tags)||t.push("items["+a+"].tags must be an array"),Array.isArray(o.concepts)||t.push("items["+a+"].concepts must be an array"),o.privacy&&s(o.privacy.classification)||t.push("items["+a+"].privacy.classification is required");return t}(o.items).map(e=>"items."+e));else{const e=c({schema_version:"aiwg.fortemi.index.export.v1",generated_at:i?.generated_at??"1970-01-01T00:00:00.000Z",source:i?.source??{repo:"chunk",privacy:"public"},items:o.items});n.push(...e.errors.map(e=>"items."+e))}return{valid:0===n.length,errors:n}}function h(e,t,r){const i=p(e,t,r);if(!i.valid)throw new Error("Invalid AIWG Fortemi chunk part:\n"+i.errors.join("\n"));return e}function m(e){return async t=>{const r=e?new URL(t.href,e).toString():t.href,i=await fetch(r);if(!i.ok)throw new Error("Failed to fetch AIWG index chunk "+r+": "+i.status);return i.json()}}function l(e){return async(t,r)=>{if(!r.detail?.href)throw new Error("Manifest has no detail.href for record resolution");const i=r.detail.href.replace("{id}",encodeURIComponent(t)),s=e?new URL(i,e).toString():i,n=await fetch(s);if(!n.ok)throw new Error("Failed to fetch AIWG index detail "+s+": "+n.status);return n.json()}}function g(e){const t={};for(const r of e){n(t,"type",r.type),n(t,"privacy",r.privacy.classification);for(const e of r.tags)n(t,"tag",e);for(const e of r.concepts)n(t,"concept",e);for(const[e,i]of Object.entries(r.facets))for(const r of i)n(t,e,r)}return t}function y(e,t={}){const r=o(t.partSize)?t.partSize:500,i=t.projection,s=e.items,n=e=>String(e).padStart(4,"0"),a=e=>{if(!i)return e;const t={};for(const r of i)t[r]=e[r];return t},c=[],u=[];for(let e=0,t=0;e<s.length;e+=r,t+=1){const i=s.slice(e,e+r),o="part-"+n(t)+".json";c.push({href:o,part:{schema_version:"aiwg.fortemi.index.chunk.v1",manifest_schema_version:"aiwg.fortemi.index.chunk-manifest.v1",offset:e,items:i.map(a)}}),u.push({href:o,offset:e,count:i.length})}return{manifest:{schema_version:"aiwg.fortemi.index.chunk-manifest.v1",generated_at:t.generatedAt??e.generated_at,source:e.source,total:s.length,part_size:r,facets:g(s),parts:u,...i?{projection:i,detail:{href:t.detailHref??"detail/{id}.json"}}:{}},parts:c,details:i?s.map(e=>({id:e.id,record:e})):[]}}function v(e,t){if(!t||0===t.length)return!0;const r=new Set(e);return t.every(e=>r.has(e))}function w(e,t){if(!t)return[];const r=[];e.title.toLowerCase().includes(t)&&r.push({field:"title",value:e.title}),e.text.toLowerCase().includes(t)&&r.push({field:"text",value:e.text});for(const i of e.tags)i.toLowerCase().includes(t)&&r.push({field:"tag",value:i});for(const i of e.concepts)i.toLowerCase().includes(t)&&r.push({field:"concept",value:i});return r}function x(e,t){return e.reduce((e,r)=>e+t[r.field],0)}function _(e,t,r,i){const s=t.find(e=>"text"===e.field),n=t.find(e=>"title"===e.field),a=s??n??t[0];return function(e,t,r){const i=Math.max(20,r);if(!e)return"";if(!t)return e.length>i?`${e.slice(0,i).trimEnd()}...`:e;const s=e.toLowerCase().indexOf(t);if(s<0)return e.length>i?`${e.slice(0,i).trimEnd()}...`:e;const n=Math.max(0,Math.floor((i-t.length)/2)),a=Math.max(0,s-n),o=Math.min(e.length,a+i),c=a>0?"...":"",u=o<e.length?"...":"";return`${c}${e.slice(a,o).trim()}${u}`}(a?.value??e.text,r,i)}function b(e,t,r,s=0){const n={...i,...r.weights};return e.map((e,r)=>({item:e,ordinal:s+r,matches:w(e,t)})).filter(({item:e,matches:i})=>!(t&&0===i.length||r.types&&!r.types.includes(e.type)||r.privacy&&!r.privacy.includes(e.privacy.classification)||!v(e.tags,r.tags)||!v(e.concepts,r.concepts)||!function(e,t){return!t||Object.entries(t).every(([t,r])=>v(e.facets[t]??[],r))}(e,r.facets)||r.relationshipTargetId&&!(e.relationships??[]).some(e=>e.target_id===r.relationshipTargetId))).map(({item:e,ordinal:t,matches:r})=>({item:e,ordinal:t,rank:x(r,n),matches:r}))}function C(e,t,r){const i=function(e,t){return[...e].sort((e,r)=>t&&r.rank-e.rank||e.ordinal-r.ordinal)}(e,r.rank),s=r.offset??0,n=r.limit??i.length,a=i.slice(s,s+n),o={items:a.map(e=>e.item),total:i.length,facets:g(i.map(e=>e.item))};if(r.rank||r.snippets||r.includeMatches){const e=r.snippetLength??160;o.rankedItems=a.map(i=>({item:i.item,rank:i.rank,...r.snippets?{snippet:_(i.item,i.matches,t,e)}:{},...r.includeMatches?{matches:i.matches}:{}}))}return o}function A(e,t="",r={}){const i=t.trim().toLowerCase();return C(b(e.items,i,r),i,r)}function k(e){return o(e)?e:32}async function j(e,t){const r=function(e){return`${e.offset}:${e.href}`}(t),i=e.partCache.get(r);if(i)return e.partCache.delete(r),e.partCache.set(r,i),{part:i,fetched:!1};const s=h(await e.loader(t,e.manifest),t,e.manifest);for(e.partCache.set(r,s);e.partCache.size>e.maxCachedParts;){const t=e.partCache.keys().next().value;if(void 0===t)break;e.partCache.delete(t)}return{part:s,fetched:!0}}function S(e,t,r=(new Date).toISOString()){return{schema_version:"aiwg.fortemi.review-decisions.v1",generated_at:r,source_export_schema_version:e.schema_version,decisions:[...t].sort((e,t)=>e.item_id.localeCompare(t.item_id))}}function q(e){let t=e??null,r=null,i=null,s=null,n=[];const a=new Set,c=()=>({index:t,chunked:r?{manifest:r.manifest,cachedParts:r.partCache.size,maxCachedParts:r.maxCachedParts}:null,data:i,error:s,reviewDecisions:[...n]}),f=()=>{const e=c();for(const t of a)t(e)},p=()=>{if(!t)throw new Error("No AIWG index export loaded");return t};return{loadIndex(e){try{const a=u(e);return t=a,r=null,i=null,n=[],s=null,f(),a}catch(e){throw s=e instanceof Error?e:new Error(String(e)),f(),s}},loadChunkedIndex(e,a,c={}){try{const p=d(e);return t=null,r={manifest:p,loader:a,maxCachedParts:(u=c.maxCachedParts,o(u)?u:3),partCache:new Map,detailLoader:c.detailLoader,maxCachedDetails:k(c.maxCachedDetails),detailCache:new Map},i=null,n=[],s=null,f(),p}catch(e){throw s=e instanceof Error?e:new Error(String(e)),f(),s}var u},getIndex:()=>t,getChunkedManifest:()=>r?.manifest??null,getSnapshot:()=>c(),query(e="",t){const r=A(p(),e,t);return i=r,s=null,f(),r},async queryChunked(e="",t){if(!r)throw new Error("No AIWG chunked index manifest loaded");try{const n=await async function(e,t="",r={}){const i=t.trim().toLowerCase();let s=0,n=0;if(function(e,t){return!(""!==e.trim()||t.rank||t.snippets||t.includeMatches||t.types||t.facets||t.tags||t.concepts||t.privacy||t.relationshipTargetId)}(t,r)){const t=r.offset??0,i=r.limit??e.manifest.total,a=function(e,t,r){const i=t+r;return e.parts.filter(e=>e.count>0&&e.offset<i&&e.offset+e.count>t)}(e.manifest,t,i),o=[];for(const c of a){const u=await j(e,c);u.fetched&&(n+=1),s+=1,r.onProgress?.({phase:"part",done:s,total:a.length,href:c.href});const f=Math.max(0,t-c.offset),d=Math.min(u.part.items.length,t+i-c.offset);o.push(...u.part.items.slice(f,d))}return{items:o,total:e.manifest.total,facets:e.manifest.facets??{},manifestTotal:e.manifest.total,scannedParts:s,fetchedParts:n,complete:!0}}const a=[];for(const t of e.manifest.parts){const o=await j(e,t);o.fetched&&(n+=1),s+=1,r.onProgress?.({phase:"part",done:s,total:e.manifest.parts.length,href:t.href}),a.push(...b(o.part.items,i,r,t.offset)),r.onProgress?.({phase:"query",done:s,total:e.manifest.parts.length,href:t.href})}return{...C(a,i,r),manifestTotal:e.manifest.total,scannedParts:s,fetchedParts:n,complete:!0}}(r,e,t);return i=n,s=null,f(),n}catch(e){throw s=e instanceof Error?e:new Error(String(e)),f(),s}},async getRecord(e){if(r)try{return await async function(e,t){const r=e.detailCache.get(t);if(r)return e.detailCache.delete(t),e.detailCache.set(t,r),r;if(!e.manifest.projection)for(const r of e.partCache.values()){const e=r.items.find(e=>e.id===t);if(e)return e}if(!e.detailLoader)throw new Error("No detailLoader configured to resolve record "+t);const i=await e.detailLoader(t,e.manifest),s=u({schema_version:"aiwg.fortemi.index.export.v1",generated_at:e.manifest.generated_at,source:e.manifest.source,items:[i]}).items[0];if(s.id!==t)throw new Error("Detail record id mismatch: expected "+t+", got "+s.id);for(e.detailCache.set(t,s);e.detailCache.size>e.maxCachedDetails;){const t=e.detailCache.keys().next().value;if(void 0===t)break;e.detailCache.delete(t)}return s}(r,e)}catch(e){throw s=e instanceof Error?e:new Error(String(e)),f(),s}const t=p().items.find(t=>t.id===e);if(!t)throw new Error("Record not found: "+e);return t},clearChunkCache(){r?.partCache.clear(),r?.detailCache.clear(),s=null,f()},toCommunityGraph:e=>E(p(),e),setReviewDecision(e){const t={...e,updated_at:(new Date).toISOString()};return n=[...n.filter(e=>e.item_id!==t.item_id),t].sort((e,t)=>e.item_id.localeCompare(t.item_id)),s=null,f(),t},clearReviewDecision(e){n=n.filter(t=>t.item_id!==e),s=null,f()},createReviewDecisionExport:e=>S(p(),n,e),subscribe:e=>(a.add(e),()=>{a.delete(e)})}}function E(e,t={}){const r=new Set(e.items.map(e=>e.id)),i=t.relationshipWeights??{},s=new Map;for(const n of e.items)for(const e of n.relationships){if(!r.has(e.target_id)&&!t.includeDanglingRelationships)continue;const a=e.type,o=i[a]??1,c=`${n.id}\0${e.target_id}\0${a}`,u=s.get(c);u?u.weight+=o:s.set(c,{source:n.id,target:e.target_id,kind:a,weight:o})}const n=new Map;for(const r of e.items){const e=I(r,t);for(const t of e){const e=n.get(t)??[];e.push(r.id),n.set(t,e)}}return{nodes:e.items.map(e=>({id:e.id})),edges:Array.from(s.values()).sort((e,t)=>e.source.localeCompare(t.source)||e.target.localeCompare(t.target)||e.kind.localeCompare(t.kind)),communities:Array.from(n.entries()).map(([e,t])=>({id:e,nodes:[...new Set(t)].sort()})).sort((e,t)=>e.id.localeCompare(t.id))}}function I(e,t){if(t.communityFacet){const r=e.facets[t.communityFacet]??[];if(r.length>0)return r.map(e=>`${t.communityFacet}:${e}`)}if(t.communityTagPrefix){const r=t.communityTagPrefix,i=e.tags.filter(e=>e.startsWith(r));if(i.length>0)return i}return e.concepts.length>0?e.concepts.map(e=>`concept:${e}`):[`type:${e.type}`]}export{e as AIWG_SCAN_REQUIRED_FIELDS,E as aiwgFortemiIndexToCommunityGraph,d as assertAiwgFortemiChunkManifest,h as assertAiwgFortemiChunkPart,u as assertAiwgFortemiIndexExport,y as buildAiwgChunkedIndex,m as createAiwgFetchChunkLoader,l as createAiwgFetchDetailLoader,q as createAiwgIndexController,S as createAiwgReviewDecisionExport,g as getAiwgFortemiFacets,A as queryAiwgFortemiIndex,f as validateAiwgFortemiChunkManifest,p as validateAiwgFortemiChunkPart,c as validateAiwgFortemiIndexExport};
|
package/src/index.html
CHANGED
|
@@ -5,8 +5,22 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
6
6
|
<title>Docs Toolkit</title>
|
|
7
7
|
<meta name="description" content="Reusable documentation toolkit for multi-tenant services." />
|
|
8
|
-
<
|
|
9
|
-
|
|
8
|
+
<script>
|
|
9
|
+
// Resolve all asset/module URLs against the tenant root in both deploy
|
|
10
|
+
// modes: domain-per-tenant at root (production, with SPA fallback) and a
|
|
11
|
+
// subpath mount such as /<tenant>/ (the dev preview server). The tenant id
|
|
12
|
+
// is injected at build time. On a domain root the path never starts with
|
|
13
|
+
// "/<tenant>/", so the base falls back to "/". For the un-replaced default
|
|
14
|
+
// build the placeholder starts with "_", which also yields "/".
|
|
15
|
+
(function () {
|
|
16
|
+
var t = "__PAGENARY_TENANT__";
|
|
17
|
+
var p = location.pathname;
|
|
18
|
+
var base = t && p.indexOf("/" + t + "/") === 0 ? "/" + t + "/" : "/";
|
|
19
|
+
document.write('<base href="' + base + '">');
|
|
20
|
+
})();
|
|
21
|
+
</script>
|
|
22
|
+
<link rel="icon" type="image/png" href="./favicon.png" />
|
|
23
|
+
<link rel="stylesheet" href="./styles.css" />
|
|
10
24
|
</head>
|
|
11
25
|
<body>
|
|
12
26
|
<a class="skip-link" href="#app">Skip to content</a>
|
|
@@ -51,6 +65,6 @@
|
|
|
51
65
|
<ul id="commandList" class="cmd-list" role="listbox"></ul>
|
|
52
66
|
</div>
|
|
53
67
|
</div>
|
|
54
|
-
<script type="module" src="
|
|
68
|
+
<script type="module" src="./app.js"></script>
|
|
55
69
|
</body>
|
|
56
70
|
</html>
|
|
@@ -51,6 +51,11 @@ interface AiwgFortemiChunkPartRef {
|
|
|
51
51
|
offset: number;
|
|
52
52
|
count: number;
|
|
53
53
|
}
|
|
54
|
+
declare const AIWG_SCAN_REQUIRED_FIELDS: Array<keyof AiwgFortemiRecord>;
|
|
55
|
+
type AiwgFortemiProjectedRecord = Pick<AiwgFortemiRecord, 'schema_version' | 'id' | 'type' | 'title' | 'text' | 'facets' | 'tags' | 'concepts' | 'privacy'> & Partial<AiwgFortemiRecord>;
|
|
56
|
+
interface AiwgFortemiChunkDetailRef {
|
|
57
|
+
href: string;
|
|
58
|
+
}
|
|
54
59
|
interface AiwgFortemiChunkManifest {
|
|
55
60
|
schema_version: 'aiwg.fortemi.index.chunk-manifest.v1';
|
|
56
61
|
generated_at: string;
|
|
@@ -58,6 +63,8 @@ interface AiwgFortemiChunkManifest {
|
|
|
58
63
|
total: number;
|
|
59
64
|
part_size: number;
|
|
60
65
|
facets?: Record<string, Record<string, number>>;
|
|
66
|
+
projection?: Array<keyof AiwgFortemiRecord>;
|
|
67
|
+
detail?: AiwgFortemiChunkDetailRef;
|
|
61
68
|
parts: AiwgFortemiChunkPartRef[];
|
|
62
69
|
}
|
|
63
70
|
interface AiwgFortemiChunkPart {
|
|
@@ -113,8 +120,11 @@ interface AiwgIndexQueryResult {
|
|
|
113
120
|
rankedItems?: AiwgIndexQueryRankedItem[];
|
|
114
121
|
}
|
|
115
122
|
type AiwgChunkedIndexLoader = (part: AiwgFortemiChunkPartRef, manifest: AiwgFortemiChunkManifest) => Promise<unknown>;
|
|
123
|
+
type AiwgChunkedIndexDetailLoader = (id: string, manifest: AiwgFortemiChunkManifest) => Promise<unknown>;
|
|
116
124
|
interface AiwgChunkedIndexLoadOptions {
|
|
117
125
|
maxCachedParts?: number;
|
|
126
|
+
detailLoader?: AiwgChunkedIndexDetailLoader;
|
|
127
|
+
maxCachedDetails?: number;
|
|
118
128
|
}
|
|
119
129
|
type AiwgChunkedIndexProgressPhase = 'part' | 'query';
|
|
120
130
|
interface AiwgChunkedIndexProgress {
|
|
@@ -175,6 +185,7 @@ interface AiwgIndexController {
|
|
|
175
185
|
getSnapshot(): AiwgIndexControllerSnapshot;
|
|
176
186
|
query(query?: string, options?: AiwgIndexQueryOptions): AiwgIndexQueryResult;
|
|
177
187
|
queryChunked(query?: string, options?: AiwgChunkedIndexQueryOptions): Promise<AiwgChunkedIndexQueryResult>;
|
|
188
|
+
getRecord(id: string): Promise<AiwgFortemiRecord>;
|
|
178
189
|
clearChunkCache(): void;
|
|
179
190
|
toCommunityGraph(options?: AiwgIndexGraphOptions): ReturnType<typeof aiwgFortemiIndexToCommunityGraph>;
|
|
180
191
|
setReviewDecision(input: AiwgReviewInput): AiwgReviewDecision;
|
|
@@ -189,7 +200,26 @@ declare function assertAiwgFortemiChunkManifest(value: unknown): AiwgFortemiChun
|
|
|
189
200
|
declare function validateAiwgFortemiChunkPart(value: unknown, partRef?: AiwgFortemiChunkPartRef, manifest?: AiwgFortemiChunkManifest): AiwgChunkedIndexValidationResult;
|
|
190
201
|
declare function assertAiwgFortemiChunkPart(value: unknown, partRef?: AiwgFortemiChunkPartRef, manifest?: AiwgFortemiChunkManifest): AiwgFortemiChunkPart;
|
|
191
202
|
declare function createAiwgFetchChunkLoader(baseUrl?: string | URL): AiwgChunkedIndexLoader;
|
|
203
|
+
declare function createAiwgFetchDetailLoader(baseUrl?: string | URL): AiwgChunkedIndexDetailLoader;
|
|
192
204
|
declare function getAiwgFortemiFacets(items: AiwgFortemiRecord[]): Record<string, Record<string, number>>;
|
|
205
|
+
interface AiwgChunkedIndexBuildOptions {
|
|
206
|
+
partSize?: number;
|
|
207
|
+
projection?: Array<keyof AiwgFortemiRecord>;
|
|
208
|
+
detailHref?: string;
|
|
209
|
+
generatedAt?: string;
|
|
210
|
+
}
|
|
211
|
+
interface AiwgChunkedIndexBuildResult {
|
|
212
|
+
manifest: AiwgFortemiChunkManifest;
|
|
213
|
+
parts: Array<{
|
|
214
|
+
href: string;
|
|
215
|
+
part: AiwgFortemiChunkPart;
|
|
216
|
+
}>;
|
|
217
|
+
details: Array<{
|
|
218
|
+
id: string;
|
|
219
|
+
record: AiwgFortemiRecord;
|
|
220
|
+
}>;
|
|
221
|
+
}
|
|
222
|
+
declare function buildAiwgChunkedIndex(index: AiwgFortemiIndexExport, options?: AiwgChunkedIndexBuildOptions): AiwgChunkedIndexBuildResult;
|
|
193
223
|
declare function queryAiwgFortemiIndex(index: AiwgFortemiIndexExport, query?: string, options?: AiwgIndexQueryOptions): AiwgIndexQueryResult;
|
|
194
224
|
declare function createAiwgReviewDecisionExport(source: AiwgFortemiIndexExport, decisions: AiwgReviewDecision[], generatedAt?: string): AiwgReviewDecisionExport;
|
|
195
225
|
declare function createAiwgIndexController(initialIndex?: AiwgFortemiIndexExport): AiwgIndexController;
|
|
@@ -209,4 +239,4 @@ declare function aiwgFortemiIndexToCommunityGraph(index: AiwgFortemiIndexExport,
|
|
|
209
239
|
}[];
|
|
210
240
|
};
|
|
211
241
|
|
|
212
|
-
export { type AiwgChunkedIndexLoadOptions, type AiwgChunkedIndexLoader, type AiwgChunkedIndexProgress, type AiwgChunkedIndexProgressPhase, type AiwgChunkedIndexQueryOptions, type AiwgChunkedIndexQueryResult, type AiwgChunkedIndexValidationResult, type AiwgFortemiChunkManifest, type AiwgFortemiChunkPart, type AiwgFortemiChunkPartRef, type AiwgFortemiIndexExport, type AiwgFortemiProvenance, type AiwgFortemiRecord, type AiwgFortemiRecordSource, type AiwgFortemiRecordType, type AiwgFortemiRelationship, type AiwgIndexController, type AiwgIndexControllerListener, type AiwgIndexControllerSnapshot, type AiwgIndexGraphOptions, type AiwgIndexQueryMatch, type AiwgIndexQueryOptions, type AiwgIndexQueryRankedItem, type AiwgIndexQueryResult, type AiwgIndexQueryWeights, type AiwgIndexValidationResult, type AiwgPrivacyClassification, type AiwgProvenanceConfidence, type AiwgReviewAction, type AiwgReviewDecision, type AiwgReviewDecisionExport, type AiwgReviewInput, aiwgFortemiIndexToCommunityGraph, assertAiwgFortemiChunkManifest, assertAiwgFortemiChunkPart, assertAiwgFortemiIndexExport, createAiwgFetchChunkLoader, createAiwgIndexController, createAiwgReviewDecisionExport, getAiwgFortemiFacets, queryAiwgFortemiIndex, validateAiwgFortemiChunkManifest, validateAiwgFortemiChunkPart, validateAiwgFortemiIndexExport };
|
|
242
|
+
export { AIWG_SCAN_REQUIRED_FIELDS, type AiwgChunkedIndexBuildOptions, type AiwgChunkedIndexBuildResult, type AiwgChunkedIndexDetailLoader, type AiwgChunkedIndexLoadOptions, type AiwgChunkedIndexLoader, type AiwgChunkedIndexProgress, type AiwgChunkedIndexProgressPhase, type AiwgChunkedIndexQueryOptions, type AiwgChunkedIndexQueryResult, type AiwgChunkedIndexValidationResult, type AiwgFortemiChunkDetailRef, type AiwgFortemiChunkManifest, type AiwgFortemiChunkPart, type AiwgFortemiChunkPartRef, type AiwgFortemiIndexExport, type AiwgFortemiProjectedRecord, type AiwgFortemiProvenance, type AiwgFortemiRecord, type AiwgFortemiRecordSource, type AiwgFortemiRecordType, type AiwgFortemiRelationship, type AiwgIndexController, type AiwgIndexControllerListener, type AiwgIndexControllerSnapshot, type AiwgIndexGraphOptions, type AiwgIndexQueryMatch, type AiwgIndexQueryOptions, type AiwgIndexQueryRankedItem, type AiwgIndexQueryResult, type AiwgIndexQueryWeights, type AiwgIndexValidationResult, type AiwgPrivacyClassification, type AiwgProvenanceConfidence, type AiwgReviewAction, type AiwgReviewDecision, type AiwgReviewDecisionExport, type AiwgReviewInput, aiwgFortemiIndexToCommunityGraph, assertAiwgFortemiChunkManifest, assertAiwgFortemiChunkPart, assertAiwgFortemiIndexExport, buildAiwgChunkedIndex, createAiwgFetchChunkLoader, createAiwgFetchDetailLoader, createAiwgIndexController, createAiwgReviewDecisionExport, getAiwgFortemiFacets, queryAiwgFortemiIndex, validateAiwgFortemiChunkManifest, validateAiwgFortemiChunkPart, validateAiwgFortemiIndexExport };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* VENDORED — @fortemi/core/aiwg-index
|
|
3
3
|
*
|
|
4
|
-
* Source : @fortemi/core@2026.6.
|
|
5
|
-
* SHA-256:
|
|
4
|
+
* Source : @fortemi/core@2026.6.3 → dist/aiwg-index.js
|
|
5
|
+
* SHA-256: f2e6793fd7e52e20441459e18633a9546c7cfc6281cbc63ab28f0767422946bf (upstream dist file)
|
|
6
6
|
* License: AGPL-3.0-only (compatible with this package's AGPL-3.0-or-later)
|
|
7
7
|
* Why : Pagenary's publisher build is a no-bundler copy-src→dist pipeline that
|
|
8
8
|
* loads ES modules by relative path; bare specifiers (`@fortemi/core`)
|
|
@@ -11,8 +11,20 @@
|
|
|
11
11
|
* browser (runtime search). See .aiwg/architecture/adr/ADR-015-*.md.
|
|
12
12
|
* Update : Re-vendor by copying the dist file from a newer @fortemi/core release
|
|
13
13
|
* and refreshing the SHA-256 above. Do not hand-edit below this banner.
|
|
14
|
+
* 6.3 adds buildAiwgChunkedIndex / createAiwgFetchDetailLoader (additive).
|
|
14
15
|
*/
|
|
15
16
|
// src/aiwg-index.ts
|
|
17
|
+
var AIWG_SCAN_REQUIRED_FIELDS = [
|
|
18
|
+
"schema_version",
|
|
19
|
+
"id",
|
|
20
|
+
"type",
|
|
21
|
+
"title",
|
|
22
|
+
"text",
|
|
23
|
+
"facets",
|
|
24
|
+
"tags",
|
|
25
|
+
"concepts",
|
|
26
|
+
"privacy"
|
|
27
|
+
];
|
|
16
28
|
var REQUIRED_RECORD_FIELDS = [
|
|
17
29
|
"schema_version",
|
|
18
30
|
"id",
|
|
@@ -124,6 +136,20 @@ function validateAiwgFortemiChunkManifest(value) {
|
|
|
124
136
|
if (data.facets !== void 0 && !isFacetCounts(data.facets)) {
|
|
125
137
|
errors.push("facets must be a nested string-to-number count object");
|
|
126
138
|
}
|
|
139
|
+
if (data.projection !== void 0) {
|
|
140
|
+
if (!Array.isArray(data.projection) || !data.projection.every((field) => typeof field === "string")) {
|
|
141
|
+
errors.push("projection must be an array of field names");
|
|
142
|
+
} else {
|
|
143
|
+
const present = new Set(data.projection);
|
|
144
|
+
for (const field of AIWG_SCAN_REQUIRED_FIELDS) {
|
|
145
|
+
if (!present.has(field)) errors.push("projection must include scan-required field " + field);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (data.detail !== void 0) {
|
|
150
|
+
if (!hasString(data.detail.href)) errors.push("detail.href is required");
|
|
151
|
+
else if (!data.detail.href.includes("{id}")) errors.push("detail.href must contain the {id} placeholder");
|
|
152
|
+
}
|
|
127
153
|
if (!Array.isArray(data?.parts)) errors.push("parts must be an array");
|
|
128
154
|
let expectedOffset = 0;
|
|
129
155
|
const parts = Array.isArray(data?.parts) ? data.parts : [];
|
|
@@ -148,6 +174,35 @@ function assertAiwgFortemiChunkManifest(value) {
|
|
|
148
174
|
}
|
|
149
175
|
return value;
|
|
150
176
|
}
|
|
177
|
+
function validateProjectedRecords(items) {
|
|
178
|
+
const errors = [];
|
|
179
|
+
const ids = /* @__PURE__ */ new Set();
|
|
180
|
+
let previousId = "";
|
|
181
|
+
for (const [index, item] of items.entries()) {
|
|
182
|
+
if (item.schema_version !== "aiwg.fortemi.index.record.v1") {
|
|
183
|
+
errors.push("items[" + index + "].schema_version must be aiwg.fortemi.index.record.v1");
|
|
184
|
+
}
|
|
185
|
+
if (!hasString(item.id)) errors.push("items[" + index + "].id is required");
|
|
186
|
+
if (hasString(item.id) && ids.has(item.id)) errors.push("duplicate id: " + item.id);
|
|
187
|
+
if (hasString(item.id)) ids.add(item.id);
|
|
188
|
+
if (previousId && hasString(item.id) && previousId.localeCompare(item.id) > 0) {
|
|
189
|
+
errors.push("items must be sorted by id: " + previousId + " before " + item.id);
|
|
190
|
+
}
|
|
191
|
+
if (hasString(item.id)) previousId = item.id;
|
|
192
|
+
if (!item.type || !VALID_TYPES.has(item.type)) errors.push("items[" + index + "].type is invalid");
|
|
193
|
+
if (!hasString(item.title)) errors.push("items[" + index + "].title is required");
|
|
194
|
+
if (typeof item.text !== "string") errors.push("items[" + index + "].text is required");
|
|
195
|
+
if (!item.facets || typeof item.facets !== "object" || Array.isArray(item.facets)) {
|
|
196
|
+
errors.push("items[" + index + "].facets must be an object");
|
|
197
|
+
}
|
|
198
|
+
if (!Array.isArray(item.tags)) errors.push("items[" + index + "].tags must be an array");
|
|
199
|
+
if (!Array.isArray(item.concepts)) errors.push("items[" + index + "].concepts must be an array");
|
|
200
|
+
if (!item.privacy || !hasString(item.privacy.classification)) {
|
|
201
|
+
errors.push("items[" + index + "].privacy.classification is required");
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return errors;
|
|
205
|
+
}
|
|
151
206
|
function validateAiwgFortemiChunkPart(value, partRef, manifest) {
|
|
152
207
|
const errors = [];
|
|
153
208
|
const data = value;
|
|
@@ -166,13 +221,17 @@ function validateAiwgFortemiChunkPart(value, partRef, manifest) {
|
|
|
166
221
|
errors.push("items length must match manifest part count " + partRef.count);
|
|
167
222
|
}
|
|
168
223
|
if (Array.isArray(data?.items)) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
224
|
+
if (manifest?.projection) {
|
|
225
|
+
errors.push(...validateProjectedRecords(data.items).map((error) => "items." + error));
|
|
226
|
+
} else {
|
|
227
|
+
const validation = validateAiwgFortemiIndexExport({
|
|
228
|
+
schema_version: "aiwg.fortemi.index.export.v1",
|
|
229
|
+
generated_at: manifest?.generated_at ?? "1970-01-01T00:00:00.000Z",
|
|
230
|
+
source: manifest?.source ?? { repo: "chunk", privacy: "public" },
|
|
231
|
+
items: data.items
|
|
232
|
+
});
|
|
233
|
+
errors.push(...validation.errors.map((error) => "items." + error));
|
|
234
|
+
}
|
|
176
235
|
}
|
|
177
236
|
return { valid: errors.length === 0, errors };
|
|
178
237
|
}
|
|
@@ -191,6 +250,16 @@ function createAiwgFetchChunkLoader(baseUrl) {
|
|
|
191
250
|
return response.json();
|
|
192
251
|
};
|
|
193
252
|
}
|
|
253
|
+
function createAiwgFetchDetailLoader(baseUrl) {
|
|
254
|
+
return async (id, manifest) => {
|
|
255
|
+
if (!manifest.detail?.href) throw new Error("Manifest has no detail.href for record resolution");
|
|
256
|
+
const relative = manifest.detail.href.replace("{id}", encodeURIComponent(id));
|
|
257
|
+
const href = baseUrl ? new URL(relative, baseUrl).toString() : relative;
|
|
258
|
+
const response = await fetch(href);
|
|
259
|
+
if (!response.ok) throw new Error("Failed to fetch AIWG index detail " + href + ": " + response.status);
|
|
260
|
+
return response.json();
|
|
261
|
+
};
|
|
262
|
+
}
|
|
194
263
|
function getAiwgFortemiFacets(items) {
|
|
195
264
|
const result = {};
|
|
196
265
|
for (const item of items) {
|
|
@@ -204,6 +273,49 @@ function getAiwgFortemiFacets(items) {
|
|
|
204
273
|
}
|
|
205
274
|
return result;
|
|
206
275
|
}
|
|
276
|
+
function buildAiwgChunkedIndex(index, options = {}) {
|
|
277
|
+
const partSize = hasPositiveInteger(options.partSize) ? options.partSize : 500;
|
|
278
|
+
const projection = options.projection;
|
|
279
|
+
const items = index.items;
|
|
280
|
+
const pad = (value) => String(value).padStart(4, "0");
|
|
281
|
+
const project = (record) => {
|
|
282
|
+
if (!projection) return record;
|
|
283
|
+
const slim = {};
|
|
284
|
+
for (const field of projection) slim[field] = record[field];
|
|
285
|
+
return slim;
|
|
286
|
+
};
|
|
287
|
+
const parts = [];
|
|
288
|
+
const partRefs = [];
|
|
289
|
+
for (let offset = 0, partIndex = 0; offset < items.length; offset += partSize, partIndex += 1) {
|
|
290
|
+
const slice = items.slice(offset, offset + partSize);
|
|
291
|
+
const href = "part-" + pad(partIndex) + ".json";
|
|
292
|
+
parts.push({
|
|
293
|
+
href,
|
|
294
|
+
part: {
|
|
295
|
+
schema_version: "aiwg.fortemi.index.chunk.v1",
|
|
296
|
+
manifest_schema_version: "aiwg.fortemi.index.chunk-manifest.v1",
|
|
297
|
+
offset,
|
|
298
|
+
items: slice.map(project)
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
partRefs.push({ href, offset, count: slice.length });
|
|
302
|
+
}
|
|
303
|
+
const manifest = {
|
|
304
|
+
schema_version: "aiwg.fortemi.index.chunk-manifest.v1",
|
|
305
|
+
generated_at: options.generatedAt ?? index.generated_at,
|
|
306
|
+
source: index.source,
|
|
307
|
+
total: items.length,
|
|
308
|
+
part_size: partSize,
|
|
309
|
+
facets: getAiwgFortemiFacets(items),
|
|
310
|
+
parts: partRefs,
|
|
311
|
+
...projection ? { projection, detail: { href: options.detailHref ?? "detail/{id}.json" } } : {}
|
|
312
|
+
};
|
|
313
|
+
return {
|
|
314
|
+
manifest,
|
|
315
|
+
parts,
|
|
316
|
+
details: projection ? items.map((record) => ({ id: record.id, record })) : []
|
|
317
|
+
};
|
|
318
|
+
}
|
|
207
319
|
function includesAll(actual, expected) {
|
|
208
320
|
if (!expected || expected.length === 0) return true;
|
|
209
321
|
const actualSet = new Set(actual);
|
|
@@ -258,7 +370,7 @@ function createRankedEntries(items, q, options, ordinalBase = 0) {
|
|
|
258
370
|
if (!includesAll(item.tags, options.tags)) return false;
|
|
259
371
|
if (!includesAll(item.concepts, options.concepts)) return false;
|
|
260
372
|
if (!matchesFacetFilters(item, options.facets)) return false;
|
|
261
|
-
if (options.relationshipTargetId && !item.relationships.some((rel) => rel.target_id === options.relationshipTargetId)) {
|
|
373
|
+
if (options.relationshipTargetId && !(item.relationships ?? []).some((rel) => rel.target_id === options.relationshipTargetId)) {
|
|
262
374
|
return false;
|
|
263
375
|
}
|
|
264
376
|
return true;
|
|
@@ -307,6 +419,10 @@ function clampMaxCachedParts(value) {
|
|
|
307
419
|
if (!hasPositiveInteger(value)) return 3;
|
|
308
420
|
return value;
|
|
309
421
|
}
|
|
422
|
+
function clampMaxCachedDetails(value) {
|
|
423
|
+
if (!hasPositiveInteger(value)) return 32;
|
|
424
|
+
return value;
|
|
425
|
+
}
|
|
310
426
|
function isDirectChunkBrowse(query, options) {
|
|
311
427
|
return query.trim() === "" && !options.rank && !options.snippets && !options.includeMatches && !options.types && !options.facets && !options.tags && !options.concepts && !options.privacy && !options.relationshipTargetId;
|
|
312
428
|
}
|
|
@@ -331,6 +447,40 @@ async function loadChunkPart(runtime, part) {
|
|
|
331
447
|
}
|
|
332
448
|
return { part: parsed, fetched: true };
|
|
333
449
|
}
|
|
450
|
+
async function getChunkRecord(runtime, id) {
|
|
451
|
+
const cached = runtime.detailCache.get(id);
|
|
452
|
+
if (cached) {
|
|
453
|
+
runtime.detailCache.delete(id);
|
|
454
|
+
runtime.detailCache.set(id, cached);
|
|
455
|
+
return cached;
|
|
456
|
+
}
|
|
457
|
+
if (!runtime.manifest.projection) {
|
|
458
|
+
for (const part of runtime.partCache.values()) {
|
|
459
|
+
const found = part.items.find((item) => item.id === id);
|
|
460
|
+
if (found) return found;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
if (!runtime.detailLoader) {
|
|
464
|
+
throw new Error("No detailLoader configured to resolve record " + id);
|
|
465
|
+
}
|
|
466
|
+
const raw = await runtime.detailLoader(id, runtime.manifest);
|
|
467
|
+
const record = assertAiwgFortemiIndexExport({
|
|
468
|
+
schema_version: "aiwg.fortemi.index.export.v1",
|
|
469
|
+
generated_at: runtime.manifest.generated_at,
|
|
470
|
+
source: runtime.manifest.source,
|
|
471
|
+
items: [raw]
|
|
472
|
+
}).items[0];
|
|
473
|
+
if (record.id !== id) {
|
|
474
|
+
throw new Error("Detail record id mismatch: expected " + id + ", got " + record.id);
|
|
475
|
+
}
|
|
476
|
+
runtime.detailCache.set(id, record);
|
|
477
|
+
while (runtime.detailCache.size > runtime.maxCachedDetails) {
|
|
478
|
+
const oldest = runtime.detailCache.keys().next().value;
|
|
479
|
+
if (oldest === void 0) break;
|
|
480
|
+
runtime.detailCache.delete(oldest);
|
|
481
|
+
}
|
|
482
|
+
return record;
|
|
483
|
+
}
|
|
334
484
|
async function queryChunkedAiwgFortemiIndex(runtime, query = "", options = {}) {
|
|
335
485
|
const q = query.trim().toLowerCase();
|
|
336
486
|
let scannedParts = 0;
|
|
@@ -435,7 +585,10 @@ function createAiwgIndexController(initialIndex) {
|
|
|
435
585
|
manifest: parsed,
|
|
436
586
|
loader,
|
|
437
587
|
maxCachedParts: clampMaxCachedParts(options.maxCachedParts),
|
|
438
|
-
partCache: /* @__PURE__ */ new Map()
|
|
588
|
+
partCache: /* @__PURE__ */ new Map(),
|
|
589
|
+
detailLoader: options.detailLoader,
|
|
590
|
+
maxCachedDetails: clampMaxCachedDetails(options.maxCachedDetails),
|
|
591
|
+
detailCache: /* @__PURE__ */ new Map()
|
|
439
592
|
};
|
|
440
593
|
data = null;
|
|
441
594
|
reviewDecisions = [];
|
|
@@ -478,8 +631,23 @@ function createAiwgIndexController(initialIndex) {
|
|
|
478
631
|
throw error;
|
|
479
632
|
}
|
|
480
633
|
},
|
|
634
|
+
async getRecord(id) {
|
|
635
|
+
if (chunked) {
|
|
636
|
+
try {
|
|
637
|
+
return await getChunkRecord(chunked, id);
|
|
638
|
+
} catch (err) {
|
|
639
|
+
error = err instanceof Error ? err : new Error(String(err));
|
|
640
|
+
notify();
|
|
641
|
+
throw error;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
const found = requireIndex().items.find((item) => item.id === id);
|
|
645
|
+
if (!found) throw new Error("Record not found: " + id);
|
|
646
|
+
return found;
|
|
647
|
+
},
|
|
481
648
|
clearChunkCache() {
|
|
482
649
|
chunked?.partCache.clear();
|
|
650
|
+
chunked?.detailCache.clear();
|
|
483
651
|
error = null;
|
|
484
652
|
notify();
|
|
485
653
|
},
|
|
@@ -559,6 +727,6 @@ function communityIdsFor(item, options) {
|
|
|
559
727
|
return [`type:${item.type}`];
|
|
560
728
|
}
|
|
561
729
|
|
|
562
|
-
export { aiwgFortemiIndexToCommunityGraph, assertAiwgFortemiChunkManifest, assertAiwgFortemiChunkPart, assertAiwgFortemiIndexExport, createAiwgFetchChunkLoader, createAiwgIndexController, createAiwgReviewDecisionExport, getAiwgFortemiFacets, queryAiwgFortemiIndex, validateAiwgFortemiChunkManifest, validateAiwgFortemiChunkPart, validateAiwgFortemiIndexExport };
|
|
730
|
+
export { AIWG_SCAN_REQUIRED_FIELDS, aiwgFortemiIndexToCommunityGraph, assertAiwgFortemiChunkManifest, assertAiwgFortemiChunkPart, assertAiwgFortemiIndexExport, buildAiwgChunkedIndex, createAiwgFetchChunkLoader, createAiwgFetchDetailLoader, createAiwgIndexController, createAiwgReviewDecisionExport, getAiwgFortemiFacets, queryAiwgFortemiIndex, validateAiwgFortemiChunkManifest, validateAiwgFortemiChunkPart, validateAiwgFortemiIndexExport };
|
|
563
731
|
//# sourceMappingURL=aiwg-index.js.map
|
|
564
732
|
//# sourceMappingURL=aiwg-index.js.map
|