@kelpie/server 0.4.0 → 0.5.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 (182) hide show
  1. package/dist/app.d.ts.map +1 -1
  2. package/dist/app.js +14 -0
  3. package/dist/app.js.map +1 -1
  4. package/dist/index.d.ts +2 -1
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +1 -0
  7. package/dist/index.js.map +1 -1
  8. package/dist/lib/appUrl.d.ts +27 -0
  9. package/dist/lib/appUrl.d.ts.map +1 -0
  10. package/dist/lib/appUrl.js +53 -0
  11. package/dist/lib/appUrl.js.map +1 -0
  12. package/dist/lib/clientIp.d.ts +24 -0
  13. package/dist/lib/clientIp.d.ts.map +1 -0
  14. package/dist/lib/clientIp.js +34 -0
  15. package/dist/lib/clientIp.js.map +1 -0
  16. package/dist/lib/config.d.ts +11 -0
  17. package/dist/lib/config.d.ts.map +1 -1
  18. package/dist/lib/config.js +23 -12
  19. package/dist/lib/config.js.map +1 -1
  20. package/dist/lib/egress.d.ts +44 -0
  21. package/dist/lib/egress.d.ts.map +1 -0
  22. package/dist/lib/egress.js +111 -0
  23. package/dist/lib/egress.js.map +1 -0
  24. package/dist/lib/email.d.ts +53 -9
  25. package/dist/lib/email.d.ts.map +1 -1
  26. package/dist/lib/email.js +49 -4
  27. package/dist/lib/email.js.map +1 -1
  28. package/dist/lib/ids.d.ts +2 -0
  29. package/dist/lib/ids.d.ts.map +1 -1
  30. package/dist/lib/ids.js +2 -0
  31. package/dist/lib/ids.js.map +1 -1
  32. package/dist/lib/rateLimit.d.ts +12 -4
  33. package/dist/lib/rateLimit.d.ts.map +1 -1
  34. package/dist/lib/rateLimit.js +6 -0
  35. package/dist/lib/rateLimit.js.map +1 -1
  36. package/dist/modules/agent-tasks/dispatch.d.ts +4 -2
  37. package/dist/modules/agent-tasks/dispatch.d.ts.map +1 -1
  38. package/dist/modules/agent-tasks/dispatch.js +4 -2
  39. package/dist/modules/agent-tasks/dispatch.js.map +1 -1
  40. package/dist/modules/agent-tasks/index.d.ts.map +1 -1
  41. package/dist/modules/agent-tasks/index.js +3 -1
  42. package/dist/modules/agent-tasks/index.js.map +1 -1
  43. package/dist/modules/auth/index.d.ts.map +1 -1
  44. package/dist/modules/auth/index.js +10 -3
  45. package/dist/modules/auth/index.js.map +1 -1
  46. package/dist/modules/auth/repository.d.ts +13 -1
  47. package/dist/modules/auth/repository.d.ts.map +1 -1
  48. package/dist/modules/auth/repository.js +32 -1
  49. package/dist/modules/auth/repository.js.map +1 -1
  50. package/dist/modules/auth/routes.d.ts.map +1 -1
  51. package/dist/modules/auth/routes.js +29 -6
  52. package/dist/modules/auth/routes.js.map +1 -1
  53. package/dist/modules/auth/schema.d.ts +130 -0
  54. package/dist/modules/auth/schema.d.ts.map +1 -1
  55. package/dist/modules/auth/schema.js +16 -0
  56. package/dist/modules/auth/schema.js.map +1 -1
  57. package/dist/modules/auth/service.d.ts +7 -1
  58. package/dist/modules/auth/service.d.ts.map +1 -1
  59. package/dist/modules/auth/service.js +53 -4
  60. package/dist/modules/auth/service.js.map +1 -1
  61. package/dist/modules/deals/repository.d.ts.map +1 -1
  62. package/dist/modules/deals/repository.js +1 -0
  63. package/dist/modules/deals/repository.js.map +1 -1
  64. package/dist/modules/decisions/repository.d.ts.map +1 -1
  65. package/dist/modules/decisions/repository.js +8 -7
  66. package/dist/modules/decisions/repository.js.map +1 -1
  67. package/dist/modules/forms/index.d.ts.map +1 -1
  68. package/dist/modules/forms/index.js +6 -1
  69. package/dist/modules/forms/index.js.map +1 -1
  70. package/dist/modules/forms/publicRoutes.d.ts +3 -0
  71. package/dist/modules/forms/publicRoutes.d.ts.map +1 -1
  72. package/dist/modules/forms/publicRoutes.js +10 -4
  73. package/dist/modules/forms/publicRoutes.js.map +1 -1
  74. package/dist/modules/forms/submission.d.ts +3 -0
  75. package/dist/modules/forms/submission.d.ts.map +1 -1
  76. package/dist/modules/forms/submission.js +6 -0
  77. package/dist/modules/forms/submission.js.map +1 -1
  78. package/dist/modules/opportunities/repository.d.ts.map +1 -1
  79. package/dist/modules/opportunities/repository.js +1 -0
  80. package/dist/modules/opportunities/repository.js.map +1 -1
  81. package/dist/modules/partnerships/repository.d.ts.map +1 -1
  82. package/dist/modules/partnerships/repository.js +1 -0
  83. package/dist/modules/partnerships/repository.js.map +1 -1
  84. package/dist/modules/raises/repository.d.ts.map +1 -1
  85. package/dist/modules/raises/repository.js +1 -0
  86. package/dist/modules/raises/repository.js.map +1 -1
  87. package/dist/modules/rate-limit/middleware.d.ts +4 -4
  88. package/dist/modules/rate-limit/middleware.d.ts.map +1 -1
  89. package/dist/modules/rate-limit/middleware.js +57 -11
  90. package/dist/modules/rate-limit/middleware.js.map +1 -1
  91. package/dist/modules/webhooks/delivery.d.ts +5 -2
  92. package/dist/modules/webhooks/delivery.d.ts.map +1 -1
  93. package/dist/modules/webhooks/delivery.js +5 -2
  94. package/dist/modules/webhooks/delivery.js.map +1 -1
  95. package/dist/modules/webhooks/index.d.ts.map +1 -1
  96. package/dist/modules/webhooks/index.js +3 -1
  97. package/dist/modules/webhooks/index.js.map +1 -1
  98. package/dist/modules/workspace/idempotencyMiddleware.d.ts.map +1 -1
  99. package/dist/modules/workspace/idempotencyMiddleware.js +14 -1
  100. package/dist/modules/workspace/idempotencyMiddleware.js.map +1 -1
  101. package/dist/modules/workspace/index.d.ts.map +1 -1
  102. package/dist/modules/workspace/index.js +3 -0
  103. package/dist/modules/workspace/index.js.map +1 -1
  104. package/dist/modules/workspace/repository.d.ts +1 -1
  105. package/dist/modules/workspace/repository.d.ts.map +1 -1
  106. package/dist/modules/workspace/repository.js +15 -9
  107. package/dist/modules/workspace/repository.js.map +1 -1
  108. package/dist/modules/workspace/routes.d.ts +0 -4
  109. package/dist/modules/workspace/routes.d.ts.map +1 -1
  110. package/dist/modules/workspace/routes.js +2 -7
  111. package/dist/modules/workspace/routes.js.map +1 -1
  112. package/dist/modules/workspace/service.d.ts +4 -2
  113. package/dist/modules/workspace/service.d.ts.map +1 -1
  114. package/dist/modules/workspace/service.js +42 -17
  115. package/dist/modules/workspace/service.js.map +1 -1
  116. package/dist/modules/workspace/tools.d.ts.map +1 -1
  117. package/dist/modules/workspace/tools.js +6 -7
  118. package/dist/modules/workspace/tools.js.map +1 -1
  119. package/dist/operator.d.ts +40 -0
  120. package/dist/operator.d.ts.map +1 -0
  121. package/dist/operator.js +50 -0
  122. package/dist/operator.js.map +1 -0
  123. package/dist/runtime/module.d.ts +25 -1
  124. package/dist/runtime/module.d.ts.map +1 -1
  125. package/dist/runtime/registry.d.ts +16 -1
  126. package/dist/runtime/registry.d.ts.map +1 -1
  127. package/dist/runtime/registry.js +34 -0
  128. package/dist/runtime/registry.js.map +1 -1
  129. package/dist/testing/client.d.ts +3 -1
  130. package/dist/testing/client.d.ts.map +1 -1
  131. package/dist/testing/client.js +19 -2
  132. package/dist/testing/client.js.map +1 -1
  133. package/dist/testing/environment.d.ts +2 -0
  134. package/dist/testing/environment.d.ts.map +1 -1
  135. package/dist/testing/environment.js +3 -0
  136. package/dist/testing/environment.js.map +1 -1
  137. package/dist/webBundle.d.ts +8 -0
  138. package/dist/webBundle.d.ts.map +1 -1
  139. package/dist/webBundle.js +9 -7
  140. package/dist/webBundle.js.map +1 -1
  141. package/migrations/0018_hard_mystique.sql +12 -0
  142. package/migrations/meta/0018_snapshot.json +5113 -0
  143. package/migrations/meta/_journal.json +8 -1
  144. package/package.json +4 -2
  145. package/src/app.ts +16 -0
  146. package/src/index.ts +9 -1
  147. package/src/lib/appUrl.ts +64 -0
  148. package/src/lib/clientIp.ts +41 -0
  149. package/src/lib/config.ts +30 -12
  150. package/src/lib/egress.ts +149 -0
  151. package/src/lib/email.ts +75 -7
  152. package/src/lib/ids.ts +2 -0
  153. package/src/lib/rateLimit.ts +16 -4
  154. package/src/modules/agent-tasks/dispatch.ts +9 -2
  155. package/src/modules/agent-tasks/index.ts +3 -1
  156. package/src/modules/auth/index.ts +12 -5
  157. package/src/modules/auth/repository.ts +56 -1
  158. package/src/modules/auth/routes.ts +34 -6
  159. package/src/modules/auth/schema.ts +17 -0
  160. package/src/modules/auth/service.ts +78 -5
  161. package/src/modules/deals/repository.ts +1 -0
  162. package/src/modules/decisions/repository.ts +8 -7
  163. package/src/modules/forms/index.ts +6 -1
  164. package/src/modules/forms/publicRoutes.ts +14 -4
  165. package/src/modules/forms/submission.ts +10 -0
  166. package/src/modules/opportunities/repository.ts +1 -0
  167. package/src/modules/partnerships/repository.ts +1 -0
  168. package/src/modules/raises/repository.ts +1 -0
  169. package/src/modules/rate-limit/middleware.ts +74 -11
  170. package/src/modules/webhooks/delivery.ts +10 -2
  171. package/src/modules/webhooks/index.ts +3 -1
  172. package/src/modules/workspace/idempotencyMiddleware.ts +17 -1
  173. package/src/modules/workspace/index.ts +4 -0
  174. package/src/modules/workspace/repository.ts +15 -8
  175. package/src/modules/workspace/routes.ts +0 -8
  176. package/src/modules/workspace/service.ts +53 -20
  177. package/src/modules/workspace/tools.ts +7 -21
  178. package/src/runtime/module.ts +25 -1
  179. package/src/runtime/registry.ts +59 -1
  180. package/src/testing/client.ts +22 -2
  181. package/src/testing/environment.ts +4 -0
  182. package/src/webBundle.ts +21 -7
@@ -1,6 +1,9 @@
1
+ import { eq } from 'drizzle-orm'
1
2
  import type { Hono } from 'hono'
2
3
 
3
4
  import type { AppBindings } from '../app.ts'
5
+ import type { Database } from '../lib/database.ts'
6
+ import { users } from '../modules/auth/schema.ts'
4
7
 
5
8
  /**
6
9
  * An HTTP client for integration tests, plus the two setups almost every suite
@@ -68,7 +71,8 @@ export function readCursor(payload: unknown): string | null {
68
71
  return payload.next_cursor
69
72
  }
70
73
 
71
- export function createTestClient(app: Hono<AppBindings>): TestClient {
74
+ /** @param db Verifies a fresh signup directly, so almost every suite can start from a working owner. */
75
+ export function createTestClient(app: Hono<AppBindings>, db: Database): TestClient {
72
76
  function send(method: string, path: string, options: TestRequestOptions = {}): Promise<Response> {
73
77
  return Promise.resolve(
74
78
  app.request(path, {
@@ -83,15 +87,31 @@ export function createTestClient(app: Hono<AppBindings>): TestClient {
83
87
  )
84
88
  }
85
89
 
90
+ /**
91
+ * Signs up and verifies directly against the database.
92
+ *
93
+ * These suites are about the CRM, not verification: going through the real
94
+ * emailed link for every signup would mean threading the email spy through
95
+ * this client too, for a step almost nothing here is testing. `auth.test.ts`
96
+ * and `workspace.test.ts` cover the real link.
97
+ */
86
98
  async function signUp(email: string): Promise<string> {
87
99
  const response = await send('POST', '/v1/auth/signup', {
88
- body: { email, name: 'Someone', password: 'correct horse battery staple' },
100
+ body: {
101
+ email,
102
+ name: 'Someone',
103
+ password: 'correct horse battery staple',
104
+ },
89
105
  })
90
106
 
91
107
  if (response.status !== 201) {
92
108
  throw new Error(`Signing up ${email} answered ${String(response.status)}`)
93
109
  }
94
110
 
111
+ const accountId = readString(readRecord(await response.json()).account, 'id')
112
+
113
+ await db.update(users).set({ emailVerifiedAt: new Date() }).where(eq(users.id, accountId))
114
+
95
115
  // Only the cookie's name=value matters to a later request; the attributes
96
116
  // after the first semicolon are for a browser.
97
117
  return (response.headers.get('Set-Cookie') ?? '').split(';')[0] ?? ''
@@ -16,7 +16,11 @@ import type { Environment } from '../lib/config.ts'
16
16
  */
17
17
  export const TEST_SECRET_ENCRYPTION_KEY = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA='
18
18
 
19
+ /** The base URL every emailed link is built from in tests. */
20
+ export const TEST_APP_BASE_URL = 'https://kelpie.test'
21
+
19
22
  export const TEST_ENVIRONMENT: Environment = {
20
23
  NODE_ENV: 'test',
21
24
  SECRET_ENCRYPTION_KEY: TEST_SECRET_ENCRYPTION_KEY,
25
+ APP_BASE_URL: TEST_APP_BASE_URL,
22
26
  }
package/src/webBundle.ts CHANGED
@@ -25,6 +25,14 @@ import { MCP_ROUTE_PREFIX } from './modules/mcp/index.ts'
25
25
  export interface WebBundleOptions {
26
26
  /** Directory holding the built `index.html` and its assets. */
27
27
  readonly directory: string
28
+ /**
29
+ * Prefixes the assembly's own modules answer on beyond core's, via
30
+ * `appRoute`/`appMiddleware`. An unmatched GET under one stays a JSON
31
+ * error instead of becoming the app shell, the same rule `/v1` gets. The
32
+ * assembly names them here because only it knows which of its declared
33
+ * paths are API rather than pages.
34
+ */
35
+ readonly apiPrefixes?: readonly string[]
28
36
  }
29
37
 
30
38
  /** The bundle directory does not hold a build. Thrown at boot, never per request. */
@@ -36,15 +44,16 @@ export class WebBundleError extends Error {
36
44
  }
37
45
 
38
46
  /**
39
- * The prefixes `createApp` answers on.
47
+ * The prefixes `createApp` itself answers on.
40
48
  *
41
49
  * `/v1/public` needs no entry of its own: it sits under `/v1`. `MCP_ROUTE_PREFIX`
42
50
  * is imported rather than written out, so moving the endpoint moves this with it.
51
+ * An assembly's own API prefixes arrive through `WebBundleOptions.apiPrefixes`.
43
52
  */
44
- const API_PREFIXES: readonly string[] = ['/v1', MCP_ROUTE_PREFIX, '/healthz']
53
+ const CORE_API_PREFIXES: readonly string[] = ['/v1', MCP_ROUTE_PREFIX, '/healthz']
45
54
 
46
- function isApiRequest(path: string): boolean {
47
- return API_PREFIXES.some((prefix) => path === prefix || path.startsWith(`${prefix}/`))
55
+ function isApiRequestFor(prefixes: readonly string[]): (path: string) => boolean {
56
+ return (path) => prefixes.some((prefix) => path === prefix || path.startsWith(`${prefix}/`))
48
57
  }
49
58
 
50
59
  /**
@@ -60,7 +69,10 @@ function isApiRequest(path: string): boolean {
60
69
  * not a page request, and only a `GET` or a `HEAD` can sensibly be answered with
61
70
  * a document.
62
71
  */
63
- function webRequestsOnly(handler: MiddlewareHandler): MiddlewareHandler {
72
+ function webRequestsOnly(
73
+ isApiRequest: (path: string) => boolean,
74
+ handler: MiddlewareHandler,
75
+ ): MiddlewareHandler {
64
76
  return async (context, next) => {
65
77
  const isDocumentRequest = context.req.method === 'GET' || context.req.method === 'HEAD'
66
78
 
@@ -93,10 +105,12 @@ export function serveWebBundle(app: Hono<AppBindings>, options: WebBundleOptions
93
105
  )
94
106
  }
95
107
 
108
+ const isApiRequest = isApiRequestFor([...CORE_API_PREFIXES, ...(options.apiPrefixes ?? [])])
109
+
96
110
  // Two registrations rather than one: `serveStatic` calls `next()` when it
97
111
  // finds no file, which is exactly the signal the fallback needs. A request
98
112
  // for a real asset is answered by the first and never reaches the second.
99
- app.use('*', webRequestsOnly(serveStatic({ root: directory })))
113
+ app.use('*', webRequestsOnly(isApiRequest, serveStatic({ root: directory })))
100
114
 
101
115
  // The single-page fallback. The app decides what to draw from the address, so
102
116
  // a deep link to `/people/per_01J…` has to return the same `index.html` even
@@ -106,5 +120,5 @@ export function serveWebBundle(app: Hono<AppBindings>, options: WebBundleOptions
106
120
  // does by default. Narrowing this by `Accept` would turn a stale asset
107
121
  // reference into a clean 404, and would also turn `curl /people/per_01J…`
108
122
  // into one, so it is left alone.
109
- app.use('*', webRequestsOnly(serveStatic({ path: indexHtml })))
123
+ app.use('*', webRequestsOnly(isApiRequest, serveStatic({ path: indexHtml })))
110
124
  }