@hollware/core 0.1.0 → 0.3.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/dist/index.d.mts +474 -4
- package/dist/index.mjs +288 -15
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
|
@@ -288,6 +288,21 @@ interface SiteDefinition {
|
|
|
288
288
|
readonly collections: Readonly<Record<string, CollectionDefinition>>;
|
|
289
289
|
/** Guidance for whoever writes anywhere in the site, in Markdown. */
|
|
290
290
|
readonly styleGuide?: string;
|
|
291
|
+
/** The public origin of the site, such as `https://example.com`: entries' URLs start with it. */
|
|
292
|
+
readonly url?: string;
|
|
293
|
+
/** Who hears about publications (ADR-025). */
|
|
294
|
+
readonly events?: {
|
|
295
|
+
/** Each gets every event, signed with HMAC-SHA256 by the Worker secret named in `secret`. */
|
|
296
|
+
readonly webhooks?: readonly {
|
|
297
|
+
readonly name: string;
|
|
298
|
+
readonly url: string;
|
|
299
|
+
readonly secret: string;
|
|
300
|
+
}[];
|
|
301
|
+
/** Tells IndexNow the URLs that changed; `key` names the Worker secret with the key. */
|
|
302
|
+
readonly indexNow?: {
|
|
303
|
+
readonly key: string;
|
|
304
|
+
};
|
|
305
|
+
};
|
|
291
306
|
}
|
|
292
307
|
/** What `hollware.config.ts` exports. */
|
|
293
308
|
interface HollwareConfig {
|
|
@@ -296,6 +311,17 @@ interface HollwareConfig {
|
|
|
296
311
|
* content exists. v0.1 serves exactly one site per deployment.
|
|
297
312
|
*/
|
|
298
313
|
readonly sites: Readonly<Record<string, SiteDefinition>>;
|
|
314
|
+
/** How MCP clients and the admin get tokens (ADR-026). */
|
|
315
|
+
readonly auth?: AuthDefinition;
|
|
316
|
+
}
|
|
317
|
+
/** The OAuth server of a deployment. */
|
|
318
|
+
interface AuthDefinition {
|
|
319
|
+
/**
|
|
320
|
+
* Whether a client may register itself (RFC 7591), for MCP clients that cannot present a
|
|
321
|
+
* Client ID Metadata Document. Off by default: a registration is a write to KV that anyone can
|
|
322
|
+
* make, and MCP 2026-07-28 prefers metadata documents.
|
|
323
|
+
*/
|
|
324
|
+
readonly dynamicClientRegistration?: boolean;
|
|
299
325
|
}
|
|
300
326
|
/** A collection once the config is resolved: checked, with every default filled in. */
|
|
301
327
|
interface ResolvedCollection {
|
|
@@ -318,10 +344,31 @@ interface ResolvedSite {
|
|
|
318
344
|
readonly defaultLocale: string;
|
|
319
345
|
readonly styleGuide: string | null;
|
|
320
346
|
readonly collections: ReadonlyMap<string, ResolvedCollection>;
|
|
347
|
+
/** The public origin of the site, or `null`. */
|
|
348
|
+
readonly url: string | null;
|
|
349
|
+
readonly events: ResolvedEvents;
|
|
350
|
+
}
|
|
351
|
+
/** A webhook of a site; `secret` names the Worker secret that signs its deliveries. */
|
|
352
|
+
interface ResolvedWebhook {
|
|
353
|
+
readonly name: string;
|
|
354
|
+
readonly url: string;
|
|
355
|
+
readonly secret: string;
|
|
356
|
+
}
|
|
357
|
+
/** Who hears about the publications of a site. */
|
|
358
|
+
interface ResolvedEvents {
|
|
359
|
+
readonly webhooks: readonly ResolvedWebhook[];
|
|
360
|
+
readonly indexNow: {
|
|
361
|
+
readonly key: string;
|
|
362
|
+
} | null;
|
|
321
363
|
}
|
|
322
364
|
/** The config once resolved, at the start of the Worker. v0.1 holds exactly one site. */
|
|
323
365
|
interface ResolvedConfig {
|
|
324
366
|
readonly sites: ReadonlyMap<string, ResolvedSite>;
|
|
367
|
+
readonly auth: ResolvedAuth;
|
|
368
|
+
}
|
|
369
|
+
/** The OAuth server once the config is resolved. */
|
|
370
|
+
interface ResolvedAuth {
|
|
371
|
+
readonly dynamicClientRegistration: boolean;
|
|
325
372
|
}
|
|
326
373
|
/**
|
|
327
374
|
* Checks a config and fills in its defaults. It throws one error that lists every problem, each
|
|
@@ -381,7 +428,7 @@ export declare const DEFAULT_MAX_MARKDOWN_BYTES = 524288;
|
|
|
381
428
|
*/
|
|
382
429
|
export declare const MAX_MARKDOWN_BYTES_CEILING = 786432;
|
|
383
430
|
declare namespace schemas_d_exports {
|
|
384
|
-
export { acknowledgedWarning, collectionDescription, collectionName, componentDescription, conflict, contractDescription, document, entry, entryId, entryStatus, entrySummary, format, locale, markdown, phase, publication, revisionId, severity, siteDescription, slug, verdict, version, violation };
|
|
431
|
+
export { acknowledgedWarning, collectionDescription, collectionName, componentDescription, conflict, contractDescription, document, entry, entryId, entryStatus, entrySummary, format, locale, markdown, phase, publication, review, reviewId, reviewStatus, revisionId, severity, siteDescription, slug, verdict, version, violation };
|
|
385
432
|
}
|
|
386
433
|
/**
|
|
387
434
|
* The pieces the operation schemas share. Their descriptions become the JSON Schema that agents
|
|
@@ -608,6 +655,34 @@ declare const entry: z.ZodObject<{
|
|
|
608
655
|
}, z.core.$strip>>;
|
|
609
656
|
html: z.ZodOptional<z.ZodString>;
|
|
610
657
|
}, z.core.$strip>;
|
|
658
|
+
declare const reviewId: z.ZodString;
|
|
659
|
+
declare const reviewStatus: z.ZodEnum<{
|
|
660
|
+
approved: "approved";
|
|
661
|
+
cancelled: "cancelled";
|
|
662
|
+
changes_requested: "changes_requested";
|
|
663
|
+
pending: "pending";
|
|
664
|
+
}>;
|
|
665
|
+
/**
|
|
666
|
+
* A request to publish a revision, made by someone who may write but not publish, and what a
|
|
667
|
+
* person who may publish decided about it.
|
|
668
|
+
*/
|
|
669
|
+
declare const review: z.ZodObject<{
|
|
670
|
+
id: z.ZodString;
|
|
671
|
+
entryId: z.ZodString;
|
|
672
|
+
revision: z.ZodString;
|
|
673
|
+
status: z.ZodEnum<{
|
|
674
|
+
approved: "approved";
|
|
675
|
+
cancelled: "cancelled";
|
|
676
|
+
changes_requested: "changes_requested";
|
|
677
|
+
pending: "pending";
|
|
678
|
+
}>;
|
|
679
|
+
requestedBy: z.ZodString;
|
|
680
|
+
requestedByName: z.ZodNullable<z.ZodString>;
|
|
681
|
+
requestedAt: z.ZodString;
|
|
682
|
+
decidedBy: z.ZodNullable<z.ZodString>;
|
|
683
|
+
decidedAt: z.ZodNullable<z.ZodString>;
|
|
684
|
+
note: z.ZodNullable<z.ZodString>;
|
|
685
|
+
}, z.core.$strip>;
|
|
611
686
|
declare const conflict: z.ZodObject<{
|
|
612
687
|
currentVersion: z.ZodNumber;
|
|
613
688
|
headRevision: z.ZodString;
|
|
@@ -960,10 +1035,19 @@ export declare class HollwareError extends Error {
|
|
|
960
1035
|
*/
|
|
961
1036
|
/** The kinds of ids and their prefixes. A new kind adds its prefix here. */
|
|
962
1037
|
export declare const ID_PREFIXES: {
|
|
1038
|
+
readonly challenge: 'chl';
|
|
1039
|
+
readonly delivery: 'dlv';
|
|
963
1040
|
readonly entry: 'ent';
|
|
1041
|
+
readonly event: 'evt';
|
|
1042
|
+
readonly invitation: 'inv';
|
|
1043
|
+
readonly member: 'mbr';
|
|
1044
|
+
readonly passkey: 'pky';
|
|
964
1045
|
readonly publication: 'pub';
|
|
965
1046
|
readonly revision: 'rev';
|
|
966
1047
|
readonly revisionTag: 'tag';
|
|
1048
|
+
readonly review: 'rvw';
|
|
1049
|
+
readonly schedule: 'sch';
|
|
1050
|
+
readonly session: 'ses';
|
|
967
1051
|
readonly token: 'tok';
|
|
968
1052
|
readonly user: 'usr';
|
|
969
1053
|
};
|
|
@@ -1152,6 +1236,7 @@ export declare const getEntry: OperationSpec<"get_entry", z.ZodObject<{
|
|
|
1152
1236
|
html: "html";
|
|
1153
1237
|
markdown: "markdown";
|
|
1154
1238
|
}>>>;
|
|
1239
|
+
published: z.ZodDefault<z.ZodBoolean>;
|
|
1155
1240
|
}, z.core.$strict>, z.ZodObject<{
|
|
1156
1241
|
outcome: z.ZodEnum<{
|
|
1157
1242
|
not_found: "not_found";
|
|
@@ -1450,13 +1535,20 @@ export declare const publish: OperationSpec<"publish", z.ZodObject<{
|
|
|
1450
1535
|
contract: z.ZodString;
|
|
1451
1536
|
path: z.ZodString;
|
|
1452
1537
|
}, z.core.$strip>>>;
|
|
1538
|
+
group: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
1539
|
+
id: z.ZodString;
|
|
1540
|
+
version: z.ZodNumber;
|
|
1541
|
+
}, z.core.$strict>>>;
|
|
1542
|
+
publishAt: z.ZodOptional<z.ZodISODateTime>;
|
|
1453
1543
|
}, z.core.$strict>, z.ZodObject<{
|
|
1454
1544
|
outcome: z.ZodEnum<{
|
|
1455
1545
|
blocked: "blocked";
|
|
1456
1546
|
conflict: "conflict";
|
|
1457
1547
|
needs_acknowledgement: "needs_acknowledgement";
|
|
1458
1548
|
not_found: "not_found";
|
|
1549
|
+
pending_review: "pending_review";
|
|
1459
1550
|
published: "published";
|
|
1551
|
+
scheduled: "scheduled";
|
|
1460
1552
|
}>;
|
|
1461
1553
|
entry: z.ZodOptional<z.ZodObject<{
|
|
1462
1554
|
id: z.ZodString;
|
|
@@ -1510,6 +1602,24 @@ export declare const publish: OperationSpec<"publish", z.ZodObject<{
|
|
|
1510
1602
|
currentVersion: z.ZodNumber;
|
|
1511
1603
|
headRevision: z.ZodString;
|
|
1512
1604
|
}, z.core.$strip>>;
|
|
1605
|
+
review: z.ZodOptional<z.ZodObject<{
|
|
1606
|
+
id: z.ZodString;
|
|
1607
|
+
entryId: z.ZodString;
|
|
1608
|
+
revision: z.ZodString;
|
|
1609
|
+
status: z.ZodEnum<{
|
|
1610
|
+
approved: "approved";
|
|
1611
|
+
cancelled: "cancelled";
|
|
1612
|
+
changes_requested: "changes_requested";
|
|
1613
|
+
pending: "pending";
|
|
1614
|
+
}>;
|
|
1615
|
+
requestedBy: z.ZodString;
|
|
1616
|
+
requestedByName: z.ZodNullable<z.ZodString>;
|
|
1617
|
+
requestedAt: z.ZodString;
|
|
1618
|
+
decidedBy: z.ZodNullable<z.ZodString>;
|
|
1619
|
+
decidedAt: z.ZodNullable<z.ZodString>;
|
|
1620
|
+
note: z.ZodNullable<z.ZodString>;
|
|
1621
|
+
}, z.core.$strip>>;
|
|
1622
|
+
failedEntry: z.ZodOptional<z.ZodString>;
|
|
1513
1623
|
}, z.core.$strip>>;
|
|
1514
1624
|
export declare const unpublish: OperationSpec<"unpublish", z.ZodObject<{
|
|
1515
1625
|
id: z.ZodString;
|
|
@@ -1537,8 +1647,242 @@ export declare const unpublish: OperationSpec<"unpublish", z.ZodObject<{
|
|
|
1537
1647
|
updatedAt: z.ZodString;
|
|
1538
1648
|
}, z.core.$strip>>;
|
|
1539
1649
|
}, z.core.$strip>>;
|
|
1540
|
-
|
|
1541
|
-
|
|
1650
|
+
export declare const listReviews: OperationSpec<"list_reviews", z.ZodObject<{
|
|
1651
|
+
status: z.ZodDefault<z.ZodEnum<{
|
|
1652
|
+
approved: "approved";
|
|
1653
|
+
cancelled: "cancelled";
|
|
1654
|
+
changes_requested: "changes_requested";
|
|
1655
|
+
pending: "pending";
|
|
1656
|
+
}>>;
|
|
1657
|
+
limit: z.ZodDefault<z.ZodNumber>;
|
|
1658
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
1659
|
+
outcome: z.ZodEnum<{
|
|
1660
|
+
ok: "ok";
|
|
1661
|
+
}>;
|
|
1662
|
+
reviews: z.ZodArray<z.ZodObject<{
|
|
1663
|
+
id: z.ZodString;
|
|
1664
|
+
entryId: z.ZodString;
|
|
1665
|
+
revision: z.ZodString;
|
|
1666
|
+
status: z.ZodEnum<{
|
|
1667
|
+
approved: "approved";
|
|
1668
|
+
cancelled: "cancelled";
|
|
1669
|
+
changes_requested: "changes_requested";
|
|
1670
|
+
pending: "pending";
|
|
1671
|
+
}>;
|
|
1672
|
+
requestedBy: z.ZodString;
|
|
1673
|
+
requestedByName: z.ZodNullable<z.ZodString>;
|
|
1674
|
+
requestedAt: z.ZodString;
|
|
1675
|
+
decidedBy: z.ZodNullable<z.ZodString>;
|
|
1676
|
+
decidedAt: z.ZodNullable<z.ZodString>;
|
|
1677
|
+
note: z.ZodNullable<z.ZodString>;
|
|
1678
|
+
entry: z.ZodObject<{
|
|
1679
|
+
id: z.ZodString;
|
|
1680
|
+
collection: z.ZodString;
|
|
1681
|
+
locale: z.ZodString;
|
|
1682
|
+
slug: z.ZodString;
|
|
1683
|
+
translationGroup: z.ZodNullable<z.ZodString>;
|
|
1684
|
+
status: z.ZodEnum<{
|
|
1685
|
+
draft: "draft";
|
|
1686
|
+
published: "published";
|
|
1687
|
+
scheduled: "scheduled";
|
|
1688
|
+
}>;
|
|
1689
|
+
version: z.ZodNumber;
|
|
1690
|
+
frontmatter: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
1691
|
+
tags: z.ZodArray<z.ZodString>;
|
|
1692
|
+
hasUnpublishedChanges: z.ZodBoolean;
|
|
1693
|
+
publishedAt: z.ZodNullable<z.ZodString>;
|
|
1694
|
+
updatedAt: z.ZodString;
|
|
1695
|
+
}, z.core.$strip>;
|
|
1696
|
+
}, z.core.$strip>>;
|
|
1697
|
+
}, z.core.$strip>>;
|
|
1698
|
+
export declare const approveReview: OperationSpec<"approve_review", z.ZodObject<{
|
|
1699
|
+
id: z.ZodString;
|
|
1700
|
+
acknowledgeWarnings: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
1701
|
+
contract: z.ZodString;
|
|
1702
|
+
path: z.ZodString;
|
|
1703
|
+
}, z.core.$strip>>>;
|
|
1704
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
1705
|
+
outcome: z.ZodEnum<{
|
|
1706
|
+
blocked: "blocked";
|
|
1707
|
+
needs_acknowledgement: "needs_acknowledgement";
|
|
1708
|
+
not_found: "not_found";
|
|
1709
|
+
not_pending: "not_pending";
|
|
1710
|
+
published: "published";
|
|
1711
|
+
}>;
|
|
1712
|
+
review: z.ZodOptional<z.ZodObject<{
|
|
1713
|
+
id: z.ZodString;
|
|
1714
|
+
entryId: z.ZodString;
|
|
1715
|
+
revision: z.ZodString;
|
|
1716
|
+
status: z.ZodEnum<{
|
|
1717
|
+
approved: "approved";
|
|
1718
|
+
cancelled: "cancelled";
|
|
1719
|
+
changes_requested: "changes_requested";
|
|
1720
|
+
pending: "pending";
|
|
1721
|
+
}>;
|
|
1722
|
+
requestedBy: z.ZodString;
|
|
1723
|
+
requestedByName: z.ZodNullable<z.ZodString>;
|
|
1724
|
+
requestedAt: z.ZodString;
|
|
1725
|
+
decidedBy: z.ZodNullable<z.ZodString>;
|
|
1726
|
+
decidedAt: z.ZodNullable<z.ZodString>;
|
|
1727
|
+
note: z.ZodNullable<z.ZodString>;
|
|
1728
|
+
}, z.core.$strip>>;
|
|
1729
|
+
entry: z.ZodOptional<z.ZodObject<{
|
|
1730
|
+
id: z.ZodString;
|
|
1731
|
+
collection: z.ZodString;
|
|
1732
|
+
locale: z.ZodString;
|
|
1733
|
+
slug: z.ZodString;
|
|
1734
|
+
translationGroup: z.ZodNullable<z.ZodString>;
|
|
1735
|
+
status: z.ZodEnum<{
|
|
1736
|
+
draft: "draft";
|
|
1737
|
+
published: "published";
|
|
1738
|
+
scheduled: "scheduled";
|
|
1739
|
+
}>;
|
|
1740
|
+
version: z.ZodNumber;
|
|
1741
|
+
frontmatter: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
1742
|
+
tags: z.ZodArray<z.ZodString>;
|
|
1743
|
+
hasUnpublishedChanges: z.ZodBoolean;
|
|
1744
|
+
publishedAt: z.ZodNullable<z.ZodString>;
|
|
1745
|
+
updatedAt: z.ZodString;
|
|
1746
|
+
}, z.core.$strip>>;
|
|
1747
|
+
verdict: z.ZodOptional<z.ZodObject<{
|
|
1748
|
+
ok: z.ZodBoolean;
|
|
1749
|
+
phase: z.ZodEnum<{
|
|
1750
|
+
publish: "publish";
|
|
1751
|
+
write: "write";
|
|
1752
|
+
}>;
|
|
1753
|
+
errors: z.ZodArray<z.ZodObject<{
|
|
1754
|
+
contract: z.ZodString;
|
|
1755
|
+
severity: z.ZodEnum<{
|
|
1756
|
+
error: "error";
|
|
1757
|
+
warning: "warning";
|
|
1758
|
+
}>;
|
|
1759
|
+
path: z.ZodString;
|
|
1760
|
+
message: z.ZodString;
|
|
1761
|
+
fix: z.ZodString;
|
|
1762
|
+
line: z.ZodOptional<z.ZodNumber>;
|
|
1763
|
+
}, z.core.$strip>>;
|
|
1764
|
+
warnings: z.ZodArray<z.ZodObject<{
|
|
1765
|
+
contract: z.ZodString;
|
|
1766
|
+
severity: z.ZodEnum<{
|
|
1767
|
+
error: "error";
|
|
1768
|
+
warning: "warning";
|
|
1769
|
+
}>;
|
|
1770
|
+
path: z.ZodString;
|
|
1771
|
+
message: z.ZodString;
|
|
1772
|
+
fix: z.ZodString;
|
|
1773
|
+
line: z.ZodOptional<z.ZodNumber>;
|
|
1774
|
+
}, z.core.$strip>>;
|
|
1775
|
+
contractsHash: z.ZodString;
|
|
1776
|
+
}, z.core.$strip>>;
|
|
1777
|
+
}, z.core.$strip>>;
|
|
1778
|
+
export declare const requestChanges: OperationSpec<"request_changes", z.ZodObject<{
|
|
1779
|
+
id: z.ZodString;
|
|
1780
|
+
note: z.ZodString;
|
|
1781
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
1782
|
+
outcome: z.ZodEnum<{
|
|
1783
|
+
changes_requested: "changes_requested";
|
|
1784
|
+
not_found: "not_found";
|
|
1785
|
+
not_pending: "not_pending";
|
|
1786
|
+
}>;
|
|
1787
|
+
review: z.ZodOptional<z.ZodObject<{
|
|
1788
|
+
id: z.ZodString;
|
|
1789
|
+
entryId: z.ZodString;
|
|
1790
|
+
revision: z.ZodString;
|
|
1791
|
+
status: z.ZodEnum<{
|
|
1792
|
+
approved: "approved";
|
|
1793
|
+
cancelled: "cancelled";
|
|
1794
|
+
changes_requested: "changes_requested";
|
|
1795
|
+
pending: "pending";
|
|
1796
|
+
}>;
|
|
1797
|
+
requestedBy: z.ZodString;
|
|
1798
|
+
requestedByName: z.ZodNullable<z.ZodString>;
|
|
1799
|
+
requestedAt: z.ZodString;
|
|
1800
|
+
decidedBy: z.ZodNullable<z.ZodString>;
|
|
1801
|
+
decidedAt: z.ZodNullable<z.ZodString>;
|
|
1802
|
+
note: z.ZodNullable<z.ZodString>;
|
|
1803
|
+
}, z.core.$strip>>;
|
|
1804
|
+
}, z.core.$strip>>;
|
|
1805
|
+
/** Every operation, sorted by name: the order of `tools/list`. */
|
|
1806
|
+
export declare const OPERATIONS: readonly [OperationSpec<"approve_review", z.ZodObject<{
|
|
1807
|
+
id: z.ZodString;
|
|
1808
|
+
acknowledgeWarnings: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
1809
|
+
contract: z.ZodString;
|
|
1810
|
+
path: z.ZodString;
|
|
1811
|
+
}, z.core.$strip>>>;
|
|
1812
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
1813
|
+
outcome: z.ZodEnum<{
|
|
1814
|
+
blocked: "blocked";
|
|
1815
|
+
needs_acknowledgement: "needs_acknowledgement";
|
|
1816
|
+
not_found: "not_found";
|
|
1817
|
+
not_pending: "not_pending";
|
|
1818
|
+
published: "published";
|
|
1819
|
+
}>;
|
|
1820
|
+
review: z.ZodOptional<z.ZodObject<{
|
|
1821
|
+
id: z.ZodString;
|
|
1822
|
+
entryId: z.ZodString;
|
|
1823
|
+
revision: z.ZodString;
|
|
1824
|
+
status: z.ZodEnum<{
|
|
1825
|
+
approved: "approved";
|
|
1826
|
+
cancelled: "cancelled";
|
|
1827
|
+
changes_requested: "changes_requested";
|
|
1828
|
+
pending: "pending";
|
|
1829
|
+
}>;
|
|
1830
|
+
requestedBy: z.ZodString;
|
|
1831
|
+
requestedByName: z.ZodNullable<z.ZodString>;
|
|
1832
|
+
requestedAt: z.ZodString;
|
|
1833
|
+
decidedBy: z.ZodNullable<z.ZodString>;
|
|
1834
|
+
decidedAt: z.ZodNullable<z.ZodString>;
|
|
1835
|
+
note: z.ZodNullable<z.ZodString>;
|
|
1836
|
+
}, z.core.$strip>>;
|
|
1837
|
+
entry: z.ZodOptional<z.ZodObject<{
|
|
1838
|
+
id: z.ZodString;
|
|
1839
|
+
collection: z.ZodString;
|
|
1840
|
+
locale: z.ZodString;
|
|
1841
|
+
slug: z.ZodString;
|
|
1842
|
+
translationGroup: z.ZodNullable<z.ZodString>;
|
|
1843
|
+
status: z.ZodEnum<{
|
|
1844
|
+
draft: "draft";
|
|
1845
|
+
published: "published";
|
|
1846
|
+
scheduled: "scheduled";
|
|
1847
|
+
}>;
|
|
1848
|
+
version: z.ZodNumber;
|
|
1849
|
+
frontmatter: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
1850
|
+
tags: z.ZodArray<z.ZodString>;
|
|
1851
|
+
hasUnpublishedChanges: z.ZodBoolean;
|
|
1852
|
+
publishedAt: z.ZodNullable<z.ZodString>;
|
|
1853
|
+
updatedAt: z.ZodString;
|
|
1854
|
+
}, z.core.$strip>>;
|
|
1855
|
+
verdict: z.ZodOptional<z.ZodObject<{
|
|
1856
|
+
ok: z.ZodBoolean;
|
|
1857
|
+
phase: z.ZodEnum<{
|
|
1858
|
+
publish: "publish";
|
|
1859
|
+
write: "write";
|
|
1860
|
+
}>;
|
|
1861
|
+
errors: z.ZodArray<z.ZodObject<{
|
|
1862
|
+
contract: z.ZodString;
|
|
1863
|
+
severity: z.ZodEnum<{
|
|
1864
|
+
error: "error";
|
|
1865
|
+
warning: "warning";
|
|
1866
|
+
}>;
|
|
1867
|
+
path: z.ZodString;
|
|
1868
|
+
message: z.ZodString;
|
|
1869
|
+
fix: z.ZodString;
|
|
1870
|
+
line: z.ZodOptional<z.ZodNumber>;
|
|
1871
|
+
}, z.core.$strip>>;
|
|
1872
|
+
warnings: z.ZodArray<z.ZodObject<{
|
|
1873
|
+
contract: z.ZodString;
|
|
1874
|
+
severity: z.ZodEnum<{
|
|
1875
|
+
error: "error";
|
|
1876
|
+
warning: "warning";
|
|
1877
|
+
}>;
|
|
1878
|
+
path: z.ZodString;
|
|
1879
|
+
message: z.ZodString;
|
|
1880
|
+
fix: z.ZodString;
|
|
1881
|
+
line: z.ZodOptional<z.ZodNumber>;
|
|
1882
|
+
}, z.core.$strip>>;
|
|
1883
|
+
contractsHash: z.ZodString;
|
|
1884
|
+
}, z.core.$strip>>;
|
|
1885
|
+
}, z.core.$strip>>, OperationSpec<"create_draft", z.ZodObject<{
|
|
1542
1886
|
collection: z.ZodString;
|
|
1543
1887
|
locale: z.ZodString;
|
|
1544
1888
|
slug: z.ZodString;
|
|
@@ -1643,6 +1987,7 @@ export declare const OPERATIONS: readonly [OperationSpec<"create_draft", z.ZodOb
|
|
|
1643
1987
|
html: "html";
|
|
1644
1988
|
markdown: "markdown";
|
|
1645
1989
|
}>>>;
|
|
1990
|
+
published: z.ZodDefault<z.ZodBoolean>;
|
|
1646
1991
|
}, z.core.$strict>, z.ZodObject<{
|
|
1647
1992
|
outcome: z.ZodEnum<{
|
|
1648
1993
|
not_found: "not_found";
|
|
@@ -1802,6 +2147,53 @@ export declare const OPERATIONS: readonly [OperationSpec<"create_draft", z.ZodOb
|
|
|
1802
2147
|
updatedAt: z.ZodString;
|
|
1803
2148
|
}, z.core.$strip>>;
|
|
1804
2149
|
nextCursor: z.ZodNullable<z.ZodString>;
|
|
2150
|
+
}, z.core.$strip>>, OperationSpec<"list_reviews", z.ZodObject<{
|
|
2151
|
+
status: z.ZodDefault<z.ZodEnum<{
|
|
2152
|
+
approved: "approved";
|
|
2153
|
+
cancelled: "cancelled";
|
|
2154
|
+
changes_requested: "changes_requested";
|
|
2155
|
+
pending: "pending";
|
|
2156
|
+
}>>;
|
|
2157
|
+
limit: z.ZodDefault<z.ZodNumber>;
|
|
2158
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
2159
|
+
outcome: z.ZodEnum<{
|
|
2160
|
+
ok: "ok";
|
|
2161
|
+
}>;
|
|
2162
|
+
reviews: z.ZodArray<z.ZodObject<{
|
|
2163
|
+
id: z.ZodString;
|
|
2164
|
+
entryId: z.ZodString;
|
|
2165
|
+
revision: z.ZodString;
|
|
2166
|
+
status: z.ZodEnum<{
|
|
2167
|
+
approved: "approved";
|
|
2168
|
+
cancelled: "cancelled";
|
|
2169
|
+
changes_requested: "changes_requested";
|
|
2170
|
+
pending: "pending";
|
|
2171
|
+
}>;
|
|
2172
|
+
requestedBy: z.ZodString;
|
|
2173
|
+
requestedByName: z.ZodNullable<z.ZodString>;
|
|
2174
|
+
requestedAt: z.ZodString;
|
|
2175
|
+
decidedBy: z.ZodNullable<z.ZodString>;
|
|
2176
|
+
decidedAt: z.ZodNullable<z.ZodString>;
|
|
2177
|
+
note: z.ZodNullable<z.ZodString>;
|
|
2178
|
+
entry: z.ZodObject<{
|
|
2179
|
+
id: z.ZodString;
|
|
2180
|
+
collection: z.ZodString;
|
|
2181
|
+
locale: z.ZodString;
|
|
2182
|
+
slug: z.ZodString;
|
|
2183
|
+
translationGroup: z.ZodNullable<z.ZodString>;
|
|
2184
|
+
status: z.ZodEnum<{
|
|
2185
|
+
draft: "draft";
|
|
2186
|
+
published: "published";
|
|
2187
|
+
scheduled: "scheduled";
|
|
2188
|
+
}>;
|
|
2189
|
+
version: z.ZodNumber;
|
|
2190
|
+
frontmatter: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
2191
|
+
tags: z.ZodArray<z.ZodString>;
|
|
2192
|
+
hasUnpublishedChanges: z.ZodBoolean;
|
|
2193
|
+
publishedAt: z.ZodNullable<z.ZodString>;
|
|
2194
|
+
updatedAt: z.ZodString;
|
|
2195
|
+
}, z.core.$strip>;
|
|
2196
|
+
}, z.core.$strip>>;
|
|
1805
2197
|
}, z.core.$strip>>, OperationSpec<"publish", z.ZodObject<{
|
|
1806
2198
|
id: z.ZodString;
|
|
1807
2199
|
version: z.ZodNumber;
|
|
@@ -1809,13 +2201,20 @@ export declare const OPERATIONS: readonly [OperationSpec<"create_draft", z.ZodOb
|
|
|
1809
2201
|
contract: z.ZodString;
|
|
1810
2202
|
path: z.ZodString;
|
|
1811
2203
|
}, z.core.$strip>>>;
|
|
2204
|
+
group: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
2205
|
+
id: z.ZodString;
|
|
2206
|
+
version: z.ZodNumber;
|
|
2207
|
+
}, z.core.$strict>>>;
|
|
2208
|
+
publishAt: z.ZodOptional<z.ZodISODateTime>;
|
|
1812
2209
|
}, z.core.$strict>, z.ZodObject<{
|
|
1813
2210
|
outcome: z.ZodEnum<{
|
|
1814
2211
|
blocked: "blocked";
|
|
1815
2212
|
conflict: "conflict";
|
|
1816
2213
|
needs_acknowledgement: "needs_acknowledgement";
|
|
1817
2214
|
not_found: "not_found";
|
|
2215
|
+
pending_review: "pending_review";
|
|
1818
2216
|
published: "published";
|
|
2217
|
+
scheduled: "scheduled";
|
|
1819
2218
|
}>;
|
|
1820
2219
|
entry: z.ZodOptional<z.ZodObject<{
|
|
1821
2220
|
id: z.ZodString;
|
|
@@ -1869,6 +2268,50 @@ export declare const OPERATIONS: readonly [OperationSpec<"create_draft", z.ZodOb
|
|
|
1869
2268
|
currentVersion: z.ZodNumber;
|
|
1870
2269
|
headRevision: z.ZodString;
|
|
1871
2270
|
}, z.core.$strip>>;
|
|
2271
|
+
review: z.ZodOptional<z.ZodObject<{
|
|
2272
|
+
id: z.ZodString;
|
|
2273
|
+
entryId: z.ZodString;
|
|
2274
|
+
revision: z.ZodString;
|
|
2275
|
+
status: z.ZodEnum<{
|
|
2276
|
+
approved: "approved";
|
|
2277
|
+
cancelled: "cancelled";
|
|
2278
|
+
changes_requested: "changes_requested";
|
|
2279
|
+
pending: "pending";
|
|
2280
|
+
}>;
|
|
2281
|
+
requestedBy: z.ZodString;
|
|
2282
|
+
requestedByName: z.ZodNullable<z.ZodString>;
|
|
2283
|
+
requestedAt: z.ZodString;
|
|
2284
|
+
decidedBy: z.ZodNullable<z.ZodString>;
|
|
2285
|
+
decidedAt: z.ZodNullable<z.ZodString>;
|
|
2286
|
+
note: z.ZodNullable<z.ZodString>;
|
|
2287
|
+
}, z.core.$strip>>;
|
|
2288
|
+
failedEntry: z.ZodOptional<z.ZodString>;
|
|
2289
|
+
}, z.core.$strip>>, OperationSpec<"request_changes", z.ZodObject<{
|
|
2290
|
+
id: z.ZodString;
|
|
2291
|
+
note: z.ZodString;
|
|
2292
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
2293
|
+
outcome: z.ZodEnum<{
|
|
2294
|
+
changes_requested: "changes_requested";
|
|
2295
|
+
not_found: "not_found";
|
|
2296
|
+
not_pending: "not_pending";
|
|
2297
|
+
}>;
|
|
2298
|
+
review: z.ZodOptional<z.ZodObject<{
|
|
2299
|
+
id: z.ZodString;
|
|
2300
|
+
entryId: z.ZodString;
|
|
2301
|
+
revision: z.ZodString;
|
|
2302
|
+
status: z.ZodEnum<{
|
|
2303
|
+
approved: "approved";
|
|
2304
|
+
cancelled: "cancelled";
|
|
2305
|
+
changes_requested: "changes_requested";
|
|
2306
|
+
pending: "pending";
|
|
2307
|
+
}>;
|
|
2308
|
+
requestedBy: z.ZodString;
|
|
2309
|
+
requestedByName: z.ZodNullable<z.ZodString>;
|
|
2310
|
+
requestedAt: z.ZodString;
|
|
2311
|
+
decidedBy: z.ZodNullable<z.ZodString>;
|
|
2312
|
+
decidedAt: z.ZodNullable<z.ZodString>;
|
|
2313
|
+
note: z.ZodNullable<z.ZodString>;
|
|
2314
|
+
}, z.core.$strip>>;
|
|
1872
2315
|
}, z.core.$strip>>, OperationSpec<"unpublish", z.ZodObject<{
|
|
1873
2316
|
id: z.ZodString;
|
|
1874
2317
|
}, z.core.$strict>, z.ZodObject<{
|
|
@@ -2026,7 +2469,34 @@ export declare function isPersonalToken(value: string): boolean;
|
|
|
2026
2469
|
/** The SHA-256 of a token, in lowercase hex: what the `tokens` table stores and looks up. */
|
|
2027
2470
|
export declare function hashPersonalToken(token: string): Promise<string>;
|
|
2028
2471
|
//#endregion
|
|
2472
|
+
//#region src/webhooks.d.ts
|
|
2473
|
+
/**
|
|
2474
|
+
* The signature of Hollware's webhooks (ADR-025), for the Worker that sends them and for whoever
|
|
2475
|
+
* receives them: HMAC-SHA256 of `<timestamp>.<body>` with the webhook's secret, sent as
|
|
2476
|
+
* `hollware-signature: v1=<hex>` with `hollware-timestamp` (seconds since the epoch) and
|
|
2477
|
+
* `hollware-event-id`. Web Crypto only, so it runs in any runtime.
|
|
2478
|
+
*/
|
|
2479
|
+
export declare const SIGNATURE_HEADER = "hollware-signature";
|
|
2480
|
+
export declare const TIMESTAMP_HEADER = "hollware-timestamp";
|
|
2481
|
+
export declare const EVENT_ID_HEADER = "hollware-event-id";
|
|
2482
|
+
/** The value of `hollware-signature` for a body sent at `timestamp` (seconds). */
|
|
2483
|
+
export declare function signWebhook(secret: string, timestamp: number, body: string): Promise<string>;
|
|
2484
|
+
export interface WebhookCheck {
|
|
2485
|
+
readonly secret: string;
|
|
2486
|
+
/** The raw body, as received: a parsed and re-serialized body no longer matches. */
|
|
2487
|
+
readonly body: string;
|
|
2488
|
+
readonly headers: Headers;
|
|
2489
|
+
/** Now, in seconds since the epoch; the system clock when absent. */
|
|
2490
|
+
readonly now?: number;
|
|
2491
|
+
readonly toleranceSeconds?: number;
|
|
2492
|
+
}
|
|
2493
|
+
/**
|
|
2494
|
+
* Whether a request is a webhook of Hollware: its signature matches the body and the secret, and
|
|
2495
|
+
* its timestamp is within the tolerance, so a captured request cannot be replayed later.
|
|
2496
|
+
*/
|
|
2497
|
+
export declare function verifyWebhook(check: WebhookCheck): Promise<boolean>;
|
|
2498
|
+
//#endregion
|
|
2029
2499
|
//#region src/index.d.ts
|
|
2030
2500
|
export declare const name = "@hollware/core";
|
|
2031
2501
|
//#endregion
|
|
2032
|
-
export { type AcknowledgedWarning, type BuiltInContractId, type BuiltInOptions, type CollectionDefinition, type ComponentDefinition, type ComponentUse, type ConfigProblem, type Contract, type ContractContext, type ContractDescription, type ContractFinding, type ContractText, type CustomContractOptions, type DescribedSchema, type DisallowedMarker, type Effect, type EntryDocument, type EntryFacts, type EntryLookup, type EntryRef, type ErrorCode, type ExtractedContent, type Heading, type HollwareConfig, type IdPrefix, type ImageRef, type LinkRef, type LinkTarget, type Operation, type OperationByName, type OperationName, type OperationSpec, type OutcomeInfo, type OutcomeOf, type Phase, type ProcessMarkdown, type ProcessMarkdownOptions, type ProcessResult, type ProcessedMarkdown, type RenderHtml, type ResolveConfig, type ResolvedCollection, type ResolvedConfig, type ResolvedSite, type RunContractsInput, type SchemaOutput, type Scope, type Severity, type SiteDefinition, type SiteDescription, type Verdict, type Violation, schemas_d_exports as schemas };
|
|
2502
|
+
export { type AcknowledgedWarning, type AuthDefinition, type BuiltInContractId, type BuiltInOptions, type CollectionDefinition, type ComponentDefinition, type ComponentUse, type ConfigProblem, type Contract, type ContractContext, type ContractDescription, type ContractFinding, type ContractText, type CustomContractOptions, type DescribedSchema, type DisallowedMarker, type Effect, type EntryDocument, type EntryFacts, type EntryLookup, type EntryRef, type ErrorCode, type ExtractedContent, type Heading, type HollwareConfig, type IdPrefix, type ImageRef, type LinkRef, type LinkTarget, type Operation, type OperationByName, type OperationName, type OperationSpec, type OutcomeInfo, type OutcomeOf, type Phase, type ProcessMarkdown, type ProcessMarkdownOptions, type ProcessResult, type ProcessedMarkdown, type RenderHtml, type ResolveConfig, type ResolvedAuth, type ResolvedCollection, type ResolvedConfig, type ResolvedSite, type RunContractsInput, type SchemaOutput, type Scope, type Severity, type SiteDefinition, type SiteDescription, type Verdict, type Violation, schemas_d_exports as schemas };
|
package/dist/index.mjs
CHANGED
|
@@ -69,7 +69,7 @@ const MAX_MARKDOWN_BYTES_CEILING = 786432;
|
|
|
69
69
|
* letter, at most 63 characters. A component name is what Markdown writes after the colons
|
|
70
70
|
* (`::callout`). A site key has its own rule (`site-key.ts`).
|
|
71
71
|
*/
|
|
72
|
-
const NAME = /^[a-z][a-z0-9-]{0,62}$/;
|
|
72
|
+
const NAME$1 = /^[a-z][a-z0-9-]{0,62}$/;
|
|
73
73
|
/** A language tag: a language, then an optional script and region: `es`, `pt-BR`, `zh-Hant-TW`. */
|
|
74
74
|
const LOCALE_TAG = /^[a-z]{2,3}(?:-[A-Z][a-z]{3})?(?:-(?:[A-Z]{2}|[0-9]{3}))?$/;
|
|
75
75
|
/** The id of a contract: lowercase words joined by single hyphens. */
|
|
@@ -1224,7 +1224,7 @@ function readTranslations(path, raw, schema, problems) {
|
|
|
1224
1224
|
}
|
|
1225
1225
|
const COMPONENT_FIX = "Declare it with defineComponent({ props: z.strictObject({ … }) }).";
|
|
1226
1226
|
function readComponent(path, name, raw, problems) {
|
|
1227
|
-
if (!NAME.test(name)) problems.add(path, `${quote(name)} is not a valid component name`, nameFix("callout"));
|
|
1227
|
+
if (!NAME$1.test(name)) problems.add(path, `${quote(name)} is not a valid component name`, nameFix("callout"));
|
|
1228
1228
|
if (!isRecord$1(raw)) {
|
|
1229
1229
|
problems.add(path, wrongType(raw, "a component"), COMPONENT_FIX);
|
|
1230
1230
|
return null;
|
|
@@ -1347,7 +1347,7 @@ function readMaxMarkdownBytes(path, raw, problems) {
|
|
|
1347
1347
|
* problem already: `resolveConfig` throws when there is any, so nothing else keeps track of them.
|
|
1348
1348
|
*/
|
|
1349
1349
|
function readCollection(path, name, raw, site, problems) {
|
|
1350
|
-
if (!NAME.test(name)) problems.add(path, `${quote(name)} is not a valid collection name`, nameFix("blog"));
|
|
1350
|
+
if (!NAME$1.test(name)) problems.add(path, `${quote(name)} is not a valid collection name`, nameFix("blog"));
|
|
1351
1351
|
if (!isRecord$1(raw)) {
|
|
1352
1352
|
problems.add(path, wrongType(raw, "a collection"), "Declare it with defineCollection({ schema, routes, … }).");
|
|
1353
1353
|
return null;
|
|
@@ -1376,6 +1376,90 @@ function readCollection(path, name, raw, site, problems) {
|
|
|
1376
1376
|
};
|
|
1377
1377
|
}
|
|
1378
1378
|
//#endregion
|
|
1379
|
+
//#region src/config/events.ts
|
|
1380
|
+
/** The name of a Worker secret or variable: what `wrangler secret put` takes. */
|
|
1381
|
+
const BINDING_NAME = /^[A-Z][A-Z0-9_]*$/;
|
|
1382
|
+
/** A subscriber's name: lowercase words joined by hyphens. */
|
|
1383
|
+
const NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
1384
|
+
/** The public URL of a site: an absolute http or https URL with no path, query or fragment. */
|
|
1385
|
+
function readSiteUrl(path, raw, problems) {
|
|
1386
|
+
if (raw === void 0) return null;
|
|
1387
|
+
const fix = "Write the public origin of the site, such as \"https://example.com\".";
|
|
1388
|
+
if (typeof raw !== "string" || !URL.canParse(raw)) {
|
|
1389
|
+
problems.add(path, wrongType(raw, "an absolute URL"), fix);
|
|
1390
|
+
return null;
|
|
1391
|
+
}
|
|
1392
|
+
const url = new URL(raw);
|
|
1393
|
+
if (url.protocol !== "https:" && url.protocol !== "http:" || url.pathname !== "/" || url.search !== "" || url.hash !== "") {
|
|
1394
|
+
problems.add(path, `${quote(raw)} is not an origin`, fix);
|
|
1395
|
+
return null;
|
|
1396
|
+
}
|
|
1397
|
+
return url.origin;
|
|
1398
|
+
}
|
|
1399
|
+
function readBindingName(path, raw, problems) {
|
|
1400
|
+
if (typeof raw === "string" && BINDING_NAME.test(raw)) return raw;
|
|
1401
|
+
problems.add(path, wrongType(raw, "the name of a Worker secret"), "Name the secret that holds the value, such as \"WEBHOOK_SECRET\", and set it with wrangler secret put.");
|
|
1402
|
+
return null;
|
|
1403
|
+
}
|
|
1404
|
+
function readWebhook(path, raw, problems) {
|
|
1405
|
+
if (!isRecord$1(raw)) {
|
|
1406
|
+
problems.add(path, wrongType(raw, "a webhook"), "Declare it as { name: \"newsletter\", url: \"https://…\", secret: \"WEBHOOK_SECRET\" }.");
|
|
1407
|
+
return null;
|
|
1408
|
+
}
|
|
1409
|
+
const name = raw["name"];
|
|
1410
|
+
if (typeof name !== "string" || !NAME.test(name)) problems.add(keyPath(path, "name"), wrongType(name, "a name"), "Name it with lowercase words joined by hyphens, such as \"newsletter\".");
|
|
1411
|
+
const url = raw["url"];
|
|
1412
|
+
const urlOk = typeof url === "string" && URL.canParse(url) && new URL(url).protocol === "https:";
|
|
1413
|
+
if (!urlOk) problems.add(keyPath(path, "url"), wrongType(url, "an https URL"), "Give the https URL that receives the events.");
|
|
1414
|
+
const secret = readBindingName(keyPath(path, "secret"), raw["secret"], problems);
|
|
1415
|
+
if (typeof name !== "string" || !NAME.test(name) || !urlOk || secret === null) return null;
|
|
1416
|
+
return {
|
|
1417
|
+
name,
|
|
1418
|
+
url: String(url),
|
|
1419
|
+
secret
|
|
1420
|
+
};
|
|
1421
|
+
}
|
|
1422
|
+
/** Who hears about publications (ADR-025): webhooks, and IndexNow when the site has a URL. */
|
|
1423
|
+
function readEvents(path, raw, siteUrl, problems) {
|
|
1424
|
+
const events = {
|
|
1425
|
+
webhooks: [],
|
|
1426
|
+
indexNow: null
|
|
1427
|
+
};
|
|
1428
|
+
if (raw === void 0) return events;
|
|
1429
|
+
if (!isRecord$1(raw)) {
|
|
1430
|
+
problems.add(path, wrongType(raw, "an object"), "Declare { webhooks: [ … ], indexNow: { … } }.");
|
|
1431
|
+
return events;
|
|
1432
|
+
}
|
|
1433
|
+
const webhooks = raw["webhooks"];
|
|
1434
|
+
if (webhooks !== void 0) {
|
|
1435
|
+
if (!Array.isArray(webhooks)) problems.add(keyPath(path, "webhooks"), wrongType(webhooks, "a list"), "List the webhooks.");
|
|
1436
|
+
else {
|
|
1437
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1438
|
+
webhooks.forEach((item, index) => {
|
|
1439
|
+
const webhook = readWebhook(itemPath(keyPath(path, "webhooks"), index), item, problems);
|
|
1440
|
+
if (webhook === null) return;
|
|
1441
|
+
if (seen.has(webhook.name)) {
|
|
1442
|
+
problems.add(itemPath(keyPath(path, "webhooks"), index), `repeats the name ${quote(webhook.name)}`, "Give each webhook a name of its own.");
|
|
1443
|
+
return;
|
|
1444
|
+
}
|
|
1445
|
+
seen.add(webhook.name);
|
|
1446
|
+
events.webhooks.push(webhook);
|
|
1447
|
+
});
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
const indexNow = raw["indexNow"];
|
|
1451
|
+
if (indexNow !== void 0) {
|
|
1452
|
+
const indexPath = keyPath(path, "indexNow");
|
|
1453
|
+
if (!isRecord$1(indexNow)) problems.add(indexPath, wrongType(indexNow, "an object"), "Declare { key: \"INDEXNOW_KEY\" }.");
|
|
1454
|
+
else {
|
|
1455
|
+
const key = readBindingName(keyPath(indexPath, "key"), indexNow["key"], problems);
|
|
1456
|
+
if (siteUrl === null) problems.add(indexPath, "needs the URL of the site", "Set url on the site: IndexNow is told the public URLs of the entries.");
|
|
1457
|
+
else if (key !== null) events.indexNow = { key };
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
return events;
|
|
1461
|
+
}
|
|
1462
|
+
//#endregion
|
|
1379
1463
|
//#region src/config/site-key.ts
|
|
1380
1464
|
/**
|
|
1381
1465
|
* The key of a site, which is the id of its row in the database: lowercase letters and digits
|
|
@@ -1480,12 +1564,16 @@ function readSite(key, raw, problems) {
|
|
|
1480
1564
|
localesOk: ok,
|
|
1481
1565
|
routes: /* @__PURE__ */ new Map()
|
|
1482
1566
|
}, problems);
|
|
1567
|
+
const styleGuide = readOptionalString(keyPath(path, "styleGuide"), raw["styleGuide"], "Write the guidance as a Markdown string, or remove it.", problems);
|
|
1568
|
+
const url = readSiteUrl(keyPath(path, "url"), raw["url"], problems);
|
|
1483
1569
|
return {
|
|
1484
1570
|
key,
|
|
1485
1571
|
locales,
|
|
1486
1572
|
defaultLocale,
|
|
1487
|
-
styleGuide
|
|
1488
|
-
collections
|
|
1573
|
+
styleGuide,
|
|
1574
|
+
collections,
|
|
1575
|
+
url,
|
|
1576
|
+
events: readEvents(keyPath(path, "events"), raw["events"], url, problems)
|
|
1489
1577
|
};
|
|
1490
1578
|
}
|
|
1491
1579
|
function readSites(raw, problems) {
|
|
@@ -1503,6 +1591,21 @@ function readSites(raw, problems) {
|
|
|
1503
1591
|
}
|
|
1504
1592
|
return sites;
|
|
1505
1593
|
}
|
|
1594
|
+
/** `auth` of the config: absent, or an object whose `dynamicClientRegistration` is a boolean. */
|
|
1595
|
+
function readAuth(raw, problems) {
|
|
1596
|
+
if (raw === void 0) return { dynamicClientRegistration: false };
|
|
1597
|
+
if (!isRecord$1(raw)) {
|
|
1598
|
+
problems.add("auth", wrongType(raw, "an object"), "Write auth: { dynamicClientRegistration: true }, or leave auth out.");
|
|
1599
|
+
return { dynamicClientRegistration: false };
|
|
1600
|
+
}
|
|
1601
|
+
const value = raw["dynamicClientRegistration"];
|
|
1602
|
+
if (value === void 0) return { dynamicClientRegistration: false };
|
|
1603
|
+
if (typeof value !== "boolean") {
|
|
1604
|
+
problems.add("auth.dynamicClientRegistration", wrongType(value, "true or false"), "Write true to let MCP clients register themselves, or leave it out.");
|
|
1605
|
+
return { dynamicClientRegistration: false };
|
|
1606
|
+
}
|
|
1607
|
+
return { dynamicClientRegistration: value };
|
|
1608
|
+
}
|
|
1506
1609
|
/**
|
|
1507
1610
|
* Checks the config of the deployment and fills in its defaults, or throws a `ConfigError` with
|
|
1508
1611
|
* every problem it found, so that a broken `hollware.config.ts` stops the Worker at startup with
|
|
@@ -1532,10 +1635,16 @@ function readSites(raw, problems) {
|
|
|
1532
1635
|
function resolveConfig(config) {
|
|
1533
1636
|
const problems = new Problems();
|
|
1534
1637
|
let sites = /* @__PURE__ */ new Map();
|
|
1535
|
-
|
|
1536
|
-
|
|
1638
|
+
let auth = { dynamicClientRegistration: false };
|
|
1639
|
+
if (isRecord$1(config)) {
|
|
1640
|
+
sites = readSites(config["sites"], problems);
|
|
1641
|
+
auth = readAuth(config["auth"], problems);
|
|
1642
|
+
} else problems.add("config", wrongType(config, "an object"), "Export the result of defineConfig({ sites: { … } }) from hollware.config.ts.");
|
|
1537
1643
|
if (problems.list.length > 0) throw new ConfigError(problems.list);
|
|
1538
|
-
return {
|
|
1644
|
+
return {
|
|
1645
|
+
sites,
|
|
1646
|
+
auth
|
|
1647
|
+
};
|
|
1539
1648
|
}
|
|
1540
1649
|
//#endregion
|
|
1541
1650
|
//#region src/errors.ts
|
|
@@ -1577,10 +1686,19 @@ var HollwareError = class extends Error {
|
|
|
1577
1686
|
const CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
1578
1687
|
/** The kinds of ids and their prefixes. A new kind adds its prefix here. */
|
|
1579
1688
|
const ID_PREFIXES = {
|
|
1689
|
+
challenge: "chl",
|
|
1690
|
+
delivery: "dlv",
|
|
1580
1691
|
entry: "ent",
|
|
1692
|
+
event: "evt",
|
|
1693
|
+
invitation: "inv",
|
|
1694
|
+
member: "mbr",
|
|
1695
|
+
passkey: "pky",
|
|
1581
1696
|
publication: "pub",
|
|
1582
1697
|
revision: "rev",
|
|
1583
1698
|
revisionTag: "tag",
|
|
1699
|
+
review: "rvw",
|
|
1700
|
+
schedule: "sch",
|
|
1701
|
+
session: "ses",
|
|
1584
1702
|
token: "tok",
|
|
1585
1703
|
user: "usr"
|
|
1586
1704
|
};
|
|
@@ -2916,6 +3034,9 @@ var schemas_exports = /* @__PURE__ */ __exportAll({
|
|
|
2916
3034
|
markdown: () => markdown,
|
|
2917
3035
|
phase: () => phase,
|
|
2918
3036
|
publication: () => publication,
|
|
3037
|
+
review: () => review,
|
|
3038
|
+
reviewId: () => reviewId,
|
|
3039
|
+
reviewStatus: () => reviewStatus,
|
|
2919
3040
|
revisionId: () => revisionId,
|
|
2920
3041
|
severity: () => severity,
|
|
2921
3042
|
siteDescription: () => siteDescription,
|
|
@@ -2995,6 +3116,29 @@ const entry = entrySummary.extend({
|
|
|
2995
3116
|
document: document.optional(),
|
|
2996
3117
|
html: z.string().optional()
|
|
2997
3118
|
});
|
|
3119
|
+
const reviewId = z.string().regex(/^rvw_[0-9A-HJKMNP-TV-Z]{26}$/).describe("The id of a review (rvw_…), as publish and list_reviews return it.");
|
|
3120
|
+
const reviewStatus = z.enum([
|
|
3121
|
+
"pending",
|
|
3122
|
+
"approved",
|
|
3123
|
+
"changes_requested",
|
|
3124
|
+
"cancelled"
|
|
3125
|
+
]);
|
|
3126
|
+
/**
|
|
3127
|
+
* A request to publish a revision, made by someone who may write but not publish, and what a
|
|
3128
|
+
* person who may publish decided about it.
|
|
3129
|
+
*/
|
|
3130
|
+
const review = z.object({
|
|
3131
|
+
id: reviewId,
|
|
3132
|
+
entryId,
|
|
3133
|
+
revision: revisionId.describe("The revision asked for."),
|
|
3134
|
+
status: reviewStatus.describe("pending until someone decides; cancelled when a new revision of the entry is saved."),
|
|
3135
|
+
requestedBy: z.string().describe("The user who asked."),
|
|
3136
|
+
requestedByName: z.string().nullable().describe("The name of the user who asked, if they gave one."),
|
|
3137
|
+
requestedAt: z.string(),
|
|
3138
|
+
decidedBy: z.string().nullable(),
|
|
3139
|
+
decidedAt: z.string().nullable(),
|
|
3140
|
+
note: z.string().nullable().describe("What the reviewer asked to change, or null.")
|
|
3141
|
+
});
|
|
2998
3142
|
const conflict = z.object({
|
|
2999
3143
|
currentVersion: z.number().int().min(1),
|
|
3000
3144
|
headRevision: revisionId
|
|
@@ -3090,7 +3234,8 @@ const getEntry = defineOperation({
|
|
|
3090
3234
|
collection: collectionName,
|
|
3091
3235
|
locale,
|
|
3092
3236
|
slug,
|
|
3093
|
-
formats: z.array(format).min(1).default(["markdown"]).describe("What to return of the revision: markdown, the Comark document, or html.")
|
|
3237
|
+
formats: z.array(format).min(1).default(["markdown"]).describe("What to return of the revision: markdown, the Comark document, or html."),
|
|
3238
|
+
published: z.boolean().default(false).describe("Read the revision that is live instead of the latest one: not_found when the entry is not published.")
|
|
3094
3239
|
}),
|
|
3095
3240
|
output: z.object({
|
|
3096
3241
|
outcome: z.enum(["ok", "not_found"]),
|
|
@@ -3196,30 +3341,41 @@ const validate = defineOperation({
|
|
|
3196
3341
|
const publish = defineOperation({
|
|
3197
3342
|
name: "publish",
|
|
3198
3343
|
title: "Publish an entry",
|
|
3199
|
-
description: "Publishes the latest revision of an entry, if the entry is still at the version you read. It is blocked while the contracts report errors. Warnings have to be acknowledged: an answer of needs_acknowledgement lists them, and publish again with them in acknowledgeWarnings.",
|
|
3344
|
+
description: "Publishes the latest revision of an entry, if the entry is still at the version you read, and with group, other entries in the same step. It is blocked while the contracts report errors. Warnings have to be acknowledged: an answer of needs_acknowledgement lists them, and publish again with them in acknowledgeWarnings. Publish entries that link to each other as one group. Without content:publish, the same checks run and the answer is pending_review: a person who may publish approves the revision or asks for changes, and a new revision of the entry cancels the request. A review is for one entry, now: never with group or publishAt.",
|
|
3200
3345
|
input: z.strictObject({
|
|
3201
3346
|
id: entryId,
|
|
3202
3347
|
version,
|
|
3203
|
-
acknowledgeWarnings: z.array(acknowledgedWarning).default([]).describe("The warnings you accept, as needs_acknowledgement listed them.")
|
|
3348
|
+
acknowledgeWarnings: z.array(acknowledgedWarning).default([]).describe("The warnings you accept, as needs_acknowledgement listed them."),
|
|
3349
|
+
group: z.array(z.strictObject({
|
|
3350
|
+
id: entryId,
|
|
3351
|
+
version
|
|
3352
|
+
})).max(99).default([]).describe("Other entries to publish in the same step, each with the version you read: all of them are published, or none. A link between two of them counts as published, and an acknowledged warning holds for every entry that reports it."),
|
|
3353
|
+
publishAt: z.iso.datetime({ offset: true }).optional().describe("Publish at this time instead of now, as an ISO 8601 date and time: the entry is checked now and waits as scheduled, and it is published within a minute of the time, if nobody changes it before. One entry at a time, never with group.")
|
|
3204
3354
|
}),
|
|
3205
3355
|
output: z.object({
|
|
3206
3356
|
outcome: z.enum([
|
|
3207
3357
|
"published",
|
|
3358
|
+
"scheduled",
|
|
3208
3359
|
"blocked",
|
|
3209
3360
|
"needs_acknowledgement",
|
|
3361
|
+
"pending_review",
|
|
3210
3362
|
"conflict",
|
|
3211
3363
|
"not_found"
|
|
3212
3364
|
]),
|
|
3213
3365
|
entry: entrySummary.optional(),
|
|
3214
3366
|
verdict: verdict.optional(),
|
|
3215
|
-
conflict: conflict.optional()
|
|
3367
|
+
conflict: conflict.optional(),
|
|
3368
|
+
review: review.optional().describe("With pending_review: the request, as list_reviews shows it."),
|
|
3369
|
+
failedEntry: entryId.optional().describe("In a group, the entry that kept the group from being published: blocked, stale, missing, or with a warning to acknowledge.")
|
|
3216
3370
|
}),
|
|
3217
|
-
scopes: ["content:
|
|
3371
|
+
scopes: ["content:write"],
|
|
3218
3372
|
effect: "write",
|
|
3219
3373
|
outcomes: {
|
|
3220
3374
|
published: { isError: false },
|
|
3375
|
+
scheduled: { isError: false },
|
|
3221
3376
|
blocked: { isError: true },
|
|
3222
3377
|
needs_acknowledgement: { isError: false },
|
|
3378
|
+
pending_review: { isError: false },
|
|
3223
3379
|
conflict: { isError: true },
|
|
3224
3380
|
not_found: { isError: true }
|
|
3225
3381
|
}
|
|
@@ -3240,14 +3396,87 @@ const unpublish = defineOperation({
|
|
|
3240
3396
|
not_found: { isError: true }
|
|
3241
3397
|
}
|
|
3242
3398
|
});
|
|
3243
|
-
|
|
3399
|
+
const listReviews = defineOperation({
|
|
3400
|
+
name: "list_reviews",
|
|
3401
|
+
title: "List the reviews",
|
|
3402
|
+
description: "Lists the requests to publish, newest first, with the entry each one is for: the pending ones unless you name another status.",
|
|
3403
|
+
input: z.strictObject({
|
|
3404
|
+
status: reviewStatus.default("pending"),
|
|
3405
|
+
limit: z.number().int().min(1).max(100).default(50)
|
|
3406
|
+
}),
|
|
3407
|
+
output: z.object({
|
|
3408
|
+
outcome: z.enum(["ok"]),
|
|
3409
|
+
reviews: z.array(review.extend({ entry: entrySummary }))
|
|
3410
|
+
}),
|
|
3411
|
+
scopes: ["content:read"],
|
|
3412
|
+
effect: "read",
|
|
3413
|
+
outcomes: { ok: { isError: false } }
|
|
3414
|
+
});
|
|
3415
|
+
const approveReview = defineOperation({
|
|
3416
|
+
name: "approve_review",
|
|
3417
|
+
title: "Approve a review",
|
|
3418
|
+
description: "Publishes the revision a review asked for, in your name, after the contracts run again as publish runs them. The warnings the request acknowledged count as acknowledged; a new one answers needs_acknowledgement, and you approve again with it in acknowledgeWarnings.",
|
|
3419
|
+
input: z.strictObject({
|
|
3420
|
+
id: reviewId,
|
|
3421
|
+
acknowledgeWarnings: z.array(acknowledgedWarning).default([]).describe("Warnings you accept besides those the request acknowledged.")
|
|
3422
|
+
}),
|
|
3423
|
+
output: z.object({
|
|
3424
|
+
outcome: z.enum([
|
|
3425
|
+
"published",
|
|
3426
|
+
"blocked",
|
|
3427
|
+
"needs_acknowledgement",
|
|
3428
|
+
"not_pending",
|
|
3429
|
+
"not_found"
|
|
3430
|
+
]),
|
|
3431
|
+
review: review.optional(),
|
|
3432
|
+
entry: entrySummary.optional(),
|
|
3433
|
+
verdict: verdict.optional()
|
|
3434
|
+
}),
|
|
3435
|
+
scopes: ["content:publish"],
|
|
3436
|
+
effect: "write",
|
|
3437
|
+
outcomes: {
|
|
3438
|
+
published: { isError: false },
|
|
3439
|
+
blocked: { isError: true },
|
|
3440
|
+
needs_acknowledgement: { isError: false },
|
|
3441
|
+
not_pending: { isError: true },
|
|
3442
|
+
not_found: { isError: true }
|
|
3443
|
+
}
|
|
3444
|
+
});
|
|
3445
|
+
const requestChanges = defineOperation({
|
|
3446
|
+
name: "request_changes",
|
|
3447
|
+
title: "Ask for changes",
|
|
3448
|
+
description: "Closes a pending review without publishing, with a note that says what to change. The author saves a new revision and asks again.",
|
|
3449
|
+
input: z.strictObject({
|
|
3450
|
+
id: reviewId,
|
|
3451
|
+
note: z.string().trim().min(1).max(4e3).describe("What to change, for the author.")
|
|
3452
|
+
}),
|
|
3453
|
+
output: z.object({
|
|
3454
|
+
outcome: z.enum([
|
|
3455
|
+
"changes_requested",
|
|
3456
|
+
"not_pending",
|
|
3457
|
+
"not_found"
|
|
3458
|
+
]),
|
|
3459
|
+
review: review.optional()
|
|
3460
|
+
}),
|
|
3461
|
+
scopes: ["content:publish"],
|
|
3462
|
+
effect: "edit",
|
|
3463
|
+
outcomes: {
|
|
3464
|
+
changes_requested: { isError: false },
|
|
3465
|
+
not_pending: { isError: true },
|
|
3466
|
+
not_found: { isError: true }
|
|
3467
|
+
}
|
|
3468
|
+
});
|
|
3469
|
+
/** Every operation, sorted by name: the order of `tools/list`. */
|
|
3244
3470
|
const OPERATIONS = [
|
|
3471
|
+
approveReview,
|
|
3245
3472
|
createDraft,
|
|
3246
3473
|
describeSite,
|
|
3247
3474
|
getEntry,
|
|
3248
3475
|
getTranslations,
|
|
3249
3476
|
listEntries,
|
|
3477
|
+
listReviews,
|
|
3250
3478
|
publish,
|
|
3479
|
+
requestChanges,
|
|
3251
3480
|
unpublish,
|
|
3252
3481
|
updateDraft,
|
|
3253
3482
|
validate
|
|
@@ -3303,7 +3532,51 @@ async function hashPersonalToken(token) {
|
|
|
3303
3532
|
return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
3304
3533
|
}
|
|
3305
3534
|
//#endregion
|
|
3535
|
+
//#region src/webhooks.ts
|
|
3536
|
+
/**
|
|
3537
|
+
* The signature of Hollware's webhooks (ADR-025), for the Worker that sends them and for whoever
|
|
3538
|
+
* receives them: HMAC-SHA256 of `<timestamp>.<body>` with the webhook's secret, sent as
|
|
3539
|
+
* `hollware-signature: v1=<hex>` with `hollware-timestamp` (seconds since the epoch) and
|
|
3540
|
+
* `hollware-event-id`. Web Crypto only, so it runs in any runtime.
|
|
3541
|
+
*/
|
|
3542
|
+
const SIGNATURE_HEADER = "hollware-signature";
|
|
3543
|
+
const TIMESTAMP_HEADER = "hollware-timestamp";
|
|
3544
|
+
const EVENT_ID_HEADER = "hollware-event-id";
|
|
3545
|
+
/** How far a timestamp may be from the receiver's clock, by default: five minutes. */
|
|
3546
|
+
const TOLERANCE_SECONDS = 300;
|
|
3547
|
+
const encoder = new TextEncoder();
|
|
3548
|
+
async function hmac(secret, text) {
|
|
3549
|
+
const key = await crypto.subtle.importKey("raw", encoder.encode(secret), {
|
|
3550
|
+
name: "HMAC",
|
|
3551
|
+
hash: "SHA-256"
|
|
3552
|
+
}, false, ["sign"]);
|
|
3553
|
+
return [...new Uint8Array(await crypto.subtle.sign("HMAC", key, encoder.encode(text)))].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
3554
|
+
}
|
|
3555
|
+
/** The value of `hollware-signature` for a body sent at `timestamp` (seconds). */
|
|
3556
|
+
async function signWebhook(secret, timestamp, body) {
|
|
3557
|
+
return `v1=${await hmac(secret, `${timestamp}.${body}`)}`;
|
|
3558
|
+
}
|
|
3559
|
+
/** Compares two strings in time that does not depend on where they differ. */
|
|
3560
|
+
function sameText(a, b) {
|
|
3561
|
+
if (a.length !== b.length) return false;
|
|
3562
|
+
let difference = 0;
|
|
3563
|
+
for (let index = 0; index < a.length; index++) difference |= a.charCodeAt(index) ^ b.charCodeAt(index);
|
|
3564
|
+
return difference === 0;
|
|
3565
|
+
}
|
|
3566
|
+
/**
|
|
3567
|
+
* Whether a request is a webhook of Hollware: its signature matches the body and the secret, and
|
|
3568
|
+
* its timestamp is within the tolerance, so a captured request cannot be replayed later.
|
|
3569
|
+
*/
|
|
3570
|
+
async function verifyWebhook(check) {
|
|
3571
|
+
const signature = check.headers.get(SIGNATURE_HEADER);
|
|
3572
|
+
const timestamp = Number(check.headers.get(TIMESTAMP_HEADER));
|
|
3573
|
+
if (signature === null || !Number.isInteger(timestamp)) return false;
|
|
3574
|
+
const now = check.now ?? Math.floor(Date.now() / 1e3);
|
|
3575
|
+
if (Math.abs(now - timestamp) > (check.toleranceSeconds ?? TOLERANCE_SECONDS)) return false;
|
|
3576
|
+
return sameText(signature, await signWebhook(check.secret, timestamp, check.body));
|
|
3577
|
+
}
|
|
3578
|
+
//#endregion
|
|
3306
3579
|
//#region src/index.ts
|
|
3307
3580
|
const name = "@hollware/core";
|
|
3308
3581
|
//#endregion
|
|
3309
|
-
export { BUILT_IN_CONTRACT_IDS, COMARK_VERSION, ConfigError, DEFAULT_MAX_MARKDOWN_BYTES, ERROR_CODES, HollwareError, ID_PREFIXES, MAX_MARKDOWN_BYTES_CEILING, OPERATIONS, PERSONAL_TOKEN_PREFIX, PERSONAL_TOKEN_TTL_DAYS, PIPELINE_VERSION, REFUSAL_CONTRACT_IDS, SCOPES, SITE_KEY_MAX_LENGTH, SITE_KEY_PATTERN, contracts, createDraft, defineCollection, defineComponent, defineConfig, defineContract, defineOperation, describeBuiltInContracts, describeContract, describeResolvedSite, describeSite, generatePersonalToken, getEntry, getTranslations, hashPersonalToken, isBuiltInContract, isId, isPersonalToken, isScope, isSiteKey, listEntries, name, newId, processMarkdown, publish, renderHtml, resolveConfig, runContracts, schemas_exports as schemas, sizeLimitViolation, ulid, uniqueSlugViolation, uniqueTranslationViolation, unpublish, updateDraft, validate };
|
|
3582
|
+
export { BUILT_IN_CONTRACT_IDS, COMARK_VERSION, ConfigError, DEFAULT_MAX_MARKDOWN_BYTES, ERROR_CODES, EVENT_ID_HEADER, HollwareError, ID_PREFIXES, MAX_MARKDOWN_BYTES_CEILING, OPERATIONS, PERSONAL_TOKEN_PREFIX, PERSONAL_TOKEN_TTL_DAYS, PIPELINE_VERSION, REFUSAL_CONTRACT_IDS, SCOPES, SIGNATURE_HEADER, SITE_KEY_MAX_LENGTH, SITE_KEY_PATTERN, TIMESTAMP_HEADER, approveReview, contracts, createDraft, defineCollection, defineComponent, defineConfig, defineContract, defineOperation, describeBuiltInContracts, describeContract, describeResolvedSite, describeSite, generatePersonalToken, getEntry, getTranslations, hashPersonalToken, isBuiltInContract, isId, isPersonalToken, isScope, isSiteKey, listEntries, listReviews, name, newId, processMarkdown, publish, renderHtml, requestChanges, resolveConfig, runContracts, schemas_exports as schemas, signWebhook, sizeLimitViolation, ulid, uniqueSlugViolation, uniqueTranslationViolation, unpublish, updateDraft, validate, verifyWebhook };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hollware/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Contracts, operation specs, schemas and the Markdown pipeline of Hollware. No Cloudflare dependencies.",
|
|
5
5
|
"homepage": "https://hollware.com",
|
|
6
6
|
"bugs": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"comark": "0.7.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@cloudflare/vitest-plugin": "1.4.
|
|
36
|
+
"@cloudflare/vitest-plugin": "1.4.1",
|
|
37
37
|
"@types/node": "24.19.2",
|
|
38
38
|
"publint": "0.3.25",
|
|
39
39
|
"typescript": "7.0.2",
|