@chartcoach/catalog 0.1.7 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +324 -38
- package/dist/duckdb-wasm.d.ts +8 -0
- package/dist/duckdb-wasm.js +22 -0
- package/dist/duckdb.d.ts +8 -0
- package/dist/duckdb.js +9 -0
- package/dist/index-cache-Cx1gIK6N.js +280 -0
- package/dist/index.d.ts +40 -8
- package/dist/index.js +22 -7
- package/dist/json-CBWjz9b4.js +29 -0
- package/dist/model-De0MF-zg.d.ts +255 -0
- package/dist/node.d.ts +15 -0
- package/dist/node.js +218 -0
- package/dist/open-Nnzkr_bE.d.ts +15 -0
- package/dist/open-Uu3-r8kp.js +1244 -0
- package/dist/registration-CgQGMUIH.d.ts +6 -0
- package/dist/registration-DXhS7vgr.js +41 -0
- package/dist/tables-DcgKnXwF.js +389 -0
- package/package.json +47 -18
- package/src/catalog/artifacts.ts +309 -100
- package/src/catalog/description.ts +198 -0
- package/src/catalog/errors.ts +26 -1
- package/src/catalog/identity.ts +69 -0
- package/src/catalog/json.ts +32 -0
- package/src/catalog/labels.ts +8 -6
- package/src/catalog/manifest.ts +81 -20
- package/src/catalog/markdown.ts +2 -2
- package/src/catalog/model.ts +222 -37
- package/src/catalog/open.ts +428 -0
- package/src/catalog/parquet.ts +164 -0
- package/src/catalog/profile-layout.ts +86 -0
- package/src/catalog/profile.ts +345 -0
- package/src/catalog/query.ts +125 -0
- package/src/catalog/read.ts +101 -0
- package/src/catalog/references.ts +336 -0
- package/src/catalog/registration.ts +74 -0
- package/src/catalog/tables.ts +250 -0
- package/src/catalog/wire.ts +38 -65
- package/src/duckdb-wasm.ts +33 -0
- package/src/duckdb.ts +18 -0
- package/src/index.ts +51 -20
- package/src/node/index-cache.ts +432 -0
- package/src/node.ts +347 -0
- package/dist/catalog/artifacts.d.ts +0 -28
- package/dist/catalog/artifacts.d.ts.map +0 -1
- package/dist/catalog/artifacts.js +0 -89
- package/dist/catalog/chartcoach-defaults.d.ts +0 -17
- package/dist/catalog/chartcoach-defaults.d.ts.map +0 -1
- package/dist/catalog/chartcoach-defaults.js +0 -8
- package/dist/catalog/errors.d.ts +0 -4
- package/dist/catalog/errors.d.ts.map +0 -1
- package/dist/catalog/errors.js +0 -6
- package/dist/catalog/labels.d.ts +0 -9
- package/dist/catalog/labels.d.ts.map +0 -1
- package/dist/catalog/labels.js +0 -23
- package/dist/catalog/load-parquet-core.d.ts +0 -15
- package/dist/catalog/load-parquet-core.d.ts.map +0 -1
- package/dist/catalog/load-parquet-core.js +0 -45
- package/dist/catalog/manifest.d.ts +0 -15
- package/dist/catalog/manifest.d.ts.map +0 -1
- package/dist/catalog/manifest.js +0 -143
- package/dist/catalog/markdown.d.ts +0 -3
- package/dist/catalog/markdown.d.ts.map +0 -1
- package/dist/catalog/markdown.js +0 -13
- package/dist/catalog/model.d.ts +0 -33
- package/dist/catalog/model.d.ts.map +0 -1
- package/dist/catalog/model.js +0 -67
- package/dist/catalog/wire.d.ts +0 -18
- package/dist/catalog/wire.d.ts.map +0 -1
- package/dist/catalog/wire.js +0 -78
- package/dist/index.d.ts.map +0 -1
- package/src/catalog/chartcoach-defaults.ts +0 -17
- package/src/catalog/load-parquet-core.ts +0 -78
package/src/catalog/model.ts
CHANGED
|
@@ -1,57 +1,110 @@
|
|
|
1
1
|
import { CatalogError } from "./errors";
|
|
2
|
+
import { isJsonObject, isJsonString, type JsonValue } from "./json";
|
|
3
|
+
import { normalizeLabel } from "./labels";
|
|
2
4
|
import type { CatalogManifest } from "./manifest";
|
|
3
|
-
import { validateManifestCoverage } from "./manifest";
|
|
5
|
+
import { copyCatalogManifest, validateManifestCoverage } from "./manifest";
|
|
6
|
+
import {
|
|
7
|
+
describeCatalog,
|
|
8
|
+
type CatalogInfo,
|
|
9
|
+
type DescribeOptions,
|
|
10
|
+
type DescriptionContext,
|
|
11
|
+
type ProfileLoader,
|
|
12
|
+
} from "./description";
|
|
13
|
+
import { queryCatalog, type QueryOptions, type EntryCandidate } from "./query";
|
|
14
|
+
import { readCatalog, type ReadOptions, type GuidelineEntryRecord } from "./read";
|
|
15
|
+
import { citationRecords, type CitationRecord, type CiteOptions } from "./references";
|
|
16
|
+
import { releaseProfiles } from "./profile-layout";
|
|
17
|
+
import type { CatalogRelease } from "./artifacts";
|
|
18
|
+
import { copyCatalogRelease } from "./artifacts";
|
|
19
|
+
import { catalogTable, type CatalogTables, type TableName } from "./tables";
|
|
4
20
|
|
|
5
|
-
export type GuidelineSection = {
|
|
21
|
+
export type GuidelineSection = Readonly<{
|
|
6
22
|
role: string;
|
|
7
23
|
title: string;
|
|
8
24
|
content: string;
|
|
9
|
-
}
|
|
25
|
+
}>;
|
|
10
26
|
|
|
11
|
-
export type Guideline = {
|
|
27
|
+
export type Guideline = Readonly<{
|
|
12
28
|
id: string;
|
|
13
29
|
title: string;
|
|
14
|
-
bibliography?: string;
|
|
15
30
|
description: string;
|
|
16
31
|
labels: readonly string[];
|
|
17
32
|
body: string;
|
|
18
33
|
sections: readonly GuidelineSection[];
|
|
19
34
|
references: readonly string[];
|
|
20
|
-
}
|
|
35
|
+
}>;
|
|
21
36
|
|
|
22
|
-
export type
|
|
23
|
-
|
|
24
|
-
|
|
37
|
+
export type GuidelineInput = Omit<Guideline, "body">;
|
|
38
|
+
|
|
39
|
+
export type CatalogReleaseContext = Readonly<{
|
|
40
|
+
release: CatalogRelease;
|
|
41
|
+
releaseUrl: string;
|
|
42
|
+
profileLoader?: ProfileLoader;
|
|
43
|
+
artifactLoader: (path: string, signal?: AbortSignal) => Promise<Uint8Array>;
|
|
44
|
+
}>;
|
|
45
|
+
|
|
46
|
+
const releaseByCatalog = new WeakMap<Catalog, CatalogReleaseContext>();
|
|
47
|
+
|
|
48
|
+
const descriptionByCatalog = new WeakMap<Catalog, DescriptionContext>();
|
|
49
|
+
|
|
50
|
+
export type ArtifactOptions = Readonly<{ signal?: AbortSignal }>;
|
|
25
51
|
|
|
26
52
|
export class Catalog implements Iterable<Guideline> {
|
|
27
53
|
readonly guidelines: readonly Guideline[];
|
|
28
|
-
readonly manifest
|
|
54
|
+
readonly manifest: CatalogManifest;
|
|
29
55
|
private readonly byId: Map<string, Guideline>;
|
|
30
56
|
|
|
31
|
-
constructor(guidelines: Iterable<
|
|
57
|
+
constructor(guidelines: Iterable<GuidelineInput>, manifest: CatalogManifest);
|
|
58
|
+
constructor(guidelines: Iterable<JsonValue>, manifest: CatalogManifest) {
|
|
32
59
|
const records = Array.from(guidelines, copyGuideline);
|
|
60
|
+
const ownedManifest = copyCatalogManifest(manifest);
|
|
33
61
|
const byId = new Map<string, Guideline>();
|
|
62
|
+
|
|
34
63
|
for (const guideline of records) {
|
|
35
64
|
if (byId.has(guideline.id)) {
|
|
36
|
-
throw new CatalogError(`Catalog contains duplicate guideline
|
|
65
|
+
throw new CatalogError(`Catalog contains duplicate guideline entry ID: ${guideline.id}.`);
|
|
37
66
|
}
|
|
67
|
+
|
|
38
68
|
byId.set(guideline.id, guideline);
|
|
39
69
|
}
|
|
40
|
-
if (options.manifest) {
|
|
41
|
-
validateManifestCoverage(records, options.manifest);
|
|
42
|
-
}
|
|
43
70
|
|
|
44
|
-
|
|
45
|
-
|
|
71
|
+
validateManifestCoverage(records, ownedManifest);
|
|
72
|
+
|
|
73
|
+
this.guidelines = Object.freeze(records);
|
|
74
|
+
this.manifest = ownedManifest;
|
|
46
75
|
this.byId = byId;
|
|
76
|
+
Object.freeze(this);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
get release(): CatalogRelease | undefined {
|
|
80
|
+
return releaseByCatalog.get(this)?.release;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
get releaseUrl(): string | undefined {
|
|
84
|
+
return releaseByCatalog.get(this)?.releaseUrl;
|
|
47
85
|
}
|
|
48
86
|
|
|
49
87
|
get length(): number {
|
|
50
88
|
return this.guidelines.length;
|
|
51
89
|
}
|
|
52
90
|
|
|
53
|
-
|
|
54
|
-
|
|
91
|
+
async artifact(path: string, options: ArtifactOptions = {}): Promise<Uint8Array> {
|
|
92
|
+
const context = releaseByCatalog.get(this);
|
|
93
|
+
|
|
94
|
+
if (!context) {
|
|
95
|
+
throw new CatalogError("Artifact access requires a catalog release.", {
|
|
96
|
+
code: "unavailable_capability",
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!Object.hasOwn(context.release.artifacts, path)) {
|
|
101
|
+
throw new CatalogError(`Unknown release artifact: ${path}`, {
|
|
102
|
+
code: "lookup",
|
|
103
|
+
details: { path, available: Object.keys(context.release.artifacts) },
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return context.artifactLoader(path, options.signal);
|
|
55
108
|
}
|
|
56
109
|
|
|
57
110
|
get(id: string): Guideline | undefined {
|
|
@@ -60,42 +113,174 @@ export class Catalog implements Iterable<Guideline> {
|
|
|
60
113
|
|
|
61
114
|
require(id: string): Guideline {
|
|
62
115
|
const guideline = this.get(id);
|
|
116
|
+
|
|
63
117
|
if (!guideline) {
|
|
64
|
-
throw new CatalogError(`Unknown guideline
|
|
118
|
+
throw new CatalogError(`Unknown guideline entry ID: ${id}`, {
|
|
119
|
+
code: "lookup",
|
|
120
|
+
details: { id },
|
|
121
|
+
hints: ["Call `catalog.query()` to inspect guideline entry IDs."],
|
|
122
|
+
});
|
|
65
123
|
}
|
|
124
|
+
|
|
66
125
|
return guideline;
|
|
67
126
|
}
|
|
68
127
|
|
|
128
|
+
query(options: QueryOptions = {}): readonly EntryCandidate[] {
|
|
129
|
+
return queryCatalog(this, options);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
read(options: ReadOptions): readonly GuidelineEntryRecord[] {
|
|
133
|
+
return readCatalog(this, options);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
cite(options: CiteOptions): readonly CitationRecord[] {
|
|
137
|
+
return citationRecords(this, options);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
table<Name extends TableName>(name: Name): CatalogTables[Name] {
|
|
141
|
+
return catalogTable(this, name);
|
|
142
|
+
}
|
|
143
|
+
|
|
69
144
|
labels(): string[] {
|
|
70
145
|
return sortedUnique(this.guidelines.flatMap((guideline) => [...guideline.labels]));
|
|
71
146
|
}
|
|
72
147
|
|
|
73
148
|
sectionRoles(): string[] {
|
|
74
149
|
return sortedUnique(
|
|
75
|
-
this.guidelines.flatMap((guideline) =>
|
|
150
|
+
this.guidelines.flatMap((guideline) =>
|
|
151
|
+
guideline.sections.map((section) => section.role).filter((role) => role !== "__dangling__"),
|
|
152
|
+
),
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
describe(options: DescribeOptions = {}): Promise<CatalogInfo> {
|
|
157
|
+
return describeCatalog(
|
|
158
|
+
this,
|
|
159
|
+
descriptionByCatalog.get(this) ?? emptyDescriptionContext(),
|
|
160
|
+
options,
|
|
76
161
|
);
|
|
77
162
|
}
|
|
78
163
|
|
|
79
|
-
[Symbol.iterator]():
|
|
164
|
+
[Symbol.iterator](): IterableIterator<Guideline> {
|
|
80
165
|
return this.guidelines[Symbol.iterator]();
|
|
81
166
|
}
|
|
82
167
|
}
|
|
83
168
|
|
|
84
|
-
function
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
169
|
+
export function catalogWithRelease(catalog: Catalog, context: CatalogReleaseContext): Catalog {
|
|
170
|
+
const release = copyCatalogRelease(context.release);
|
|
171
|
+
const profiles = releaseProfiles(release);
|
|
172
|
+
releaseByCatalog.set(
|
|
173
|
+
catalog,
|
|
174
|
+
Object.freeze({
|
|
175
|
+
release,
|
|
176
|
+
releaseUrl: context.releaseUrl,
|
|
177
|
+
artifactLoader: context.artifactLoader,
|
|
178
|
+
}),
|
|
179
|
+
);
|
|
180
|
+
descriptionByCatalog.set(
|
|
181
|
+
catalog,
|
|
182
|
+
Object.freeze({
|
|
183
|
+
resolvedLocation: context.releaseUrl,
|
|
184
|
+
releaseDigest: release.digest,
|
|
185
|
+
profiles,
|
|
186
|
+
profileLoader: context.profileLoader,
|
|
187
|
+
profileCache: new Map(),
|
|
188
|
+
}),
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
return catalog;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function emptyDescriptionContext(): DescriptionContext {
|
|
195
|
+
return Object.freeze({
|
|
196
|
+
resolvedLocation: null,
|
|
197
|
+
releaseDigest: null,
|
|
198
|
+
profiles: Object.freeze([]),
|
|
199
|
+
profileCache: new Map(),
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function isGuidelineInput(value: JsonValue): value is GuidelineInput {
|
|
204
|
+
if (!isJsonObject(value)) return false;
|
|
205
|
+
const { description, id, labels, references, sections, title } = value;
|
|
206
|
+
|
|
207
|
+
if (!isJsonString(id) || id.length === 0) return false;
|
|
208
|
+
|
|
209
|
+
if (!isJsonString(title) || !isJsonString(description)) return false;
|
|
210
|
+
|
|
211
|
+
if (!isStringArray(labels) || !isStringArray(references)) return false;
|
|
212
|
+
|
|
213
|
+
if (!Array.isArray(sections) || sections.length === 0 || !sections.every(isGuidelineSection)) {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const dangling = sections.flatMap((section, index) =>
|
|
218
|
+
section.role === "__dangling__" ? [index] : [],
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
if (dangling.length > 0 && (dangling.length !== 1 || dangling[0] !== 0)) return false;
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
return labels.every((label) => normalizeLabel(label) === label);
|
|
225
|
+
} catch {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function copyGuideline(value: JsonValue): Guideline {
|
|
231
|
+
if (!isGuidelineInput(value)) {
|
|
232
|
+
throw new CatalogError("Invalid guideline record.");
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const sections = Object.freeze(
|
|
236
|
+
value.sections.map((section) =>
|
|
237
|
+
Object.freeze({
|
|
238
|
+
role: section.role,
|
|
239
|
+
title: section.title,
|
|
240
|
+
content: section.content,
|
|
241
|
+
}),
|
|
242
|
+
),
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
return Object.freeze({
|
|
246
|
+
id: value.id,
|
|
247
|
+
title: value.title,
|
|
248
|
+
description: value.description,
|
|
249
|
+
labels: Object.freeze([...value.labels]),
|
|
250
|
+
body: bodyFromSections(sections),
|
|
251
|
+
sections,
|
|
252
|
+
references: Object.freeze([...value.references]),
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function isGuidelineSection(value: JsonValue): value is GuidelineSection {
|
|
257
|
+
if (!isJsonObject(value)) return false;
|
|
258
|
+
const { content, role, title } = value;
|
|
259
|
+
|
|
260
|
+
return (
|
|
261
|
+
isJsonString(role) &&
|
|
262
|
+
role.length > 0 &&
|
|
263
|
+
role === role.trim() &&
|
|
264
|
+
isJsonString(title) &&
|
|
265
|
+
title === title.trim() &&
|
|
266
|
+
isJsonString(content) &&
|
|
267
|
+
content === content.trim() &&
|
|
268
|
+
(role === "__dangling__" ? title.length === 0 && content.length > 0 : title.length > 0)
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function isStringArray(value: JsonValue | undefined): value is string[] {
|
|
273
|
+
return Array.isArray(value) && value.every(isJsonString);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function bodyFromSections(sections: readonly GuidelineSection[]): string {
|
|
277
|
+
return sections
|
|
278
|
+
.map((section) => {
|
|
279
|
+
if (section.role === "__dangling__") return section.content;
|
|
280
|
+
|
|
281
|
+
return `## ${section.title} <!-- role: ${section.role} -->\n\n${section.content}`;
|
|
282
|
+
})
|
|
283
|
+
.join("\n\n");
|
|
99
284
|
}
|
|
100
285
|
|
|
101
286
|
function sortedUnique(values: Iterable<string>): string[] {
|