@odla-ai/chapter 0.26.0 → 0.26.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -373,6 +373,7 @@ export const chapter = defineChapter({
373
373
  id: "example-chapter",
374
374
  name: "Example Chapter",
375
375
  url: "https://chapter.example.com",
376
+ binding: "EXAMPLE_CHAPTER",
376
377
  fields: {
377
378
  person: ["name", "email", "firstName", "lastName", "phone", "linkedin"],
378
379
  company: ["name", "domain", "industry", "location", "linkedin"],
@@ -387,7 +388,7 @@ Treat the network as websites connected by explicit delivery edges:
387
388
  - each website is a node with its own `appId`, ODLA tenants/keys, CRM config,
388
389
  Clerk application, publishable keys, issuer, roles, and local users;
389
390
  - each `network.targets[]` entry is a directed edge from one leader to one
390
- follower: `{ id, name, url, secretName?, fields }`;
391
+ follower: `{ id, name, url, binding?, secretName?, fields }`;
391
392
  - the payload is a versioned, allowlisted business record
392
393
  `{ version: 2, source: { siteId, recordId }, type, input }`, never an
393
394
  identity-provider session, another site's pipeline/account/billing state, or
@@ -406,6 +407,32 @@ payload against its own CRM config, and writes with its own server-side
406
407
  is never needed, the leader never receives the follower's database key, and
407
408
  sites do not share a Clerk application.
408
409
 
410
+ When both sites are Cloudflare Workers, configure `binding` and add the matching
411
+ service binding to the leader's Wrangler environment. Cloudflare does not
412
+ reliably dispatch same-account `workers.dev` subrequests into the target
413
+ Worker; Chapter therefore signs against `url` but sends through the bound
414
+ Worker:
415
+
416
+ ```jsonc
417
+ {
418
+ "env": {
419
+ "dev": {
420
+ "services": [{
421
+ "binding": "EXAMPLE_CHAPTER",
422
+ "service": "example-chapter-dev"
423
+ }]
424
+ }
425
+ }
426
+ }
427
+ ```
428
+
429
+ The example assumes the follower sets its development Worker `name` explicitly
430
+ to `example-chapter-dev`. If it instead uses Cloudflare's named-environment
431
+ addressing, declare the base `service` plus `"environment": "dev"`. The binding
432
+ name is public configuration, not a credential. If a configured binding is
433
+ missing at runtime, the target fails explicitly instead of falling back to a
434
+ potentially misrouted public fetch.
435
+
409
436
  Vault the same random value as `network_share_secret` in the follower and as
410
437
  `network_share_example_chapter` in the leader (or set a different
411
438
  `secretName`). No secret is placed in source, browser data, or provisioning
@@ -531,9 +558,10 @@ data.
531
558
 
532
559
  Use distinct development tenants and follower origins for the first delivery:
533
560
 
534
- 1. Confirm every target `id` exactly equals the follower's `chapter.id`. Vault
535
- one random value as `network_share_secret` in the follower and under
536
- the target's resolved `secretName` in the leader.
561
+ 1. Confirm every target `id` exactly equals the follower's `chapter.id`. For
562
+ Cloudflare Workers, confirm its `binding` exists in the leader's development
563
+ Wrangler environment. Vault one random value as `network_share_secret` in
564
+ the follower and under the target's resolved `secretName` in the leader.
537
565
  2. Confirm `GET /api/admin/network/targets` lists the development follower with
538
566
  the expected compatible record types.
539
567
  3. Share one test person and one test business from the leader's record drawer.
@@ -65,6 +65,10 @@ interface ChapterNetworkTarget {
65
65
  name?: string;
66
66
  /** Absolute follower origin, e.g. `https://chapter.example.com`. */
67
67
  url: string;
68
+ /** Optional Cloudflare service-binding name for Worker-to-Worker delivery.
69
+ * The URL still defines the signed request origin; the binding only supplies
70
+ * the transport when a `workers.dev` subrequest cannot enter another Worker. */
71
+ binding?: string;
68
72
  /** Leader-vault key holding this follower's share secret. Defaults to
69
73
  * `network_share_<id-with-underscores>`. */
70
74
  secretName?: string;
@@ -107,6 +111,7 @@ interface ResolvedNetworkTarget {
107
111
  id: string;
108
112
  name: string;
109
113
  url: string;
114
+ binding?: string;
110
115
  secretName: string;
111
116
  fields?: Record<string, readonly string[]>;
112
117
  }
package/dist/index.cjs CHANGED
@@ -997,6 +997,7 @@ function formationFields(crm, formation) {
997
997
  // src/config.ts
998
998
  var SLUG = /^[a-z0-9][a-z0-9-]{1,62}$/;
999
999
  var FIELD = /^[a-z][a-zA-Z0-9_]*$/;
1000
+ var BINDING = /^[A-Z][A-Z0-9_]{0,127}$/;
1000
1001
  function resolveNetwork(config, crm) {
1001
1002
  const seen = /* @__PURE__ */ new Set();
1002
1003
  const targets = [];
@@ -1042,10 +1043,17 @@ function resolveNetwork(config, crm) {
1042
1043
  if (!/^[a-zA-Z][a-zA-Z0-9_-]{1,127}$/.test(secretName)) {
1043
1044
  throw new Error(`defineChapter.network.targets.${target.id}.secretName: must be a vault-key identifier`);
1044
1045
  }
1046
+ const binding = target.binding?.trim();
1047
+ if (binding && !BINDING.test(binding)) {
1048
+ throw new Error(
1049
+ `defineChapter.network.targets.${target.id}.binding: must be an uppercase Worker binding identifier`
1050
+ );
1051
+ }
1045
1052
  targets.push({
1046
1053
  id: target.id,
1047
1054
  name: target.name?.trim() || target.id,
1048
1055
  url: url.origin,
1056
+ ...binding ? { binding } : {},
1049
1057
  secretName,
1050
1058
  ...fields ? { fields } : {}
1051
1059
  });