@forumone/throughline-core 0.2.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/CHANGELOG.md +105 -0
  2. package/README.md +39 -8
  3. package/dist/auth/authenticator.d.ts +4 -0
  4. package/dist/auth/authenticator.d.ts.map +1 -1
  5. package/dist/auth/authenticator.js +11 -0
  6. package/dist/auth/authenticator.js.map +1 -1
  7. package/dist/index.d.ts +4 -5
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +2 -3
  10. package/dist/index.js.map +1 -1
  11. package/dist/mcp/audit-context.d.ts +39 -0
  12. package/dist/mcp/audit-context.d.ts.map +1 -0
  13. package/dist/mcp/audit-context.js +36 -0
  14. package/dist/mcp/audit-context.js.map +1 -0
  15. package/dist/mcp/handler.d.ts.map +1 -1
  16. package/dist/mcp/handler.js +27 -1
  17. package/dist/mcp/handler.js.map +1 -1
  18. package/dist/mcp/index.d.ts +4 -0
  19. package/dist/mcp/index.d.ts.map +1 -1
  20. package/dist/mcp/index.js +2 -0
  21. package/dist/mcp/index.js.map +1 -1
  22. package/dist/mcp/payload-mcp.d.ts +66 -0
  23. package/dist/mcp/payload-mcp.d.ts.map +1 -0
  24. package/dist/mcp/payload-mcp.js +63 -0
  25. package/dist/mcp/payload-mcp.js.map +1 -0
  26. package/dist/utils/content-hash.d.ts +31 -0
  27. package/dist/utils/content-hash.d.ts.map +1 -0
  28. package/dist/utils/content-hash.js +94 -0
  29. package/dist/utils/content-hash.js.map +1 -0
  30. package/dist/utils/index.d.ts +2 -2
  31. package/dist/utils/index.d.ts.map +1 -1
  32. package/dist/utils/index.js +1 -2
  33. package/dist/utils/index.js.map +1 -1
  34. package/package.json +3 -7
  35. package/dist/env/index.d.ts +0 -51
  36. package/dist/env/index.d.ts.map +0 -1
  37. package/dist/env/index.js +0 -59
  38. package/dist/env/index.js.map +0 -1
  39. package/dist/utils/diff.d.ts +0 -13
  40. package/dist/utils/diff.d.ts.map +0 -1
  41. package/dist/utils/diff.js +0 -31
  42. package/dist/utils/diff.js.map +0 -1
  43. package/dist/utils/id.d.ts +0 -6
  44. package/dist/utils/id.d.ts.map +0 -1
  45. package/dist/utils/id.js +0 -13
  46. package/dist/utils/id.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,110 @@
1
1
  # @forumone/throughline-core
2
2
 
3
+ ## 0.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 40839b5: Stop publishing code nothing imports
8
+
9
+ `@forumone/throughline-core` loses three things no package in the suite, and no consumer, has ever called:
10
+ - **`./env`** — `ENV_VARS`, `validateBaseEnv`, `requireEnv`, `optionalEnv`, and the subpath export that served them. The idea was that plugins would read `process.env` through shared constants instead of hard-coded strings; every plugin hard-codes the string, including the ones in this repo. A convention with no adherents is not a convention.
11
+ - **`shallowDiff`** — written for the audit writer's `diff` field, never wired to it. The writer still takes a caller-supplied diff, and Payload's own version diffing is the better answer if one is ever wanted.
12
+ - **`generateId`** — an id generator in a framework where Payload assigns the ids.
13
+
14
+ `@forumone/throughline-plugin-contract` stops shipping `examplePlugin`. It is documentation of a shape, and it now lives in the playground, which is where a shape gets demonstrated — the published package was carrying 74 lines of example for every consumer that installs it.
15
+
16
+ Removing exports from a published package, hence minor rather than patch. Nothing in this repository, and nothing in the suite's only consumer, imports any of it.
17
+
18
+ - 9f39ace: Enforce API-key scopes, which until now were only a label
19
+
20
+ The API-keys collection has always had a required `scopes` field, the README has always told you to mint keys with `--scopes publishing.execute`, and the scheduled-publish factory documents that its key "must carry `publishing.execute` scope". Nothing read the field. Every key could do whatever its linked user could, whatever it said on the label.
21
+
22
+ A tool may now declare `requiredScope`, and the handler holds callers to it: the tool is hidden from `tools/list` and refused on a direct call unless the key names that scope. Hidden as well as refused, because an agent shown a tool it will be turned away from will try it, fail, and report the tool as broken when what is narrow is the key.
23
+
24
+ The consequential tools are annotated — `publish`, `unpublish`, `schedule_publish`, `rollback` (`publishing.execute`); `request_approval` (`approvals.request`); `respond_to_approval` (`approvals.decide`); the three form writers (`forms.manage`); `trigger_sync` and `test_integration` (`integrations.trigger`). Reads are left unscoped, which is the right default for a read.
25
+
26
+ **This narrows existing keys.** A key minted with one scope could previously call every tool on every server and now cannot. That is the point, but it will change what an existing MCP client can do — check the scopes on your keys before upgrading. A key carrying no scopes at all passes nothing scoped: absent is read as none, not as everything.
27
+
28
+ - f138b3d: One audit actor shape for every tool, and stop recording agents as people
29
+
30
+ Ten tools built the audit actor by hand and four of them disagreed. Three were only untidy — a dropped `userName`, conditional spreads, an assumption that `ctx.user` is non-null. The fourth was wrong: the component tools wrote `type: 'user'` unconditionally, so a call made with an API key and no linked user was recorded as a person. An audit log that cannot tell an agent from an editor is not an audit log.
31
+
32
+ `auditContext(ctx, meta)` is now exported from core and used at all eight tool call sites. `type` follows the rule the publishing service already used — a call carrying a user is that user's, one without is the system's — and `apiKeyName` rides along either way, because a key acting for a linked user is still worth naming.
33
+
34
+ It also passes `sessionId` through for the first time. The column has been on the audit collection since it was written and nothing ever filled it; it is what lets somebody reading the log group one conversation's writes instead of reading them one at a time.
35
+
36
+ - 6fac789: Add `toPayloadMcpTool`, so Throughline's tools can be served by Payload's own MCP plugin
37
+
38
+ Payload ships `@payloadcms/plugin-mcp`, exact-pinned to the Payload version, built on the official MCP SDK: streamable HTTP, sessions, per-key per-tool capability checkboxes, and generic CRUD tools derived from the field configs. Against that, `createMcpHandler` here is a 146-line JSON-RPC subset speaking `tools/list` and `tools/call`, mounted six times over.
39
+
40
+ The transport was never the product. The tools are. This adapter is what makes moving between the two a configuration change rather than a rewrite of every tool: it translates the input schema (`withMeta`'s `z.object` to the raw shape the plugin registers), the context (a `PayloadRequest` to an `McpToolContext`), and the result (a tool's own object to MCP content blocks).
41
+
42
+ Nothing is wired to it. It is the outcome of a spike, and the servers move over one at a time.
43
+
44
+ ### Patch Changes
45
+
46
+ - Updated dependencies [40839b5]
47
+ - Updated dependencies [9f39ace]
48
+ - @forumone/throughline-plugin-contract@0.3.0
49
+
50
+ ## 0.3.0
51
+
52
+ ### Minor Changes
53
+
54
+ - d20f909: Bind an approval to the document's content rather than to its `updatedAt`.
55
+
56
+ `request_approval` stored `String(document['updatedAt'] ?? …)` as `targetVersion`,
57
+ and publishing's approval step recomputed the same expression at publish time. So
58
+ an approval was tied to a timestamp that moves on **every** save. An editor fixing
59
+ a typo between an approver opening the request and clicking approve invalidated the
60
+ approval — and the approver spent that time reading a version that no longer
61
+ existed.
62
+
63
+ Requiring re-approval after an edit is a defensible rule. Inheriting it from
64
+ whichever timestamp field happened to be nearby is not, and it is why **autosave
65
+ could not be turned on** anywhere the approvals plugin is installed: autosave moves
66
+ `updatedAt` every couple of seconds of typing, so a pending approval would be
67
+ invalidated continuously.
68
+
69
+ Both sides now call `documentContentHash(document)`, new in
70
+ `@forumone/throughline-core`. It hashes the document with the metadata that moves
71
+ without the content moving stripped at every level — `id`, `createdAt`,
72
+ `updatedAt`, `_status`, `__v`, `_id`, `globalType` — over keys in sorted order,
73
+ since blocks come back out of JSONB in no promised order. Array order is preserved,
74
+ because that is the order of the blocks on the page. `{ exclude }` adds
75
+ app-specific bookkeeping fields to the strip list.
76
+
77
+ The rule is now the one that was wanted all along: a save that changed nothing
78
+ keeps a granted approval, a save that changed something invalidates it, and an edit
79
+ that is reverted brings the approval back. That last one is why this is a content
80
+ hash rather than a version id — a version id moves whether or not the content did.
81
+
82
+ The two sides only agree because they load the document identically, with
83
+ `payload.findByID({ collection, id, draft: true })` at the config's default depth.
84
+ A populated relationship and a bare relationship id are different values and no
85
+ normalising makes them one, so a caller hashing a document fetched at some other
86
+ depth gets a hash that matches nothing. That is stated on the function.
87
+
88
+ **Approvals pending at upgrade must be re-requested.** Their `targetVersion` holds
89
+ an ISO timestamp; the publish step now computes a hash, so nothing matches and
90
+ those documents report `approval-required` until a fresh request is granted. No
91
+ migration is offered, because the old value cannot be converted — the content it
92
+ was granted against is not recoverable from a timestamp. Grant a moment for
93
+ in-flight requests to clear before upgrading, or expect approvers to be asked once
94
+ more.
95
+
96
+ Also exports `isDraftWrite` from `@forumone/throughline-publishing`. It is the
97
+ predicate the plugin's own trust boundary uses to tell a "Save draft" apart from
98
+ an unpublish, and it is unavailable to host code that needs the same answer: an
99
+ `afterChange` hook cannot work it out, because Payload sets `data._status =
100
+ 'draft'` on any `draft: true` update before the hooks run and `previousDoc` is the
101
+ latest _version_ rather than the live document. With autosave on, a host hook that
102
+ drops a cache or sends a notification fires every few seconds of typing unless it
103
+ asks this first.
104
+
105
+ Minor rather than patch on all three: `documentContentHash` and `isDraftWrite` are
106
+ new public API, and the stored meaning of `targetVersion` changes.
107
+
3
108
  ## 0.2.2
4
109
 
5
110
  ### Patch Changes
package/README.md CHANGED
@@ -10,9 +10,8 @@ The shared plumbing every Throughline server package depends on. Drop it into a
10
10
  | Auth | `./auth` | `createApiKeysCollection`, `createBearerTokenAuthenticator`, `generateApiKey`, `sha256Hex` |
11
11
  | Events | `./events` | `createInngestClient`, `CoreEvents`, `FrameworkEvents` (module-augmentation seam) |
12
12
  | MCP | `./mcp` | `createMcpHandler`, `McpMetaSchema`, `withMeta` |
13
- | Env | `./env` | `ENV_VARS`, `validateBaseEnv`, `requireEnv`, `optionalEnv` |
14
13
  | Logger | (main) | `defaultLogger`, `createNamedLogger` |
15
- | Utils | (main) | `shallowDiff`, `generateId` |
14
+ | Utils | (main) | `documentContentHash` |
16
15
 
17
16
  The main entry re-exports everything; the subpath exports keep bundles smaller for consumers who only need one slice.
18
17
 
@@ -88,6 +87,34 @@ const handleMcp = createMcpHandler({
88
87
  export const POST = (req: Request) => handleMcp(req)
89
88
  ```
90
89
 
90
+ ### Scopes
91
+
92
+ A key carries `scopes`, and a tool may declare the one it needs:
93
+
94
+ ```ts
95
+ const publishTool: McpToolDefinition = {
96
+ name: 'publish',
97
+ requiredScope: 'publishing.execute',
98
+ // …
99
+ }
100
+ ```
101
+
102
+ A tool that declares no `requiredScope` is callable by any authenticated key, which is the right default for a read. A tool that declares one is **hidden from `tools/list`** and refused on a direct call unless the key names that scope — hidden as well as refused, because an agent shown a tool it will be turned away from will try it, fail, and report the tool as broken when what is narrow is the key.
103
+
104
+ A key carrying no scopes at all passes nothing scoped. Absent is read as none, not as everything.
105
+
106
+ The consequential tools in this suite and the scopes they require:
107
+
108
+ | Scope | Tools |
109
+ |---|---|
110
+ | `publishing.execute` | `publish`, `unpublish`, `schedule_publish`, `rollback` |
111
+ | `approvals.request` | `request_approval` |
112
+ | `approvals.decide` | `respond_to_approval` |
113
+ | `forms.manage` | `create_form`, `update_form_fields`, `update_form_destinations` |
114
+ | `integrations.trigger` | `trigger_sync`, `test_integration` |
115
+
116
+ Everything else — the component tools, the audit queries, the read side of publishing and approvals — needs only a valid key.
117
+
91
118
  ## Events
92
119
 
93
120
  `CoreEvents` enumerates the events the framework fires today. Server packages add their own via TypeScript module augmentation:
@@ -104,18 +131,22 @@ declare module '@forumone/throughline-core/events' {
104
131
 
105
132
  After augmentation, `inngest.send({ name: 'approval/decided', data: { ... } })` is type-checked everywhere.
106
133
 
107
- ## Env vars
108
134
 
109
- `ENV_VARS` is the canonical list of names the framework reads. Plugins reach into `process.env` through these constants rather than hard-coded strings:
135
+ ## Document content hashing
110
136
 
111
- ```ts
112
- import { ENV_VARS, requireEnv, validateBaseEnv } from '@forumone/throughline-core'
137
+ `documentContentHash(document)` reduces a Payload document to a hash of the part an editor authored, ignoring the metadata that moves without the content moving — `id`, `createdAt`, `updatedAt`, `_status`, `__v`, `_id`, `globalType`, stripped at every level of the document. Object key order does not affect the result, because blocks come back out of JSONB in no promised order; array order does, because that is the order of the blocks on the page.
113
138
 
114
- validateBaseEnv() // throws on missing PAYLOAD_SECRET / DATABASE_URI / NEXT_PUBLIC_SERVER_URL
139
+ ```ts
140
+ import { documentContentHash } from '@forumone/throughline-core'
115
141
 
116
- const apiKey = requireEnv(ENV_VARS.PUBLISHING_SERVER_API_KEY)
142
+ const version = await documentContentHash(page)
143
+ const withExtras = await documentContentHash(page, { exclude: ['syncedAt'] })
117
144
  ```
118
145
 
146
+ It exists so that approvals can bind to *what an approver read* rather than to when it was last saved. Approvals writes it as `targetVersion`; publishing recomputes it at publish time. Two consequences worth stating: a save that changed nothing keeps a granted approval, and an edit that is reverted brings one back.
147
+
148
+ **Two callers only agree if they hash a document loaded the same way.** Both of the above use `payload.findByID({ collection, id, draft: true })` at the config's default depth. A populated relationship and a bare relationship id are different values, and normalising cannot turn one into the other — so a third caller fetching at a different depth would produce a hash that matches nothing.
149
+
119
150
  ## Why all of this lives in one package
120
151
 
121
152
  Server packages (Component, Publishing, Approvals, Audit Query, Forms, Integrations) all depend on the same audit log, the same authentication pattern, and the same event taxonomy. Splitting these across packages would create circular dependencies — every server package would need the audit writer, and an audit-only package would need to know about every server. Consolidating the plumbing here keeps the dependency graph one-way: core → server packages → client app.
@@ -10,6 +10,10 @@ export interface BearerTokenAuthenticatorOptions {
10
10
  * against the API-keys collection. Returns the linked user plus key metadata
11
11
  * on success, `null` on any failure (no token, unknown token, disabled key,
12
12
  * key without linked user, expired key).
13
+ *
14
+ * The key's `scopes` come back with it. They are what the handler holds a
15
+ * scoped tool against; until they did, the field was a label on a key that
16
+ * could do everything its linked user could.
13
17
  */
14
18
  export declare function createBearerTokenAuthenticator(options: BearerTokenAuthenticatorOptions): McpAuthenticator;
15
19
  //# sourceMappingURL=authenticator.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"authenticator.d.ts","sourceRoot":"","sources":["../../src/auth/authenticator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,KAAK,EAGV,gBAAgB,EACjB,MAAM,uCAAuC,CAAA;AAG9C,MAAM,WAAW,+BAA+B;IAC9C,OAAO,EAAE,OAAO,CAAA;IAChB,mEAAmE;IACnE,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED;;;;;GAKG;AACH,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,+BAA+B,GACvC,gBAAgB,CAwClB"}
1
+ {"version":3,"file":"authenticator.d.ts","sourceRoot":"","sources":["../../src/auth/authenticator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,KAAK,EAGV,gBAAgB,EACjB,MAAM,uCAAuC,CAAA;AAG9C,MAAM,WAAW,+BAA+B;IAC9C,OAAO,EAAE,OAAO,CAAA;IAChB,mEAAmE;IACnE,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,+BAA+B,GACvC,gBAAgB,CAyClB"}
@@ -4,6 +4,10 @@ import { DEFAULT_API_KEYS_SLUG, sha256Hex } from './api-keys.js';
4
4
  * against the API-keys collection. Returns the linked user plus key metadata
5
5
  * on success, `null` on any failure (no token, unknown token, disabled key,
6
6
  * key without linked user, expired key).
7
+ *
8
+ * The key's `scopes` come back with it. They are what the handler holds a
9
+ * scoped tool against; until they did, the field was a label on a key that
10
+ * could do everything its linked user could.
7
11
  */
8
12
  export function createBearerTokenAuthenticator(options) {
9
13
  const { payload, collectionSlug = DEFAULT_API_KEYS_SLUG } = options;
@@ -38,6 +42,7 @@ export function createBearerTokenAuthenticator(options) {
38
42
  user,
39
43
  apiKeyName: String(apiKey['name'] ?? ''),
40
44
  apiKeyId: String(apiKey['id']),
45
+ scopes: toScopes(apiKey['scopes']),
41
46
  };
42
47
  },
43
48
  };
@@ -60,4 +65,10 @@ function toAuthenticatedUser(raw) {
60
65
  groups: raw['groups'] ?? [],
61
66
  };
62
67
  }
68
+ /** The stored `scopes` as a string list, whatever shape the row came back in. */
69
+ function toScopes(raw) {
70
+ if (!Array.isArray(raw))
71
+ return [];
72
+ return raw.filter((scope) => typeof scope === 'string');
73
+ }
63
74
  //# sourceMappingURL=authenticator.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"authenticator.js","sourceRoot":"","sources":["../../src/auth/authenticator.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,qBAAqB,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAQhE;;;;;GAKG;AACH,MAAM,UAAU,8BAA8B,CAC5C,OAAwC;IAExC,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,qBAAqB,EAAE,GAAG,OAAO,CAAA;IAEnE,OAAO;QACL,KAAK,CAAC,YAAY,CAAC,OAAgB;YACjC,MAAM,KAAK,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAA;YACzC,IAAI,CAAC,KAAK;gBAAE,OAAO,IAAI,CAAA;YAEvB,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,KAAK,CAAC,CAAA;YAEnC,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC;gBAChC,UAAU,EAAE,cAAc;gBAC1B,KAAK,EAAE;oBACL,GAAG,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC;iBACpE;gBACD,KAAK,EAAE,CAAC;gBACR,KAAK,EAAE,CAAC;aACT,CAAC,CAAA;YAEF,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;YAC7B,IAAI,CAAC,MAAM;gBAAE,OAAO,IAAI,CAAA;YAExB,MAAM,SAAS,GAAG,MAAM,CAAC,WAAW,CAAC,CAAA;YACrC,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;gBACzE,OAAO,IAAI,CAAA;YACb,CAAC;YAED,MAAM,UAAU,GAAG,MAAM,CAAC,YAAY,CAAC,CAAA;YACvC,IAAI,CAAC,UAAU,IAAI,OAAO,UAAU,KAAK,QAAQ;gBAAE,OAAO,IAAI,CAAA;YAE9D,MAAM,IAAI,GAAG,mBAAmB,CAAC,UAAqC,CAAC,CAAA;YACvE,IAAI,CAAC,IAAI;gBAAE,OAAO,IAAI,CAAA;YAEtB,OAAO;gBACL,IAAI;gBACJ,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;gBACxC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;aAC/B,CAAA;QACH,CAAC;KACF,CAAA;AACH,CAAC;AAED,SAAS,kBAAkB,CAAC,OAAgB;IAC1C,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAA;IACnD,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAA;IACxB,MAAM,KAAK,GAAG,kBAAkB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAC7C,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,IAAI,CAAA;AACnC,CAAC;AAED,SAAS,mBAAmB,CAAC,GAA4B;IACvD,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,SAAS,IAAI,GAAG,CAAC,OAAO,CAAC,KAAK,SAAS;QAAE,OAAO,IAAI,CAAA;IACtE,OAAO;QACL,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACrB,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC3B,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;QACzC,KAAK,EAAG,GAAG,CAAC,OAAO,CAA0B,IAAI,EAAE;QACnD,MAAM,EAAG,GAAG,CAAC,QAAQ,CAA0B,IAAI,EAAE;KACtD,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"authenticator.js","sourceRoot":"","sources":["../../src/auth/authenticator.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -2,13 +2,12 @@ export { AUDIT_ACTIONS, AUDIT_MCP_SERVERS, DEFAULT_AUDIT_SLUG, auditPlugin, crea
2
2
  export type { AuditAction, AuditActor, AuditCollectionOptions, AuditEventInput, AuditMcpServer, AuditPluginOptions, AuditWriter, AuditWriterOptions, } from './audit/index.js';
3
3
  export { DEFAULT_API_KEYS_SLUG, createApiKeysCollection, createBearerTokenAuthenticator, generateApiKey, sha256Hex, } from './auth/index.js';
4
4
  export type { ApiKeysCollectionOptions, BearerTokenAuthenticatorOptions } from './auth/index.js';
5
- export { ENV_VARS, validateBaseEnv, requireEnv, optionalEnv } from './env/index.js';
6
- export type { EnvVarName, BaseEnv } from './env/index.js';
7
5
  export { createInngestClient } from './events/index.js';
8
6
  export type { CoreEvents, FrameworkEvents, InngestClientOptions } from './events/index.js';
9
- export { McpMetaSchema, createMcpHandler, withMeta } from './mcp/index.js';
10
- export type { McpHandlerOptions, McpMeta } from './mcp/index.js';
7
+ export { McpMetaSchema, auditContext, createMcpHandler, toPayloadMcpTool, toPayloadMcpTools, withMeta, } from './mcp/index.js';
8
+ export type { AuditContextFields, McpHandlerOptions, McpMeta, PayloadMcpRequest, PayloadMcpTool, ToPayloadMcpToolOptions, } from './mcp/index.js';
11
9
  export { defaultLogger, createNamedLogger } from './logger/index.js';
12
- export { shallowDiff, generateId } from './utils/index.js';
10
+ export { documentContentHash } from './utils/index.js';
11
+ export type { DocumentContentHashOptions } from './utils/index.js';
13
12
  export type { AuthenticatedUser, BaseCorePluginOptions, CorePlugin, Logger, McpAuthResult, McpAuthenticator, McpToolContext, McpToolDefinition, PluginRegistry, PluginRegistryEntry, } from '@forumone/throughline-plugin-contract';
14
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,kBAAkB,EAClB,WAAW,EACX,qBAAqB,EACrB,iBAAiB,EACjB,cAAc,GACf,MAAM,kBAAkB,CAAA;AACzB,YAAY,EACV,WAAW,EACX,UAAU,EACV,sBAAsB,EACtB,eAAe,EACf,cAAc,EACd,kBAAkB,EAClB,WAAW,EACX,kBAAkB,GACnB,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,8BAA8B,EAC9B,cAAc,EACd,SAAS,GACV,MAAM,iBAAiB,CAAA;AACxB,YAAY,EAAE,wBAAwB,EAAE,+BAA+B,EAAE,MAAM,iBAAiB,CAAA;AAEhG,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AACnF,YAAY,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAEzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AACvD,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAE1F,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAC1E,YAAY,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAEhE,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAEpE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAI1D,YAAY,EACV,iBAAiB,EACjB,qBAAqB,EACrB,UAAU,EACV,MAAM,EACN,aAAa,EACb,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,cAAc,EACd,mBAAmB,GACpB,MAAM,uCAAuC,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,kBAAkB,EAClB,WAAW,EACX,qBAAqB,EACrB,iBAAiB,EACjB,cAAc,GACf,MAAM,kBAAkB,CAAA;AACzB,YAAY,EACV,WAAW,EACX,UAAU,EACV,sBAAsB,EACtB,eAAe,EACf,cAAc,EACd,kBAAkB,EAClB,WAAW,EACX,kBAAkB,GACnB,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,8BAA8B,EAC9B,cAAc,EACd,SAAS,GACV,MAAM,iBAAiB,CAAA;AACxB,YAAY,EAAE,wBAAwB,EAAE,+BAA+B,EAAE,MAAM,iBAAiB,CAAA;AAEhG,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AACvD,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAE1F,OAAO,EACL,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,QAAQ,GACT,MAAM,gBAAgB,CAAA;AACvB,YAAY,EACV,kBAAkB,EAClB,iBAAiB,EACjB,OAAO,EACP,iBAAiB,EACjB,cAAc,EACd,uBAAuB,GACxB,MAAM,gBAAgB,CAAA;AAEvB,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAEpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AACtD,YAAY,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAA;AAIlE,YAAY,EACV,iBAAiB,EACjB,qBAAqB,EACrB,UAAU,EACV,MAAM,EACN,aAAa,EACb,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,cAAc,EACd,mBAAmB,GACpB,MAAM,uCAAuC,CAAA"}
package/dist/index.js CHANGED
@@ -2,9 +2,8 @@
2
2
  // surface here mirrors the package's `./<subpath>` exports.
3
3
  export { AUDIT_ACTIONS, AUDIT_MCP_SERVERS, DEFAULT_AUDIT_SLUG, auditPlugin, createAuditCollection, createAuditWriter, getAuditWriter, } from './audit/index.js';
4
4
  export { DEFAULT_API_KEYS_SLUG, createApiKeysCollection, createBearerTokenAuthenticator, generateApiKey, sha256Hex, } from './auth/index.js';
5
- export { ENV_VARS, validateBaseEnv, requireEnv, optionalEnv } from './env/index.js';
6
5
  export { createInngestClient } from './events/index.js';
7
- export { McpMetaSchema, createMcpHandler, withMeta } from './mcp/index.js';
6
+ export { McpMetaSchema, auditContext, createMcpHandler, toPayloadMcpTool, toPayloadMcpTools, withMeta, } from './mcp/index.js';
8
7
  export { defaultLogger, createNamedLogger } from './logger/index.js';
9
- export { shallowDiff, generateId } from './utils/index.js';
8
+ export { documentContentHash } from './utils/index.js';
10
9
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,4DAA4D;AAE5D,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,kBAAkB,EAClB,WAAW,EACX,qBAAqB,EACrB,iBAAiB,EACjB,cAAc,GACf,MAAM,kBAAkB,CAAA;AAYzB,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,8BAA8B,EAC9B,cAAc,EACd,SAAS,GACV,MAAM,iBAAiB,CAAA;AAGxB,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAGnF,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AAGvD,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAG1E,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAEpE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,4DAA4D;AAE5D,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,kBAAkB,EAClB,WAAW,EACX,qBAAqB,EACrB,iBAAiB,EACjB,cAAc,GACf,MAAM,kBAAkB,CAAA;AAYzB,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,8BAA8B,EAC9B,cAAc,EACd,SAAS,GACV,MAAM,iBAAiB,CAAA;AAGxB,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AAGvD,OAAO,EACL,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,QAAQ,GACT,MAAM,gBAAgB,CAAA;AAUvB,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAEpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA"}
@@ -0,0 +1,39 @@
1
+ import type { McpToolContext } from '@forumone/throughline-plugin-contract';
2
+ import type { AuditActor } from '../audit/writer.js';
3
+ import type { McpMeta } from './meta.js';
4
+ /**
5
+ * The audit fields every tool derives from its request rather than from its
6
+ * arguments: who called, and whatever narrative context the client attached.
7
+ *
8
+ * Spread into an `AuditEventInput` alongside the fields the tool itself knows —
9
+ * `action`, `mcpServer`, `mcpTool`, the target.
10
+ */
11
+ export interface AuditContextFields {
12
+ actor: AuditActor;
13
+ prompt?: string | undefined;
14
+ reasoning?: string | undefined;
15
+ changesSummary?: string | undefined;
16
+ }
17
+ /**
18
+ * One answer to "who did this", for every tool in every server.
19
+ *
20
+ * Ten tools built this block by hand and four of them disagreed. Three variants
21
+ * were only untidy — a dropped `userName`, conditional spreads, an assumption
22
+ * that `ctx.user` is non-null. The fourth was wrong: the component tools wrote
23
+ * `type: 'user'` unconditionally, so a call made with an API key and no linked
24
+ * user was recorded as a person. An audit log that cannot tell an agent from an
25
+ * editor is not an audit log, and this is the log the MCP pilot will be judged
26
+ * on.
27
+ *
28
+ * `type` follows the rule `publishing`'s service already used: a call carrying a
29
+ * user is that user's, and one without is the system's. `apiKeyName` rides along
30
+ * either way, because a key acting on behalf of a linked user is still worth
31
+ * naming.
32
+ *
33
+ * `sessionId` is passed through for the first time. The column has existed on
34
+ * the audit collection since it was written and nothing ever filled it; it is
35
+ * what lets somebody reading the log group a whole conversation's writes rather
36
+ * than reading them one at a time.
37
+ */
38
+ export declare function auditContext(ctx: McpToolContext, meta?: McpMeta): AuditContextFields;
39
+ //# sourceMappingURL=audit-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-context.d.ts","sourceRoot":"","sources":["../../src/mcp/audit-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAA;AAC3E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAIpD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAExC;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,UAAU,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC9B,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CACpC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,kBAAkB,CAapF"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * One answer to "who did this", for every tool in every server.
3
+ *
4
+ * Ten tools built this block by hand and four of them disagreed. Three variants
5
+ * were only untidy — a dropped `userName`, conditional spreads, an assumption
6
+ * that `ctx.user` is non-null. The fourth was wrong: the component tools wrote
7
+ * `type: 'user'` unconditionally, so a call made with an API key and no linked
8
+ * user was recorded as a person. An audit log that cannot tell an agent from an
9
+ * editor is not an audit log, and this is the log the MCP pilot will be judged
10
+ * on.
11
+ *
12
+ * `type` follows the rule `publishing`'s service already used: a call carrying a
13
+ * user is that user's, and one without is the system's. `apiKeyName` rides along
14
+ * either way, because a key acting on behalf of a linked user is still worth
15
+ * naming.
16
+ *
17
+ * `sessionId` is passed through for the first time. The column has existed on
18
+ * the audit collection since it was written and nothing ever filled it; it is
19
+ * what lets somebody reading the log group a whole conversation's writes rather
20
+ * than reading them one at a time.
21
+ */
22
+ export function auditContext(ctx, meta) {
23
+ return {
24
+ actor: {
25
+ type: ctx.user ? 'user' : 'system',
26
+ userId: ctx.user?.id,
27
+ userName: ctx.user?.name,
28
+ apiKeyName: ctx.apiKeyName,
29
+ sessionId: ctx.sessionId,
30
+ },
31
+ prompt: meta?.userPrompt,
32
+ reasoning: meta?.reasoning,
33
+ changesSummary: meta?.changesSummary,
34
+ };
35
+ }
36
+ //# sourceMappingURL=audit-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-context.js","sourceRoot":"","sources":["../../src/mcp/audit-context.ts"],"names":[],"mappings":"AAqBA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,YAAY,CAAC,GAAmB,EAAE,IAAc;IAC9D,OAAO;QACL,KAAK,EAAE;YACL,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ;YAClC,MAAM,EAAE,GAAG,CAAC,IAAI,EAAE,EAAE;YACpB,QAAQ,EAAE,GAAG,CAAC,IAAI,EAAE,IAAI;YACxB,UAAU,EAAE,GAAG,CAAC,UAAU;YAC1B,SAAS,EAAE,GAAG,CAAC,SAAS;SACzB;QACD,MAAM,EAAE,IAAI,EAAE,UAAU;QACxB,SAAS,EAAE,IAAI,EAAE,SAAS;QAC1B,cAAc,EAAE,IAAI,EAAE,cAAc;KACrC,CAAA;AACH,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/mcp/handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AAGtC,OAAO,KAAK,EACV,MAAM,EACN,gBAAgB,EAChB,iBAAiB,EAClB,MAAM,uCAAuC,CAAA;AAI9C,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAA;IAChB,gEAAgE;IAChE,UAAU,EAAE,MAAM,CAAA;IAClB,kEAAkE;IAClE,KAAK,EAAE,iBAAiB,EAAE,CAAA;IAC1B,mEAAmE;IACnE,aAAa,CAAC,EAAE,gBAAgB,CAAA;IAChC,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAoFpG"}
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/mcp/handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AAGtC,OAAO,KAAK,EACV,MAAM,EACN,gBAAgB,EAChB,iBAAiB,EAClB,MAAM,uCAAuC,CAAA;AAI9C,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAA;IAChB,gEAAgE;IAChE,UAAU,EAAE,MAAM,CAAA;IAClB,kEAAkE;IAClE,KAAK,EAAE,iBAAiB,EAAE,CAAA;IAC1B,mEAAmE;IACnE,aAAa,CAAC,EAAE,gBAAgB,CAAA;IAChC,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CA0GpG"}
@@ -30,8 +30,17 @@ export function createMcpHandler(options) {
30
30
  const id = rpc.id ?? null;
31
31
  try {
32
32
  if (rpc.method === 'tools/list') {
33
+ /*
34
+ Only what this key may actually call.
35
+
36
+ An agent shown a tool it will be refused for will try it, be refused,
37
+ and try something else — and the transcript reads like the tool is
38
+ broken rather than like the key is narrow. Hiding it is the difference
39
+ between "you cannot publish" and "publishing is unavailable", and only
40
+ one of those is true.
41
+ */
33
42
  return jsonRpcResult(id, {
34
- tools: options.tools.map((tool) => ({
43
+ tools: options.tools.filter(permits(auth.scopes)).map((tool) => ({
35
44
  name: tool.name,
36
45
  description: tool.description,
37
46
  inputSchema: zodToJsonSchema(tool.inputSchema, { target: 'jsonSchema7' }),
@@ -47,6 +56,12 @@ export function createMcpHandler(options) {
47
56
  if (!tool) {
48
57
  return jsonRpcError(id, JSON_RPC_METHOD_NOT_FOUND, `Unknown tool: ${params.data.name}`);
49
58
  }
59
+ // Named rather than merely refused: a caller that cannot see why it was
60
+ // turned away has no way to ask for the right key.
61
+ if (!permits(auth.scopes)(tool)) {
62
+ logger.warn(`${tag} refused ${tool.name}: key "${auth.apiKeyName}" lacks scope ${String(tool.requiredScope)}`);
63
+ return jsonRpcError(id, JSON_RPC_INVALID_REQUEST, `This API key does not carry the "${String(tool.requiredScope)}" scope, which ${tool.name} requires.`);
64
+ }
50
65
  const inputResult = tool.inputSchema.safeParse(params.data.arguments ?? {});
51
66
  if (!inputResult.success) {
52
67
  return jsonRpcError(id, JSON_RPC_INVALID_PARAMS, `Invalid arguments: ${inputResult.error.message}`);
@@ -106,4 +121,15 @@ function jsonRpcResult(id, result) {
106
121
  function jsonRpcError(id, code, message) {
107
122
  return jsonResponse({ jsonrpc: '2.0', id, error: { code, message } });
108
123
  }
124
+ /**
125
+ * Whether a key holding `scopes` may call a given tool.
126
+ *
127
+ * A tool declaring no `requiredScope` is callable by any authenticated key —
128
+ * the right default for a read. A tool that declares one needs it named on the
129
+ * key, and a key carrying no scopes at all passes nothing.
130
+ */
131
+ function permits(scopes) {
132
+ const held = new Set(scopes ?? []);
133
+ return (tool) => tool.requiredScope === undefined || held.has(tool.requiredScope);
134
+ }
109
135
  //# sourceMappingURL=handler.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"handler.js","sourceRoot":"","sources":["../../src/mcp/handler.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAMpD,OAAO,EAAE,8BAA8B,EAAE,MAAM,kBAAkB,CAAA;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAclD;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAA0B;IACzD,MAAM,aAAa,GACjB,OAAO,CAAC,aAAa,IAAI,8BAA8B,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;IACvF,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;IAClE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,aAAa,CAAA;IAC9C,MAAM,GAAG,GAAG,IAAI,OAAO,CAAC,UAAU,GAAG,CAAA;IAErC,OAAO,KAAK,UAAU,SAAS,CAAC,OAAgB;QAC9C,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,YAAY,CAAC,OAAO,CAAC,CAAA;QACtD,IAAI,CAAC,IAAI;YAAE,OAAO,YAAY,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE,GAAG,CAAC,CAAA;QAE9D,IAAI,IAAa,CAAA;QACjB,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,CAAA;QAC7B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,YAAY,CAAC,IAAI,EAAE,oBAAoB,EAAE,aAAa,CAAC,CAAA;QAChE,CAAC;QAED,MAAM,MAAM,GAAG,oBAAoB,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;QACnD,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,OAAO,YAAY,CAAC,IAAI,EAAE,wBAAwB,EAAE,iBAAiB,CAAC,CAAA;QAE3F,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAA;QACvB,MAAM,EAAE,GAAG,GAAG,CAAC,EAAE,IAAI,IAAI,CAAA;QAEzB,IAAI,CAAC;YACH,IAAI,GAAG,CAAC,MAAM,KAAK,YAAY,EAAE,CAAC;gBAChC,OAAO,aAAa,CAAC,EAAE,EAAE;oBACvB,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;wBAClC,IAAI,EAAE,IAAI,CAAC,IAAI;wBACf,WAAW,EAAE,IAAI,CAAC,WAAW;wBAC7B,WAAW,EAAE,eAAe,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;qBAC1E,CAAC,CAAC;iBACJ,CAAC,CAAA;YACJ,CAAC;YAED,IAAI,GAAG,CAAC,MAAM,KAAK,YAAY,EAAE,CAAC;gBAChC,MAAM,MAAM,GAAG,oBAAoB,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;gBACzD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;oBACpB,OAAO,YAAY,CAAC,EAAE,EAAE,uBAAuB,EAAE,gBAAgB,CAAC,CAAA;gBACpE,CAAC;gBAED,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;gBAC9C,IAAI,CAAC,IAAI,EAAE,CAAC;oBACV,OAAO,YAAY,CAAC,EAAE,EAAE,yBAAyB,EAAE,iBAAiB,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAA;gBACzF,CAAC;gBAED,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,CAAA;gBAC3E,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;oBACzB,OAAO,YAAY,CACjB,EAAE,EACF,uBAAuB,EACvB,sBAAsB,WAAW,CAAC,KAAK,CAAC,OAAO,EAAE,CAClD,CAAA;gBACH,CAAC;gBAED,MAAM,UAAU,GAAW;oBACzB,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;oBAC/C,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;oBAC7C,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;oBAC7C,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;iBAChD,CAAA;gBAED,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,EAAE;oBAClD,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,UAAU,EAAE,IAAI,CAAC,UAAU;oBAC3B,MAAM,EAAE,UAAU;iBACnB,CAAC,CAAA;gBAEF,OAAO,aAAa,CAAC,EAAE,EAAE;oBACvB,OAAO,EAAE;wBACP;4BACE,IAAI,EAAE,MAAM;4BACZ,IAAI,EAAE,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;yBACnE;qBACF;iBACF,CAAC,CAAA;YACJ,CAAC;YAED,OAAO,YAAY,CAAC,EAAE,EAAE,yBAAyB,EAAE,qBAAqB,GAAG,CAAC,MAAM,EAAE,CAAC,CAAA;QACvF,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,oBAAoB,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAA;YACtF,OAAO,YAAY,CAAC,EAAE,EAAE,uBAAuB,EAAE,gBAAgB,CAAC,CAAA;QACpE,CAAC;IACH,CAAC,CAAA;AACH,CAAC;AAED,MAAM,oBAAoB,GAAG,CAAC,KAAK,CAAA;AACnC,MAAM,wBAAwB,GAAG,CAAC,KAAK,CAAA;AACvC,MAAM,yBAAyB,GAAG,CAAC,KAAK,CAAA;AACxC,MAAM,uBAAuB,GAAG,CAAC,KAAK,CAAA;AACtC,MAAM,uBAAuB,GAAG,CAAC,KAAK,CAAA;AAEtC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IACpC,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC;IACzB,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE;IAC1D,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;IAClB,MAAM,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;CAC/B,CAAC,CAAA;AAEF,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IACpC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,EAAE;CACxD,CAAC,CAAA;AAEF,SAAS,YAAY,CAAC,IAAa,EAAE,MAAM,GAAG,GAAG;IAC/C,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE;QACxC,MAAM;QACN,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;KAChD,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,aAAa,CAAC,EAA0B,EAAE,MAAe;IAChE,OAAO,YAAY,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,CAAA;AACrD,CAAC;AAED,SAAS,YAAY,CAAC,EAA0B,EAAE,IAAY,EAAE,OAAe;IAC7E,OAAO,YAAY,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,CAAA;AACvE,CAAC"}
1
+ {"version":3,"file":"handler.js","sourceRoot":"","sources":["../../src/mcp/handler.ts"],"names":[],"mappings":"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"}
@@ -2,4 +2,8 @@ export { createMcpHandler } from './handler.js';
2
2
  export type { McpHandlerOptions } from './handler.js';
3
3
  export { McpMetaSchema, withMeta } from './meta.js';
4
4
  export type { McpMeta } from './meta.js';
5
+ export { auditContext } from './audit-context.js';
6
+ export type { AuditContextFields } from './audit-context.js';
7
+ export { toPayloadMcpTool, toPayloadMcpTools } from './payload-mcp.js';
8
+ export type { PayloadMcpRequest, PayloadMcpTool, ToPayloadMcpToolOptions, } from './payload-mcp.js';
5
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAC/C,YAAY,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAErD,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AACnD,YAAY,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAC/C,YAAY,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAErD,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AACnD,YAAY,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAExC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,YAAY,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAE5D,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACtE,YAAY,EACV,iBAAiB,EACjB,cAAc,EACd,uBAAuB,GACxB,MAAM,kBAAkB,CAAA"}
package/dist/mcp/index.js CHANGED
@@ -1,3 +1,5 @@
1
1
  export { createMcpHandler } from './handler.js';
2
2
  export { McpMetaSchema, withMeta } from './meta.js';
3
+ export { auditContext } from './audit-context.js';
4
+ export { toPayloadMcpTool, toPayloadMcpTools } from './payload-mcp.js';
3
5
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAG/C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAG/C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAGnD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAGjD,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA"}
@@ -0,0 +1,66 @@
1
+ import { z } from 'zod';
2
+ import type { Logger, McpToolDefinition } from '@forumone/throughline-plugin-contract';
3
+ /** The tool shape `plugin-mcp` accepts under its `mcp.tools` option. */
4
+ export interface PayloadMcpTool {
5
+ name: string;
6
+ description: string;
7
+ parameters: z.ZodRawShape;
8
+ handler: (args: Record<string, unknown>, req: PayloadMcpRequest, extra: unknown) => Promise<{
9
+ content: Array<{
10
+ type: 'text';
11
+ text: string;
12
+ }>;
13
+ }>;
14
+ }
15
+ /**
16
+ * The half of `PayloadRequest` this adapter reads.
17
+ *
18
+ * Declared structurally rather than imported so `core` keeps `payload` a peer
19
+ * it does not name in a type position — and because `payloadAPI` is a module
20
+ * augmentation `plugin-mcp` adds, which is only present when that package is
21
+ * installed.
22
+ */
23
+ export interface PayloadMcpRequest {
24
+ user?: {
25
+ id?: unknown;
26
+ email?: unknown;
27
+ name?: unknown;
28
+ roles?: unknown;
29
+ groups?: unknown;
30
+ } | null;
31
+ payloadAPI?: string;
32
+ payload?: {
33
+ logger?: Logger;
34
+ };
35
+ }
36
+ export interface ToPayloadMcpToolOptions {
37
+ /**
38
+ * What to record as the calling key's name.
39
+ *
40
+ * `plugin-mcp` authenticates against its own key collection and puts the user
41
+ * on the request, not the key — so unlike this package's handler, the key's
42
+ * own name is not recoverable from the request. Pass one if the audit trail
43
+ * should name something more useful than the strategy.
44
+ */
45
+ apiKeyName?: string;
46
+ logger?: Logger;
47
+ }
48
+ /**
49
+ * Wraps one Throughline tool so Payload's MCP plugin can serve it.
50
+ *
51
+ * Three things are being translated, and none of them is the tool's logic:
52
+ *
53
+ * - **The schema.** `plugin-mcp` wants the raw shape; `withMeta` produces a
54
+ * `z.object` around it. `.shape` is the whole conversion, which is why tools
55
+ * must build their input with `withMeta` or `z.object` rather than an
56
+ * arbitrary `ZodType`.
57
+ * - **The context.** Payload hands the handler a request. Throughline's tools
58
+ * take a `McpToolContext`, so one is built from it.
59
+ * - **The result.** Payload wants MCP content blocks; Throughline's tools
60
+ * return their own objects, exactly as they do through this package's own
61
+ * handler, which does the same wrapping a layer up.
62
+ */
63
+ export declare function toPayloadMcpTool(tool: McpToolDefinition, options?: ToPayloadMcpToolOptions): PayloadMcpTool;
64
+ /** Every tool in a server, in one call. */
65
+ export declare function toPayloadMcpTools(tools: McpToolDefinition[], options?: ToPayloadMcpToolOptions): PayloadMcpTool[];
66
+ //# sourceMappingURL=payload-mcp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payload-mcp.d.ts","sourceRoot":"","sources":["../../src/mcp/payload-mcp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,MAAM,EAAkB,iBAAiB,EAAE,MAAM,uCAAuC,CAAA;AAsBtG,wEAAwE;AACxE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,CAAC,CAAC,WAAW,CAAA;IACzB,OAAO,EAAE,CACP,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,GAAG,EAAE,iBAAiB,EACtB,KAAK,EAAE,OAAO,KACX,OAAO,CAAC;QAAE,OAAO,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC,CAAA;CACjE;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAA;IAClG,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAC9B;AAED,MAAM,WAAW,uBAAuB;IACtC;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,iBAAiB,EACvB,OAAO,GAAE,uBAA4B,GACpC,cAAc,CAYhB;AAED,2CAA2C;AAC3C,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,iBAAiB,EAAE,EAC1B,OAAO,GAAE,uBAA4B,GACpC,cAAc,EAAE,CAElB"}
@@ -0,0 +1,63 @@
1
+ import { z } from 'zod';
2
+ import { defaultLogger } from '../logger/index.js';
3
+ /**
4
+ * Wraps one Throughline tool so Payload's MCP plugin can serve it.
5
+ *
6
+ * Three things are being translated, and none of them is the tool's logic:
7
+ *
8
+ * - **The schema.** `plugin-mcp` wants the raw shape; `withMeta` produces a
9
+ * `z.object` around it. `.shape` is the whole conversion, which is why tools
10
+ * must build their input with `withMeta` or `z.object` rather than an
11
+ * arbitrary `ZodType`.
12
+ * - **The context.** Payload hands the handler a request. Throughline's tools
13
+ * take a `McpToolContext`, so one is built from it.
14
+ * - **The result.** Payload wants MCP content blocks; Throughline's tools
15
+ * return their own objects, exactly as they do through this package's own
16
+ * handler, which does the same wrapping a layer up.
17
+ */
18
+ export function toPayloadMcpTool(tool, options = {}) {
19
+ const shape = shapeOf(tool);
20
+ return {
21
+ name: tool.name,
22
+ description: tool.description,
23
+ parameters: shape,
24
+ handler: async (args, req) => {
25
+ const result = await tool.handler(args, contextFrom(req, options));
26
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
27
+ },
28
+ };
29
+ }
30
+ /** Every tool in a server, in one call. */
31
+ export function toPayloadMcpTools(tools, options = {}) {
32
+ return tools.map(tool => toPayloadMcpTool(tool, options));
33
+ }
34
+ function shapeOf(tool) {
35
+ const schema = tool.inputSchema;
36
+ if (schema instanceof z.ZodObject)
37
+ return schema.shape;
38
+ throw new Error(`Tool "${tool.name}" has an input schema that is not a z.object, so it has no shape to hand ` +
39
+ `Payload's MCP plugin. Build tool inputs with \`withMeta({ … })\` or \`z.object({ … })\`.`);
40
+ }
41
+ function contextFrom(req, options) {
42
+ const user = req.user
43
+ ? {
44
+ id: String(req.user.id ?? ''),
45
+ email: String(req.user.email ?? ''),
46
+ name: String(req.user.name ?? req.user.email ?? ''),
47
+ roles: Array.isArray(req.user.roles) ? req.user.roles : [],
48
+ groups: Array.isArray(req.user.groups) ? req.user.groups : [],
49
+ }
50
+ : null;
51
+ return {
52
+ user,
53
+ /*
54
+ The strategy name is the honest fallback. `plugin-mcp` resolves a key to its
55
+ linked user and does not carry the key document forward, so "which key" is
56
+ not a question the request can answer — and an audit row saying
57
+ `mcp-api-key` is better than one asserting a name nothing checked.
58
+ */
59
+ apiKeyName: options.apiKeyName ?? (req.payloadAPI === 'MCP' ? 'mcp-api-key' : ''),
60
+ logger: options.logger ?? req.payload?.logger ?? defaultLogger,
61
+ };
62
+ }
63
+ //# sourceMappingURL=payload-mcp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payload-mcp.js","sourceRoot":"","sources":["../../src/mcp/payload-mcp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AA4DlD;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,gBAAgB,CAC9B,IAAuB,EACvB,UAAmC,EAAE;IAErC,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAE3B,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,UAAU,EAAE,KAAK;QACjB,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE;YAC3B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAA;YAClE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAA;QAC/E,CAAC;KACF,CAAA;AACH,CAAC;AAED,2CAA2C;AAC3C,MAAM,UAAU,iBAAiB,CAC/B,KAA0B,EAC1B,UAAmC,EAAE;IAErC,OAAO,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAA;AAC3D,CAAC;AAED,SAAS,OAAO,CAAC,IAAuB;IACtC,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,CAAA;IAC/B,IAAI,MAAM,YAAY,CAAC,CAAC,SAAS;QAAE,OAAO,MAAM,CAAC,KAAsB,CAAA;IACvE,MAAM,IAAI,KAAK,CACb,SAAS,IAAI,CAAC,IAAI,2EAA2E;QAC3F,0FAA0F,CAC7F,CAAA;AACH,CAAC;AAED,SAAS,WAAW,CAAC,GAAsB,EAAE,OAAgC;IAC3E,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI;QACnB,CAAC,CAAC;YACE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC;YAC7B,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;YACnC,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;YACnD,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAE,GAAG,CAAC,IAAI,CAAC,KAAkB,CAAC,CAAC,CAAC,EAAE;YACxE,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAE,GAAG,CAAC,IAAI,CAAC,MAAmB,CAAC,CAAC,CAAC,EAAE;SAC5E;QACH,CAAC,CAAC,IAAI,CAAA;IAER,OAAO;QACL,IAAI;QACJ;;;;;UAKE;QACF,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,CAAC,GAAG,CAAC,UAAU,KAAK,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC;QACjF,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,GAAG,CAAC,OAAO,EAAE,MAAM,IAAI,aAAa;KAC/D,CAAA;AACH,CAAC"}
@@ -0,0 +1,31 @@
1
+ export interface DocumentContentHashOptions {
2
+ /**
3
+ * Extra field names to treat as volatile, stripped at every level.
4
+ * For app-specific bookkeeping written on save — a sync timestamp, a
5
+ * cached count — that no approver is looking at.
6
+ */
7
+ exclude?: string[];
8
+ }
9
+ /**
10
+ * Hashes the *content* of a document, ignoring the metadata that moves on
11
+ * every save.
12
+ *
13
+ * This is the binding an approval is tied to. Two documents hash the same
14
+ * when an approver would read the same thing, which means:
15
+ *
16
+ * - a save that changed nothing leaves a granted approval standing, and
17
+ * - a save that changed something invalidates it.
18
+ *
19
+ * That is the rule stated deliberately, rather than inherited from
20
+ * whichever timestamp field happened to be nearby.
21
+ *
22
+ * **Both sides must hash a document fetched the same way.** The approvals
23
+ * request tool and publishing's approval step both use
24
+ * `payload.findByID({ collection, id, draft: true })` at the config's
25
+ * default depth, so they see the same shape. A caller that hashes a
26
+ * document fetched at some other depth gets a hash that matches nothing:
27
+ * a populated relationship and a bare relationship id are not the same
28
+ * value, and no amount of normalising makes them one.
29
+ */
30
+ export declare function documentContentHash(document: Record<string, unknown>, options?: DocumentContentHashOptions): Promise<string>;
31
+ //# sourceMappingURL=content-hash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-hash.d.ts","sourceRoot":"","sources":["../../src/utils/content-hash.ts"],"names":[],"mappings":"AAiBA,MAAM,WAAW,0BAA0B;IACzC;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;CACnB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,MAAM,CAAC,CAIjB"}
@@ -0,0 +1,94 @@
1
+ import { sha256Hex } from '../auth/api-keys.js';
2
+ /**
3
+ * Fields that move without the content moving. Stripped at every level of
4
+ * the document before hashing.
5
+ *
6
+ * `updatedAt` is the reason this module exists: an approval bound to it is
7
+ * invalidated by any save at all, including one that changed nothing, and
8
+ * including every tick of autosave. `createdAt`, `_status`, `__v`,
9
+ * `globalType` and `_id` are storage and publishing machinery rather than
10
+ * anything an approver read.
11
+ *
12
+ * The document's own `id` is stripped separately, at the top level only —
13
+ * see `normalize`.
14
+ */
15
+ const VOLATILE_FIELDS = new Set(['createdAt', 'updatedAt', '_status', '__v', '_id', 'globalType']);
16
+ /**
17
+ * Hashes the *content* of a document, ignoring the metadata that moves on
18
+ * every save.
19
+ *
20
+ * This is the binding an approval is tied to. Two documents hash the same
21
+ * when an approver would read the same thing, which means:
22
+ *
23
+ * - a save that changed nothing leaves a granted approval standing, and
24
+ * - a save that changed something invalidates it.
25
+ *
26
+ * That is the rule stated deliberately, rather than inherited from
27
+ * whichever timestamp field happened to be nearby.
28
+ *
29
+ * **Both sides must hash a document fetched the same way.** The approvals
30
+ * request tool and publishing's approval step both use
31
+ * `payload.findByID({ collection, id, draft: true })` at the config's
32
+ * default depth, so they see the same shape. A caller that hashes a
33
+ * document fetched at some other depth gets a hash that matches nothing:
34
+ * a populated relationship and a bare relationship id are not the same
35
+ * value, and no amount of normalising makes them one.
36
+ */
37
+ export async function documentContentHash(document, options = {}) {
38
+ const volatile = new Set(VOLATILE_FIELDS);
39
+ for (const field of options.exclude ?? [])
40
+ volatile.add(field);
41
+ return sha256Hex(stableStringify(normalize(document, volatile, true)));
42
+ }
43
+ /**
44
+ * Strips volatile fields and normalises values whose representation can
45
+ * vary without their meaning varying.
46
+ *
47
+ * `id` is dropped only at the top level, where it is the document's own id
48
+ * and constant for the life of the document. Nested `id`s are kept, because
49
+ * that is where a populated relationship carries which document it points
50
+ * at — dropping those would make a page that swapped one image for another
51
+ * with the same alt text hash identically to the one an approver read.
52
+ */
53
+ function normalize(value, volatile, topLevel = false) {
54
+ if (value instanceof Date)
55
+ return value.toISOString();
56
+ if (Array.isArray(value))
57
+ return value.map((entry) => normalize(entry, volatile));
58
+ if (value === null || typeof value !== 'object')
59
+ return value;
60
+ const result = {};
61
+ for (const [key, entry] of Object.entries(value)) {
62
+ if (volatile.has(key))
63
+ continue;
64
+ if (topLevel && key === 'id')
65
+ continue;
66
+ // A field the caller never set and one set to `undefined` are the same
67
+ // document. JSON.stringify already drops these; doing it here keeps the
68
+ // sorted key list honest.
69
+ if (entry === undefined)
70
+ continue;
71
+ result[key] = normalize(entry, volatile);
72
+ }
73
+ return result;
74
+ }
75
+ /**
76
+ * `JSON.stringify` with object keys in sorted order, so that two documents
77
+ * differing only in key order hash the same. Payload reads blocks back out
78
+ * of JSONB, and neither Postgres nor the JSON parser promises to hand them
79
+ * back in the order they went in.
80
+ *
81
+ * Array order is meaningful — it is the order of the blocks on the page —
82
+ * and is preserved.
83
+ */
84
+ function stableStringify(value) {
85
+ if (Array.isArray(value)) {
86
+ return `[${value.map(stableStringify).join(',')}]`;
87
+ }
88
+ if (value !== null && typeof value === 'object') {
89
+ const entries = Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
90
+ return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`).join(',')}}`;
91
+ }
92
+ return JSON.stringify(value) ?? 'null';
93
+ }
94
+ //# sourceMappingURL=content-hash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-hash.js","sourceRoot":"","sources":["../../src/utils/content-hash.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAE/C;;;;;;;;;;;;GAYG;AACH,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC,CAAA;AAWlG;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,QAAiC,EACjC,UAAsC,EAAE;IAExC,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,eAAe,CAAC,CAAA;IACzC,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,OAAO,IAAI,EAAE;QAAE,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;IAC9D,OAAO,SAAS,CAAC,eAAe,CAAC,SAAS,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,CAAA;AACxE,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,SAAS,CAAC,KAAc,EAAE,QAAqB,EAAE,QAAQ,GAAG,KAAK;IACxE,IAAI,KAAK,YAAY,IAAI;QAAE,OAAO,KAAK,CAAC,WAAW,EAAE,CAAA;IACrD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAA;IACjF,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IAE7D,MAAM,MAAM,GAA4B,EAAE,CAAA;IAC1C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC,EAAE,CAAC;QAC5E,IAAI,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,SAAQ;QAC/B,IAAI,QAAQ,IAAI,GAAG,KAAK,IAAI;YAAE,SAAQ;QACtC,uEAAuE;QACvE,wEAAwE;QACxE,0BAA0B;QAC1B,IAAI,KAAK,KAAK,SAAS;YAAE,SAAQ;QACjC,MAAM,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAA;IAC1C,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,eAAe,CAAC,KAAc;IACrC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,IAAI,KAAK,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAA;IACpD,CAAC;IACD,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAChD,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CACjF,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAC3B,CAAA;QACD,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAA;IAC/F,CAAC;IACD,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,MAAM,CAAA;AACxC,CAAC"}
@@ -1,3 +1,3 @@
1
- export { shallowDiff } from './diff.js';
2
- export { generateId } from './id.js';
1
+ export { documentContentHash } from './content-hash.js';
2
+ export type { DocumentContentHashOptions } from './content-hash.js';
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AACvD,YAAY,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAA"}
@@ -1,3 +1,2 @@
1
- export { shallowDiff } from './diff.js';
2
- export { generateId } from './id.js';
1
+ export { documentContentHash } from './content-hash.js';
3
2
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@forumone/throughline-core",
3
- "version": "0.2.2",
4
- "description": "Core plumbing for Throughline: audit log, MCP authentication and handler, event taxonomy + Inngest client factory, env handling, logger, shared utilities.",
3
+ "version": "0.4.0",
4
+ "description": "Core plumbing for Throughline: audit log, MCP authentication and handler, event taxonomy + Inngest client factory, logger, shared utilities.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -25,10 +25,6 @@
25
25
  "./mcp": {
26
26
  "types": "./dist/mcp/index.d.ts",
27
27
  "default": "./dist/mcp/index.js"
28
- },
29
- "./env": {
30
- "types": "./dist/env/index.d.ts",
31
- "default": "./dist/env/index.js"
32
28
  }
33
29
  },
34
30
  "files": [
@@ -62,7 +58,7 @@
62
58
  "dependencies": {
63
59
  "zod": "^3.23.0",
64
60
  "zod-to-json-schema": "^3.25.2",
65
- "@forumone/throughline-plugin-contract": "0.2.1"
61
+ "@forumone/throughline-plugin-contract": "0.3.0"
66
62
  },
67
63
  "devDependencies": {
68
64
  "@types/node": "^20.17.0",
@@ -1,51 +0,0 @@
1
- import { z } from 'zod';
2
- /**
3
- * Canonical env-var names used across Throughline packages. Plugins read
4
- * `process.env[ENV_VARS.X]` rather than hard-coding strings so renames stay
5
- * coordinated.
6
- */
7
- export declare const ENV_VARS: {
8
- readonly PAYLOAD_SECRET: "PAYLOAD_SECRET";
9
- readonly DATABASE_URI: "DATABASE_URI";
10
- readonly NEXT_PUBLIC_SERVER_URL: "NEXT_PUBLIC_SERVER_URL";
11
- readonly INNGEST_EVENT_KEY: "INNGEST_EVENT_KEY";
12
- readonly INNGEST_SIGNING_KEY: "INNGEST_SIGNING_KEY";
13
- readonly RESEND_API_KEY: "RESEND_API_KEY";
14
- readonly EMAIL_FROM_ADDRESS: "EMAIL_FROM_ADDRESS";
15
- readonly EMAIL_FROM_NAME: "EMAIL_FROM_NAME";
16
- readonly EMAIL_REPLY_TO: "EMAIL_REPLY_TO";
17
- readonly APPROVAL_TOKEN_SECRET: "APPROVAL_TOKEN_SECRET";
18
- readonly COMPONENT_SERVER_API_KEY: "COMPONENT_SERVER_API_KEY";
19
- readonly PUBLISHING_SERVER_API_KEY: "PUBLISHING_SERVER_API_KEY";
20
- readonly APPROVALS_SERVER_API_KEY: "APPROVALS_SERVER_API_KEY";
21
- readonly AUDIT_SERVER_API_KEY: "AUDIT_SERVER_API_KEY";
22
- readonly FORMS_SERVER_API_KEY: "FORMS_SERVER_API_KEY";
23
- readonly INTEGRATIONS_SERVER_API_KEY: "INTEGRATIONS_SERVER_API_KEY";
24
- };
25
- export type EnvVarName = (typeof ENV_VARS)[keyof typeof ENV_VARS];
26
- declare const BaseEnvSchema: z.ZodObject<{
27
- PAYLOAD_SECRET: z.ZodString;
28
- DATABASE_URI: z.ZodString;
29
- NEXT_PUBLIC_SERVER_URL: z.ZodString;
30
- }, "strip", z.ZodTypeAny, {
31
- PAYLOAD_SECRET: string;
32
- DATABASE_URI: string;
33
- NEXT_PUBLIC_SERVER_URL: string;
34
- }, {
35
- PAYLOAD_SECRET: string;
36
- DATABASE_URI: string;
37
- NEXT_PUBLIC_SERVER_URL: string;
38
- }>;
39
- export type BaseEnv = z.infer<typeof BaseEnvSchema>;
40
- /**
41
- * Validates the base env vars every Throughline deployment requires.
42
- * Plugins extend with their own checks. Throws on validation failure with
43
- * a multi-line, path-qualified message.
44
- */
45
- export declare function validateBaseEnv(env?: NodeJS.ProcessEnv): BaseEnv;
46
- /** Returns an env var, throwing with a clear error if missing or empty. */
47
- export declare function requireEnv(name: string, message?: string): string;
48
- /** Returns an optional env var or a fallback. */
49
- export declare function optionalEnv(name: string, fallback?: string): string | undefined;
50
- export {};
51
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/env/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB;;;;GAIG;AACH,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;CAiBX,CAAA;AAEV,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,QAAQ,CAAC,CAAC,MAAM,OAAO,QAAQ,CAAC,CAAA;AAEjE,QAAA,MAAM,aAAa;;;;;;;;;;;;EAMjB,CAAA;AAEF,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAA;AAEnD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAS7E;AAED,2EAA2E;AAC3E,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAMjE;AAED,iDAAiD;AACjD,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE/E"}
package/dist/env/index.js DELETED
@@ -1,59 +0,0 @@
1
- import { z } from 'zod';
2
- /**
3
- * Canonical env-var names used across Throughline packages. Plugins read
4
- * `process.env[ENV_VARS.X]` rather than hard-coding strings so renames stay
5
- * coordinated.
6
- */
7
- export const ENV_VARS = {
8
- PAYLOAD_SECRET: 'PAYLOAD_SECRET',
9
- DATABASE_URI: 'DATABASE_URI',
10
- NEXT_PUBLIC_SERVER_URL: 'NEXT_PUBLIC_SERVER_URL',
11
- INNGEST_EVENT_KEY: 'INNGEST_EVENT_KEY',
12
- INNGEST_SIGNING_KEY: 'INNGEST_SIGNING_KEY',
13
- RESEND_API_KEY: 'RESEND_API_KEY',
14
- EMAIL_FROM_ADDRESS: 'EMAIL_FROM_ADDRESS',
15
- EMAIL_FROM_NAME: 'EMAIL_FROM_NAME',
16
- EMAIL_REPLY_TO: 'EMAIL_REPLY_TO',
17
- APPROVAL_TOKEN_SECRET: 'APPROVAL_TOKEN_SECRET',
18
- COMPONENT_SERVER_API_KEY: 'COMPONENT_SERVER_API_KEY',
19
- PUBLISHING_SERVER_API_KEY: 'PUBLISHING_SERVER_API_KEY',
20
- APPROVALS_SERVER_API_KEY: 'APPROVALS_SERVER_API_KEY',
21
- AUDIT_SERVER_API_KEY: 'AUDIT_SERVER_API_KEY',
22
- FORMS_SERVER_API_KEY: 'FORMS_SERVER_API_KEY',
23
- INTEGRATIONS_SERVER_API_KEY: 'INTEGRATIONS_SERVER_API_KEY',
24
- };
25
- const BaseEnvSchema = z.object({
26
- [ENV_VARS.PAYLOAD_SECRET]: z
27
- .string()
28
- .min(32, `${ENV_VARS.PAYLOAD_SECRET} must be at least 32 characters`),
29
- [ENV_VARS.DATABASE_URI]: z.string().min(1, `${ENV_VARS.DATABASE_URI} must be set`),
30
- [ENV_VARS.NEXT_PUBLIC_SERVER_URL]: z.string().url(),
31
- });
32
- /**
33
- * Validates the base env vars every Throughline deployment requires.
34
- * Plugins extend with their own checks. Throws on validation failure with
35
- * a multi-line, path-qualified message.
36
- */
37
- export function validateBaseEnv(env = process.env) {
38
- const result = BaseEnvSchema.safeParse(env);
39
- if (!result.success) {
40
- const issues = result.error.issues
41
- .map((i) => ` - ${String(i.path[0] ?? '(root)')}: ${i.message}`)
42
- .join('\n');
43
- throw new Error(`Invalid environment:\n${issues}`);
44
- }
45
- return result.data;
46
- }
47
- /** Returns an env var, throwing with a clear error if missing or empty. */
48
- export function requireEnv(name, message) {
49
- const value = process.env[name];
50
- if (!value) {
51
- throw new Error(message ?? `Environment variable ${name} is required but not set`);
52
- }
53
- return value;
54
- }
55
- /** Returns an optional env var or a fallback. */
56
- export function optionalEnv(name, fallback) {
57
- return process.env[name] ?? fallback;
58
- }
59
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/env/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB;;;;GAIG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,cAAc,EAAE,gBAAgB;IAChC,YAAY,EAAE,cAAc;IAC5B,sBAAsB,EAAE,wBAAwB;IAChD,iBAAiB,EAAE,mBAAmB;IACtC,mBAAmB,EAAE,qBAAqB;IAC1C,cAAc,EAAE,gBAAgB;IAChC,kBAAkB,EAAE,oBAAoB;IACxC,eAAe,EAAE,iBAAiB;IAClC,cAAc,EAAE,gBAAgB;IAChC,qBAAqB,EAAE,uBAAuB;IAC9C,wBAAwB,EAAE,0BAA0B;IACpD,yBAAyB,EAAE,2BAA2B;IACtD,wBAAwB,EAAE,0BAA0B;IACpD,oBAAoB,EAAE,sBAAsB;IAC5C,oBAAoB,EAAE,sBAAsB;IAC5C,2BAA2B,EAAE,6BAA6B;CAClD,CAAA;AAIV,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC;IAC7B,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;SACzB,MAAM,EAAE;SACR,GAAG,CAAC,EAAE,EAAE,GAAG,QAAQ,CAAC,cAAc,iCAAiC,CAAC;IACvE,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,QAAQ,CAAC,YAAY,cAAc,CAAC;IAClF,CAAC,QAAQ,CAAC,sBAAsB,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;CACpD,CAAC,CAAA;AAIF;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,MAAyB,OAAO,CAAC,GAAG;IAClE,MAAM,MAAM,GAAG,aAAa,CAAC,SAAS,CAAC,GAAG,CAAC,CAAA;IAC3C,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM;aAC/B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;aAChE,IAAI,CAAC,IAAI,CAAC,CAAA;QACb,MAAM,IAAI,KAAK,CAAC,yBAAyB,MAAM,EAAE,CAAC,CAAA;IACpD,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAA;AACpB,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,OAAgB;IACvD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IAC/B,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,OAAO,IAAI,wBAAwB,IAAI,0BAA0B,CAAC,CAAA;IACpF,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,QAAiB;IACzD,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAA;AACtC,CAAC"}
@@ -1,13 +0,0 @@
1
- /**
2
- * Computes a shallow diff between two records. Returns an entry per
3
- * field whose value changed, with `before` and `after` values.
4
- *
5
- * Field equality is determined by JSON-string equality for objects and
6
- * arrays, and `===` for primitives. That is sufficient for audit-log
7
- * payloads where structures are JSON-shaped to begin with.
8
- */
9
- export declare function shallowDiff<T extends Record<string, unknown>>(before: T, after: T): Record<string, {
10
- before: unknown;
11
- after: unknown;
12
- }>;
13
- //# sourceMappingURL=diff.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../src/utils/diff.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC3D,MAAM,EAAE,CAAC,EACT,KAAK,EAAE,CAAC,GACP,MAAM,CAAC,MAAM,EAAE;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC,CASrD"}
@@ -1,31 +0,0 @@
1
- /**
2
- * Computes a shallow diff between two records. Returns an entry per
3
- * field whose value changed, with `before` and `after` values.
4
- *
5
- * Field equality is determined by JSON-string equality for objects and
6
- * arrays, and `===` for primitives. That is sufficient for audit-log
7
- * payloads where structures are JSON-shaped to begin with.
8
- */
9
- export function shallowDiff(before, after) {
10
- const diff = {};
11
- const keys = new Set([...Object.keys(before), ...Object.keys(after)]);
12
- for (const key of keys) {
13
- if (!isEqual(before[key], after[key])) {
14
- diff[key] = { before: before[key], after: after[key] };
15
- }
16
- }
17
- return diff;
18
- }
19
- function isEqual(a, b) {
20
- if (Object.is(a, b))
21
- return true;
22
- if (a === null || b === null || a === undefined || b === undefined)
23
- return false;
24
- if (typeof a !== typeof b)
25
- return false;
26
- if (typeof a === 'object') {
27
- return JSON.stringify(a) === JSON.stringify(b);
28
- }
29
- return false;
30
- }
31
- //# sourceMappingURL=diff.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/utils/diff.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CACzB,MAAS,EACT,KAAQ;IAER,MAAM,IAAI,GAAwD,EAAE,CAAA;IACpE,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;IACrE,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YACtC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,CAAA;QACxD,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED,SAAS,OAAO,CAAC,CAAU,EAAE,CAAU;IACrC,IAAI,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;QAAE,OAAO,IAAI,CAAA;IAChC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,SAAS;QAAE,OAAO,KAAK,CAAA;IAChF,IAAI,OAAO,CAAC,KAAK,OAAO,CAAC;QAAE,OAAO,KAAK,CAAA;IACvC,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;QAC1B,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAA;IAChD,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC"}
@@ -1,6 +0,0 @@
1
- /**
2
- * Generates a short, URL-safe, hex-encoded random ID. Optionally prefixed
3
- * with a token category (e.g. `evt_a1b2c3...`).
4
- */
5
- export declare function generateId(prefix?: string): string;
6
- //# sourceMappingURL=id.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"id.d.ts","sourceRoot":"","sources":["../../src/utils/id.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,UAAU,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAOlD"}
package/dist/utils/id.js DELETED
@@ -1,13 +0,0 @@
1
- /**
2
- * Generates a short, URL-safe, hex-encoded random ID. Optionally prefixed
3
- * with a token category (e.g. `evt_a1b2c3...`).
4
- */
5
- export function generateId(prefix) {
6
- const bytes = new Uint8Array(12);
7
- crypto.getRandomValues(bytes);
8
- const id = Array.from(bytes)
9
- .map((b) => b.toString(16).padStart(2, '0'))
10
- .join('');
11
- return prefix ? `${prefix}_${id}` : id;
12
- }
13
- //# sourceMappingURL=id.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"id.js","sourceRoot":"","sources":["../../src/utils/id.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,MAAe;IACxC,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,EAAE,CAAC,CAAA;IAChC,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC,CAAA;IAC7B,MAAM,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;SACzB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;SAC3C,IAAI,CAAC,EAAE,CAAC,CAAA;IACX,OAAO,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;AACxC,CAAC"}