@pithy-sh/core 0.1.5 → 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.
@@ -392,6 +392,24 @@ export interface Capability<Databases extends DatabaseSpecMap = DatabaseSpecMap,
392
392
  * every {@link Capability.secretRegistry} slice here into one combined registry.
393
393
  */
394
394
  compose?: (context: CapabilityComposeContext) => void;
395
+ /**
396
+ * Optional **entrypoint** hook, called once by {@link createEntrypoint} with every composed
397
+ * capability, after {@link Capability.compose} has run for all of them.
398
+ *
399
+ * **The distinction from `compose` is the audience, and it is load-bearing.** `createBackend` assembles
400
+ * a backend for anything that wants to look at one, and the CLI is a caller: `pithy ui` builds the app
401
+ * to read its route tree, and `composeCapabilities` runs every `compose` hook to read what a capability
402
+ * reports. `createEntrypoint` is the other thing — it is only ever called by a Worker's own
403
+ * `src/index.ts`, so a throw from here is a Worker that does not serve, and nothing else.
404
+ *
405
+ * So this is where a capability refuses a composition that must never take a request but must still
406
+ * *load*, because the adopter's own toolchain has to keep working on the project while they fix it.
407
+ * `@pithy-sh/payments` is the case it exists for: `pithy add` scaffolds an unimplemented
408
+ * `resolveSubject`, every CLI command keeps loading that config, and the Worker refuses to boot until
409
+ * the adopter has written it (#500). A check that can be made at composition still belongs at
410
+ * composition — earlier is better, and a refusal the CLI can raise is a refusal an adopter sees sooner.
411
+ */
412
+ boot?: (context: CapabilityComposeContext) => void;
395
413
  /** Mounts a Hono sub-router. */
396
414
  routes?: (app: Hono<PithyHonoEnv>) => void;
397
415
  /** Composable middleware (e.g. turnstile(), requireAuth()). */
@@ -1 +1 @@
1
- {"version":3,"file":"capability.d.ts","sourceRoot":"","sources":["../../src/capability/capability.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"capability.d.ts","sourceRoot":"","sources":["../../src/capability/capability.ts"],"names":[],"mappings":"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"}
@@ -118,6 +118,22 @@ export declare function renderConfigOptionLine(key: string, value: ConfigOptionV
118
118
  * cannot reach the throw, because every manifest is parsed before it is rendered.
119
119
  */
120
120
  export declare function renderConfigOptionComment(describe: string, indent: string): string;
121
+ /**
122
+ * The line a writer puts in `pithy.config.ts` for a **seam** — the option a capability takes that is a
123
+ * function, so no renderer can ever state its value.
124
+ *
125
+ * Shorthand (`resolveSubject,`) rather than `key: value`, because the two are the same identifier: the
126
+ * scaffolded module exports the name the factory takes, and a config that spelled it twice would invite
127
+ * an adopter to rename one half of it.
128
+ *
129
+ * **It takes the seam's key and nothing else.** That is the same closed-set argument
130
+ * {@link CONFIG_CONSTANTS} makes one function up, and it has to be made again here for a stronger
131
+ * reason: a constant is one identifier in an expression position, and a seam is an identifier *plus* an
132
+ * import statement plus a file written into the adopter's repository. A manifest states which seam, by a
133
+ * key this package enumerates; every character that reaches the adopter's TypeScript comes from
134
+ * {@link CONFIG_SEAMS}.
135
+ */
136
+ export declare function renderConfigSeamLine(seam: ConfigSeam, indent: string): string;
121
137
  /**
122
138
  * The one import line a capability's wiring is written as.
123
139
  *
@@ -199,11 +215,55 @@ export declare const ConfigConstant: z.ZodEnum<{
199
215
  publicOrigin: "publicOrigin";
200
216
  }>;
201
217
  export type ConfigConstant = z.infer<typeof ConfigConstant>;
218
+ /** One seam a capability takes: what `pithy add` writes for it, and what the config then references. */
219
+ export interface ConfigSeamSpec {
220
+ /** The identifier the scaffolded module exports, and the key the capability's factory takes for it. */
221
+ readonly binding: string;
222
+ /** The module `pithy add` writes, relative to the Worker's own directory. */
223
+ readonly module: string;
224
+ /** How that module is imported from the Worker's `pithy.config.ts`. */
225
+ readonly specifier: string;
226
+ /** The comment written above the seam's line, saying what the adopter still owes it. */
227
+ readonly describe: string;
228
+ }
229
+ /**
230
+ * The seams `pithy add` knows how to scaffold — a capability option whose value is **behavior**, named
231
+ * from a closed set exactly as {@link CONFIG_CONSTANTS} names an identifier.
232
+ *
233
+ * A missing *value* has always been scaffoldable: `pithy add secrets` writes an empty registry and the
234
+ * project still loads, because an empty registry is a valid empty state. A missing *behavior* was not,
235
+ * so `payments`' `billingSubject: "organization"` was refused outright — and its remedy routed through
236
+ * `--set billingSubject=user`, a value that is untrue for every project that wanted the other one (#500).
237
+ *
238
+ * What makes the behavior scaffoldable is that the scaffold is **not a stub that answers**. A resolver
239
+ * returning nothing composes cleanly and then denies every gate, which is indistinguishable from a
240
+ * customer who has not paid. The module written here is branded as unimplemented by the capability that
241
+ * owns it, and that capability refuses the Worker's entrypoint while the brand is still on it — so the
242
+ * adopter says what they mean, gets the file and the signature, and the project still refuses to boot
243
+ * until they have written the one thing only they can write.
244
+ *
245
+ * One entry today. Every string in it is this package's, because all four of them reach the adopter's
246
+ * repository unquoted: an identifier, a path, an import specifier, a comment.
247
+ */
248
+ export declare const CONFIG_SEAMS: {
249
+ readonly paymentsSubject: {
250
+ readonly binding: "resolveSubject";
251
+ readonly module: "src/billing/subject.ts";
252
+ readonly specifier: "./src/billing/subject";
253
+ readonly describe: "Which organization this caller is acting for. Scaffolded unimplemented — this Worker refuses to boot until src/billing/subject.ts answers it.";
254
+ };
255
+ };
256
+ /** Which scaffolded seam a choice needs, by key. */
257
+ export declare const ConfigSeam: z.ZodEnum<{
258
+ paymentsSubject: "paymentsSubject";
259
+ }>;
260
+ export type ConfigSeam = z.infer<typeof ConfigSeam>;
202
261
  export declare const ConfigOption: z.ZodObject<{
203
262
  key: z.ZodString;
204
263
  default: z.ZodOptional<z.ZodType<ConfigOptionValue, unknown, z.core.$ZodTypeInternals<ConfigOptionValue, unknown>>>;
205
264
  choices: z.ZodOptional<z.ZodArray<z.ZodString>>;
206
265
  choicesNeedingCode: z.ZodOptional<z.ZodPipe<z.ZodCustom<Record<string, string>, Record<string, string>>, z.ZodType<Record<string, string>, Record<string, string>, z.core.$ZodTypeInternals<Record<string, string>, Record<string, string>>>>>;
266
+ choicesNeedingSeam: z.ZodOptional<z.ZodPipe<z.ZodCustom<Record<string, "paymentsSubject">, Record<string, "paymentsSubject">>, z.ZodType<Record<string, "paymentsSubject">, Record<string, "paymentsSubject">, z.core.$ZodTypeInternals<Record<string, "paymentsSubject">, Record<string, "paymentsSubject">>>>>;
207
267
  constant: z.ZodOptional<z.ZodEnum<{
208
268
  publicOrigin: "publicOrigin";
209
269
  }>>;
@@ -289,6 +349,7 @@ export declare const CapabilityManifest: z.ZodObject<{
289
349
  default: z.ZodOptional<z.ZodType<ConfigOptionValue, unknown, z.core.$ZodTypeInternals<ConfigOptionValue, unknown>>>;
290
350
  choices: z.ZodOptional<z.ZodArray<z.ZodString>>;
291
351
  choicesNeedingCode: z.ZodOptional<z.ZodPipe<z.ZodCustom<Record<string, string>, Record<string, string>>, z.ZodType<Record<string, string>, Record<string, string>, z.core.$ZodTypeInternals<Record<string, string>, Record<string, string>>>>>;
352
+ choicesNeedingSeam: z.ZodOptional<z.ZodPipe<z.ZodCustom<Record<string, "paymentsSubject">, Record<string, "paymentsSubject">>, z.ZodType<Record<string, "paymentsSubject">, Record<string, "paymentsSubject">, z.core.$ZodTypeInternals<Record<string, "paymentsSubject">, Record<string, "paymentsSubject">>>>>;
292
353
  constant: z.ZodOptional<z.ZodEnum<{
293
354
  publicOrigin: "publicOrigin";
294
355
  }>>;
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/capability/manifest.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAgDxB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,iBAAiB,EAAE,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB,CAAA;CAAE,CAAC;AAEvH,eAAO,MAAM,iBAAiB,EAAE,CAAC,CAAC,OAAO,CAAC,iBAAiB,CAUgD,CAAC;AA6D5G;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,iBAAiB,GAAG,MAAM,CASlE;AAED;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAErC;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,WAAW,CAAC;AAE7C;;;;;;;;;;;GAWG;AACH;;;;;;;;;GASG;AACH,MAAM,WAAW,iBAAiB;IAChC,oEAAoE;IACpE,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;CACnC;AAOD,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,iBAAiB,GAAG,iBAAiB,EAC5C,MAAM,EAAE,MAAM,GACb,MAAM,CAaR;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CASlF;AAwDD;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAU9E;AAED,8EAA8E;AAC9E,MAAM,WAAW,sBAAsB;IACrC,+FAA+F;IAC/F,IAAI,EAAE,MAAM,CAAC;IACb,qFAAqF;IACrF,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAAC,EAC3C,IAAI,EACJ,MAAM,EACN,WAAgB,EAChB,aAAoB,GACrB,EAAE,sBAAsB,GAAG,MAAM,CAKjC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,gBAAgB;aAAK,YAAY,EAAE,eAAe;CAAW,CAAC;AAE3E,8DAA8D;AAC9D,eAAO,MAAM,cAAc;;EAEuF,CAAC;AACnH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,eAAO,MAAM,YAAY;;;;;;;;;iBA4EoE,CAAC;AAC9F,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAExD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiE0D,CAAC;AAC1F,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC"}
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/capability/manifest.ts"],"names":[],"mappings":"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"}
@@ -175,6 +175,24 @@ function renderConfigOptionComment(describe, indent) {
175
175
  return `${indent}// ${describe}`;
176
176
  }
177
177
  /**
178
+ * The line a writer puts in `pithy.config.ts` for a **seam** — the option a capability takes that is a
179
+ * function, so no renderer can ever state its value.
180
+ *
181
+ * Shorthand (`resolveSubject,`) rather than `key: value`, because the two are the same identifier: the
182
+ * scaffolded module exports the name the factory takes, and a config that spelled it twice would invite
183
+ * an adopter to rename one half of it.
184
+ *
185
+ * **It takes the seam's key and nothing else.** That is the same closed-set argument
186
+ * {@link CONFIG_CONSTANTS} makes one function up, and it has to be made again here for a stronger
187
+ * reason: a constant is one identifier in an expression position, and a seam is an identifier *plus* an
188
+ * import statement plus a file written into the adopter's repository. A manifest states which seam, by a
189
+ * key this package enumerates; every character that reaches the adopter's TypeScript comes from
190
+ * {@link CONFIG_SEAMS}.
191
+ */
192
+ function renderConfigSeamLine(seam, indent) {
193
+ return `${indent}${CONFIG_SEAMS[seam].binding},`;
194
+ }
195
+ /**
178
196
  * A capability's own name, as generated source carries it.
179
197
  *
180
198
  * The same shape a config option's key is held to, and for the same reason: it is written **bare**, and
@@ -299,11 +317,39 @@ function renderCapabilityRegistration({ name, indent, optionLines = [], trailing
299
317
  const CONFIG_CONSTANTS = { publicOrigin: "PUBLIC_ORIGIN" };
300
318
  /** Which scaffolded constant an option's value is, by key. */
301
319
  const ConfigConstant = z.enum(Object.keys(CONFIG_CONSTANTS)).describe("A constant the scaffolded pithy.config.ts defines, named from a closed set rather than spelled out.");
320
+ /**
321
+ * The seams `pithy add` knows how to scaffold — a capability option whose value is **behavior**, named
322
+ * from a closed set exactly as {@link CONFIG_CONSTANTS} names an identifier.
323
+ *
324
+ * A missing *value* has always been scaffoldable: `pithy add secrets` writes an empty registry and the
325
+ * project still loads, because an empty registry is a valid empty state. A missing *behavior* was not,
326
+ * so `payments`' `billingSubject: "organization"` was refused outright — and its remedy routed through
327
+ * `--set billingSubject=user`, a value that is untrue for every project that wanted the other one (#500).
328
+ *
329
+ * What makes the behavior scaffoldable is that the scaffold is **not a stub that answers**. A resolver
330
+ * returning nothing composes cleanly and then denies every gate, which is indistinguishable from a
331
+ * customer who has not paid. The module written here is branded as unimplemented by the capability that
332
+ * owns it, and that capability refuses the Worker's entrypoint while the brand is still on it — so the
333
+ * adopter says what they mean, gets the file and the signature, and the project still refuses to boot
334
+ * until they have written the one thing only they can write.
335
+ *
336
+ * One entry today. Every string in it is this package's, because all four of them reach the adopter's
337
+ * repository unquoted: an identifier, a path, an import specifier, a comment.
338
+ */
339
+ const CONFIG_SEAMS = { paymentsSubject: {
340
+ binding: "resolveSubject",
341
+ module: "src/billing/subject.ts",
342
+ specifier: "./src/billing/subject",
343
+ describe: "Which organization this caller is acting for. Scaffolded unimplemented — this Worker refuses to boot until src/billing/subject.ts answers it."
344
+ } };
345
+ /** Which scaffolded seam a choice needs, by key. */
346
+ const ConfigSeam = z.enum(Object.keys(CONFIG_SEAMS)).describe("A seam pithy add knows how to scaffold, named from a closed set rather than described.");
302
347
  const ConfigOption = z.object({
303
348
  key: z.string().regex(BARE_KEY, { error: (issue) => `A config option key must be a bare identifier, and ${JSON.stringify(issue.input)} is not — it is written bare into pithy.config.ts.` }).describe("Option name passed to the capability factory (e.g. \"basePath\"); a bare identifier."),
304
349
  default: ConfigOptionValue.optional().describe("The value rendered into pithy.config.ts when no override is given — and **its absence is what makes an option required**. There is no flag beside it saying so: an option that states a default has an answer the kit is willing to pick, and an option that states none has one only the adopter can. A required option is settled before a byte is written — a prompt asks it with nothing to accept by pressing enter, `--set key=value` answers it non-interactively, and a run carrying neither is refused naming the flag. Nothing is guessed, because a decision worth refusing over is one that is expensive to reverse: payments' `billingSubject` decides whether an entitlement is held by a person or a company, and it lands in a column and a UNIQUE index."),
305
350
  choices: z.array(PrintableString).min(1).optional().describe("The values this option accepts, when they are a closed set. Three things follow from stating them, and none of them can be had without: the prompt is a select rather than free text, `--set` refuses a value outside them instead of writing it into the config, and a refusal can name what is legal — which is the whole of what a caller needs in order to answer a required option. Held to the same printable-string rule as a default, because a choice becomes the value written into the adopter's TypeScript."),
306
351
  choicesNeedingCode: manifestRecord(z.record(PrintableString, z.string().min(1))).optional().describe("Choices `pithy add` must refuse, each mapped to the sentence telling the adopter what to do instead — a value whose composition needs a seam only they can write. `pithy add` renders JSON into `pithy.config.ts`; it cannot render a function, so a choice whose validity depends on one is a choice it can write and the kit will then refuse to load. `payments`' `billingSubject: \"organization\"` is the one: it requires a `resolveSubject` saying which organization a caller acts for, deliberately has no default because a capability that guessed would key a company's plan to whoever signed in first, and so a config carrying the choice without the seam is refused at assembly — bricking every later command in the project, since they all begin by loading the config (#483). Refusing the choice up front costs the adopter one hand-edit; writing it costs them the project."),
352
+ choicesNeedingSeam: manifestRecord(z.record(PrintableString, ConfigSeam)).optional().describe("Choices `pithy add` writes **together with the code they need** — the other half of `choicesNeedingCode`, for a choice whose missing half the kit can scaffold. Each names a seam from `CONFIG_SEAMS`; `pithy add` writes that module into the Worker, imports it, and passes it to the capability's factory beside the choice. `payments`' `billingSubject: \"organization\"` is the one: reaching it used to mean passing `--set billingSubject=user`, a value that is wrong for the project, and then hand-editing both halves (#500). The scaffolded module is deliberately **not** a working stub — it is branded unimplemented, and the capability refuses the Worker's entrypoint while the brand is on it, because a resolver that answers nothing denies every gate and reads exactly like a customer who has not paid. A choice states this or `choicesNeedingCode`, never both: one is a refusal and the other is a scaffold."),
307
353
  constant: ConfigConstant.optional().describe("A constant the scaffolded pithy.config.ts defines, rendered *unquoted* in place of `default` when the target config declares it. The one that exists is `publicOrigin` — `PUBLIC_ORIGIN`, this Worker's address for the environment it composes in. An option whose value is an origin must name it rather than state a URL, because a URL written down is production's URL written into staging: it is what mailed staging's testers magic links into production and unsubscribed them there (#256). `default` is still required *for such an option* and is what a config with no such constant gets, so an older project is never handed an identifier it does not define — the one place the absent-default rule does not reach, and it is checked rather than trusted."),
308
354
  describe: z.string().min(1).refine(isPrintableComment, { error: (issue) => `A config option's rationale must be one line with no trailing whitespace, and ${JSON.stringify(issue.input)} is not — it is written into pithy.config.ts as a // comment.` }).describe("Rationale rendered as the comment above this option in pithy.config.ts; one line.")
309
355
  }).check((ctx) => {
@@ -320,6 +366,20 @@ const ConfigOption = z.object({
320
366
  path: ["choicesNeedingCode", choice],
321
367
  message: `A choice needing code must be one this option offers, and ${JSON.stringify(choice)} is not — ${option.key} offers ${JSON.stringify(option.choices ?? [])}.`
322
368
  });
369
+ for (const choice of Object.keys(option.choicesNeedingSeam ?? {})) {
370
+ if (!option.choices?.includes(choice)) ctx.issues.push({
371
+ code: "custom",
372
+ input: choice,
373
+ path: ["choicesNeedingSeam", choice],
374
+ message: `A choice needing a seam must be one this option offers, and ${JSON.stringify(choice)} is not — ${option.key} offers ${JSON.stringify(option.choices ?? [])}.`
375
+ });
376
+ if (option.choicesNeedingCode?.[choice] !== void 0) ctx.issues.push({
377
+ code: "custom",
378
+ input: choice,
379
+ path: ["choicesNeedingSeam", choice],
380
+ message: `A choice is refused or scaffolded, never both, and ${option.key} states ${JSON.stringify(choice)} in choicesNeedingCode and choicesNeedingSeam alike.`
381
+ });
382
+ }
323
383
  if (option.constant !== void 0 && option.default === void 0) ctx.issues.push({
324
384
  code: "custom",
325
385
  input: option.constant,
@@ -355,4 +415,4 @@ const CapabilityManifest = z.object({
355
415
  whenToEnable: z.string().optional().describe("Self-documenting rationale the CLI surfaces when offering this capability.")
356
416
  }).describe("Declarative, CLI-facing description of a capability (pithy.manifest.json).");
357
417
  //#endregion
358
- export { CONFIG_CONSTANTS, CONFIG_LINE_WIDTH, CONFIG_OPTION_INDENT, CapabilityManifest, ConfigConstant, ConfigOption, ConfigOptionValue, renderCapabilityImport, renderCapabilityRegistration, renderConfigOptionComment, renderConfigOptionLine, renderConfigValue };
418
+ export { CONFIG_CONSTANTS, CONFIG_LINE_WIDTH, CONFIG_OPTION_INDENT, CONFIG_SEAMS, CapabilityManifest, ConfigConstant, ConfigOption, ConfigOptionValue, ConfigSeam, renderCapabilityImport, renderCapabilityRegistration, renderConfigOptionComment, renderConfigOptionLine, renderConfigSeamLine, renderConfigValue };
@@ -1 +1 @@
1
- {"version":3,"file":"createEntrypoint.d.ts","sourceRoot":"","sources":["../src/createEntrypoint.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAC3F,OAAO,KAAK,EAAE,UAAU,EAA0B,MAAM,yBAAyB,CAAC;AAClF,OAAO,EAAE,KAAK,oBAAoB,EAAiB,MAAM,iBAAiB,CAAC;AAI3E;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe;IAC9B,0DAA0D;IAC1D,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,gBAAgB,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC/G;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,uBAAuB,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjH;;;;;;;;;;;;;;;;;;;OAmBG;IACH,SAAS,CAAC,EAAE,CAAC,UAAU,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACzG;AAkBD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,CAAC,IAAI,SAAS,SAAS,UAAU,EAAE,EACxC,KAAK,CAAC,GAAG,SAAS,UAAU,GAAG,UAAU,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,EACrF,OAAO,EAAE,oBAAoB,CAAC,IAAI,EAAE,GAAG,CAAC,GAAG,eAAe,CA+C3D"}
1
+ {"version":3,"file":"createEntrypoint.d.ts","sourceRoot":"","sources":["../src/createEntrypoint.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAC3F,OAAO,KAAK,EAAE,UAAU,EAA0B,MAAM,yBAAyB,CAAC;AAClF,OAAO,EAAE,KAAK,oBAAoB,EAAiB,MAAM,iBAAiB,CAAC;AAI3E;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe;IAC9B,0DAA0D;IAC1D,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,gBAAgB,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC/G;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,uBAAuB,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjH;;;;;;;;;;;;;;;;;;;OAmBG;IACH,SAAS,CAAC,EAAE,CAAC,UAAU,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACzG;AAkBD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,CAAC,IAAI,SAAS,SAAS,UAAU,EAAE,EACxC,KAAK,CAAC,GAAG,SAAS,UAAU,GAAG,UAAU,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,EACrF,OAAO,EAAE,oBAAoB,CAAC,IAAI,EAAE,GAAG,CAAC,GAAG,eAAe,CAoD3D"}
@@ -28,6 +28,7 @@ function createEntrypoint(options) {
28
28
  const app = createBackend(options);
29
29
  const all = options.app ? [...options.capabilities, options.app] : [...options.capabilities];
30
30
  const handlers = all.flatMap((cap) => cap.email ? [cap.email] : []);
31
+ for (const cap of all) cap.boot?.({ capabilities: all });
31
32
  const registry = composeWorkflows(all);
32
33
  const scheduled = scheduledWorkflows(registry);
33
34
  const entrypoint = { fetch: (request, env, ctx) => app.fetch(request, env, ctx) };
@@ -3,5 +3,5 @@
3
3
  /** This package's npm name — the join key against a release feed. */
4
4
  export declare const PACKAGE_NAME = "@pithy-sh/core";
5
5
  /** This package's version, stamped from its own package.json at generation time. */
6
- export declare const PACKAGE_VERSION = "0.1.5";
6
+ export declare const PACKAGE_VERSION = "0.2.0";
7
7
  //# sourceMappingURL=version.generated.d.ts.map
@@ -4,6 +4,6 @@
4
4
  /** This package's npm name — the join key against a release feed. */
5
5
  const PACKAGE_NAME = "@pithy-sh/core";
6
6
  /** This package's version, stamped from its own package.json at generation time. */
7
- const PACKAGE_VERSION = "0.1.5";
7
+ const PACKAGE_VERSION = "0.2.0";
8
8
  //#endregion
9
9
  export { PACKAGE_NAME, PACKAGE_VERSION };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pithy-sh/core",
3
- "version": "0.1.5",
3
+ "version": "0.2.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -410,6 +410,24 @@ export interface Capability<
410
410
  * every {@link Capability.secretRegistry} slice here into one combined registry.
411
411
  */
412
412
  compose?: (context: CapabilityComposeContext) => void;
413
+ /**
414
+ * Optional **entrypoint** hook, called once by {@link createEntrypoint} with every composed
415
+ * capability, after {@link Capability.compose} has run for all of them.
416
+ *
417
+ * **The distinction from `compose` is the audience, and it is load-bearing.** `createBackend` assembles
418
+ * a backend for anything that wants to look at one, and the CLI is a caller: `pithy ui` builds the app
419
+ * to read its route tree, and `composeCapabilities` runs every `compose` hook to read what a capability
420
+ * reports. `createEntrypoint` is the other thing — it is only ever called by a Worker's own
421
+ * `src/index.ts`, so a throw from here is a Worker that does not serve, and nothing else.
422
+ *
423
+ * So this is where a capability refuses a composition that must never take a request but must still
424
+ * *load*, because the adopter's own toolchain has to keep working on the project while they fix it.
425
+ * `@pithy-sh/payments` is the case it exists for: `pithy add` scaffolds an unimplemented
426
+ * `resolveSubject`, every CLI command keeps loading that config, and the Worker refuses to boot until
427
+ * the adopter has written it (#500). A check that can be made at composition still belongs at
428
+ * composition — earlier is better, and a refusal the CLI can raise is a refusal an adopter sees sooner.
429
+ */
430
+ boot?: (context: CapabilityComposeContext) => void;
413
431
  /** Mounts a Hono sub-router. */
414
432
  routes?: (app: Hono<PithyHonoEnv>) => void;
415
433
  /** Composable middleware (e.g. turnstile(), requireAuth()). */
@@ -281,6 +281,25 @@ export function renderConfigOptionComment(describe: string, indent: string): str
281
281
  return `${indent}// ${describe}`;
282
282
  }
283
283
 
284
+ /**
285
+ * The line a writer puts in `pithy.config.ts` for a **seam** — the option a capability takes that is a
286
+ * function, so no renderer can ever state its value.
287
+ *
288
+ * Shorthand (`resolveSubject,`) rather than `key: value`, because the two are the same identifier: the
289
+ * scaffolded module exports the name the factory takes, and a config that spelled it twice would invite
290
+ * an adopter to rename one half of it.
291
+ *
292
+ * **It takes the seam's key and nothing else.** That is the same closed-set argument
293
+ * {@link CONFIG_CONSTANTS} makes one function up, and it has to be made again here for a stronger
294
+ * reason: a constant is one identifier in an expression position, and a seam is an identifier *plus* an
295
+ * import statement plus a file written into the adopter's repository. A manifest states which seam, by a
296
+ * key this package enumerates; every character that reaches the adopter's TypeScript comes from
297
+ * {@link CONFIG_SEAMS}.
298
+ */
299
+ export function renderConfigSeamLine(seam: ConfigSeam, indent: string): string {
300
+ return `${indent}${CONFIG_SEAMS[seam].binding},`;
301
+ }
302
+
284
303
  /**
285
304
  * A capability's own name, as generated source carries it.
286
305
  *
@@ -439,6 +458,53 @@ export const ConfigConstant = z
439
458
  .describe("A constant the scaffolded pithy.config.ts defines, named from a closed set rather than spelled out.");
440
459
  export type ConfigConstant = z.infer<typeof ConfigConstant>;
441
460
 
461
+ /** One seam a capability takes: what `pithy add` writes for it, and what the config then references. */
462
+ export interface ConfigSeamSpec {
463
+ /** The identifier the scaffolded module exports, and the key the capability's factory takes for it. */
464
+ readonly binding: string;
465
+ /** The module `pithy add` writes, relative to the Worker's own directory. */
466
+ readonly module: string;
467
+ /** How that module is imported from the Worker's `pithy.config.ts`. */
468
+ readonly specifier: string;
469
+ /** The comment written above the seam's line, saying what the adopter still owes it. */
470
+ readonly describe: string;
471
+ }
472
+
473
+ /**
474
+ * The seams `pithy add` knows how to scaffold — a capability option whose value is **behavior**, named
475
+ * from a closed set exactly as {@link CONFIG_CONSTANTS} names an identifier.
476
+ *
477
+ * A missing *value* has always been scaffoldable: `pithy add secrets` writes an empty registry and the
478
+ * project still loads, because an empty registry is a valid empty state. A missing *behavior* was not,
479
+ * so `payments`' `billingSubject: "organization"` was refused outright — and its remedy routed through
480
+ * `--set billingSubject=user`, a value that is untrue for every project that wanted the other one (#500).
481
+ *
482
+ * What makes the behavior scaffoldable is that the scaffold is **not a stub that answers**. A resolver
483
+ * returning nothing composes cleanly and then denies every gate, which is indistinguishable from a
484
+ * customer who has not paid. The module written here is branded as unimplemented by the capability that
485
+ * owns it, and that capability refuses the Worker's entrypoint while the brand is still on it — so the
486
+ * adopter says what they mean, gets the file and the signature, and the project still refuses to boot
487
+ * until they have written the one thing only they can write.
488
+ *
489
+ * One entry today. Every string in it is this package's, because all four of them reach the adopter's
490
+ * repository unquoted: an identifier, a path, an import specifier, a comment.
491
+ */
492
+ export const CONFIG_SEAMS = {
493
+ paymentsSubject: {
494
+ binding: "resolveSubject",
495
+ module: "src/billing/subject.ts",
496
+ specifier: "./src/billing/subject",
497
+ describe:
498
+ "Which organization this caller is acting for. Scaffolded unimplemented — this Worker refuses to boot until src/billing/subject.ts answers it.",
499
+ },
500
+ } as const satisfies Record<string, ConfigSeamSpec>;
501
+
502
+ /** Which scaffolded seam a choice needs, by key. */
503
+ export const ConfigSeam = z
504
+ .enum(Object.keys(CONFIG_SEAMS) as [keyof typeof CONFIG_SEAMS])
505
+ .describe("A seam pithy add knows how to scaffold, named from a closed set rather than described.");
506
+ export type ConfigSeam = z.infer<typeof ConfigSeam>;
507
+
442
508
  export const ConfigOption = z
443
509
  .object({
444
510
  key: z
@@ -467,6 +533,11 @@ export const ConfigOption = z
467
533
  .describe(
468
534
  "Choices `pithy add` must refuse, each mapped to the sentence telling the adopter what to do instead — a value whose composition needs a seam only they can write. `pithy add` renders JSON into `pithy.config.ts`; it cannot render a function, so a choice whose validity depends on one is a choice it can write and the kit will then refuse to load. `payments`' `billingSubject: \"organization\"` is the one: it requires a `resolveSubject` saying which organization a caller acts for, deliberately has no default because a capability that guessed would key a company's plan to whoever signed in first, and so a config carrying the choice without the seam is refused at assembly — bricking every later command in the project, since they all begin by loading the config (#483). Refusing the choice up front costs the adopter one hand-edit; writing it costs them the project.",
469
535
  ),
536
+ choicesNeedingSeam: manifestRecord(z.record(PrintableString, ConfigSeam))
537
+ .optional()
538
+ .describe(
539
+ "Choices `pithy add` writes **together with the code they need** — the other half of `choicesNeedingCode`, for a choice whose missing half the kit can scaffold. Each names a seam from `CONFIG_SEAMS`; `pithy add` writes that module into the Worker, imports it, and passes it to the capability's factory beside the choice. `payments`' `billingSubject: \"organization\"` is the one: reaching it used to mean passing `--set billingSubject=user`, a value that is wrong for the project, and then hand-editing both halves (#500). The scaffolded module is deliberately **not** a working stub — it is branded unimplemented, and the capability refuses the Worker's entrypoint while the brand is on it, because a resolver that answers nothing denies every gate and reads exactly like a customer who has not paid. A choice states this or `choicesNeedingCode`, never both: one is a refusal and the other is a scaffold.",
540
+ ),
470
541
  constant: ConfigConstant.optional().describe(
471
542
  "A constant the scaffolded pithy.config.ts defines, rendered *unquoted* in place of `default` when the target config declares it. The one that exists is `publicOrigin` — `PUBLIC_ORIGIN`, this Worker's address for the environment it composes in. An option whose value is an origin must name it rather than state a URL, because a URL written down is production's URL written into staging: it is what mailed staging's testers magic links into production and unsubscribed them there (#256). `default` is still required *for such an option* and is what a config with no such constant gets, so an older project is never handed an identifier it does not define — the one place the absent-default rule does not reach, and it is checked rather than trusted.",
472
543
  ),
@@ -506,6 +577,29 @@ export const ConfigOption = z
506
577
  });
507
578
  }
508
579
  }
580
+ // The same rule the line above holds `choicesNeedingCode` to, for the same reason: a seam keyed on a
581
+ // value this option does not offer never fires, and the choice is written with no seam beside it.
582
+ for (const choice of Object.keys(option.choicesNeedingSeam ?? {})) {
583
+ if (!option.choices?.includes(choice)) {
584
+ ctx.issues.push({
585
+ code: "custom",
586
+ input: choice,
587
+ path: ["choicesNeedingSeam", choice],
588
+ message: `A choice needing a seam must be one this option offers, and ${JSON.stringify(choice)} is not — ${option.key} offers ${JSON.stringify(option.choices ?? [])}.`,
589
+ });
590
+ }
591
+ // Refused *and* scaffolded is not a state with an answer. `pithy add` reads one map to decide
592
+ // whether to stop and the other to decide what to write, so a choice in both would be refused by
593
+ // the first reader and the second would never run — a scaffold declared and silently never written.
594
+ if (option.choicesNeedingCode?.[choice] !== undefined) {
595
+ ctx.issues.push({
596
+ code: "custom",
597
+ input: choice,
598
+ path: ["choicesNeedingSeam", choice],
599
+ message: `A choice is refused or scaffolded, never both, and ${option.key} states ${JSON.stringify(choice)} in choicesNeedingCode and choicesNeedingSeam alike.`,
600
+ });
601
+ }
602
+ }
509
603
  if (option.constant !== undefined && option.default === undefined) {
510
604
  ctx.issues.push({
511
605
  code: "custom",
@@ -80,6 +80,11 @@ export function createEntrypoint<
80
80
  const all: Capability[] = options.app ? [...options.capabilities, options.app] : [...options.capabilities];
81
81
  const handlers: CapabilityEmailHandler[] = all.flatMap((cap) => (cap.email ? [cap.email] : []));
82
82
 
83
+ // The last gate before this Worker can take a request, and the first one the CLI never reaches: only a
84
+ // Worker's own entry calls `createEntrypoint`, while `createBackend` is assembled by tooling too. A
85
+ // capability that must load for the adopter's toolchain and must not serve refuses here (#500).
86
+ for (const cap of all) cap.boot?.({ capabilities: all });
87
+
83
88
  const registry = composeWorkflows(all);
84
89
  const scheduled = scheduledWorkflows(registry);
85
90
 
@@ -13,4 +13,4 @@
13
13
  export const PACKAGE_NAME = "@pithy-sh/core";
14
14
 
15
15
  /** This package's version, stamped from its own package.json at generation time. */
16
- export const PACKAGE_VERSION = "0.1.5";
16
+ export const PACKAGE_VERSION = "0.2.0";