@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.
Files changed (72) hide show
  1. package/README.md +324 -38
  2. package/dist/duckdb-wasm.d.ts +8 -0
  3. package/dist/duckdb-wasm.js +22 -0
  4. package/dist/duckdb.d.ts +8 -0
  5. package/dist/duckdb.js +9 -0
  6. package/dist/index-cache-Cx1gIK6N.js +280 -0
  7. package/dist/index.d.ts +40 -8
  8. package/dist/index.js +22 -7
  9. package/dist/json-CBWjz9b4.js +29 -0
  10. package/dist/model-De0MF-zg.d.ts +255 -0
  11. package/dist/node.d.ts +15 -0
  12. package/dist/node.js +218 -0
  13. package/dist/open-Nnzkr_bE.d.ts +15 -0
  14. package/dist/open-Uu3-r8kp.js +1244 -0
  15. package/dist/registration-CgQGMUIH.d.ts +6 -0
  16. package/dist/registration-DXhS7vgr.js +41 -0
  17. package/dist/tables-DcgKnXwF.js +389 -0
  18. package/package.json +47 -18
  19. package/src/catalog/artifacts.ts +309 -100
  20. package/src/catalog/description.ts +198 -0
  21. package/src/catalog/errors.ts +26 -1
  22. package/src/catalog/identity.ts +69 -0
  23. package/src/catalog/json.ts +32 -0
  24. package/src/catalog/labels.ts +8 -6
  25. package/src/catalog/manifest.ts +81 -20
  26. package/src/catalog/markdown.ts +2 -2
  27. package/src/catalog/model.ts +222 -37
  28. package/src/catalog/open.ts +428 -0
  29. package/src/catalog/parquet.ts +164 -0
  30. package/src/catalog/profile-layout.ts +86 -0
  31. package/src/catalog/profile.ts +345 -0
  32. package/src/catalog/query.ts +125 -0
  33. package/src/catalog/read.ts +101 -0
  34. package/src/catalog/references.ts +336 -0
  35. package/src/catalog/registration.ts +74 -0
  36. package/src/catalog/tables.ts +250 -0
  37. package/src/catalog/wire.ts +38 -65
  38. package/src/duckdb-wasm.ts +33 -0
  39. package/src/duckdb.ts +18 -0
  40. package/src/index.ts +51 -20
  41. package/src/node/index-cache.ts +432 -0
  42. package/src/node.ts +347 -0
  43. package/dist/catalog/artifacts.d.ts +0 -28
  44. package/dist/catalog/artifacts.d.ts.map +0 -1
  45. package/dist/catalog/artifacts.js +0 -89
  46. package/dist/catalog/chartcoach-defaults.d.ts +0 -17
  47. package/dist/catalog/chartcoach-defaults.d.ts.map +0 -1
  48. package/dist/catalog/chartcoach-defaults.js +0 -8
  49. package/dist/catalog/errors.d.ts +0 -4
  50. package/dist/catalog/errors.d.ts.map +0 -1
  51. package/dist/catalog/errors.js +0 -6
  52. package/dist/catalog/labels.d.ts +0 -9
  53. package/dist/catalog/labels.d.ts.map +0 -1
  54. package/dist/catalog/labels.js +0 -23
  55. package/dist/catalog/load-parquet-core.d.ts +0 -15
  56. package/dist/catalog/load-parquet-core.d.ts.map +0 -1
  57. package/dist/catalog/load-parquet-core.js +0 -45
  58. package/dist/catalog/manifest.d.ts +0 -15
  59. package/dist/catalog/manifest.d.ts.map +0 -1
  60. package/dist/catalog/manifest.js +0 -143
  61. package/dist/catalog/markdown.d.ts +0 -3
  62. package/dist/catalog/markdown.d.ts.map +0 -1
  63. package/dist/catalog/markdown.js +0 -13
  64. package/dist/catalog/model.d.ts +0 -33
  65. package/dist/catalog/model.d.ts.map +0 -1
  66. package/dist/catalog/model.js +0 -67
  67. package/dist/catalog/wire.d.ts +0 -18
  68. package/dist/catalog/wire.d.ts.map +0 -1
  69. package/dist/catalog/wire.js +0 -78
  70. package/dist/index.d.ts.map +0 -1
  71. package/src/catalog/chartcoach-defaults.ts +0 -17
  72. package/src/catalog/load-parquet-core.ts +0 -78
@@ -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 CatalogOptions = {
23
- manifest?: CatalogManifest;
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?: CatalogManifest;
54
+ readonly manifest: CatalogManifest;
29
55
  private readonly byId: Map<string, Guideline>;
30
56
 
31
- constructor(guidelines: Iterable<Guideline> = [], options: CatalogOptions = {}) {
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 id: ${guideline.id}.`);
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
- this.guidelines = records;
45
- this.manifest = options.manifest;
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
- get size(): number {
54
- return this.guidelines.length;
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 id: ${id}.`);
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) => guideline.sections.map((section) => section.role)),
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](): Iterator<Guideline> {
164
+ [Symbol.iterator](): IterableIterator<Guideline> {
80
165
  return this.guidelines[Symbol.iterator]();
81
166
  }
82
167
  }
83
168
 
84
- function copyGuideline(guideline: Guideline): Guideline {
85
- return {
86
- id: guideline.id,
87
- title: guideline.title,
88
- bibliography: guideline.bibliography,
89
- description: guideline.description,
90
- labels: [...guideline.labels],
91
- body: guideline.body,
92
- sections: guideline.sections.map((section) => ({
93
- role: section.role,
94
- title: section.title,
95
- content: section.content,
96
- })),
97
- references: [...guideline.references],
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[] {