@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 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. v0.1 holds exactly one site. */
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. v0.1 serves exactly one site per deployment.
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 v0.1 serves exactly one site per deployment", SITE_FIX);
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. v0.1 serves exactly one site per deployment.
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
- if (random.length !== SECRET_LENGTH) throw new RangeError(`a personal token takes ${SECRET_LENGTH} random bytes, got ${random.length}`);
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 PERSONAL_TOKEN_PREFIX + secret;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hollware/core",
3
- "version": "0.3.0",
3
+ "version": "0.4.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": {