@hollware/core 0.3.0 → 0.4.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 +22 -2
- package/dist/index.mjs +37 -6
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -290,6 +290,13 @@ interface SiteDefinition {
|
|
|
290
290
|
readonly styleGuide?: string;
|
|
291
291
|
/** The public origin of the site, such as `https://example.com`: entries' URLs start with it. */
|
|
292
292
|
readonly url?: string;
|
|
293
|
+
/**
|
|
294
|
+
* The origins whose pages may read the published content of the site from the browser, over
|
|
295
|
+
* the read API with a read token (`hw_read_`). None by default.
|
|
296
|
+
*/
|
|
297
|
+
readonly cors?: {
|
|
298
|
+
readonly origins: readonly string[];
|
|
299
|
+
};
|
|
293
300
|
/** Who hears about publications (ADR-025). */
|
|
294
301
|
readonly events?: {
|
|
295
302
|
/** Each gets every event, signed with HMAC-SHA256 by the Worker secret named in `secret`. */
|
|
@@ -346,6 +353,10 @@ interface ResolvedSite {
|
|
|
346
353
|
readonly collections: ReadonlyMap<string, ResolvedCollection>;
|
|
347
354
|
/** The public origin of the site, or `null`. */
|
|
348
355
|
readonly url: string | null;
|
|
356
|
+
/** The origins that may call the read API from a page, each as `URL.origin` gives it. */
|
|
357
|
+
readonly cors: {
|
|
358
|
+
readonly origins: readonly string[];
|
|
359
|
+
};
|
|
349
360
|
readonly events: ResolvedEvents;
|
|
350
361
|
}
|
|
351
362
|
/** A webhook of a site; `secret` names the Worker secret that signs its deliveries. */
|
|
@@ -361,7 +372,7 @@ interface ResolvedEvents {
|
|
|
361
372
|
readonly key: string;
|
|
362
373
|
} | null;
|
|
363
374
|
}
|
|
364
|
-
/** The config once resolved, at the start of the Worker
|
|
375
|
+
/** The config once resolved, at the start of the Worker: one site or more (ADR-027). */
|
|
365
376
|
interface ResolvedConfig {
|
|
366
377
|
readonly sites: ReadonlyMap<string, ResolvedSite>;
|
|
367
378
|
readonly auth: ResolvedAuth;
|
|
@@ -954,7 +965,7 @@ export declare function describeResolvedSite(site: ResolvedSite, options?: {
|
|
|
954
965
|
/**
|
|
955
966
|
* Checks the config of the deployment and fills in its defaults, or throws a `ConfigError` with
|
|
956
967
|
* every problem it found, so that a broken `hollware.config.ts` stops the Worker at startup with
|
|
957
|
-
* a message that says what to change.
|
|
968
|
+
* a message that says what to change. A deployment serves one site or more.
|
|
958
969
|
*
|
|
959
970
|
* The parameter is `unknown` because the config comes from user code that may be plain
|
|
960
971
|
* JavaScript; a `HollwareConfig` is accepted as is, so this is a `ResolveConfig`. Notes on the
|
|
@@ -2456,6 +2467,11 @@ type OperationByName<Name extends OperationName> = Extract<Operation, {
|
|
|
2456
2467
|
* row of a token has an id of its own (`tok_…`, see `ids.ts`), its scopes and its expiry.
|
|
2457
2468
|
*/
|
|
2458
2469
|
export declare const PERSONAL_TOKEN_PREFIX = "hw_pat_";
|
|
2470
|
+
/**
|
|
2471
|
+
* Read tokens: `hw_read_` and the same 52 characters. A read token reads the published content of
|
|
2472
|
+
* one site over the read API (`GET /v1/…`) and nothing else, so a site may ship one to a browser.
|
|
2473
|
+
*/
|
|
2474
|
+
export declare const READ_TOKEN_PREFIX = "hw_read_";
|
|
2459
2475
|
/** Days until a new personal token expires, unless whoever creates it asks for another expiry. */
|
|
2460
2476
|
export declare const PERSONAL_TOKEN_TTL_DAYS = 90;
|
|
2461
2477
|
/**
|
|
@@ -2464,8 +2480,12 @@ export declare const PERSONAL_TOKEN_TTL_DAYS = 90;
|
|
|
2464
2480
|
* tests pass their own bytes.
|
|
2465
2481
|
*/
|
|
2466
2482
|
export declare function generatePersonalToken(random?: Uint8Array): string;
|
|
2483
|
+
/** A new read token, made as a personal token is. */
|
|
2484
|
+
export declare function generateReadToken(random?: Uint8Array): string;
|
|
2467
2485
|
/** Whether a string has the shape of a personal token. It says nothing about whether one exists. */
|
|
2468
2486
|
export declare function isPersonalToken(value: string): boolean;
|
|
2487
|
+
/** Whether a string has the shape of a read token. It says nothing about whether one exists. */
|
|
2488
|
+
export declare function isReadToken(value: string): boolean;
|
|
2469
2489
|
/** The SHA-256 of a token, in lowercase hex: what the `tokens` table stores and looks up. */
|
|
2470
2490
|
export declare function hashPersonalToken(token: string): Promise<string>;
|
|
2471
2491
|
//#endregion
|
package/dist/index.mjs
CHANGED
|
@@ -1396,6 +1396,20 @@ function readSiteUrl(path, raw, problems) {
|
|
|
1396
1396
|
}
|
|
1397
1397
|
return url.origin;
|
|
1398
1398
|
}
|
|
1399
|
+
/** The origins of a site's pages that may read its published content from the browser. */
|
|
1400
|
+
function readCors(path, raw, problems) {
|
|
1401
|
+
if (raw === void 0) return { origins: [] };
|
|
1402
|
+
if (!isRecord$1(raw) || !Array.isArray(raw["origins"])) {
|
|
1403
|
+
problems.add(path, wrongType(raw, "an object with origins"), "Declare it as { origins: [\"https://example.com\"] }, or remove it.");
|
|
1404
|
+
return { origins: [] };
|
|
1405
|
+
}
|
|
1406
|
+
const origins = [];
|
|
1407
|
+
for (const [index, origin] of raw["origins"].entries()) {
|
|
1408
|
+
const read = readSiteUrl(itemPath(keyPath(path, "origins"), index), origin, problems);
|
|
1409
|
+
if (read !== null && !origins.includes(read)) origins.push(read);
|
|
1410
|
+
}
|
|
1411
|
+
return { origins };
|
|
1412
|
+
}
|
|
1399
1413
|
function readBindingName(path, raw, problems) {
|
|
1400
1414
|
if (typeof raw === "string" && BINDING_NAME.test(raw)) return raw;
|
|
1401
1415
|
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.");
|
|
@@ -1573,6 +1587,7 @@ function readSite(key, raw, problems) {
|
|
|
1573
1587
|
styleGuide,
|
|
1574
1588
|
collections,
|
|
1575
1589
|
url,
|
|
1590
|
+
cors: readCors(keyPath(path, "cors"), raw["cors"], problems),
|
|
1576
1591
|
events: readEvents(keyPath(path, "events"), raw["events"], url, problems)
|
|
1577
1592
|
};
|
|
1578
1593
|
}
|
|
@@ -1583,8 +1598,7 @@ function readSites(raw, problems) {
|
|
|
1583
1598
|
return sites;
|
|
1584
1599
|
}
|
|
1585
1600
|
const keys = Object.keys(raw);
|
|
1586
|
-
if (keys.length === 0) problems.add("sites", "has no site, and
|
|
1587
|
-
else if (keys.length > 1) problems.add("sites", `has ${keys.length} sites (${keys.map(quote).join(", ")}), and v0.1 serves exactly one site per deployment`, "Keep exactly one site in sites.");
|
|
1601
|
+
if (keys.length === 0) problems.add("sites", "has no site, and a deployment serves at least one", SITE_FIX);
|
|
1588
1602
|
for (const key of keys) {
|
|
1589
1603
|
const site = readSite(key, raw[key], problems);
|
|
1590
1604
|
if (site !== null) sites.set(key, site);
|
|
@@ -1609,7 +1623,7 @@ function readAuth(raw, problems) {
|
|
|
1609
1623
|
/**
|
|
1610
1624
|
* Checks the config of the deployment and fills in its defaults, or throws a `ConfigError` with
|
|
1611
1625
|
* every problem it found, so that a broken `hollware.config.ts` stops the Worker at startup with
|
|
1612
|
-
* a message that says what to change.
|
|
1626
|
+
* a message that says what to change. A deployment serves one site or more.
|
|
1613
1627
|
*
|
|
1614
1628
|
* The parameter is `unknown` because the config comes from user code that may be plain
|
|
1615
1629
|
* JavaScript; a `HollwareConfig` is accepted as is, so this is a `ResolveConfig`. Notes on the
|
|
@@ -3506,26 +3520,43 @@ function isScope(value) {
|
|
|
3506
3520
|
* row of a token has an id of its own (`tok_…`, see `ids.ts`), its scopes and its expiry.
|
|
3507
3521
|
*/
|
|
3508
3522
|
const PERSONAL_TOKEN_PREFIX = "hw_pat_";
|
|
3523
|
+
/**
|
|
3524
|
+
* Read tokens: `hw_read_` and the same 52 characters. A read token reads the published content of
|
|
3525
|
+
* one site over the read API (`GET /v1/…`) and nothing else, so a site may ship one to a browser.
|
|
3526
|
+
*/
|
|
3527
|
+
const READ_TOKEN_PREFIX = "hw_read_";
|
|
3509
3528
|
/** Days until a new personal token expires, unless whoever creates it asks for another expiry. */
|
|
3510
3529
|
const PERSONAL_TOKEN_TTL_DAYS = 90;
|
|
3511
3530
|
const ALPHABET = "0123456789abcdefghjkmnpqrstvwxyz";
|
|
3512
3531
|
const SECRET_LENGTH = 52;
|
|
3513
3532
|
const TOKEN_PATTERN = /^hw_pat_[0-9a-hjkmnp-tv-z]{52}$/;
|
|
3533
|
+
const READ_TOKEN_PATTERN = /^hw_read_[0-9a-hjkmnp-tv-z]{52}$/;
|
|
3514
3534
|
/**
|
|
3515
3535
|
* A new personal token. Each of the 52 random bytes gives one character (its low 5 bits, which
|
|
3516
3536
|
* are uniform because 256 is a multiple of 32). `random` defaults to `crypto.getRandomValues`;
|
|
3517
3537
|
* tests pass their own bytes.
|
|
3518
3538
|
*/
|
|
3519
3539
|
function generatePersonalToken(random = crypto.getRandomValues(new Uint8Array(SECRET_LENGTH))) {
|
|
3520
|
-
|
|
3540
|
+
return PERSONAL_TOKEN_PREFIX + secretOf(random, "a personal token");
|
|
3541
|
+
}
|
|
3542
|
+
/** A new read token, made as a personal token is. */
|
|
3543
|
+
function generateReadToken(random = crypto.getRandomValues(new Uint8Array(SECRET_LENGTH))) {
|
|
3544
|
+
return READ_TOKEN_PREFIX + secretOf(random, "a read token");
|
|
3545
|
+
}
|
|
3546
|
+
function secretOf(random, what) {
|
|
3547
|
+
if (random.length !== SECRET_LENGTH) throw new RangeError(`${what} takes ${SECRET_LENGTH} random bytes, got ${random.length}`);
|
|
3521
3548
|
let secret = "";
|
|
3522
3549
|
for (const byte of random) secret += ALPHABET.charAt(byte & 31);
|
|
3523
|
-
return
|
|
3550
|
+
return secret;
|
|
3524
3551
|
}
|
|
3525
3552
|
/** Whether a string has the shape of a personal token. It says nothing about whether one exists. */
|
|
3526
3553
|
function isPersonalToken(value) {
|
|
3527
3554
|
return TOKEN_PATTERN.test(value);
|
|
3528
3555
|
}
|
|
3556
|
+
/** Whether a string has the shape of a read token. It says nothing about whether one exists. */
|
|
3557
|
+
function isReadToken(value) {
|
|
3558
|
+
return READ_TOKEN_PATTERN.test(value);
|
|
3559
|
+
}
|
|
3529
3560
|
/** The SHA-256 of a token, in lowercase hex: what the `tokens` table stores and looks up. */
|
|
3530
3561
|
async function hashPersonalToken(token) {
|
|
3531
3562
|
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(token));
|
|
@@ -3579,4 +3610,4 @@ async function verifyWebhook(check) {
|
|
|
3579
3610
|
//#region src/index.ts
|
|
3580
3611
|
const name = "@hollware/core";
|
|
3581
3612
|
//#endregion
|
|
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 };
|
|
3613
|
+
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, READ_TOKEN_PREFIX, 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, generateReadToken, getEntry, getTranslations, hashPersonalToken, isBuiltInContract, isId, isPersonalToken, isReadToken, 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