@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.
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +14 -0
- package/dist/app.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/appUrl.d.ts +27 -0
- package/dist/lib/appUrl.d.ts.map +1 -0
- package/dist/lib/appUrl.js +53 -0
- package/dist/lib/appUrl.js.map +1 -0
- package/dist/lib/clientIp.d.ts +24 -0
- package/dist/lib/clientIp.d.ts.map +1 -0
- package/dist/lib/clientIp.js +34 -0
- package/dist/lib/clientIp.js.map +1 -0
- package/dist/lib/config.d.ts +11 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +23 -12
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/egress.d.ts +44 -0
- package/dist/lib/egress.d.ts.map +1 -0
- package/dist/lib/egress.js +111 -0
- package/dist/lib/egress.js.map +1 -0
- package/dist/lib/email.d.ts +53 -9
- package/dist/lib/email.d.ts.map +1 -1
- package/dist/lib/email.js +49 -4
- package/dist/lib/email.js.map +1 -1
- package/dist/lib/ids.d.ts +2 -0
- package/dist/lib/ids.d.ts.map +1 -1
- package/dist/lib/ids.js +2 -0
- package/dist/lib/ids.js.map +1 -1
- package/dist/lib/rateLimit.d.ts +12 -4
- package/dist/lib/rateLimit.d.ts.map +1 -1
- package/dist/lib/rateLimit.js +6 -0
- package/dist/lib/rateLimit.js.map +1 -1
- package/dist/modules/agent-tasks/dispatch.d.ts +4 -2
- package/dist/modules/agent-tasks/dispatch.d.ts.map +1 -1
- package/dist/modules/agent-tasks/dispatch.js +4 -2
- package/dist/modules/agent-tasks/dispatch.js.map +1 -1
- package/dist/modules/agent-tasks/index.d.ts.map +1 -1
- package/dist/modules/agent-tasks/index.js +3 -1
- package/dist/modules/agent-tasks/index.js.map +1 -1
- package/dist/modules/auth/index.d.ts.map +1 -1
- package/dist/modules/auth/index.js +10 -3
- package/dist/modules/auth/index.js.map +1 -1
- package/dist/modules/auth/repository.d.ts +13 -1
- package/dist/modules/auth/repository.d.ts.map +1 -1
- package/dist/modules/auth/repository.js +32 -1
- package/dist/modules/auth/repository.js.map +1 -1
- package/dist/modules/auth/routes.d.ts.map +1 -1
- package/dist/modules/auth/routes.js +29 -6
- package/dist/modules/auth/routes.js.map +1 -1
- package/dist/modules/auth/schema.d.ts +130 -0
- package/dist/modules/auth/schema.d.ts.map +1 -1
- package/dist/modules/auth/schema.js +16 -0
- package/dist/modules/auth/schema.js.map +1 -1
- package/dist/modules/auth/service.d.ts +7 -1
- package/dist/modules/auth/service.d.ts.map +1 -1
- package/dist/modules/auth/service.js +53 -4
- package/dist/modules/auth/service.js.map +1 -1
- package/dist/modules/deals/repository.d.ts.map +1 -1
- package/dist/modules/deals/repository.js +1 -0
- package/dist/modules/deals/repository.js.map +1 -1
- package/dist/modules/decisions/repository.d.ts.map +1 -1
- package/dist/modules/decisions/repository.js +8 -7
- package/dist/modules/decisions/repository.js.map +1 -1
- package/dist/modules/forms/index.d.ts.map +1 -1
- package/dist/modules/forms/index.js +6 -1
- package/dist/modules/forms/index.js.map +1 -1
- package/dist/modules/forms/publicRoutes.d.ts +3 -0
- package/dist/modules/forms/publicRoutes.d.ts.map +1 -1
- package/dist/modules/forms/publicRoutes.js +10 -4
- package/dist/modules/forms/publicRoutes.js.map +1 -1
- package/dist/modules/forms/submission.d.ts +3 -0
- package/dist/modules/forms/submission.d.ts.map +1 -1
- package/dist/modules/forms/submission.js +6 -0
- package/dist/modules/forms/submission.js.map +1 -1
- package/dist/modules/opportunities/repository.d.ts.map +1 -1
- package/dist/modules/opportunities/repository.js +1 -0
- package/dist/modules/opportunities/repository.js.map +1 -1
- package/dist/modules/partnerships/repository.d.ts.map +1 -1
- package/dist/modules/partnerships/repository.js +1 -0
- package/dist/modules/partnerships/repository.js.map +1 -1
- package/dist/modules/raises/repository.d.ts.map +1 -1
- package/dist/modules/raises/repository.js +1 -0
- package/dist/modules/raises/repository.js.map +1 -1
- package/dist/modules/rate-limit/middleware.d.ts +4 -4
- package/dist/modules/rate-limit/middleware.d.ts.map +1 -1
- package/dist/modules/rate-limit/middleware.js +57 -11
- package/dist/modules/rate-limit/middleware.js.map +1 -1
- package/dist/modules/webhooks/delivery.d.ts +5 -2
- package/dist/modules/webhooks/delivery.d.ts.map +1 -1
- package/dist/modules/webhooks/delivery.js +5 -2
- package/dist/modules/webhooks/delivery.js.map +1 -1
- package/dist/modules/webhooks/index.d.ts.map +1 -1
- package/dist/modules/webhooks/index.js +3 -1
- package/dist/modules/webhooks/index.js.map +1 -1
- package/dist/modules/workspace/idempotencyMiddleware.d.ts.map +1 -1
- package/dist/modules/workspace/idempotencyMiddleware.js +14 -1
- package/dist/modules/workspace/idempotencyMiddleware.js.map +1 -1
- package/dist/modules/workspace/index.d.ts.map +1 -1
- package/dist/modules/workspace/index.js +3 -0
- package/dist/modules/workspace/index.js.map +1 -1
- package/dist/modules/workspace/repository.d.ts +1 -1
- package/dist/modules/workspace/repository.d.ts.map +1 -1
- package/dist/modules/workspace/repository.js +15 -9
- package/dist/modules/workspace/repository.js.map +1 -1
- package/dist/modules/workspace/routes.d.ts +0 -4
- package/dist/modules/workspace/routes.d.ts.map +1 -1
- package/dist/modules/workspace/routes.js +2 -7
- package/dist/modules/workspace/routes.js.map +1 -1
- package/dist/modules/workspace/service.d.ts +4 -2
- package/dist/modules/workspace/service.d.ts.map +1 -1
- package/dist/modules/workspace/service.js +42 -17
- package/dist/modules/workspace/service.js.map +1 -1
- package/dist/modules/workspace/tools.d.ts.map +1 -1
- package/dist/modules/workspace/tools.js +6 -7
- package/dist/modules/workspace/tools.js.map +1 -1
- package/dist/operator.d.ts +40 -0
- package/dist/operator.d.ts.map +1 -0
- package/dist/operator.js +50 -0
- package/dist/operator.js.map +1 -0
- package/dist/runtime/module.d.ts +25 -1
- package/dist/runtime/module.d.ts.map +1 -1
- package/dist/runtime/registry.d.ts +16 -1
- package/dist/runtime/registry.d.ts.map +1 -1
- package/dist/runtime/registry.js +34 -0
- package/dist/runtime/registry.js.map +1 -1
- package/dist/testing/client.d.ts +3 -1
- package/dist/testing/client.d.ts.map +1 -1
- package/dist/testing/client.js +19 -2
- package/dist/testing/client.js.map +1 -1
- package/dist/testing/environment.d.ts +2 -0
- package/dist/testing/environment.d.ts.map +1 -1
- package/dist/testing/environment.js +3 -0
- package/dist/testing/environment.js.map +1 -1
- package/dist/webBundle.d.ts +8 -0
- package/dist/webBundle.d.ts.map +1 -1
- package/dist/webBundle.js +9 -7
- package/dist/webBundle.js.map +1 -1
- package/migrations/0018_hard_mystique.sql +12 -0
- package/migrations/meta/0018_snapshot.json +5113 -0
- package/migrations/meta/_journal.json +8 -1
- package/package.json +4 -2
- package/src/app.ts +16 -0
- package/src/index.ts +9 -1
- package/src/lib/appUrl.ts +64 -0
- package/src/lib/clientIp.ts +41 -0
- package/src/lib/config.ts +30 -12
- package/src/lib/egress.ts +149 -0
- package/src/lib/email.ts +75 -7
- package/src/lib/ids.ts +2 -0
- package/src/lib/rateLimit.ts +16 -4
- package/src/modules/agent-tasks/dispatch.ts +9 -2
- package/src/modules/agent-tasks/index.ts +3 -1
- package/src/modules/auth/index.ts +12 -5
- package/src/modules/auth/repository.ts +56 -1
- package/src/modules/auth/routes.ts +34 -6
- package/src/modules/auth/schema.ts +17 -0
- package/src/modules/auth/service.ts +78 -5
- package/src/modules/deals/repository.ts +1 -0
- package/src/modules/decisions/repository.ts +8 -7
- package/src/modules/forms/index.ts +6 -1
- package/src/modules/forms/publicRoutes.ts +14 -4
- package/src/modules/forms/submission.ts +10 -0
- package/src/modules/opportunities/repository.ts +1 -0
- package/src/modules/partnerships/repository.ts +1 -0
- package/src/modules/raises/repository.ts +1 -0
- package/src/modules/rate-limit/middleware.ts +74 -11
- package/src/modules/webhooks/delivery.ts +10 -2
- package/src/modules/webhooks/index.ts +3 -1
- package/src/modules/workspace/idempotencyMiddleware.ts +17 -1
- package/src/modules/workspace/index.ts +4 -0
- package/src/modules/workspace/repository.ts +15 -8
- package/src/modules/workspace/routes.ts +0 -8
- package/src/modules/workspace/service.ts +53 -20
- package/src/modules/workspace/tools.ts +7 -21
- package/src/runtime/module.ts +25 -1
- package/src/runtime/registry.ts +59 -1
- package/src/testing/client.ts +22 -2
- package/src/testing/environment.ts +4 -0
- package/src/webBundle.ts +21 -7
package/src/testing/client.ts
CHANGED
|
@@ -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
|
-
|
|
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: {
|
|
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
|
|
53
|
+
const CORE_API_PREFIXES: readonly string[] = ['/v1', MCP_ROUTE_PREFIX, '/healthz']
|
|
45
54
|
|
|
46
|
-
function
|
|
47
|
-
return
|
|
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(
|
|
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
|
}
|