@tappify/extension-sdk 2.0.0 → 2.1.0-beta.2

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 (148) hide show
  1. package/README.md +208 -0
  2. package/dist/chunk-2CY5HVY7.mjs +59 -0
  3. package/dist/chunk-2CY5HVY7.mjs.map +1 -0
  4. package/dist/{chunk-2DBSVKAU.cjs → chunk-6HETNZJY.cjs} +1 -1
  5. package/dist/chunk-6HETNZJY.cjs.map +1 -0
  6. package/dist/{chunk-PYJMJF44.cjs → chunk-7YKH4LZN.cjs} +9 -2
  7. package/dist/chunk-7YKH4LZN.cjs.map +1 -0
  8. package/dist/{chunk-RMUWRESK.cjs → chunk-A74HCO6T.cjs} +31 -18
  9. package/dist/chunk-A74HCO6T.cjs.map +1 -0
  10. package/dist/chunk-BPCGXXMD.cjs +781 -0
  11. package/dist/chunk-BPCGXXMD.cjs.map +1 -0
  12. package/dist/chunk-EB5XWQAU.cjs +132 -0
  13. package/dist/chunk-EB5XWQAU.cjs.map +1 -0
  14. package/dist/{chunk-5AAPD5WV.cjs → chunk-GAUZYBXE.cjs} +11 -11
  15. package/dist/chunk-GAUZYBXE.cjs.map +1 -0
  16. package/dist/chunk-J5LGTIGS.mjs +10 -0
  17. package/dist/chunk-J5LGTIGS.mjs.map +1 -0
  18. package/dist/{chunk-ZT35X7KL.mjs → chunk-KZ5NGFUP.mjs} +22 -9
  19. package/dist/chunk-KZ5NGFUP.mjs.map +1 -0
  20. package/dist/chunk-NN263PGQ.cjs +70 -0
  21. package/dist/chunk-NN263PGQ.cjs.map +1 -0
  22. package/dist/{chunk-3YWVYA42.cjs → chunk-O357OJXL.cjs} +14 -18
  23. package/dist/chunk-O357OJXL.cjs.map +1 -0
  24. package/dist/chunk-ONJMPO3P.cjs +59 -0
  25. package/dist/chunk-ONJMPO3P.cjs.map +1 -0
  26. package/dist/{chunk-OPPYEA7F.cjs → chunk-PRCZMOLD.cjs} +3 -3
  27. package/dist/chunk-PRCZMOLD.cjs.map +1 -0
  28. package/dist/chunk-Q7SFCCGT.cjs +10 -0
  29. package/dist/chunk-Q7SFCCGT.cjs.map +1 -0
  30. package/dist/{chunk-QHM7XNRK.mjs → chunk-QA5AL3VZ.mjs} +2 -2
  31. package/dist/{chunk-QHM7XNRK.mjs.map → chunk-QA5AL3VZ.mjs.map} +1 -1
  32. package/dist/chunk-QJMZRV7A.mjs +132 -0
  33. package/dist/chunk-QJMZRV7A.mjs.map +1 -0
  34. package/dist/{chunk-CSTYBKZC.mjs → chunk-QXD7WVKK.mjs} +1 -1
  35. package/dist/chunk-QXD7WVKK.mjs.map +1 -0
  36. package/dist/{chunk-4C5Z3CIK.mjs → chunk-RBIQT75L.mjs} +2 -2
  37. package/dist/chunk-RBIQT75L.mjs.map +1 -0
  38. package/dist/{chunk-MNDVYPEP.mjs → chunk-SGCYEEXD.mjs} +9 -2
  39. package/dist/chunk-SGCYEEXD.mjs.map +1 -0
  40. package/dist/{chunk-BDCX6LRQ.mjs → chunk-SGEYDV3K.mjs} +3 -3
  41. package/dist/chunk-SGEYDV3K.mjs.map +1 -0
  42. package/dist/{chunk-IGK2AP6J.mjs → chunk-SLGOHIBL.mjs} +13 -17
  43. package/dist/chunk-SLGOHIBL.mjs.map +1 -0
  44. package/dist/chunk-SWG7CNTZ.mjs +781 -0
  45. package/dist/chunk-SWG7CNTZ.mjs.map +1 -0
  46. package/dist/{chunk-7KIKOH5H.cjs → chunk-VV7ELNRW.cjs} +3 -3
  47. package/dist/chunk-VV7ELNRW.cjs.map +1 -0
  48. package/dist/chunk-Z43DPNZ6.mjs +70 -0
  49. package/dist/chunk-Z43DPNZ6.mjs.map +1 -0
  50. package/dist/client-BO3kavn0.d.ts +25 -0
  51. package/dist/client-DE-vMgdO.d.cts +25 -0
  52. package/dist/codegen.cjs.map +1 -1
  53. package/dist/codegen.d.cts +43 -7
  54. package/dist/codegen.d.ts +43 -7
  55. package/dist/codegen.mjs.map +1 -1
  56. package/dist/{constants-DJ_KgZpN.d.cts → constants-Bh759FUI.d.cts} +44 -8
  57. package/dist/{constants-DJ_KgZpN.d.ts → constants-Bh759FUI.d.ts} +44 -8
  58. package/dist/{context-Dt6xh_5t.d.cts → context-DI0EJCRD.d.cts} +5 -4
  59. package/dist/{context-Ml7k_V14.d.ts → context-mr8R5CNt.d.ts} +5 -4
  60. package/dist/{errors-COsBh7kd.d.cts → errors-Cqh0wrqt.d.cts} +3 -2
  61. package/dist/{errors-COsBh7kd.d.ts → errors-Cqh0wrqt.d.ts} +3 -2
  62. package/dist/host.cjs +5 -4
  63. package/dist/host.cjs.map +1 -1
  64. package/dist/host.d.cts +5 -5
  65. package/dist/host.d.ts +5 -5
  66. package/dist/host.mjs +4 -3
  67. package/dist/host.mjs.map +1 -1
  68. package/dist/index.cjs +9 -656
  69. package/dist/index.cjs.map +1 -1
  70. package/dist/index.d.cts +69 -627
  71. package/dist/index.d.ts +69 -627
  72. package/dist/index.mjs +5 -652
  73. package/dist/index.mjs.map +1 -1
  74. package/dist/manifest.cjs +142 -86
  75. package/dist/manifest.cjs.map +1 -1
  76. package/dist/manifest.d.cts +97 -12
  77. package/dist/manifest.d.ts +97 -12
  78. package/dist/manifest.mjs +129 -73
  79. package/dist/manifest.mjs.map +1 -1
  80. package/dist/server.cjs +15 -66
  81. package/dist/server.cjs.map +1 -1
  82. package/dist/server.d.cts +6 -6
  83. package/dist/server.d.ts +6 -6
  84. package/dist/server.mjs +10 -61
  85. package/dist/server.mjs.map +1 -1
  86. package/dist/testing/index.cjs +73 -750
  87. package/dist/testing/index.cjs.map +1 -1
  88. package/dist/testing/index.d.cts +30 -11
  89. package/dist/testing/index.d.ts +30 -11
  90. package/dist/testing/index.mjs +89 -766
  91. package/dist/testing/index.mjs.map +1 -1
  92. package/dist/testing/jest.cjs +6 -5
  93. package/dist/testing/jest.cjs.map +1 -1
  94. package/dist/testing/jest.mjs +3 -2
  95. package/dist/testing/jest.mjs.map +1 -1
  96. package/dist/testing/vitest.cjs +6 -5
  97. package/dist/testing/vitest.cjs.map +1 -1
  98. package/dist/testing/vitest.d.cts +10 -0
  99. package/dist/testing/vitest.d.ts +10 -0
  100. package/dist/testing/vitest.mjs +3 -2
  101. package/dist/testing/vitest.mjs.map +1 -1
  102. package/dist/{types-DDAGLpov.d.ts → types-BdDaLnqs.d.cts} +4 -3
  103. package/dist/{types-DrpQSv_2.d.cts → types-uGHrRaOj.d.ts} +4 -3
  104. package/dist/ui/styles.css +740 -0
  105. package/dist/ui.cjs +1018 -0
  106. package/dist/ui.cjs.map +1 -0
  107. package/dist/ui.d.cts +231 -0
  108. package/dist/ui.d.ts +231 -0
  109. package/dist/ui.mjs +1018 -0
  110. package/dist/ui.mjs.map +1 -0
  111. package/dist/vite-preview-bootstrap.cjs +66 -0
  112. package/dist/vite-preview-bootstrap.cjs.map +1 -0
  113. package/dist/vite-preview-bootstrap.d.cts +15 -0
  114. package/dist/vite-preview-bootstrap.d.ts +15 -0
  115. package/dist/vite-preview-bootstrap.mjs +66 -0
  116. package/dist/vite-preview-bootstrap.mjs.map +1 -0
  117. package/dist/vite-preview.cjs +876 -0
  118. package/dist/vite-preview.cjs.map +1 -0
  119. package/dist/vite-preview.d.cts +9 -0
  120. package/dist/vite-preview.d.ts +9 -0
  121. package/dist/vite-preview.mjs +876 -0
  122. package/dist/vite-preview.mjs.map +1 -0
  123. package/dist/vite.cjs +320 -60
  124. package/dist/vite.cjs.map +1 -1
  125. package/dist/vite.d.cts +54 -8
  126. package/dist/vite.d.ts +54 -8
  127. package/dist/vite.mjs +312 -54
  128. package/dist/vite.mjs.map +1 -1
  129. package/dist/work-events-BlOVpK0k.d.ts +547 -0
  130. package/dist/work-events-D97EoqvS.d.cts +547 -0
  131. package/package.json +19 -2
  132. package/schema/extension-v2.json +440 -121
  133. package/dist/chunk-2DBSVKAU.cjs.map +0 -1
  134. package/dist/chunk-3YWVYA42.cjs.map +0 -1
  135. package/dist/chunk-4C5Z3CIK.mjs.map +0 -1
  136. package/dist/chunk-5AAPD5WV.cjs.map +0 -1
  137. package/dist/chunk-7KIKOH5H.cjs.map +0 -1
  138. package/dist/chunk-BDCX6LRQ.mjs.map +0 -1
  139. package/dist/chunk-CSTYBKZC.mjs.map +0 -1
  140. package/dist/chunk-IGK2AP6J.mjs.map +0 -1
  141. package/dist/chunk-MNDVYPEP.mjs.map +0 -1
  142. package/dist/chunk-OPPYEA7F.cjs.map +0 -1
  143. package/dist/chunk-PYJMJF44.cjs.map +0 -1
  144. package/dist/chunk-RMUWRESK.cjs.map +0 -1
  145. package/dist/chunk-ZT35X7KL.mjs.map +0 -1
  146. package/dist/handler-CZIlZ5vu.d.ts +0 -227
  147. package/dist/handler-kU9Jf36K.d.cts +0 -227
  148. package/dist/styles.css +0 -367
package/README.md ADDED
@@ -0,0 +1,208 @@
1
+ # Tappify Extension SDK
2
+
3
+ Build extensions that run inside Tappify. The SDK provides the React host
4
+ bridge, UI components, manifest contracts, Vite integration, server handlers,
5
+ code generation, and test utilities used by Tappify extensions.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pnpm add @tappify/extension-sdk
11
+ ```
12
+
13
+ New extensions should use the Tappify CLI, which creates a complete project
14
+ with a manifest, contribution examples, schemas, a server, and tests:
15
+
16
+ ```bash
17
+ npm install --global @tappify/cli
18
+ tappify extension init my-extension
19
+ cd my-extension
20
+ pnpm dev
21
+ ```
22
+
23
+ The SDK requires Node.js 20 or newer and React 19.
24
+
25
+ ## Package entry points
26
+
27
+ | Import | Purpose |
28
+ | --------------------------------------- | ---------------------------------------------------------------------- |
29
+ | `@tappify/extension-sdk` | React hooks, host types, and query helpers |
30
+ | `@tappify/extension-sdk/ui` | One UI components and layout primitives |
31
+ | `@tappify/extension-sdk/manifest` | Manifest schema, validation, registry constants, and manifest types |
32
+ | `@tappify/extension-sdk/server` | Request handler, token verification, events, and Node/Express adapters |
33
+ | `@tappify/extension-sdk/vite` | Vite and Module Federation configuration |
34
+ | `@tappify/extension-sdk/testing` | Host mocks, fixtures, rendering helpers, and validators |
35
+ | `@tappify/extension-sdk/testing/vitest` | Vitest matcher setup |
36
+ | `@tappify/extension-sdk/testing/jest` | Jest matcher setup |
37
+ | `@tappify/extension-sdk/codegen` | Manifest-driven TypeScript generation |
38
+ | `@tappify/extension-sdk/ui/styles.css` | One UI component styles |
39
+
40
+ ## Build a contribution
41
+
42
+ Declare each contribution in `tappify.extension.json`, then export the React
43
+ component from its configured entry file. Host data and capabilities are
44
+ available only when the manifest declares the corresponding scope.
45
+
46
+ ```tsx
47
+ import {
48
+ useTapFilters,
49
+ useTapQuery,
50
+ type TapWidgetProps,
51
+ } from '@tappify/extension-sdk';
52
+ import {
53
+ Card,
54
+ CardContent,
55
+ CardHeader,
56
+ CardTitle,
57
+ Metric,
58
+ Skeleton,
59
+ } from '@tappify/extension-sdk/ui';
60
+ import '@tappify/extension-sdk/ui/styles.css';
61
+
62
+ export default function DownloadsWidget(_props: TapWidgetProps) {
63
+ const filters = useTapFilters();
64
+ const downloads = useTapQuery({
65
+ kind: 'series',
66
+ metric: 'downloads',
67
+ range: filters.range,
68
+ });
69
+
70
+ return (
71
+ <Card>
72
+ <CardHeader>
73
+ <CardTitle>Downloads</CardTitle>
74
+ </CardHeader>
75
+ <CardContent>
76
+ {downloads.isLoading && <Skeleton lines={1} />}
77
+ {downloads.data && (
78
+ <Metric
79
+ label="Total"
80
+ value={downloads.data.points.reduce(
81
+ (total, point) => total + point.value,
82
+ 0,
83
+ )}
84
+ />
85
+ )}
86
+ </CardContent>
87
+ </Card>
88
+ );
89
+ }
90
+ ```
91
+
92
+ ## Migrating UI imports
93
+
94
+ Version 2 replaces the legacy UI surface with One UI. Import frontend components
95
+ from `@tappify/extension-sdk/ui` and load
96
+ `@tappify/extension-sdk/ui/styles.css`. The package root remains the entry for
97
+ hooks, host types, and query helpers.
98
+
99
+ ```tsx
100
+ // Before
101
+ import { TapButton, TapCard } from '@tappify/extension-sdk';
102
+ import '@tappify/extension-sdk/styles.css';
103
+
104
+ // After
105
+ import { Button, Card } from '@tappify/extension-sdk/ui';
106
+ import '@tappify/extension-sdk/ui/styles.css';
107
+ ```
108
+
109
+ Removed names map as follows: `TapButton` → `Button`, `TapCard` → the compound
110
+ `Card` parts, `TapPageHeader` → `PageHeader`, `TapSkeleton` → `Skeleton`,
111
+ `TapEmptyState` → `EmptyState`, `TapErrorState` → `ErrorState`, `TapStat` →
112
+ `Metric`, `TapInput` → `Field` with `Input` or `Textarea`, `TapSelect` → `Field`
113
+ with `Select`, `TapTable` → the compound `Table` parts, `TapDialog` → the
114
+ compound `Dialog` parts, `TapForm` → `SchemaForm`, and `formFields` →
115
+ `schemaFields`. No compatibility aliases are retained.
116
+
117
+ Add the SDK plugin to the extension's Vite config:
118
+
119
+ ```ts
120
+ import { tappifyExtension } from '@tappify/extension-sdk/vite';
121
+ import react from '@vitejs/plugin-react';
122
+ import { defineConfig } from 'vite';
123
+
124
+ export default defineConfig({
125
+ plugins: [react(), tappifyExtension()],
126
+ build: { target: 'esnext' },
127
+ });
128
+ ```
129
+
130
+ ## Add a server procedure
131
+
132
+ The server entry point exposes a fetch-style handler and adapters for common
133
+ Node runtimes. Procedure inputs, credentials, and responses are typed from the
134
+ extension manifest and its referenced JSON Schemas.
135
+
136
+ ```ts
137
+ import {
138
+ createTappifyHandler,
139
+ type TappifyHandlerOptions,
140
+ } from '@tappify/extension-sdk/server';
141
+
142
+ const options: TappifyHandlerOptions = {
143
+ extensionId: 'my-extension',
144
+ health: () => ({ ok: true, version: '1.0.0' }),
145
+ procedures: {
146
+ getSummary: request => ({ projectId: request.context.projectId }),
147
+ },
148
+ };
149
+
150
+ export const handler = createTappifyHandler(options);
151
+ ```
152
+
153
+ ## Test an extension
154
+
155
+ `renderWithTap` renders a contribution inside the same context contract the
156
+ host provides. It returns a mock for inspecting storage, telemetry, navigation,
157
+ toasts, events, and server calls.
158
+
159
+ ```tsx
160
+ import { renderWithTap } from '@tappify/extension-sdk/testing';
161
+ import { screen } from '@testing-library/react';
162
+ import { expect, it } from 'vitest';
163
+ import DownloadsWidget from './DownloadsWidget';
164
+
165
+ it('renders host data', async () => {
166
+ renderWithTap(<DownloadsWidget config={{}} />, {
167
+ scopes: ['ui:render', 'analytics:read'],
168
+ });
169
+
170
+ expect(await screen.findByText('Downloads')).toBeInTheDocument();
171
+ });
172
+ ```
173
+
174
+ Register the SDK matchers from the appropriate test setup entry point:
175
+
176
+ ```ts
177
+ // Vitest setup file
178
+ import '@tappify/extension-sdk/testing/vitest';
179
+ ```
180
+
181
+ ## Compatibility and releases
182
+
183
+ The `sdk` field in `tappify.extension.json` is the compatibility contract
184
+ between an extension and the Tappify host. Use a caret range within the major
185
+ version used to build the extension:
186
+
187
+ ```json
188
+ {
189
+ "sdk": "^2.0.0"
190
+ }
191
+ ```
192
+
193
+ Releases follow semantic versioning. Conventional commits drive
194
+ `semantic-release`: `fix` creates a patch, `feat` creates a minor, and a
195
+ `BREAKING CHANGE` creates a major release. The `main` branch publishes stable
196
+ versions and `develop` publishes the `beta` channel.
197
+
198
+ ## Documentation
199
+
200
+ - [Build your first extension](https://docs.tappify.ai/extensions/start/your-first-extension)
201
+ - [Manifest reference](https://docs.tappify.ai/extensions/reference/manifest)
202
+ - [Frontend API](https://docs.tappify.ai/extensions/reference/frontend-api)
203
+ - [Server API](https://docs.tappify.ai/extensions/reference/server-api)
204
+ - [Testing API](https://docs.tappify.ai/extensions/reference/testing-api)
205
+
206
+ ## License
207
+
208
+ MIT
@@ -0,0 +1,59 @@
1
+ import {
2
+ dataQueryKey,
3
+ installPrefix,
4
+ procedurePrefix,
5
+ procedureQueryKey,
6
+ storageQueryKey,
7
+ useTapAuth,
8
+ useTapContext,
9
+ useTapFilters,
10
+ useTapParams,
11
+ useTapProject,
12
+ useTapQuery,
13
+ useTapServer,
14
+ useTapSize,
15
+ useTapState,
16
+ useTapStorage,
17
+ useTapTheme
18
+ } from "./chunk-SGEYDV3K.mjs";
19
+ import {
20
+ useTap
21
+ } from "./chunk-QA5AL3VZ.mjs";
22
+ import {
23
+ TAP_ERROR_CODES,
24
+ TapError,
25
+ TapServerError
26
+ } from "./chunk-QXD7WVKK.mjs";
27
+ import {
28
+ __export
29
+ } from "./chunk-J5LGTIGS.mjs";
30
+
31
+ // src/index.ts
32
+ var src_exports = {};
33
+ __export(src_exports, {
34
+ TAP_ERROR_CODES: () => TAP_ERROR_CODES,
35
+ TapError: () => TapError,
36
+ TapServerError: () => TapServerError,
37
+ dataQueryKey: () => dataQueryKey,
38
+ installPrefix: () => installPrefix,
39
+ procedurePrefix: () => procedurePrefix,
40
+ procedureQueryKey: () => procedureQueryKey,
41
+ storageQueryKey: () => storageQueryKey,
42
+ useTap: () => useTap,
43
+ useTapAuth: () => useTapAuth,
44
+ useTapContext: () => useTapContext,
45
+ useTapFilters: () => useTapFilters,
46
+ useTapParams: () => useTapParams,
47
+ useTapProject: () => useTapProject,
48
+ useTapQuery: () => useTapQuery,
49
+ useTapServer: () => useTapServer,
50
+ useTapSize: () => useTapSize,
51
+ useTapState: () => useTapState,
52
+ useTapStorage: () => useTapStorage,
53
+ useTapTheme: () => useTapTheme
54
+ });
55
+
56
+ export {
57
+ src_exports
58
+ };
59
+ //# sourceMappingURL=chunk-2CY5HVY7.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @module index\n * @mergeModuleWith <project>\n */\nexport { useTap } from './client/context';\nexport { TAP_ERROR_CODES, TapError, TapServerError } from './client/errors';\nexport type { TapErrorCode } from './client/errors';\nexport {\n useTapAuth,\n useTapContext,\n useTapFilters,\n useTapParams,\n useTapProject,\n useTapSize,\n useTapState,\n useTapTheme,\n} from './client/hooks';\nexport {\n dataQueryKey,\n installPrefix,\n procedurePrefix,\n procedureQueryKey,\n storageQueryKey,\n useTapQuery,\n useTapServer,\n useTapStorage,\n} from './client/query';\nexport type {\n TapQueryOptions,\n TapQueryResult,\n TapStorageResult,\n} from './client/query';\nexport type * from './client/types';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;","names":[]}
@@ -62,4 +62,4 @@ var TapServerError = class extends Error {
62
62
 
63
63
 
64
64
  exports.TAP_ERROR_CODES = TAP_ERROR_CODES; exports.TapError = TapError; exports.TapServerError = TapServerError;
65
- //# sourceMappingURL=chunk-2DBSVKAU.cjs.map
65
+ //# sourceMappingURL=chunk-6HETNZJY.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/extension-sdk/extension-sdk/dist/chunk-6HETNZJY.cjs","../src/client/errors.ts"],"names":[],"mappings":"AAAA;AC4BO,IAAM,gBAAA,EAA2C;AAAA,EACtD,kBAAA;AAAA,EACA,mBAAA;AAAA,EACA,wBAAA;AAAA,EACA,uBAAA;AAAA,EACA,eAAA;AAAA,EACA,qBAAA;AAAA,EACA,sBAAA;AAAA,EACA,mBAAA;AAAA,EACA,uBAAA;AAAA,EACA,uBAAA;AAAA,EACA,mBAAA;AAAA,EACA,mBAAA;AAAA,EACA,oBAAA;AAAA,EACA,sBAAA;AAAA,EACA,kBAAA;AAAA,EACA,oBAAA;AAAA,EACA,mBAAA;AAAA,EACA,qBAAA;AAAA,EACA,oBAAA;AAAA,EACA,sBAAA;AAAA,EACA;AACF,CAAA;AAEA,IAAM,SAAA,EAAW,IAAI,GAAA,CAAY,eAAe,CAAA;AA+BzC,IAAM,SAAA,EAAN,MAAA,QAAuB,MAAM;AAAA,EAGlC,WAAA,CAAY,IAAA,EAAoB,OAAA,EAAiB;AAC/C,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,KAAA,EAAO,UAAA;AACZ,IAAA,IAAA,CAAK,KAAA,EAAO,IAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,EAAA,CAAG,KAAA,EAAmC;AAC3C,IAAA,OACE,MAAA,WAAiB,MAAA,GACjB,KAAA,CAAM,KAAA,IAAS,WAAA,GACf,OAAA,GAAU,MAAA,GACV,OAAO,KAAA,CAAM,KAAA,IAAS,SAAA,GACtB,QAAA,CAAS,GAAA,CAAI,KAAA,CAAM,IAAI,CAAA;AAAA,EAE3B;AACF,CAAA;AA2BO,IAAM,eAAA,EAAN,MAAA,QAA6B,MAAM;AAAA,EAIxC,WAAA,CAAY,IAAA,EAAc,OAAA,EAAiB,MAAA,EAAiB;AAC1D,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,KAAA,EAAO,gBAAA;AACZ,IAAA,IAAA,CAAK,KAAA,EAAO,IAAA;AACZ,IAAA,IAAA,CAAK,OAAA,EAAS,MAAA;AAAA,EAChB;AAAA;AAAA,EAGA,MAAA,CAAA,EAAuD;AACrD,IAAA,OAAO,EAAE,KAAA,EAAO,EAAE,IAAA,EAAM,IAAA,CAAK,IAAA,EAAM,OAAA,EAAS,IAAA,CAAK,QAAQ,EAAE,CAAA;AAAA,EAC7D;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,EAAA,CAAG,KAAA,EAAyC;AACjD,IAAA,OACE,MAAA,WAAiB,MAAA,GACjB,KAAA,CAAM,KAAA,IAAS,iBAAA,GACf,OAAA,GAAU,MAAA,GACV,OAAO,KAAA,CAAM,KAAA,IAAS,QAAA;AAAA,EAE1B;AACF,CAAA;ADtGA;AACA;AACE;AACA;AACA;AACF,gHAAC","file":"/home/runner/work/extension-sdk/extension-sdk/dist/chunk-6HETNZJY.cjs","sourcesContent":[null,"/** Every code a `TapError` the SDK raises carries. */\nexport type TapErrorCode =\n | 'TAP_OUTSIDE_HOST'\n | 'TAP_SCOPE_MISSING'\n | 'TAP_UNKNOWN_COLLECTION'\n | 'TAP_UNKNOWN_PROCEDURE'\n | 'TAP_NO_PORTAL'\n | 'TAP_NO_QUERY_CLIENT'\n | 'TAP_MANIFEST_INVALID'\n | 'TAP_ENTRY_MISSING'\n | 'TAP_STYLES_UNRESOLVED'\n | 'TAP_SCHEMA_UNRESOLVED'\n | 'TAP_TOKEN_MISSING'\n | 'TAP_TOKEN_INVALID'\n | 'TAP_TOKEN_AUDIENCE'\n | 'TAP_EVENT_ID_MISSING'\n | 'TAP_BODY_INVALID'\n | 'TAP_BODY_TOO_LARGE'\n | 'TAP_ROUTE_UNKNOWN'\n | 'TAP_HANDLER_MISSING'\n | 'TAP_INTERNAL_ERROR'\n | 'TAP_WEBHOOK_REJECTED'\n | 'TAP_TEST_TARGET_INVALID';\n\n/**\n * The same codes as `TapErrorCode`, as an array to iterate or check membership\n * against at runtime.\n */\nexport const TAP_ERROR_CODES: readonly TapErrorCode[] = [\n 'TAP_OUTSIDE_HOST',\n 'TAP_SCOPE_MISSING',\n 'TAP_UNKNOWN_COLLECTION',\n 'TAP_UNKNOWN_PROCEDURE',\n 'TAP_NO_PORTAL',\n 'TAP_NO_QUERY_CLIENT',\n 'TAP_MANIFEST_INVALID',\n 'TAP_ENTRY_MISSING',\n 'TAP_STYLES_UNRESOLVED',\n 'TAP_SCHEMA_UNRESOLVED',\n 'TAP_TOKEN_MISSING',\n 'TAP_TOKEN_INVALID',\n 'TAP_TOKEN_AUDIENCE',\n 'TAP_EVENT_ID_MISSING',\n 'TAP_BODY_INVALID',\n 'TAP_BODY_TOO_LARGE',\n 'TAP_ROUTE_UNKNOWN',\n 'TAP_HANDLER_MISSING',\n 'TAP_INTERNAL_ERROR',\n 'TAP_WEBHOOK_REJECTED',\n 'TAP_TEST_TARGET_INVALID',\n];\n\nconst CODE_SET = new Set<string>(TAP_ERROR_CODES);\n\n/**\n * The error the SDK and the host raise when a call cannot be served at all.\n *\n * @remarks\n * The SDK throws it for a mount outside the host (`TAP_OUTSIDE_HOST`), a\n * collection or procedure the manifest does not declare\n * (`TAP_UNKNOWN_COLLECTION`, `TAP_UNKNOWN_PROCEDURE`), a missing portal or query\n * client, a token the server rejected, and a misused testing helper. The host\n * turns a refused read or write into `TAP_SCOPE_MISSING`. Branch on `code`, which\n * is stable; the message is prose and changes. An error your own server raised is\n * a `TapServerError` instead.\n *\n * @example\n * ```tsx\n * import { TapError, useTapQuery } from '@tappify/extension-sdk';\n * import { ErrorState } from '@tappify/extension-sdk/ui';\n *\n * function Revenue() {\n * const revenue = useTapQuery({\n * kind: 'revenue',\n * range: { from: '2026-09-01', to: '2026-09-08' },\n * });\n * if (TapError.is(revenue.error) && revenue.error.code === 'TAP_SCOPE_MISSING') {\n * return <ErrorState title=\"Revenue is not shared with this extension\" />;\n * }\n * return <span>{revenue.data?.mrr ?? 0}</span>;\n * }\n * ```\n */\nexport class TapError extends Error {\n readonly code: TapErrorCode;\n\n constructor(code: TapErrorCode, message: string) {\n super(message);\n this.name = 'TapError';\n this.code = code;\n }\n\n /**\n * Narrows an unknown value to a `TapError`, by name and by a code it\n * recognises, so it holds across module instances.\n */\n static is(value: unknown): value is TapError {\n return (\n value instanceof Error &&\n value.name === 'TapError' &&\n 'code' in value &&\n typeof value.code === 'string' &&\n CODE_SET.has(value.code)\n );\n }\n}\n\n/**\n * The error a vendor server throws to answer a Tappify call with a code and a\n * message the owner sees.\n *\n * @remarks\n * `createTappifyHandler` catches it and answers `{ error: { code, message } }`\n * with `status`, or 400 when no status was given; the host relays both to the\n * calling mount, where they arrive as the `error` of `useTapServer`. Pick a code\n * of your own and keep it stable — the SDK's own codes are `SERVER_ERROR_CODES` —\n * and write the message for the owner, who reads it on screen.\n *\n * @example\n * ```ts\n * import { TapServerError } from '@tappify/extension-sdk/server';\n *\n * function assertRange(days: number) {\n * if (days > 0) return;\n * throw new TapServerError(\n * 'RANGE_TOO_SHORT',\n * 'getSummary needs a range of at least one day. Widen the date picker, then retry.',\n * 400,\n * );\n * }\n * ```\n */\nexport class TapServerError extends Error {\n readonly code: string;\n readonly status: number | undefined;\n\n constructor(code: string, message: string, status?: number) {\n super(message);\n this.name = 'TapServerError';\n this.code = code;\n this.status = status;\n }\n\n /** The wire body the handler answers with, without the status. */\n toJSON(): { error: { code: string; message: string } } {\n return { error: { code: this.code, message: this.message } };\n }\n\n /**\n * Narrows an unknown value to a `TapServerError` by name and by carrying a\n * string `code`, so it holds across module instances.\n */\n static is(value: unknown): value is TapServerError {\n return (\n value instanceof Error &&\n value.name === 'TapServerError' &&\n 'code' in value &&\n typeof value.code === 'string'\n );\n }\n}\n"]}
@@ -35,6 +35,13 @@ var SCOPES = [
35
35
  requiresSecurityReview: false,
36
36
  requiresJustification: false
37
37
  },
38
+ {
39
+ key: "review.threads:read",
40
+ label: "Read Review threads",
41
+ description: "Review threads, comments, screenshot descriptors, build context and safe device context",
42
+ requiresSecurityReview: false,
43
+ requiresJustification: false
44
+ },
38
45
  {
39
46
  key: "crashes:read",
40
47
  label: "Read crash data",
@@ -108,7 +115,7 @@ var SCOPES = [
108
115
  {
109
116
  key: "work:sync",
110
117
  label: "Sync work items",
111
- description: "Two-way comments and status on created work items",
118
+ description: "Sync comments and status on created work items",
112
119
  requiresSecurityReview: false,
113
120
  requiresJustification: true
114
121
  },
@@ -299,4 +306,4 @@ var slotsForPage = (page) => _nullishCoalesce(_optionalChain([findPage, 'call',
299
306
 
300
307
 
301
308
  exports.SCOPES = SCOPES; exports.SCOPE_KEYS = SCOPE_KEYS; exports.SLOTS = SLOTS; exports.CHARTS = CHARTS; exports.PAGES = PAGES; exports.PAGE_KEYS = PAGE_KEYS; exports.TABLES = TABLES; exports.TABLE_KEYS = TABLE_KEYS; exports.KINDS = KINDS; exports.CATEGORIES = CATEGORIES; exports.EVENTS = EVENTS; exports.SERVED_SDK_MAJORS = SERVED_SDK_MAJORS; exports.QUERY_SCOPES = QUERY_SCOPES; exports.CARD_KEYS = CARD_KEYS; exports.findScope = findScope; exports.findPage = findPage; exports.findTable = findTable; exports.slotsForPage = slotsForPage;
302
- //# sourceMappingURL=chunk-PYJMJF44.cjs.map
309
+ //# sourceMappingURL=chunk-7YKH4LZN.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/extension-sdk/extension-sdk/dist/chunk-7YKH4LZN.cjs","../src/manifest/constants.ts"],"names":[],"mappings":"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","file":"/home/runner/work/extension-sdk/extension-sdk/dist/chunk-7YKH4LZN.cjs","sourcesContent":[null,"import type { ToolDeclaration } from './types';\n\nexport const SCOPES = [\n {\n key: 'ui:render',\n label: 'Render UI',\n description:\n 'Render the declared pages, tabs, widgets, row actions and settings panel, and open chat with a visible prompt',\n requiresSecurityReview: false,\n requiresJustification: false,\n },\n {\n key: 'projects:read',\n label: 'Read the project',\n description: 'Project name, platforms and store identity',\n requiresSecurityReview: false,\n requiresJustification: false,\n },\n {\n key: 'analytics:read',\n label: 'Read analytics',\n description: 'Downloads, impressions, page views and conversion series',\n requiresSecurityReview: false,\n requiresJustification: false,\n },\n {\n key: 'store.metadata:read',\n label: 'Read store metadata',\n description: 'Listing metadata, keyword set and keyword positions',\n requiresSecurityReview: false,\n requiresJustification: false,\n },\n {\n key: 'reviews:read',\n label: 'Read store reviews',\n description: 'Review text, ratings and replies',\n requiresSecurityReview: false,\n requiresJustification: false,\n },\n {\n key: 'review.threads:read',\n label: 'Read Review threads',\n description:\n 'Review threads, comments, screenshot descriptors, build context and safe device context',\n requiresSecurityReview: false,\n requiresJustification: false,\n },\n {\n key: 'crashes:read',\n label: 'Read crash data',\n description: 'Crash-free rate and issue counts',\n requiresSecurityReview: false,\n requiresJustification: false,\n },\n {\n key: 'revenue:read',\n label: 'Read revenue',\n description: 'MRR, revenue series and refund counts',\n requiresSecurityReview: false,\n requiresJustification: true,\n },\n {\n key: 'metrics:write',\n label: 'Write connector metrics',\n description:\n 'Write the metrics this extension declares into the metrics store',\n requiresSecurityReview: false,\n requiresJustification: false,\n },\n {\n key: 'alerts:write',\n label: 'Raise alerts and banners',\n description:\n 'Turn declared webhooks into alerts and banners, at most three a day per project',\n requiresSecurityReview: false,\n requiresJustification: true,\n },\n {\n key: 'insights:write',\n label: 'Post insights',\n description: 'Post insights to Home and to chat',\n requiresSecurityReview: false,\n requiresJustification: true,\n },\n {\n key: 'ai:tools',\n label: 'Add assistant tools',\n description: 'Offer tools and mentions to the assistant',\n requiresSecurityReview: false,\n requiresJustification: false,\n },\n {\n key: 'ai:actions',\n label: 'Run assistant actions',\n description: 'Run declared actions, each behind the approval card',\n requiresSecurityReview: true,\n requiresJustification: true,\n },\n {\n key: 'store.metadata:write',\n label: 'Propose store metadata changes',\n description: 'Propose listing changes through a Tappify approval',\n requiresSecurityReview: true,\n requiresJustification: true,\n },\n {\n key: 'autopilot:trigger',\n label: 'Trigger automated store actions',\n description: 'Start automated store actions on the owner’s behalf',\n requiresSecurityReview: true,\n requiresJustification: true,\n },\n {\n key: 'work:create',\n label: 'Create work items',\n description: 'Create issues, pages or messages in the vendor’s system',\n requiresSecurityReview: false,\n requiresJustification: true,\n },\n {\n key: 'work:sync',\n label: 'Sync work items',\n description: 'Sync comments and status on created work items',\n requiresSecurityReview: false,\n requiresJustification: true,\n },\n {\n key: 'storage:write',\n label: 'Use hosted storage',\n description:\n 'Read and write this extension’s own hosted storage collections',\n requiresSecurityReview: false,\n requiresJustification: false,\n },\n {\n key: 'messaging:send',\n label: 'Send messages to end users',\n description:\n 'Send messages or push notifications to the owner’s end users through owner-supplied credentials',\n requiresSecurityReview: true,\n requiresJustification: true,\n },\n {\n key: 'ai:skills',\n label: 'Shape the assistant',\n description:\n 'Add skills, prompt templates and context providers that shape the assistant’s behaviour',\n requiresSecurityReview: false,\n requiresJustification: true,\n },\n] as const;\n\nexport type ScopeDefinition = (typeof SCOPES)[number];\n/** Every scope an extension can ask for under `scopes` in its manifest. */\nexport type ScopeKey = ScopeDefinition['key'];\n\nexport const SCOPE_KEYS: readonly ScopeKey[] = SCOPES.map(scope => scope.key);\n\nexport const SLOTS = [\n 'kpi-row',\n 'insights',\n 'sidebar',\n 'below-chart',\n 'below-list',\n] as const;\n/** The anchors on a host page a widget can be declared at. */\nexport type SlotId = (typeof SLOTS)[number];\n\nexport const CHARTS = [\n 'installs-activity',\n 'conversion',\n 'release-cadence',\n] as const;\nexport type HostChart = (typeof CHARTS)[number];\n\nexport const PAGES = [\n {\n key: 'overview',\n label: 'Home',\n route: '/projects/:id/overview',\n slots: ['kpi-row', 'insights', 'sidebar'],\n charts: ['installs-activity'],\n tabs: true,\n banners: true,\n },\n {\n key: 'analytics',\n label: 'Analytics',\n route: '/projects/:id/analytics',\n slots: ['kpi-row', 'below-chart', 'sidebar'],\n charts: ['installs-activity', 'conversion'],\n tabs: true,\n banners: true,\n },\n {\n key: 'deployments',\n label: 'Deployments',\n route: '/projects/:id/deployments',\n slots: ['sidebar', 'below-list'],\n charts: ['release-cadence'],\n tabs: true,\n banners: false,\n },\n {\n key: 'ai_chat',\n label: 'Assistant',\n route: 'chat',\n slots: [],\n charts: [],\n tabs: false,\n banners: false,\n },\n] as const;\n\nexport type PageDefinition = (typeof PAGES)[number];\n/** The host pages a contribution can attach a tab, widget or banner to. */\nexport type HostPage = PageDefinition['key'];\n\nexport const PAGE_KEYS: readonly HostPage[] = PAGES.map(page => page.key);\n\nexport const TABLES = [\n { key: 'analytics.keywords', label: 'Keywords' },\n { key: 'analytics.reviews', label: 'Reviews' },\n { key: 'deployments.releases', label: 'Releases' },\n { key: 'deployments.builds', label: 'Builds' },\n] as const;\n\nexport type TableDefinition = (typeof TABLES)[number];\nexport type HostTable = TableDefinition['key'];\n\nexport const TABLE_KEYS: readonly HostTable[] = TABLES.map(table => table.key);\n\nexport const KINDS = [\n 'page',\n 'tab',\n 'widget',\n 'series',\n 'banner',\n 'rowAction',\n 'marker',\n 'settings',\n 'storage',\n 'connector',\n 'webhook',\n 'tool',\n 'mention',\n 'action',\n 'knowledge',\n 'skill',\n 'prompt',\n 'context',\n 'work',\n] as const;\nexport type ContributionKind = (typeof KINDS)[number];\n\nexport const CATEGORIES = [\n 'market_data',\n 'subscriptions',\n 'product_analytics',\n 'crashes',\n 'attribution',\n 'experiments',\n 'messaging',\n 'session_replay',\n 'localization',\n 'design',\n 'ci_cd',\n 'trackers',\n 'support',\n 'chat',\n 'ad_monetization',\n 'warehouse',\n 'compliance',\n 'additional_stores',\n] as const;\nexport type ExtensionCategory = (typeof CATEGORIES)[number];\n\nexport const EVENTS = [\n 'release.shipped',\n 'metadata.changed',\n 'keyword.set_changed',\n 'screenshots.updated',\n 'price.changed',\n 'featuring.started',\n 'score.changed',\n 'incident.opened',\n 'incident.resolved',\n 'playbook.step_approved',\n 'review.thread_opened',\n 'review.thread_resolved',\n 'approval.requested',\n 'digest.sent',\n 'install.created',\n 'install.paused',\n 'install.resumed',\n 'install.revoked',\n 'install.token_rotated',\n 'scopes.changed',\n 'settings.changed',\n] as const;\n/** Every event Tappify emits, which a mount or a server can subscribe to. */\nexport type HostEventName = (typeof EVENTS)[number];\n\nexport const SERVED_SDK_MAJORS: readonly number[] = [2];\n\n/** The `kind` of every read `tap.data.query` accepts, one per scope it needs. */\nexport type DataQueryKind =\n | 'project'\n | 'series'\n | 'keywords'\n | 'listing'\n | 'reviews'\n | 'crashes'\n | 'revenue';\n\nexport const QUERY_SCOPES: Record<DataQueryKind, ScopeKey> = {\n project: 'projects:read',\n series: 'analytics:read',\n keywords: 'store.metadata:read',\n listing: 'store.metadata:read',\n reviews: 'reviews:read',\n crashes: 'crashes:read',\n revenue: 'revenue:read',\n};\n\n/**\n * The keys a tool's answer carries for each `returns` kind it may declare.\n * `validateToolResponse`, `tappify extension doctor` and the host all read\n * this one table, so a wrong body fails the same way wherever it is checked.\n */\nexport const CARD_KEYS: Record<ToolDeclaration['returns'], string[]> = {\n value: ['label', 'value'],\n series: ['label', 'points'],\n table: ['columns', 'rows'],\n list: ['items'],\n comparison: ['label', 'left', 'right'],\n};\n\nexport const findScope = (key: string): ScopeDefinition | undefined =>\n SCOPES.find(scope => scope.key === key);\n\nexport const findPage = (key: string): PageDefinition | undefined =>\n PAGES.find(page => page.key === key);\n\nexport const findTable = (key: string): TableDefinition | undefined =>\n TABLES.find(table => table.key === key);\n\nexport const slotsForPage = (page: string): readonly SlotId[] =>\n findPage(page)?.slots ?? [];\n"]}
@@ -1,10 +1,10 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
2
 
3
- var _chunkPYJMJF44cjs = require('./chunk-PYJMJF44.cjs');
3
+ var _chunk7YKH4LZNcjs = require('./chunk-7YKH4LZN.cjs');
4
4
 
5
5
 
6
6
 
7
- var _chunk2DBSVKAUcjs = require('./chunk-2DBSVKAU.cjs');
7
+ var _chunk6HETNZJYcjs = require('./chunk-6HETNZJY.cjs');
8
8
 
9
9
  // src/server/codes.ts
10
10
  var SERVER_ERROR_CODES = {
@@ -46,7 +46,7 @@ function toClaims(payload, audience) {
46
46
  const aud = payload.aud;
47
47
  const matches = aud === audience || Array.isArray(aud) && aud.includes(audience);
48
48
  if (!matches) {
49
- throw new (0, _chunk2DBSVKAUcjs.TapError)(
49
+ throw new (0, _chunk6HETNZJYcjs.TapError)(
50
50
  "TAP_TOKEN_AUDIENCE",
51
51
  `This install token was issued for a different extension. Create the handler with the same extensionId as the "id" in tappify.extension.json, so its audience is "${audience}".`
52
52
  );
@@ -55,7 +55,7 @@ function toClaims(payload, audience) {
55
55
  const extensionId = readString(payload, "extensionId");
56
56
  const organizationId = readString(payload, "organizationId");
57
57
  if (installId === void 0 || extensionId === void 0 || organizationId === void 0) {
58
- throw new (0, _chunk2DBSVKAUcjs.TapError)(
58
+ throw new (0, _chunk6HETNZJYcjs.TapError)(
59
59
  "TAP_TOKEN_INVALID",
60
60
  `The install token is missing the installId, extensionId or organizationId claim. Verify against ${DEFAULT_JWKS_URL} and do not reuse tokens across environments.`
61
61
  );
@@ -86,7 +86,7 @@ async function verifyTappifyToken(token, options) {
86
86
  });
87
87
  payload = verified.payload;
88
88
  } catch (e) {
89
- throw new (0, _chunk2DBSVKAUcjs.TapError)(
89
+ throw new (0, _chunk6HETNZJYcjs.TapError)(
90
90
  "TAP_TOKEN_INVALID",
91
91
  "The install token did not verify. Check that your server reads the Authorization header unchanged, that its clock is correct, and that jwksUrl points at the Tappify environment that called you."
92
92
  );
@@ -124,10 +124,15 @@ function readText(value, key) {
124
124
  // src/server/handler.ts
125
125
  var MAX_BODY_BYTES = 1048576;
126
126
  var WORK_OPERATIONS = [
127
+ "configure",
127
128
  "list_containers",
129
+ "describe_container",
128
130
  "create",
129
131
  "attach",
130
132
  "comment",
133
+ "set_status",
134
+ "hydrate_event",
135
+ "disconnect",
131
136
  "status",
132
137
  "resolve"
133
138
  ];
@@ -183,7 +188,7 @@ function toCredentials(payload) {
183
188
  return credentials;
184
189
  }
185
190
  function tooLarge() {
186
- return new (0, _chunk2DBSVKAUcjs.TapServerError)(
191
+ return new (0, _chunk6HETNZJYcjs.TapServerError)(
187
192
  SERVER_ERROR_CODES.BODY_TOO_LARGE,
188
193
  "A Tappify request body is at most 1 MB. Return a cursor or an id the host can follow instead of inlining the payload.",
189
194
  413
@@ -200,7 +205,7 @@ async function readBody(request) {
200
205
  try {
201
206
  return JSON.parse(text);
202
207
  } catch (e2) {
203
- throw new (0, _chunk2DBSVKAUcjs.TapServerError)(
208
+ throw new (0, _chunk6HETNZJYcjs.TapServerError)(
204
209
  SERVER_ERROR_CODES.BODY_INVALID,
205
210
  "The request body was not JSON. Do not parse or re-encode the body before handing the Request to createTappifyHandler().",
206
211
  400
@@ -238,24 +243,31 @@ function resolveRoute(options, method, segments, url, payload) {
238
243
  }
239
244
  if (head === "work" && method === "POST" && name !== void 0) {
240
245
  const operation = WORK_OPERATIONS.find((candidate) => candidate === name);
241
- return route(
242
- operation === void 0 ? void 0 : _optionalChain([options, 'access', _2 => _2.work, 'optionalAccess', _3 => _3[operation]]),
243
- bodyInput
244
- );
246
+ const handler = operation === void 0 ? void 0 : _optionalChain([options, 'access', _2 => _2.work, 'optionalAccess', _3 => _3[operation]]);
247
+ if (operation === "set_status" && handler === void 0) {
248
+ const input = readRecord(payload, "input");
249
+ if (readText(input, "tappifyStatus") === "resolved") {
250
+ return route(_optionalChain([options, 'access', _4 => _4.work, 'optionalAccess', _5 => _5.resolve]), {
251
+ ...input,
252
+ id: readUnknown(input, "externalItemId")
253
+ });
254
+ }
255
+ }
256
+ return route(handler, bodyInput);
245
257
  }
246
258
  if (head === "events" && method === "POST") {
247
259
  const envelope = readRecord(payload, "event");
248
260
  const eventName = readText(envelope, "name");
249
261
  if (eventName === null) {
250
- throw new (0, _chunk2DBSVKAUcjs.TapServerError)(
262
+ throw new (0, _chunk6HETNZJYcjs.TapServerError)(
251
263
  SERVER_ERROR_CODES.BODY_INVALID,
252
264
  'The events body has no event.name. Post the envelope Tappify sends unchanged, with the event name under "event".',
253
265
  400
254
266
  );
255
267
  }
256
- const declared = _chunkPYJMJF44cjs.EVENTS.find((candidate) => candidate === eventName);
268
+ const declared = _chunk7YKH4LZNcjs.EVENTS.find((candidate) => candidate === eventName);
257
269
  return route(
258
- declared === void 0 ? void 0 : _optionalChain([options, 'access', _4 => _4.events, 'optionalAccess', _5 => _5[declared]]),
270
+ declared === void 0 ? void 0 : _optionalChain([options, 'access', _6 => _6.events, 'optionalAccess', _7 => _7[declared]]),
259
271
  void 0,
260
272
  _nullishCoalesce(readUnknown(envelope, "payload"), () => ( {})),
261
273
  true
@@ -335,10 +347,10 @@ function createTappifyHandler(options) {
335
347
  const result = await matched.handler(typed);
336
348
  return matched.noContent ? new Response(null, { status: 204 }) : json(result);
337
349
  } catch (error) {
338
- if (_chunk2DBSVKAUcjs.TapServerError.is(error)) {
350
+ if (_chunk6HETNZJYcjs.TapServerError.is(error)) {
339
351
  return wireError(error.code, error.message, _nullishCoalesce(error.status, () => ( 400)));
340
352
  }
341
- if (_chunk2DBSVKAUcjs.TapError.is(error)) {
353
+ if (_chunk6HETNZJYcjs.TapError.is(error)) {
342
354
  const tokenFailure = TOKEN_FAILURES.find(
343
355
  (candidate) => candidate === error.code
344
356
  );
@@ -361,5 +373,6 @@ function createTappifyHandler(options) {
361
373
 
362
374
 
363
375
 
364
- exports.SERVER_ERROR_CODES = SERVER_ERROR_CODES; exports.INTERNAL_MESSAGE = INTERNAL_MESSAGE; exports.wireError = wireError; exports.readRecord = readRecord; exports.readText = readText; exports.DEFAULT_JWKS_URL = DEFAULT_JWKS_URL; exports.verifyTappifyToken = verifyTappifyToken; exports.createTappifyHandler = createTappifyHandler;
365
- //# sourceMappingURL=chunk-RMUWRESK.cjs.map
376
+
377
+ exports.SERVER_ERROR_CODES = SERVER_ERROR_CODES; exports.DEFAULT_JWKS_URL = DEFAULT_JWKS_URL; exports.verifyTappifyToken = verifyTappifyToken; exports.INTERNAL_MESSAGE = INTERNAL_MESSAGE; exports.wireError = wireError; exports.readUnknown = readUnknown; exports.readRecord = readRecord; exports.readText = readText; exports.createTappifyHandler = createTappifyHandler;
378
+ //# sourceMappingURL=chunk-A74HCO6T.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/extension-sdk/extension-sdk/dist/chunk-A74HCO6T.cjs","../src/server/codes.ts","../src/server/token.ts","../src/server/wire.ts","../src/server/handler.ts"],"names":[],"mappings":"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","file":"/home/runner/work/extension-sdk/extension-sdk/dist/chunk-A74HCO6T.cjs","sourcesContent":[null,"import type { TapErrorCode } from '../client/errors';\n\n/**\n * The codes `createTappifyHandler` answers with itself, keyed by a short name.\n *\n * @remarks\n * Every one of these comes from the SDK, not from your own handlers: a route or\n * handler that is not there, a token that is missing or does not verify, a missing\n * event id, a body that is not JSON or is over 1 MB, a webhook Tappify rejected,\n * and the catch-all for an error a handler let escape. Throw a `TapServerError`\n * with a code of your own for anything your server decides.\n */\nexport const SERVER_ERROR_CODES = {\n ROUTE_UNKNOWN: 'TAP_ROUTE_UNKNOWN',\n HANDLER_MISSING: 'TAP_HANDLER_MISSING',\n INTERNAL_ERROR: 'TAP_INTERNAL_ERROR',\n TOKEN_MISSING: 'TAP_TOKEN_MISSING',\n TOKEN_INVALID: 'TAP_TOKEN_INVALID',\n TOKEN_AUDIENCE: 'TAP_TOKEN_AUDIENCE',\n EVENT_ID_MISSING: 'TAP_EVENT_ID_MISSING',\n BODY_INVALID: 'TAP_BODY_INVALID',\n BODY_TOO_LARGE: 'TAP_BODY_TOO_LARGE',\n WEBHOOK_REJECTED: 'TAP_WEBHOOK_REJECTED',\n} as const satisfies Record<string, TapErrorCode>;\n\n/** Every code the handler answers with itself, as a union of its own values. */\nexport type TapServerErrorCode =\n (typeof SERVER_ERROR_CODES)[keyof typeof SERVER_ERROR_CODES];\n","import {\n createLocalJWKSet,\n createRemoteJWKSet,\n jwtVerify,\n type JWTPayload,\n type JWTVerifyGetKey,\n} from 'jose';\nimport { TapError } from '../client/errors';\nimport type { TappifyClaims, VerifyTokenOptions } from './types';\n\n/**\n * The key set `verifyTappifyToken` fetches when no `jwksUrl` or `jwks` is given.\n *\n * @remarks\n * This is production. Point `jwksUrl` at the environment that is calling you when\n * you run against another one, and pass `jwks` in tests.\n */\nexport const DEFAULT_JWKS_URL =\n 'https://api.tappify.ai/api/v1/extensions/.well-known/jwks.json';\n\nconst CLOCK_TOLERANCE_SECONDS = 60;\n\nconst remoteSets = new Map<string, JWTVerifyGetKey>();\n\nfunction keySet(options: VerifyTokenOptions): JWTVerifyGetKey {\n if (options.jwks) return createLocalJWKSet(options.jwks);\n\n const url = options.jwksUrl ?? DEFAULT_JWKS_URL;\n const cached = remoteSets.get(url);\n if (cached) return cached;\n\n const created = createRemoteJWKSet(new URL(url));\n remoteSets.set(url, created);\n return created;\n}\n\nfunction readString(payload: JWTPayload, key: string): string | undefined {\n const value = payload[key];\n return typeof value === 'string' ? value : undefined;\n}\n\nfunction toClaims(payload: JWTPayload, audience: string): TappifyClaims {\n const aud = payload.aud;\n const matches =\n aud === audience || (Array.isArray(aud) && aud.includes(audience));\n\n if (!matches) {\n throw new TapError(\n 'TAP_TOKEN_AUDIENCE',\n `This install token was issued for a different extension. Create the handler with the same extensionId as the \"id\" in tappify.extension.json, so its audience is \"${audience}\".`,\n );\n }\n\n const installId = readString(payload, 'installId');\n const extensionId = readString(payload, 'extensionId');\n const organizationId = readString(payload, 'organizationId');\n\n if (\n installId === undefined ||\n extensionId === undefined ||\n organizationId === undefined\n ) {\n throw new TapError(\n 'TAP_TOKEN_INVALID',\n `The install token is missing the installId, extensionId or organizationId claim. Verify against ${DEFAULT_JWKS_URL} and do not reuse tokens across environments.`,\n );\n }\n\n const scopes = payload.scopes;\n const userId = readString(payload, 'userId');\n\n const claims: TappifyClaims = {\n iss: readString(payload, 'iss') ?? '',\n aud: audience,\n exp: typeof payload.exp === 'number' ? payload.exp : 0,\n iat: typeof payload.iat === 'number' ? payload.iat : 0,\n installId,\n extensionId,\n projectId: readString(payload, 'projectId') ?? null,\n organizationId,\n scopes: Array.isArray(scopes)\n ? scopes.filter((scope): scope is string => typeof scope === 'string')\n : [],\n };\n\n if (userId !== undefined) claims.userId = userId;\n return claims;\n}\n\n/**\n * Verifies an install token against Tappify's signing keys and returns its\n * claims.\n *\n * @remarks\n * Checks the RS256 signature, the expiry with 60 seconds of clock tolerance, and\n * that the audience is `ext:<extensionId>`. Rejects with a `TapError` carrying\n * `TAP_TOKEN_INVALID` when the signature, the expiry or a required claim is wrong,\n * and `TAP_TOKEN_AUDIENCE` when the token was issued for another extension. A\n * remote key set is fetched once per url and cached for the process, so calling\n * this per request costs nothing after the first. `createTappifyHandler` already\n * verifies every route but health, so call this only outside the handler, or\n * where you accept a Tappify token on a route of your own.\n *\n * @example\n * ```ts\n * import { verifyTappifyToken } from '@tappify/extension-sdk/server';\n *\n * async function whoCalled(request: Request): Promise<string> {\n * const token = (request.headers.get('authorization') ?? '').replace('Bearer ', '');\n * const claims = await verifyTappifyToken(token, { extensionId: 'starter' });\n * return claims.installId;\n * }\n * ```\n */\nexport async function verifyTappifyToken(\n token: string,\n options: VerifyTokenOptions,\n): Promise<TappifyClaims> {\n const audience = `ext:${options.extensionId}`;\n\n let payload: JWTPayload;\n try {\n const verified = await jwtVerify(token, keySet(options), {\n algorithms: ['RS256'],\n clockTolerance: CLOCK_TOLERANCE_SECONDS,\n });\n payload = verified.payload;\n } catch {\n throw new TapError(\n 'TAP_TOKEN_INVALID',\n 'The install token did not verify. Check that your server reads the Authorization header unchanged, that its clock is correct, and that jwksUrl points at the Tappify environment that called you.',\n );\n }\n\n return toClaims(payload, audience);\n}\n","import type { TapServerErrorCode } from './codes';\n\nexport const INTERNAL_MESSAGE =\n 'The extension server failed to handle this call. Check your own logs; Tappify records the failure against your extension.';\n\nexport function json(data: unknown, status = 200): Response {\n return new Response(JSON.stringify(data), {\n status,\n headers: { 'content-type': 'application/json' },\n });\n}\n\nexport function wireError(\n code: string,\n message: string,\n status: number,\n): Response {\n return json({ error: { code, message } }, status);\n}\n\nexport function failure(\n code: TapServerErrorCode,\n message: string,\n status: number,\n): Response {\n return wireError(code, message, status);\n}\n\nexport function readUnknown(value: unknown, key: string): unknown {\n if (typeof value !== 'object' || value === null) return undefined;\n return (value as Record<string, unknown>)[key];\n}\n\nexport function readRecord(\n value: unknown,\n key: string,\n): Record<string, unknown> {\n const nested = readUnknown(value, key);\n return typeof nested === 'object' && nested !== null\n ? (nested as Record<string, unknown>)\n : {};\n}\n\nexport function readText(value: unknown, key: string): string | null {\n const nested = readUnknown(value, key);\n return typeof nested === 'string' ? nested : null;\n}\n","import { TapError, TapServerError } from '../client/errors';\nimport type { TapCredentialValues, TapDateRange } from '../client/types';\nimport { EVENTS } from '../manifest/constants';\nimport { SERVER_ERROR_CODES, type TapServerErrorCode } from './codes';\nimport { verifyTappifyToken } from './token';\nimport type {\n ContextBlock,\n LegacyWorkHandlers,\n MentionItems,\n MetricsInput,\n MetricsResponse,\n TappifyCallContext,\n TappifyClaims,\n TappifyDocuments,\n TappifyHandlerOptions,\n TappifyHealth,\n TappifyInstall,\n TappifyRequest,\n WorkOperation,\n} from './types';\nimport {\n INTERNAL_MESSAGE,\n failure,\n json,\n readRecord,\n readText,\n readUnknown,\n wireError,\n} from './wire';\n\n/**\n * What `createTappifyHandler` returns: a `fetch` handler, which is what Cloudflare\n * Workers, Vercel and the two adapters all take.\n */\nexport type TappifyFetchHandler = (request: Request) => Promise<Response>;\n\nconst MAX_BODY_BYTES = 1_048_576;\n\nconst WORK_OPERATIONS: readonly (WorkOperation | keyof LegacyWorkHandlers)[] = [\n 'configure',\n 'list_containers',\n 'describe_container',\n 'create',\n 'attach',\n 'comment',\n 'set_status',\n 'hydrate_event',\n 'disconnect',\n 'status',\n 'resolve',\n];\n\nconst TOKEN_FAILURES: readonly TapServerErrorCode[] = [\n SERVER_ERROR_CODES.TOKEN_INVALID,\n SERVER_ERROR_CODES.TOKEN_AUDIENCE,\n];\n\n/**\n * The token is signed by Tappify and the body is not, so the claims win wherever\n * they carry a value and the body only fills what the token leaves out.\n */\nfunction toInstall(payload: unknown, claims: TappifyClaims): TappifyInstall {\n const raw = readRecord(payload, 'install');\n return {\n id: claims.installId,\n extensionId: claims.extensionId,\n projectId: claims.projectId ?? readText(raw, 'projectId'),\n organizationId: claims.organizationId,\n };\n}\n\nfunction toPlatform(value: string | null): TappifyCallContext['platform'] {\n return value === 'ios' || value === 'android' ? value : 'all';\n}\n\nfunction toPreset(value: string | null): TapDateRange['preset'] {\n return value === '7d' || value === '30d' || value === '90d'\n ? value\n : undefined;\n}\n\nfunction toContext(payload: unknown): TappifyCallContext {\n const raw = readRecord(payload, 'context');\n const filters = readRecord(raw, 'filters');\n const rawRange = readRecord(filters, 'range');\n const from = readText(raw, 'from') ?? readText(rawRange, 'from') ?? '';\n const to = readText(raw, 'to') ?? readText(rawRange, 'to') ?? '';\n\n const range: TapDateRange = {\n from: readText(rawRange, 'from') ?? from,\n to: readText(rawRange, 'to') ?? to,\n };\n\n const preset = toPreset(readText(rawRange, 'preset'));\n if (preset !== undefined) range.preset = preset;\n\n return {\n projectId: readText(raw, 'projectId'),\n platform: toPlatform(readText(raw, 'platform')),\n from,\n to,\n filters: {\n range,\n platform: toPlatform(readText(filters, 'platform')),\n country: readText(filters, 'country') ?? 'all',\n },\n };\n}\n\nfunction toCredentials(payload: unknown): TapCredentialValues {\n const raw = readRecord(payload, 'credentials');\n const credentials: Record<string, string> = {};\n for (const [name, value] of Object.entries(raw)) {\n if (typeof value === 'string') credentials[name] = value;\n }\n return credentials;\n}\n\nfunction tooLarge(): TapServerError {\n return new TapServerError(\n SERVER_ERROR_CODES.BODY_TOO_LARGE,\n 'A Tappify request body is at most 1 MB. Return a cursor or an id the host can follow instead of inlining the payload.',\n 413,\n );\n}\n\nasync function readBody(request: Request): Promise<unknown> {\n if (request.method === 'GET' || request.method === 'HEAD') return {};\n\n const length = request.headers.get('content-length');\n if (length !== null && Number(length) > MAX_BODY_BYTES) throw tooLarge();\n\n const bytes = await request.arrayBuffer();\n if (bytes.byteLength > MAX_BODY_BYTES) throw tooLarge();\n if (bytes.byteLength === 0) return {};\n\n const text = new TextDecoder().decode(bytes);\n try {\n return JSON.parse(text);\n } catch {\n throw new TapServerError(\n SERVER_ERROR_CODES.BODY_INVALID,\n 'The request body was not JSON. Do not parse or re-encode the body before handing the Request to createTappifyHandler().',\n 400,\n );\n }\n}\n\n/**\n * The route table holds handlers whose declared input and event types the\n * manifest augmentations narrow. Method syntax keeps the parameter bivariant,\n * so a narrowed handler still fits a table the wire types as `unknown`.\n */\ntype AnyRouteHandler = {\n route(request: TappifyRequest<unknown, unknown>): unknown;\n}['route'];\n\ninterface Route {\n handler: AnyRouteHandler | undefined;\n input: unknown;\n event: unknown;\n noContent: boolean;\n}\n\nfunction route(\n handler: AnyRouteHandler | undefined,\n input: unknown,\n event?: unknown,\n noContent = false,\n): Route {\n return { handler, input, event, noContent };\n}\n\nfunction handlerFor(\n map: Partial<Record<string, AnyRouteHandler>> | undefined,\n name: string,\n): AnyRouteHandler | undefined {\n return map?.[name];\n}\n\nfunction resolveRoute(\n options: TappifyHandlerOptions,\n method: string,\n segments: string[],\n url: URL,\n payload: unknown,\n): Route | null {\n const [head, name] = segments;\n const bodyInput = readUnknown(payload, 'input') ?? {};\n\n if (head === 'metrics' && method === 'POST') {\n return route(options.metrics, bodyInput);\n }\n if (head === 'tools' && method === 'POST' && name !== undefined) {\n return route(handlerFor(options.tools, name), bodyInput);\n }\n if (head === 'mentions' && method === 'GET' && name !== undefined) {\n return route(handlerFor(options.mentions, name), {\n q: url.searchParams.get('q') ?? '',\n });\n }\n if (head === 'actions' && method === 'POST' && name !== undefined) {\n return route(handlerFor(options.actions, name), bodyInput);\n }\n if (head === 'procedures' && method === 'POST' && name !== undefined) {\n return route(handlerFor(options.procedures, name), bodyInput);\n }\n if (head === 'context' && method === 'GET' && name !== undefined) {\n return route(handlerFor(options.context, name), undefined);\n }\n if (head === 'work' && method === 'POST' && name !== undefined) {\n const operation = WORK_OPERATIONS.find(candidate => candidate === name);\n const handler =\n operation === undefined ? undefined : options.work?.[operation];\n if (operation === 'set_status' && handler === undefined) {\n const input = readRecord(payload, 'input');\n if (readText(input, 'tappifyStatus') === 'resolved') {\n // SDK v2 resolve handlers conventionally read the provider item as id.\n // eslint-disable-next-line @typescript-eslint/no-deprecated -- SDK v2 compatibility adapter.\n return route(options.work?.resolve, {\n ...input,\n id: readUnknown(input, 'externalItemId'),\n });\n }\n }\n return route(handler, bodyInput);\n }\n if (head === 'events' && method === 'POST') {\n const envelope = readRecord(payload, 'event');\n const eventName = readText(envelope, 'name');\n if (eventName === null) {\n throw new TapServerError(\n SERVER_ERROR_CODES.BODY_INVALID,\n 'The events body has no event.name. Post the envelope Tappify sends unchanged, with the event name under \"event\".',\n 400,\n );\n }\n const declared = EVENTS.find(candidate => candidate === eventName);\n return route(\n declared === undefined ? undefined : options.events?.[declared],\n undefined,\n readUnknown(envelope, 'payload') ?? {},\n true,\n );\n }\n\n return null;\n}\n\n/**\n * Builds the one `fetch` handler that answers every route Tappify calls on your\n * server.\n *\n * @remarks\n * It routes the paths under `/tappify`, minus `basePath` when your framework mounts\n * it under a prefix, and hands each matched handler a `TappifyRequest`. Every\n * route but `GET /tappify/health` needs a bearer install token, which it verifies\n * with `verifyTappifyToken`, and an `X-Tappify-Event-Id` header; a missing token is\n * 401 `TAP_TOKEN_MISSING`, a missing id 400 `TAP_EVENT_ID_MISSING`, an unknown path\n * 404 `TAP_ROUTE_UNKNOWN` and a declared name with no handler 404\n * `TAP_HANDLER_MISSING`. A body that is not JSON is 400 `TAP_BODY_INVALID` and one\n * over 1 MB is 413 `TAP_BODY_TOO_LARGE`. A `TapServerError` a handler throws is\n * answered with its own code and status, and a `TapError` other than the two token\n * failures with its own code and 400; anything else becomes 500\n * `TAP_INTERNAL_ERROR` with a fixed message, so an internal failure never reaches\n * the owner. An events route answers 204 with no body, and every other route\n * answers the handler's return value as JSON. Export the options object as well as\n * the handler: `createTestClient` takes the options.\n *\n * @example\n * ```ts\n * import {\n * createTappifyHandler,\n * type TappifyHandlerOptions,\n * } from '@tappify/extension-sdk/server';\n *\n * const handlerOptions: TappifyHandlerOptions = {\n * extensionId: 'starter',\n * health: () => ({ ok: true, version: '0.1.0' }),\n * procedures: {\n * getSummary: () => ({ installs: 1260, delta: 0.087, topKeyword: 'photo editor' }),\n * },\n * };\n *\n * const handler = createTappifyHandler(handlerOptions);\n * ```\n */\nexport function createTappifyHandler(\n options: TappifyHandlerOptions,\n): TappifyFetchHandler {\n const basePath = options.basePath ?? '';\n\n return async function handle(request: Request): Promise<Response> {\n const url = new URL(request.url);\n const path = url.pathname.startsWith(basePath)\n ? url.pathname.slice(basePath.length)\n : url.pathname;\n\n const segments = path.split('/').filter(segment => segment !== '');\n if (segments[0] !== 'tappify') {\n return failure(\n SERVER_ERROR_CODES.ROUTE_UNKNOWN,\n `Nothing is mounted at ${path}. Tappify calls paths under /tappify; set basePath if your framework mounts the handler under a prefix.`,\n 404,\n );\n }\n\n const rest = segments.slice(1);\n\n if (rest[0] === 'health' && request.method === 'GET') {\n const health: TappifyHealth = options.health\n ? await options.health()\n : { ok: true };\n return json(health);\n }\n\n const authorization = request.headers.get('authorization') ?? '';\n const token = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : '';\n\n if (token === '') {\n return failure(\n SERVER_ERROR_CODES.TOKEN_MISSING,\n 'This route needs an install token. Tappify sends it as Authorization: Bearer <token>; forward the header unchanged from your framework.',\n 401,\n );\n }\n\n const eventId = request.headers.get('x-tappify-event-id');\n if (eventId === null || eventId === '') {\n return failure(\n SERVER_ERROR_CODES.EVENT_ID_MISSING,\n 'Every Tappify call carries X-Tappify-Event-Id for idempotency. Forward the header unchanged, and use it as the key when you deduplicate.',\n 400,\n );\n }\n\n try {\n const claims = await verifyTappifyToken(token, {\n extensionId: options.extensionId,\n jwksUrl: options.jwksUrl,\n jwks: options.jwks,\n });\n\n const payload = await readBody(request);\n const matched = resolveRoute(options, request.method, rest, url, payload);\n\n if (matched === null) {\n return failure(\n SERVER_ERROR_CODES.ROUTE_UNKNOWN,\n `Tappify has no ${request.method} ${path} route. The paths it calls are listed under \"Vendor server endpoints\" in the reference; check the method and the spelling.`,\n 404,\n );\n }\n\n if (matched.handler === undefined) {\n return failure(\n SERVER_ERROR_CODES.HANDLER_MISSING,\n `No handler is registered for ${request.method} ${path}. Add it to createTappifyHandler(), or remove the contribution from tappify.extension.json.`,\n 404,\n );\n }\n\n const documents = readRecord(payload, 'documents');\n\n const typed: TappifyRequest<unknown, unknown> = {\n install: toInstall(payload, claims),\n context: toContext(payload),\n input: matched.input,\n credentials: toCredentials(payload),\n documents: documents as TappifyDocuments,\n event: matched.event,\n eventId,\n claims,\n request,\n };\n\n const result = await matched.handler(typed);\n return matched.noContent\n ? new Response(null, { status: 204 })\n : json(result);\n } catch (error) {\n if (TapServerError.is(error)) {\n return wireError(error.code, error.message, error.status ?? 400);\n }\n if (TapError.is(error)) {\n const tokenFailure = TOKEN_FAILURES.find(\n candidate => candidate === error.code,\n );\n if (tokenFailure !== undefined) {\n return failure(tokenFailure, error.message, 401);\n }\n return wireError(error.code, error.message, 400);\n }\n return failure(SERVER_ERROR_CODES.INTERNAL_ERROR, INTERNAL_MESSAGE, 500);\n }\n };\n}\n\nexport type {\n ContextBlock,\n MentionItems,\n MetricsInput,\n MetricsResponse,\n TappifyClaims,\n};\n"]}