@pithy-sh/core 0.9.0 → 0.11.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/access/access.d.ts +172 -0
- package/dist/access/access.d.ts.map +1 -0
- package/dist/access/access.js +147 -0
- package/dist/access/acting.d.ts +49 -0
- package/dist/access/acting.d.ts.map +1 -0
- package/dist/access/acting.js +21 -0
- package/dist/access/policyCheck.d.ts +33 -0
- package/dist/access/policyCheck.d.ts.map +1 -0
- package/dist/access/policyCheck.js +48 -0
- package/dist/access/usage.d.ts +83 -0
- package/dist/access/usage.d.ts.map +1 -0
- package/dist/access/usage.js +104 -0
- package/dist/access/visibility.d.ts +66 -0
- package/dist/access/visibility.d.ts.map +1 -0
- package/dist/access/visibility.js +86 -0
- package/dist/asyncapi/document.d.ts +46 -0
- package/dist/asyncapi/document.d.ts.map +1 -0
- package/dist/asyncapi/document.js +115 -0
- package/dist/audit/auditEvent.d.ts +5 -0
- package/dist/audit/auditEvent.d.ts.map +1 -1
- package/dist/audit/auditEvent.js +2 -0
- package/dist/capability/bindings.d.ts +47 -2
- package/dist/capability/bindings.d.ts.map +1 -1
- package/dist/capability/bindings.js +117 -7
- package/dist/capability/capability.d.ts +105 -28
- package/dist/capability/capability.d.ts.map +1 -1
- package/dist/capability/composition.d.ts.map +1 -1
- package/dist/capability/composition.js +5 -1
- package/dist/capability/manifest.d.ts +2 -1
- package/dist/capability/manifest.d.ts.map +1 -1
- package/dist/capability/manifest.js +1 -1
- package/dist/capability/settings.d.ts +96 -4
- package/dist/capability/settings.d.ts.map +1 -1
- package/dist/capability/settings.js +85 -4
- package/dist/cli/command.d.ts +364 -0
- package/dist/cli/command.d.ts.map +1 -0
- package/dist/cli/command.js +418 -0
- package/dist/cli/commandCatalog.generated.d.ts +2437 -0
- package/dist/cli/commandCatalog.generated.d.ts.map +1 -0
- package/dist/cli/commandCatalog.generated.js +2564 -0
- package/dist/cli/commandFacts.d.ts +164 -0
- package/dist/cli/commandFacts.d.ts.map +1 -0
- package/dist/cli/commandFacts.js +216 -0
- package/dist/controlPlane/capability.d.ts.map +1 -1
- package/dist/controlPlane/capability.js +12 -2
- package/dist/controlPlane/context.d.ts +5 -0
- package/dist/controlPlane/context.d.ts.map +1 -1
- package/dist/controlPlane/context.js +3 -1
- package/dist/controlPlane/data/connection.d.ts +47 -0
- package/dist/controlPlane/data/connection.d.ts.map +1 -1
- package/dist/controlPlane/data/connection.js +55 -1
- package/dist/controlPlane/error/errors.d.ts.map +1 -1
- package/dist/controlPlane/error/errors.js +4 -3
- package/dist/controlPlane/http/cors.d.ts +4 -4
- package/dist/controlPlane/http/cors.js +4 -4
- package/dist/controlPlane/http/guard.d.ts +11 -0
- package/dist/controlPlane/http/guard.d.ts.map +1 -1
- package/dist/controlPlane/http/guard.js +28 -2
- package/dist/controlPlane/http/handlers.d.ts +60 -0
- package/dist/controlPlane/http/handlers.d.ts.map +1 -1
- package/dist/controlPlane/http/handlers.js +31 -2
- package/dist/controlPlane/http/readiness.d.ts +33 -0
- package/dist/controlPlane/http/readiness.d.ts.map +1 -0
- package/dist/controlPlane/http/readiness.js +39 -0
- package/dist/controlPlane/http/responses.d.ts +147 -0
- package/dist/controlPlane/http/responses.d.ts.map +1 -1
- package/dist/controlPlane/http/responses.js +61 -4
- package/dist/controlPlane/http/routes.d.ts +2 -1
- package/dist/controlPlane/http/routes.d.ts.map +1 -1
- package/dist/controlPlane/http/routes.js +50 -9
- package/dist/controlPlane/http/verify.d.ts.map +1 -1
- package/dist/controlPlane/http/verify.js +2 -1
- package/dist/controlPlane/migrationState/migrationState.d.ts +102 -0
- package/dist/controlPlane/migrationState/migrationState.d.ts.map +1 -0
- package/dist/controlPlane/migrationState/migrationState.js +142 -0
- package/dist/controlPlane/migrations/0003_connection_principal.d.ts +21 -0
- package/dist/controlPlane/migrations/0003_connection_principal.d.ts.map +1 -0
- package/dist/controlPlane/migrations/0003_connection_principal.js +33 -0
- package/dist/controlPlane/scope/scope.d.ts +13 -2
- package/dist/controlPlane/scope/scope.d.ts.map +1 -1
- package/dist/controlPlane/scope/scope.js +14 -3
- package/dist/createBackend.d.ts +7 -0
- package/dist/createBackend.d.ts.map +1 -1
- package/dist/createBackend.js +41 -6
- package/dist/createEntrypoint.d.ts.map +1 -1
- package/dist/createEntrypoint.js +5 -3
- package/dist/entitlement/allowance.d.ts +25 -0
- package/dist/entitlement/allowance.d.ts.map +1 -0
- package/dist/entitlement/allowance.js +80 -0
- package/dist/entitlement/entitlement.d.ts +41 -0
- package/dist/entitlement/entitlement.d.ts.map +1 -1
- package/dist/entitlement/meter.d.ts +57 -0
- package/dist/entitlement/meter.d.ts.map +1 -0
- package/dist/entitlement/meter.js +13 -0
- package/dist/entitlement/require.js +1 -1
- package/dist/error/cause.d.ts +16 -116
- package/dist/error/cause.d.ts.map +1 -1
- package/dist/error/cause.js +19 -153
- package/dist/error/http.d.ts +184 -0
- package/dist/error/http.d.ts.map +1 -1
- package/dist/error/payload.d.ts +368 -0
- package/dist/error/payload.d.ts.map +1 -1
- package/dist/error/payload.js +126 -4
- package/dist/error/pithyError.d.ts +44 -0
- package/dist/error/pithyError.d.ts.map +1 -1
- package/dist/error/pithyError.js +56 -1
- package/dist/error/unresolved.d.ts +265 -0
- package/dist/error/unresolved.d.ts.map +1 -0
- package/dist/error/unresolved.js +599 -0
- package/dist/error/wire.d.ts +20 -0
- package/dist/error/wire.d.ts.map +1 -0
- package/dist/error/wire.js +19 -0
- package/dist/http/authContext.d.ts +7 -0
- package/dist/http/authContext.d.ts.map +1 -1
- package/dist/http/authContext.js +8 -1
- package/dist/http/oidcWebhook.d.ts +3 -3
- package/dist/http/oidcWebhook.js +3 -3
- package/dist/http/routeContract.js +1 -1
- package/dist/http/routeSpec.d.ts +66 -0
- package/dist/http/routeSpec.d.ts.map +1 -0
- package/dist/http/routeSpec.js +40 -0
- package/dist/http/sameOrigin.d.ts +1 -1
- package/dist/http/sameOrigin.js +1 -1
- package/dist/http/scopeGates.d.ts +20 -0
- package/dist/http/scopeGates.d.ts.map +1 -0
- package/dist/http/scopeGates.js +36 -0
- package/dist/http/signedWebhook.d.ts +3 -3
- package/dist/http/signedWebhook.js +3 -3
- package/dist/http/socket.d.ts +52 -0
- package/dist/http/socket.d.ts.map +1 -0
- package/dist/http/socket.js +2 -0
- package/dist/http/subprotocol.d.ts +29 -0
- package/dist/http/subprotocol.d.ts.map +1 -0
- package/dist/http/subprotocol.js +37 -0
- package/dist/http/validation.d.ts +40 -8
- package/dist/http/validation.d.ts.map +1 -1
- package/dist/http/validation.js +25 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/logger/record.d.ts +107 -0
- package/dist/logger/record.d.ts.map +1 -1
- package/dist/migrations/bookkeeping.d.ts +21 -0
- package/dist/migrations/bookkeeping.d.ts.map +1 -1
- package/dist/migrations/bookkeeping.js +32 -3
- package/dist/migrations/capabilitySets.d.ts +23 -0
- package/dist/migrations/capabilitySets.d.ts.map +1 -0
- package/dist/migrations/capabilitySets.js +74 -0
- package/dist/migrations/groups.d.ts +140 -0
- package/dist/migrations/groups.d.ts.map +1 -0
- package/dist/migrations/groups.js +191 -0
- package/dist/migrations/migrateStep.d.ts +13 -0
- package/dist/migrations/migrateStep.d.ts.map +1 -0
- package/dist/migrations/migrateStep.js +20 -0
- package/dist/migrations/owner.d.ts +7 -0
- package/dist/migrations/owner.d.ts.map +1 -1
- package/dist/migrations/owner.js +11 -7
- package/dist/migrations/registry.d.ts +26 -0
- package/dist/migrations/registry.d.ts.map +1 -1
- package/dist/migrations/registry.js +38 -7
- package/dist/migrations/retained.js +2 -2
- package/dist/migrations/runner.d.ts +76 -5
- package/dist/migrations/runner.d.ts.map +1 -1
- package/dist/migrations/runner.js +130 -19
- package/dist/migrations/state.d.ts +60 -0
- package/dist/migrations/state.d.ts.map +1 -0
- package/dist/migrations/state.js +46 -0
- package/dist/naming/domains.d.ts +17 -0
- package/dist/naming/domains.d.ts.map +1 -1
- package/dist/naming/domains.js +62 -4
- package/dist/naming/environment.d.ts.map +1 -1
- package/dist/naming/environment.js +2 -1
- package/dist/naming/feature.d.ts +27 -5
- package/dist/naming/feature.d.ts.map +1 -1
- package/dist/naming/feature.js +36 -6
- package/dist/naming/limits.d.ts +47 -3
- package/dist/naming/limits.d.ts.map +1 -1
- package/dist/naming/limits.js +47 -3
- package/dist/naming/packageName.d.ts +17 -0
- package/dist/naming/packageName.d.ts.map +1 -0
- package/dist/naming/packageName.js +22 -0
- package/dist/naming/provisionScope.d.ts +6 -0
- package/dist/naming/provisionScope.d.ts.map +1 -1
- package/dist/naming/provisionScope.js +10 -4
- package/dist/naming/resourceNames.d.ts +21 -2
- package/dist/naming/resourceNames.d.ts.map +1 -1
- package/dist/naming/resourceNames.js +21 -3
- package/dist/openapi/document.d.ts +106 -0
- package/dist/openapi/document.d.ts.map +1 -0
- package/dist/openapi/document.js +314 -0
- package/dist/openapi/routeGroups.d.ts +8 -0
- package/dist/openapi/routeGroups.d.ts.map +1 -0
- package/dist/openapi/routeGroups.js +23 -0
- package/dist/seed/writeD1.d.ts +7 -1
- package/dist/seed/writeD1.d.ts.map +1 -1
- package/dist/seed/writeD1.js +9 -3
- package/dist/seed/writeKv.d.ts +2 -0
- package/dist/seed/writeKv.d.ts.map +1 -1
- package/dist/seed/writeKv.js +6 -2
- package/dist/text/comments.js +66 -4
- package/dist/text/literals.d.ts +43 -0
- package/dist/text/literals.d.ts.map +1 -0
- package/dist/text/literals.js +293 -0
- package/dist/version.generated.d.ts +1 -1
- package/dist/version.generated.d.ts.map +1 -1
- package/dist/version.generated.js +1 -1
- package/dist/worker/serviceLoopback.d.ts +36 -0
- package/dist/worker/serviceLoopback.d.ts.map +1 -0
- package/dist/worker/serviceLoopback.js +74 -0
- package/dist/workflow/dispatchRoute.d.ts.map +1 -1
- package/dist/workflow/dispatchRoute.js +12 -6
- package/dist/workflow/faults.d.ts +12 -1
- package/dist/workflow/faults.d.ts.map +1 -1
- package/dist/workflow/faults.js +24 -18
- package/dist/workflow/hostEnv.d.ts +1 -0
- package/dist/workflow/hostEnv.d.ts.map +1 -1
- package/dist/workflow/hostEnv.js +4 -2
- package/dist/workflow/loopback.d.ts.map +1 -1
- package/dist/workflow/loopback.js +2 -1
- package/package.json +4 -4
- package/src/access/access.ts +298 -0
- package/src/access/acting.ts +59 -0
- package/src/access/policyCheck.ts +77 -0
- package/src/access/usage.ts +141 -0
- package/src/access/visibility.ts +109 -0
- package/src/asyncapi/document.ts +164 -0
- package/src/audit/auditEvent.ts +4 -0
- package/src/capability/bindings.ts +159 -13
- package/src/capability/capability.ts +106 -29
- package/src/capability/composition.ts +2 -1
- package/src/capability/settings.ts +165 -4
- package/src/cli/command.ts +865 -0
- package/src/cli/commandCatalog.generated.ts +2723 -0
- package/src/cli/commandFacts.ts +220 -0
- package/src/controlPlane/capability.ts +25 -1
- package/src/controlPlane/context.ts +4 -0
- package/src/controlPlane/data/connection.ts +90 -0
- package/src/controlPlane/error/errors.ts +10 -3
- package/src/controlPlane/http/cors.ts +4 -4
- package/src/controlPlane/http/guard.ts +35 -2
- package/src/controlPlane/http/handlers.ts +41 -0
- package/src/controlPlane/http/readiness.ts +50 -0
- package/src/controlPlane/http/responses.ts +140 -2
- package/src/controlPlane/http/routes.ts +82 -10
- package/src/controlPlane/http/verify.ts +2 -0
- package/src/controlPlane/migrationState/migrationState.ts +200 -0
- package/src/controlPlane/migrations/0003_connection_principal.ts +37 -0
- package/src/controlPlane/scope/scope.ts +14 -2
- package/src/createBackend.ts +59 -8
- package/src/createEntrypoint.ts +9 -3
- package/src/entitlement/allowance.ts +85 -0
- package/src/entitlement/entitlement.ts +38 -0
- package/src/entitlement/meter.ts +69 -0
- package/src/entitlement/require.ts +1 -1
- package/src/error/cause.ts +18 -181
- package/src/error/payload.ts +270 -4
- package/src/error/pithyError.ts +76 -0
- package/src/error/unresolved.ts +853 -0
- package/src/error/wire.ts +40 -0
- package/src/http/authContext.ts +13 -1
- package/src/http/oidcWebhook.ts +3 -3
- package/src/http/routeContract.ts +1 -1
- package/src/http/routeSpec.ts +81 -0
- package/src/http/sameOrigin.ts +1 -1
- package/src/http/scopeGates.ts +34 -0
- package/src/http/signedWebhook.ts +3 -3
- package/src/http/socket.ts +55 -0
- package/src/http/subprotocol.ts +47 -0
- package/src/http/validation.ts +50 -8
- package/src/index.ts +1 -0
- package/src/migrations/bookkeeping.ts +39 -2
- package/src/migrations/capabilitySets.ts +87 -0
- package/src/migrations/groups.ts +295 -0
- package/src/migrations/migrateStep.ts +19 -0
- package/src/migrations/owner.ts +10 -6
- package/src/migrations/registry.ts +46 -2
- package/src/migrations/retained.ts +2 -2
- package/src/migrations/runner.ts +214 -22
- package/src/migrations/state.ts +94 -0
- package/src/naming/domains.ts +75 -16
- package/src/naming/environment.ts +2 -1
- package/src/naming/feature.ts +46 -7
- package/src/naming/limits.ts +35 -3
- package/src/naming/packageName.ts +20 -0
- package/src/naming/provisionScope.ts +21 -2
- package/src/naming/resourceNames.ts +52 -5
- package/src/openapi/document.ts +412 -0
- package/src/openapi/routeGroups.ts +25 -0
- package/src/seed/writeD1.ts +13 -4
- package/src/seed/writeKv.ts +6 -2
- package/src/text/comments.ts +56 -4
- package/src/text/literals.ts +294 -0
- package/src/version.generated.ts +1 -1
- package/src/worker/serviceLoopback.ts +105 -0
- package/src/workflow/dispatchRoute.ts +13 -6
- package/src/workflow/faults.ts +56 -27
- package/src/workflow/hostEnv.ts +9 -1
- package/src/workflow/loopback.ts +2 -1
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { sql } from "kysely";
|
|
5
|
+
//#region src/access/usage.ts
|
|
6
|
+
/**
|
|
7
|
+
* What an account is holding — records and bytes, per owner and per organization — for the control plane.
|
|
8
|
+
*
|
|
9
|
+
* ## A per-owner figure is always an organization's figure
|
|
10
|
+
*
|
|
11
|
+
* **A report run by an organization for user X filters by that organization's id**, because the
|
|
12
|
+
* organization must never learn that the same person holds an account in another one. The number alone
|
|
13
|
+
* leaks it: a per-user total summed across organizations discloses that the user exists elsewhere, with no
|
|
14
|
+
* name attached and no row shown — the figure simply does not reconcile with what the organization can
|
|
15
|
+
* see. So an unscoped per-owner read is not a convenience this offers. It is a read that does not exist:
|
|
16
|
+
* {@link UsageQuery} refuses `groupBy: "owner"` and an `ownerId` filter unless an organization is named,
|
|
17
|
+
* and every per-owner row is keyed by the organization it was counted in.
|
|
18
|
+
*
|
|
19
|
+
* Duplicate detection is the same disclosure in different clothes — *does anybody hold this file* against
|
|
20
|
+
* *how much does this person hold* — and both are closed by the same column. Neither consults an
|
|
21
|
+
* adopter's permission hook: a hook that widened a per-subject total would reopen exactly this.
|
|
22
|
+
*
|
|
23
|
+
* ## The organization is the column, not a join
|
|
24
|
+
*
|
|
25
|
+
* Counted by the `organizationId` written on each record, which is the fact at the time of the write. A
|
|
26
|
+
* join through current membership would re-attribute last quarter's bytes the moment somebody changed
|
|
27
|
+
* organization. Only stored records count — a pending upload holds nothing yet, and a failed one never
|
|
28
|
+
* will.
|
|
29
|
+
*
|
|
30
|
+
* ## Indexed
|
|
31
|
+
*
|
|
32
|
+
* The tables carry `(organizationId, createdAt)` and `(ownerId, createdAt)`, so an organization's slice
|
|
33
|
+
* over a window is one index range and a grouping by organization walks the index rather than sorting.
|
|
34
|
+
*/
|
|
35
|
+
/** The sentinel that names records written with no organization in force — a query string cannot say null. */
|
|
36
|
+
const NO_ORGANIZATION = "none";
|
|
37
|
+
const Id = z.string().min(1).max(128);
|
|
38
|
+
/** What a usage read asks. */
|
|
39
|
+
const UsageQuery = z.object({
|
|
40
|
+
groupBy: z.enum(["owner", "organization"]).describe("`organization` is one row per organization; `owner` is one row per owner within the organization named, never across organizations."),
|
|
41
|
+
organizationId: Id.optional().describe("Restrict to one organization. Required with `groupBy=owner` and with `ownerId`, because a per-owner figure is only ever an organization's. `none` is the records written with no organization in force."),
|
|
42
|
+
ownerId: Id.optional().describe("Restrict to one owner, within the organization named."),
|
|
43
|
+
since: z.iso.datetime().optional().describe("Count records created at or after this instant, ISO-8601."),
|
|
44
|
+
until: z.iso.datetime().optional().describe("Count records created before this instant, ISO-8601."),
|
|
45
|
+
limit: z.coerce.number().int().min(1).max(500).optional().describe("The most rows to return. 100 by default.")
|
|
46
|
+
}).describe("A request for records and bytes held, grouped by owner or by organization.").check((ctx) => {
|
|
47
|
+
if (ctx.value.organizationId !== void 0) return;
|
|
48
|
+
if (ctx.value.groupBy !== "owner" && ctx.value.ownerId === void 0) return;
|
|
49
|
+
ctx.issues.push({
|
|
50
|
+
code: "custom",
|
|
51
|
+
input: ctx.value,
|
|
52
|
+
path: ["organizationId"],
|
|
53
|
+
message: `A per-owner read names its organization. Pass organizationId, or \`${NO_ORGANIZATION}\` for records written with none.`
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
/** One row of a usage read. */
|
|
57
|
+
const UsageRow = z.object({
|
|
58
|
+
organizationId: z.string().nullable().describe("The organization these records were written under. Null for records written with none."),
|
|
59
|
+
ownerId: z.string().nullable().optional().describe("The owner, on a per-owner read. Null for system-owned records. Absent on a per-organization read."),
|
|
60
|
+
records: z.number().int().nonnegative().describe("How many stored records."),
|
|
61
|
+
bytes: z.number().int().nonnegative().describe("Their total size in bytes. A record of unknown size counts zero.")
|
|
62
|
+
}).describe("Records and bytes held by one organization, or by one owner within one organization.");
|
|
63
|
+
/** What a usage read answers. */
|
|
64
|
+
const UsageResponse = z.object({
|
|
65
|
+
rows: z.array(UsageRow).describe("Largest first. Capped at the requested limit."),
|
|
66
|
+
truncated: z.boolean().describe("Whether more rows matched than were returned.")
|
|
67
|
+
}).describe("Records and bytes held, grouped as asked.");
|
|
68
|
+
/** The default page of a usage read. */
|
|
69
|
+
const DEFAULT_LIMIT = 100;
|
|
70
|
+
/**
|
|
71
|
+
* The statement a usage read runs, over one record table — `pithyMediaAssets`, `pithyStorageObjects` —
|
|
72
|
+
* carrying `ownerId`, `organizationId`, `size`, `status` and `createdAt`. Exported so a test can ask SQLite
|
|
73
|
+
* how it plans the very statement {@link usageRollup} executes. Every value is a bound parameter.
|
|
74
|
+
*/
|
|
75
|
+
function usageStatement(table, query) {
|
|
76
|
+
const limit = query.limit ?? DEFAULT_LIMIT;
|
|
77
|
+
const organization = sql.ref("organizationId");
|
|
78
|
+
const owner = sql.ref("ownerId");
|
|
79
|
+
const filters = [sql`${sql.ref("status")} = 'stored'`];
|
|
80
|
+
if (query.organizationId === "none") filters.push(sql`${organization} is null`);
|
|
81
|
+
else if (query.organizationId !== void 0) filters.push(sql`${organization} = ${query.organizationId}`);
|
|
82
|
+
if (query.ownerId !== void 0) filters.push(sql`${owner} = ${query.ownerId}`);
|
|
83
|
+
if (query.since !== void 0) filters.push(sql`${sql.ref("createdAt")} >= ${Date.parse(query.since)}`);
|
|
84
|
+
if (query.until !== void 0) filters.push(sql`${sql.ref("createdAt")} < ${Date.parse(query.until)}`);
|
|
85
|
+
const keys = query.groupBy === "owner" ? sql`${organization}, ${owner}` : sql`${organization}`;
|
|
86
|
+
return sql`select ${keys}, count(*) as records, coalesce(sum(${sql.ref("size")}), 0) as bytes from ${sql.table(table)} where ${sql.join(filters, sql` and `)} group by ${keys} order by bytes desc limit ${limit + 1}`;
|
|
87
|
+
}
|
|
88
|
+
/** Read usage off one record table through any Kysely over D1 with the `CamelCasePlugin`. */
|
|
89
|
+
async function usageRollup(db, table, query) {
|
|
90
|
+
const limit = query.limit ?? DEFAULT_LIMIT;
|
|
91
|
+
const result = await usageStatement(table, query).execute(db);
|
|
92
|
+
const byOwner = query.groupBy === "owner";
|
|
93
|
+
return {
|
|
94
|
+
rows: result.rows.slice(0, limit).map((row) => ({
|
|
95
|
+
organizationId: row.organizationId ?? null,
|
|
96
|
+
...byOwner ? { ownerId: row.ownerId ?? null } : {},
|
|
97
|
+
records: Number(row.records),
|
|
98
|
+
bytes: Number(row.bytes ?? 0)
|
|
99
|
+
})),
|
|
100
|
+
truncated: result.rows.length > limit
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
//#endregion
|
|
104
|
+
export { NO_ORGANIZATION, UsageQuery, UsageResponse, UsageRow, usageRollup, usageStatement };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
/**
|
|
5
|
+
* Who may read a record, and learn that it exists — one convention, written once, for every capability
|
|
6
|
+
* that holds a record a person owns.
|
|
7
|
+
*
|
|
8
|
+
* **Three levels, and the kit owns them.** `private` is the owner, acting for the organization the record
|
|
9
|
+
* was written under; acting for any other organization, not even them. `organization` is any member of the
|
|
10
|
+
* record's organization, acting for it. `public` is anyone holding the id or the URL, signed in or not.
|
|
11
|
+
* Media and storage share the levels, the policy shape and the rules, so a record means the same thing in
|
|
12
|
+
* both and there is one place the meaning can be wrong.
|
|
13
|
+
*
|
|
14
|
+
* **The project decides which levels exist, and which one a record gets when it names none.** `allowed` is
|
|
15
|
+
* a set rather than a mode, because a set says everything a mode would and one thing it cannot:
|
|
16
|
+
* `["private", "organization"]` is a guarantee that nothing can be published, even by mistake.
|
|
17
|
+
*/
|
|
18
|
+
/** Every level, weakest first. */
|
|
19
|
+
export declare const RECORD_VISIBILITIES: readonly ["private", "organization", "public"];
|
|
20
|
+
export declare const RecordVisibility: z.ZodEnum<{
|
|
21
|
+
organization: "organization";
|
|
22
|
+
private: "private";
|
|
23
|
+
public: "public";
|
|
24
|
+
}>;
|
|
25
|
+
export type RecordVisibility = z.infer<typeof RecordVisibility>;
|
|
26
|
+
/**
|
|
27
|
+
* The levels a project allows, and the one a record gets when a write names none.
|
|
28
|
+
*
|
|
29
|
+
* **`default` is `private` and must be in `allowed`**, and a policy that breaks either is refused at
|
|
30
|
+
* parse — which, for a capability, is at boot. **`allowed` defaults to all three**, so a capability that
|
|
31
|
+
* accepted `public` before this policy existed is not narrowed by it.
|
|
32
|
+
*/
|
|
33
|
+
export declare const VisibilityPolicy: z.ZodObject<{
|
|
34
|
+
default: z.ZodDefault<z.ZodEnum<{
|
|
35
|
+
organization: "organization";
|
|
36
|
+
private: "private";
|
|
37
|
+
public: "public";
|
|
38
|
+
}>>;
|
|
39
|
+
allowed: z.ZodDefault<z.ZodArray<z.ZodEnum<{
|
|
40
|
+
organization: "organization";
|
|
41
|
+
private: "private";
|
|
42
|
+
public: "public";
|
|
43
|
+
}>>>;
|
|
44
|
+
}, z.core.$strip>;
|
|
45
|
+
export type VisibilityPolicy = z.output<typeof VisibilityPolicy>;
|
|
46
|
+
export type VisibilityPolicyInput = z.input<typeof VisibilityPolicy>;
|
|
47
|
+
/**
|
|
48
|
+
* The level a write records: the one it named, or the policy's default.
|
|
49
|
+
*
|
|
50
|
+
* **Refused, never coerced.** A level outside `allowed` is a mistake somewhere — a client that did not
|
|
51
|
+
* know the policy, or a policy narrowed under a client — and quietly writing the default instead would
|
|
52
|
+
* hide it until somebody wondered why a published file was private, or worse. `organization` with no
|
|
53
|
+
* organization in force is refused for the same reason: a record no member can ever be acting for is not
|
|
54
|
+
* an organization record, it is a private one that says otherwise.
|
|
55
|
+
*
|
|
56
|
+
* Both refusals are the caller's to fix, so the remedy is in `message`; `action` names the config key for
|
|
57
|
+
* the operator who decides the policy.
|
|
58
|
+
*/
|
|
59
|
+
export declare function resolveVisibility(policy: VisibilityPolicy, requested: RecordVisibility | undefined, organizationId: string | null): RecordVisibility;
|
|
60
|
+
/**
|
|
61
|
+
* The levels a policy no longer allows — what `pithy doctor` searches records for after `allowed`
|
|
62
|
+
* narrows. Narrowing never rewrites a record: making a public record private breaks every link already
|
|
63
|
+
* published, so moving them is the adopter's explicit act, and this is how they find them.
|
|
64
|
+
*/
|
|
65
|
+
export declare function outsidePolicy(policy: VisibilityPolicy): RecordVisibility[];
|
|
66
|
+
//# sourceMappingURL=visibility.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visibility.d.ts","sourceRoot":"","sources":["../../src/access/visibility.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;;;;;;GAaG;AAEH,kCAAkC;AAClC,eAAO,MAAM,mBAAmB,YAAI,SAAS,EAAE,cAAc,EAAE,QAAQ,CAAU,CAAC;AAElF,eAAO,MAAM,gBAAgB;;;;EAI1B,CAAC;AACJ,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEhE;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;;;;iBAsBzB,CAAC;AACL,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,gBAAgB,CAAC,CAAC;AACjE,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAErE;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,gBAAgB,EACxB,SAAS,EAAE,gBAAgB,GAAG,SAAS,EACvC,cAAc,EAAE,MAAM,GAAG,IAAI,GAC5B,gBAAgB,CAmBlB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,gBAAgB,GAAG,gBAAgB,EAAE,CAE1E"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import { ValidationError } from "../error/pithyError.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
//#region src/access/visibility.ts
|
|
6
|
+
/**
|
|
7
|
+
* Who may read a record, and learn that it exists — one convention, written once, for every capability
|
|
8
|
+
* that holds a record a person owns.
|
|
9
|
+
*
|
|
10
|
+
* **Three levels, and the kit owns them.** `private` is the owner, acting for the organization the record
|
|
11
|
+
* was written under; acting for any other organization, not even them. `organization` is any member of the
|
|
12
|
+
* record's organization, acting for it. `public` is anyone holding the id or the URL, signed in or not.
|
|
13
|
+
* Media and storage share the levels, the policy shape and the rules, so a record means the same thing in
|
|
14
|
+
* both and there is one place the meaning can be wrong.
|
|
15
|
+
*
|
|
16
|
+
* **The project decides which levels exist, and which one a record gets when it names none.** `allowed` is
|
|
17
|
+
* a set rather than a mode, because a set says everything a mode would and one thing it cannot:
|
|
18
|
+
* `["private", "organization"]` is a guarantee that nothing can be published, even by mistake.
|
|
19
|
+
*/
|
|
20
|
+
/** Every level, weakest first. */
|
|
21
|
+
const RECORD_VISIBILITIES = [
|
|
22
|
+
"private",
|
|
23
|
+
"organization",
|
|
24
|
+
"public"
|
|
25
|
+
];
|
|
26
|
+
const RecordVisibility = z.enum(RECORD_VISIBILITIES).describe("Who may read a record and learn it exists: `private` is the owner acting for the record's organization, `organization` is any member acting for it, `public` is anyone holding the id or URL.");
|
|
27
|
+
/**
|
|
28
|
+
* The levels a project allows, and the one a record gets when a write names none.
|
|
29
|
+
*
|
|
30
|
+
* **`default` is `private` and must be in `allowed`**, and a policy that breaks either is refused at
|
|
31
|
+
* parse — which, for a capability, is at boot. **`allowed` defaults to all three**, so a capability that
|
|
32
|
+
* accepted `public` before this policy existed is not narrowed by it.
|
|
33
|
+
*/
|
|
34
|
+
const VisibilityPolicy = z.object({
|
|
35
|
+
default: RecordVisibility.default("private").describe("The level a record gets when a write names none. Private, so a forgotten field can never publish a file. Must be one of `allowed`."),
|
|
36
|
+
allowed: z.array(RecordVisibility).min(1).default([...RECORD_VISIBILITIES]).describe("The levels a write may name. All three by default. `[\"private\", \"organization\"]` means nothing can ever be public; a write naming a level outside it is refused, never coerced.")
|
|
37
|
+
}).describe("Which visibility levels this project allows, and the default for a write that names none.").check((ctx) => {
|
|
38
|
+
if (ctx.value.allowed.includes(ctx.value.default)) return;
|
|
39
|
+
ctx.issues.push({
|
|
40
|
+
code: "custom",
|
|
41
|
+
input: ctx.value,
|
|
42
|
+
path: ["default"],
|
|
43
|
+
message: `visibility.default is \`${ctx.value.default}\`, which visibility.allowed (${ctx.value.allowed.join(", ")}) does not include.`
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
/**
|
|
47
|
+
* The level a write records: the one it named, or the policy's default.
|
|
48
|
+
*
|
|
49
|
+
* **Refused, never coerced.** A level outside `allowed` is a mistake somewhere — a client that did not
|
|
50
|
+
* know the policy, or a policy narrowed under a client — and quietly writing the default instead would
|
|
51
|
+
* hide it until somebody wondered why a published file was private, or worse. `organization` with no
|
|
52
|
+
* organization in force is refused for the same reason: a record no member can ever be acting for is not
|
|
53
|
+
* an organization record, it is a private one that says otherwise.
|
|
54
|
+
*
|
|
55
|
+
* Both refusals are the caller's to fix, so the remedy is in `message`; `action` names the config key for
|
|
56
|
+
* the operator who decides the policy.
|
|
57
|
+
*/
|
|
58
|
+
function resolveVisibility(policy, requested, organizationId) {
|
|
59
|
+
const level = requested ?? policy.default;
|
|
60
|
+
if (!policy.allowed.includes(level)) throw new ValidationError({
|
|
61
|
+
message: `Visibility \`${level}\` is not allowed here. Allowed: ${policy.allowed.join(", ")}.`,
|
|
62
|
+
action: "Name one of visibility.allowed, or widen visibility.allowed in this Worker's pithy.config.ts.",
|
|
63
|
+
detail: `requested ${requested ?? "(default)"} resolved to ${level}, outside ${policy.allowed.join(", ")}`,
|
|
64
|
+
params: {
|
|
65
|
+
visibility: level,
|
|
66
|
+
allowed: policy.allowed.join(", ")
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
if (level === "organization" && organizationId === null) throw new ValidationError({
|
|
70
|
+
message: "Visibility `organization` needs an organization in force. Choose one, or name another level.",
|
|
71
|
+
action: "Compose @pithy-sh/organization and choose an organization, or set a different visibility.default.",
|
|
72
|
+
detail: "organization visibility requested with no acting organization",
|
|
73
|
+
params: { visibility: level }
|
|
74
|
+
});
|
|
75
|
+
return level;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The levels a policy no longer allows — what `pithy doctor` searches records for after `allowed`
|
|
79
|
+
* narrows. Narrowing never rewrites a record: making a public record private breaks every link already
|
|
80
|
+
* published, so moving them is the adopter's explicit act, and this is how they find them.
|
|
81
|
+
*/
|
|
82
|
+
function outsidePolicy(policy) {
|
|
83
|
+
return RECORD_VISIBILITIES.filter((level) => !policy.allowed.includes(level));
|
|
84
|
+
}
|
|
85
|
+
//#endregion
|
|
86
|
+
export { RECORD_VISIBILITIES, RecordVisibility, VisibilityPolicy, outsidePolicy, resolveVisibility };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import type { Capability } from "../capability/capability";
|
|
4
|
+
import type { SocketChannel } from "../http/socket";
|
|
5
|
+
import { type ContractInfo, type JsonSchema, type UndescribedField } from "../openapi/document";
|
|
6
|
+
/**
|
|
7
|
+
* **The AsyncAPI contract: every socket a Worker serves, and every frame on it.**
|
|
8
|
+
*
|
|
9
|
+
* The OpenAPI document stops at the upgrade. This picks up after it, from the `sockets` each composed
|
|
10
|
+
* capability declares (`../http/socket.ts`): one channel per socket, one message per frame, and one operation
|
|
11
|
+
* per direction — `send` for what the Worker sends, `receive` for what it accepts, as AsyncAPI names them from
|
|
12
|
+
* the application's side. Every schema is the one the socket parses with, converted by `z.toJSONSchema` with
|
|
13
|
+
* its descriptions intact, so an engine with no generator still has typed messages to write against.
|
|
14
|
+
*
|
|
15
|
+
* AsyncAPI 3.0, whose schemas are JSON Schema — the dialect `z.toJSONSchema` writes.
|
|
16
|
+
*/
|
|
17
|
+
/** The AsyncAPI version this module writes. */
|
|
18
|
+
export declare const ASYNCAPI_VERSION = "3.0.0";
|
|
19
|
+
/** One capability's sockets. Becomes the channels tagged with its name. */
|
|
20
|
+
export interface SocketGroup {
|
|
21
|
+
/** The capability's name. */
|
|
22
|
+
readonly tag: string;
|
|
23
|
+
/** The sockets it declared. */
|
|
24
|
+
readonly sockets: readonly SocketChannel[];
|
|
25
|
+
}
|
|
26
|
+
/** The sockets of every capability that declares one, grouped by capability, in composition order. */
|
|
27
|
+
export declare function socketGroupsOf(capabilities: readonly Capability[]): SocketGroup[];
|
|
28
|
+
/** The subset of an AsyncAPI 3.0 document this module writes. */
|
|
29
|
+
export interface AsyncApiDocument {
|
|
30
|
+
asyncapi: string;
|
|
31
|
+
info: ContractInfo;
|
|
32
|
+
channels: Record<string, Record<string, unknown>>;
|
|
33
|
+
operations: Record<string, Record<string, unknown>>;
|
|
34
|
+
components: {
|
|
35
|
+
messages: Record<string, Record<string, unknown>>;
|
|
36
|
+
schemas: Record<string, JsonSchema>;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** The AsyncAPI document for these capabilities' sockets. */
|
|
40
|
+
export declare function asyncApiDocument(info: ContractInfo, groups: readonly SocketGroup[]): AsyncApiDocument;
|
|
41
|
+
/**
|
|
42
|
+
* Every message property, schema property and channel parameter in the document without a description —
|
|
43
|
+
* the same rule as the OpenAPI document's, over the parts a client generates from.
|
|
44
|
+
*/
|
|
45
|
+
export declare function undescribedAsyncFields(document: AsyncApiDocument): UndescribedField[];
|
|
46
|
+
//# sourceMappingURL=document.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../../src/asyncapi/document.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAE3D,OAAO,KAAK,EAAE,aAAa,EAAiB,MAAM,gBAAgB,CAAC;AAEnE,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,UAAU,EAGf,KAAK,gBAAgB,EAEtB,MAAM,qBAAqB,CAAC;AAE7B;;;;;;;;;;GAUG;AAEH,+CAA+C;AAC/C,eAAO,MAAM,gBAAgB,UAAU,CAAC;AAExC,2EAA2E;AAC3E,MAAM,WAAW,WAAW;IAC1B,6BAA6B;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,+BAA+B;IAC/B,QAAQ,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,CAAC;CAC5C;AAED,sGAAsG;AACtG,wBAAgB,cAAc,CAAC,YAAY,EAAE,SAAS,UAAU,EAAE,GAAG,WAAW,EAAE,CAIjF;AAED,iEAAiE;AACjE,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,YAAY,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAClD,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACpD,UAAU,EAAE;QACV,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;QAClD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;KACrC,CAAC;CACH;AAyCD,6DAA6D;AAC7D,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,WAAW,EAAE,GAAG,gBAAgB,CA+CrG;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,gBAAgB,EAAE,CAWrF"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import { ErrorBody } from "../error/wire.js";
|
|
4
|
+
import { jsonSchema, openApiPath, undescribedFieldsIn } from "../openapi/document.js";
|
|
5
|
+
//#region src/asyncapi/document.ts
|
|
6
|
+
/**
|
|
7
|
+
* **The AsyncAPI contract: every socket a Worker serves, and every frame on it.**
|
|
8
|
+
*
|
|
9
|
+
* The OpenAPI document stops at the upgrade. This picks up after it, from the `sockets` each composed
|
|
10
|
+
* capability declares (`../http/socket.ts`): one channel per socket, one message per frame, and one operation
|
|
11
|
+
* per direction — `send` for what the Worker sends, `receive` for what it accepts, as AsyncAPI names them from
|
|
12
|
+
* the application's side. Every schema is the one the socket parses with, converted by `z.toJSONSchema` with
|
|
13
|
+
* its descriptions intact, so an engine with no generator still has typed messages to write against.
|
|
14
|
+
*
|
|
15
|
+
* AsyncAPI 3.0, whose schemas are JSON Schema — the dialect `z.toJSONSchema` writes.
|
|
16
|
+
*/
|
|
17
|
+
/** The AsyncAPI version this module writes. */
|
|
18
|
+
const ASYNCAPI_VERSION = "3.0.0";
|
|
19
|
+
/** The sockets of every capability that declares one, grouped by capability, in composition order. */
|
|
20
|
+
function socketGroupsOf(capabilities) {
|
|
21
|
+
return capabilities.filter((capability) => (capability.sockets ?? []).length > 0).map((capability) => ({
|
|
22
|
+
tag: capability.name,
|
|
23
|
+
sockets: capability.sockets ?? []
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
26
|
+
/** A frame's id in the document: its socket's name and its own, so two sockets' `error` frames stay apart. */
|
|
27
|
+
function messageId(socket, message, direction) {
|
|
28
|
+
return `${socket.name}.${direction}.${message.name}`;
|
|
29
|
+
}
|
|
30
|
+
/** One `components.messages` entry: a JSON frame by its schema, a text frame by its exact text. */
|
|
31
|
+
function messageOf(message) {
|
|
32
|
+
if (message.schema === void 0) return {
|
|
33
|
+
name: message.name,
|
|
34
|
+
summary: message.summary,
|
|
35
|
+
contentType: "text/plain",
|
|
36
|
+
payload: {
|
|
37
|
+
type: "string",
|
|
38
|
+
const: message.text ?? "",
|
|
39
|
+
description: message.summary
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
return {
|
|
43
|
+
name: message.name,
|
|
44
|
+
summary: message.summary,
|
|
45
|
+
contentType: "application/json",
|
|
46
|
+
payload: jsonSchema(message.schema, "input")
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** The path parameters, from the same schema the upgrade route validates them with. */
|
|
50
|
+
function parametersOf(params) {
|
|
51
|
+
if (params === void 0) return {};
|
|
52
|
+
const properties = jsonSchema(params, "input").properties ?? {};
|
|
53
|
+
return Object.fromEntries(Object.entries(properties).map(([name, property]) => [name, typeof property.description === "string" ? { description: property.description } : {}]));
|
|
54
|
+
}
|
|
55
|
+
function strategiesOf(socket) {
|
|
56
|
+
return typeof socket.verification === "string" ? [socket.verification] : socket.verification;
|
|
57
|
+
}
|
|
58
|
+
/** The AsyncAPI document for these capabilities' sockets. */
|
|
59
|
+
function asyncApiDocument(info, groups) {
|
|
60
|
+
const document = {
|
|
61
|
+
asyncapi: ASYNCAPI_VERSION,
|
|
62
|
+
info,
|
|
63
|
+
channels: {},
|
|
64
|
+
operations: {},
|
|
65
|
+
components: {
|
|
66
|
+
messages: {},
|
|
67
|
+
schemas: { Error: jsonSchema(ErrorBody, "input") }
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
for (const group of groups) for (const socket of group.sockets) {
|
|
71
|
+
const channelMessages = {};
|
|
72
|
+
for (const direction of ["send", "receive"]) {
|
|
73
|
+
const refs = [];
|
|
74
|
+
for (const message of socket[direction]) {
|
|
75
|
+
const id = messageId(socket, message, direction);
|
|
76
|
+
document.components.messages[id] = messageOf(message);
|
|
77
|
+
channelMessages[id] = { $ref: `#/components/messages/${id}` };
|
|
78
|
+
refs.push({ $ref: `#/channels/${socket.name}/messages/${id}` });
|
|
79
|
+
}
|
|
80
|
+
if (refs.length > 0) document.operations[`${socket.name}.${direction}`] = {
|
|
81
|
+
action: direction,
|
|
82
|
+
channel: { $ref: `#/channels/${socket.name}` },
|
|
83
|
+
summary: direction === "send" ? "Frames the Worker sends." : "Frames the Worker accepts.",
|
|
84
|
+
messages: refs
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const parameters = parametersOf(socket.params);
|
|
88
|
+
document.channels[socket.name] = {
|
|
89
|
+
address: openApiPath(socket.path),
|
|
90
|
+
summary: socket.summary,
|
|
91
|
+
...socket.description === void 0 ? {} : { description: socket.description },
|
|
92
|
+
...Object.keys(parameters).length === 0 ? {} : { parameters },
|
|
93
|
+
messages: channelMessages,
|
|
94
|
+
tags: [{ name: group.tag }],
|
|
95
|
+
bindings: { ws: { method: "GET" } },
|
|
96
|
+
"x-pithy-verification": strategiesOf(socket),
|
|
97
|
+
"x-pithy-subprotocols": socket.versions
|
|
98
|
+
};
|
|
99
|
+
for (const [name, schema] of Object.entries(socket.schemas ?? {})) document.components.schemas[`${socket.name}.${name}`] = jsonSchema(schema, "input");
|
|
100
|
+
}
|
|
101
|
+
return document;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Every message property, schema property and channel parameter in the document without a description —
|
|
105
|
+
* the same rule as the OpenAPI document's, over the parts a client generates from.
|
|
106
|
+
*/
|
|
107
|
+
function undescribedAsyncFields(document) {
|
|
108
|
+
return [
|
|
109
|
+
...Object.entries(document.channels).flatMap(([channel, item]) => Object.entries(item.parameters ?? {}).filter(([, parameter]) => typeof parameter.description !== "string").map(([name]) => ({ pointer: `/channels/${channel}/parameters/${name}` }))),
|
|
110
|
+
...undescribedFieldsIn(document.components.messages, "/components/messages"),
|
|
111
|
+
...undescribedFieldsIn(document.components.schemas, "/components/schemas")
|
|
112
|
+
];
|
|
113
|
+
}
|
|
114
|
+
//#endregion
|
|
115
|
+
export { ASYNCAPI_VERSION, asyncApiDocument, socketGroupsOf, undescribedAsyncFields };
|
|
@@ -76,6 +76,11 @@ export declare const AuditEvent: z.ZodObject<{
|
|
|
76
76
|
user: "user";
|
|
77
77
|
}>;
|
|
78
78
|
actorId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
79
|
+
actorKind: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
80
|
+
agent: "agent";
|
|
81
|
+
dashboard: "dashboard";
|
|
82
|
+
script: "script";
|
|
83
|
+
}>>>;
|
|
79
84
|
sessionId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
80
85
|
resourceType: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
81
86
|
resourceId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auditEvent.d.ts","sourceRoot":"","sources":["../../src/audit/auditEvent.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"auditEvent.d.ts","sourceRoot":"","sources":["../../src/audit/auditEvent.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAiBxB;;;;;GAKG;AACH,eAAO,MAAM,WAAW,aAM+F,CAAC;AACxH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,WAAW,CAAC,CAAC;AAEvD,gGAAgG;AAChG,eAAO,MAAM,YAAY;;;;EAItB,CAAC;AACJ,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,YAAY,CAAC,CAAC;AAEzD,iGAAiG;AACjG,eAAO,MAAM,aAAa;;;;EAIvB,CAAC;AACJ,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,aAAa,CAAC,CAAC;AAE3D;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc;;;;;;EAIxB,CAAC;AACJ,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,cAAc,CAAC,CAAC;AAE7D,mGAAmG;AACnG,eAAO,MAAM,aAAa,wCAIvB,CAAC;AACJ,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,aAAa,CAAC,CAAC;AAE3D;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA2CiF,CAAC;AACzG,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC;AACrD,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC"}
|
package/dist/audit/auditEvent.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
|
+
import { ControlPlaneActorKind } from "../controlPlane/data/connection.js";
|
|
3
4
|
import { z } from "zod";
|
|
4
5
|
/**
|
|
5
6
|
* The action taxonomy is **federated, not centralized**: `action` is an open, namespace-validated
|
|
@@ -57,6 +58,7 @@ const AuditEvent = z.object({
|
|
|
57
58
|
severity: AuditSeverity.default("info").describe("How serious the event is; defaults to `info`."),
|
|
58
59
|
actorType: AuditActorType.describe("The kind of principal that acted."),
|
|
59
60
|
actorId: z.string().nullish().describe("The acting principal's stable id (user id, service/token name); null for `system`/`anonymous`."),
|
|
61
|
+
actorKind: ControlPlaneActorKind.nullish().describe("For a `control-plane` actor, what kind of management client acted — `agent`, `dashboard` or `script` — from the connection it called through (#721). `actorType` names all three alike; this is what tells them apart. Stamped by `requireControlPlane`, so an emit site does not set it; null for every other actor type, and for a connection registered before the kind was recorded."),
|
|
60
62
|
sessionId: z.string().nullish().describe("The session this action belongs to, tying a chain of actions together; null when none."),
|
|
61
63
|
resourceType: z.string().nullish().describe("The type of resource the action targeted (e.g. `user`, `secret`); null when not resource-scoped."),
|
|
62
64
|
resourceId: z.string().nullish().describe("The id of the resource the action targeted; null when not resource-scoped."),
|
|
@@ -1,8 +1,33 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
import { z } from "zod";
|
|
4
|
-
export declare const BindingType: z.ZodUnion<readonly [z.ZodLiteral<"d1">, z.ZodLiteral<"kv">, z.ZodLiteral<"r2">, z.ZodLiteral<"ai">, z.ZodLiteral<"vectorize">, z.ZodLiteral<"queue">, z.ZodLiteral<"ratelimit">, z.ZodLiteral<"email">, z.ZodLiteral<"secret">, z.ZodLiteral<"workflow">, z.ZodLiteral<"service">, z.ZodLiteral<"durable_object">]>;
|
|
4
|
+
export declare const BindingType: z.ZodUnion<readonly [z.ZodLiteral<"d1">, z.ZodLiteral<"kv">, z.ZodLiteral<"r2">, z.ZodLiteral<"ai">, z.ZodLiteral<"vectorize">, z.ZodLiteral<"queue">, z.ZodLiteral<"ratelimit">, z.ZodLiteral<"email">, z.ZodLiteral<"secret">, z.ZodLiteral<"workflow">, z.ZodLiteral<"service">, z.ZodLiteral<"durable_object">, z.ZodLiteral<"hyperdrive">, z.ZodLiteral<"browser">, z.ZodLiteral<"images">, z.ZodLiteral<"analytics_engine_datasets">, z.ZodLiteral<"pipelines">, z.ZodLiteral<"mtls_certificates">, z.ZodLiteral<"dispatch_namespaces">, z.ZodLiteral<"artifacts">]>;
|
|
5
5
|
export type BindingType = z.infer<typeof BindingType>;
|
|
6
|
+
/**
|
|
7
|
+
* **How a stanza the kit creates gets this kind's entries** (#662).
|
|
8
|
+
*
|
|
9
|
+
* Every `env.<name>` stanza Pithy creates starts from the top level, because wrangler inherits none of the keys a
|
|
10
|
+
* binding lives in. What it starts with depends on whose resource the entry names, and that is a fact about the
|
|
11
|
+
* kind, not about the JSON shape wrangler happens to give it:
|
|
12
|
+
*
|
|
13
|
+
* - **`carry`** — account-level and identical everywhere. A dataset, a certificate, a rate limiter's
|
|
14
|
+
* `namespace_id`: the top level's entries come down whole into every stanza, because nothing in them is an
|
|
15
|
+
* environment's own.
|
|
16
|
+
* - **`provision`** — the environment owns the resource. Seeded empty, refilled by the provisioner, because
|
|
17
|
+
* dev's database id in staging's stanza is staging writing to dev's database.
|
|
18
|
+
* - **`branch`** — an account-level resource and a per-environment *decision*. Carried into a feature, which is a
|
|
19
|
+
* branch of `dev`; left empty in a declared environment, which is not. A `send_email` entry names an address
|
|
20
|
+
* verified once at the account, and where this environment's mail goes is a choice production does not inherit
|
|
21
|
+
* from a laptop. A Hyperdrive config is one origin database's connection, and a pipeline stream feeds one sink:
|
|
22
|
+
* the account holds both, but the top level's are dev's, and a declared environment seeded with them would read
|
|
23
|
+
* and write dev's Postgres and dev's sink with nothing failing.
|
|
24
|
+
*
|
|
25
|
+
* Three values, not a boolean. `send_email` was carried everywhere for a day and wrote a developer's address into
|
|
26
|
+
* production's first stanza (#650 review); emptied everywhere, it left a feature with no `NOTIFY`.
|
|
27
|
+
*/
|
|
28
|
+
export type SeedPolicy = "carry" | "provision" | "branch";
|
|
29
|
+
/** How a newly created stanza gets this kind's entries. See {@link SeedPolicy}. */
|
|
30
|
+
export declare function seedPolicy(type: BindingType): SeedPolicy;
|
|
6
31
|
/**
|
|
7
32
|
* Whether a provision command creates the **resource** behind this kind of binding, rather than the
|
|
8
33
|
* adopter standing it up themselves.
|
|
@@ -42,20 +67,40 @@ export declare function isProvisionedBinding(type: BindingType): boolean;
|
|
|
42
67
|
*
|
|
43
68
|
* `service` is in neither, deliberately: `pithy feature` writes it out of the target Worker's env-scoped
|
|
44
69
|
* script name, and no shipped capability declares one.
|
|
70
|
+
*
|
|
71
|
+
* **The pure-wrangler-entry kinds are written and never provisioned (#662).** `hyperdrive`, `browser`, `images`,
|
|
72
|
+
* `analytics_engine_datasets`, `pipelines`, `mtls_certificates` and `dispatch_namespaces` each take a binding name
|
|
73
|
+
* and, where wrangler requires one, the account-level resource the spec states as `target`. Pithy writes the
|
|
74
|
+
* entry; the adopter stands the resource up. `queue` writes its `queues.producers` entry under the name
|
|
75
|
+
* `pithy provision` creates the queue with, and `email` writes `send_email` with nothing but its name.
|
|
76
|
+
*
|
|
77
|
+
* **`artifacts` is written and never provisioned (#673).** Its entry is `{ binding, namespace }`, and the namespace
|
|
78
|
+
* is `<project>-<env>`, derivable offline. There is no create call to make: Cloudflare creates a namespace the first
|
|
79
|
+
* time a repo is created in it. There is a teardown call, because repos cost storage, and `pithy feature destroy`
|
|
80
|
+
* makes it.
|
|
45
81
|
*/
|
|
46
82
|
export declare function isWrittenBinding(type: BindingType): boolean;
|
|
83
|
+
/**
|
|
84
|
+
* The kinds whose wrangler entry names an account-level resource the adopter creates, and what wrangler calls
|
|
85
|
+
* that field — what `BindingSpec.target` is written as. `true` where wrangler refuses the entry without it.
|
|
86
|
+
*/
|
|
87
|
+
export declare const TARGET_KINDS: Readonly<Partial<Record<BindingType, {
|
|
88
|
+
field: string;
|
|
89
|
+
required: boolean;
|
|
90
|
+
}>>>;
|
|
47
91
|
/**
|
|
48
92
|
* Declares a Cloudflare binding a capability requires in the Worker env. The authoring
|
|
49
93
|
* shape lets `optional` be omitted (defaults false); `createBackend` normalizes via parse.
|
|
50
94
|
*/
|
|
51
95
|
export declare const BindingSpec: z.ZodObject<{
|
|
52
|
-
type: z.ZodUnion<readonly [z.ZodLiteral<"d1">, z.ZodLiteral<"kv">, z.ZodLiteral<"r2">, z.ZodLiteral<"ai">, z.ZodLiteral<"vectorize">, z.ZodLiteral<"queue">, z.ZodLiteral<"ratelimit">, z.ZodLiteral<"email">, z.ZodLiteral<"secret">, z.ZodLiteral<"workflow">, z.ZodLiteral<"service">, z.ZodLiteral<"durable_object">]>;
|
|
96
|
+
type: z.ZodUnion<readonly [z.ZodLiteral<"d1">, z.ZodLiteral<"kv">, z.ZodLiteral<"r2">, z.ZodLiteral<"ai">, z.ZodLiteral<"vectorize">, z.ZodLiteral<"queue">, z.ZodLiteral<"ratelimit">, z.ZodLiteral<"email">, z.ZodLiteral<"secret">, z.ZodLiteral<"workflow">, z.ZodLiteral<"service">, z.ZodLiteral<"durable_object">, z.ZodLiteral<"hyperdrive">, z.ZodLiteral<"browser">, z.ZodLiteral<"images">, z.ZodLiteral<"analytics_engine_datasets">, z.ZodLiteral<"pipelines">, z.ZodLiteral<"mtls_certificates">, z.ZodLiteral<"dispatch_namespaces">, z.ZodLiteral<"artifacts">]>;
|
|
53
97
|
name: z.ZodString;
|
|
54
98
|
optional: z.ZodDefault<z.ZodBoolean>;
|
|
55
99
|
className: z.ZodOptional<z.ZodString>;
|
|
56
100
|
classModule: z.ZodOptional<z.ZodString>;
|
|
57
101
|
job: z.ZodOptional<z.ZodString>;
|
|
58
102
|
service: z.ZodOptional<z.ZodString>;
|
|
103
|
+
target: z.ZodOptional<z.ZodString>;
|
|
59
104
|
remote: z.ZodOptional<z.ZodBoolean>;
|
|
60
105
|
scope: z.ZodOptional<z.ZodEnum<{
|
|
61
106
|
environment: "environment";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bindings.d.ts","sourceRoot":"","sources":["../../src/capability/bindings.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"bindings.d.ts","sourceRoot":"","sources":["../../src/capability/bindings.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,WAAW,4iBAuCuC,CAAC;AAChE,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,WAAW,GAAG,QAAQ,CAAC;AA8B1D,mFAAmF;AACnF,wBAAgB,UAAU,CAAC,IAAI,EAAE,WAAW,GAAG,UAAU,CAExD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAE/D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAE3D;AAsBD;;;GAGG;AACH,eAAO,MAAM,YAAY,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC,CAMrG,CAAC;AAqCF;;;GAGG;AACH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;iBAuJpB,CAAC;AACL,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD,wFAAwF;AACxF,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC"}
|