stacks 0.58.28 → 0.58.30

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 (233) hide show
  1. package/core/README.md +1 -1
  2. package/core/actions/dist/src/deploy/index.js +1 -1
  3. package/core/actions/dist/src/dev/components.js +1 -1
  4. package/core/actions/dist/src/dev/index.js +10 -0
  5. package/core/actions/dist/src/generate/lib-entries.js +3 -3
  6. package/core/actions/dist/src/helpers/lib-entries.js +3 -3
  7. package/core/actions/dist/src/index.js +10 -0
  8. package/core/actions/dist/src/lint/fix.js +1 -1
  9. package/core/actions/dist/src/release.js +8 -0
  10. package/core/actions/dist/src/upgrade.js +1 -1
  11. package/core/actions/package.json +1 -1
  12. package/core/actions/src/build/docs.ts +7 -0
  13. package/core/actions/src/deploy/index.ts +1 -1
  14. package/core/actions/src/dev/api.ts +2 -2
  15. package/core/actions/src/dev/components.ts +1 -1
  16. package/core/actions/src/dev/dashboard.ts +7 -0
  17. package/core/actions/src/dev/desktop.ts +3 -3
  18. package/core/actions/src/dev/index.ts +10 -0
  19. package/core/actions/src/dev/system-tray.ts +6 -0
  20. package/core/actions/src/helpers/lib-entries.ts +3 -3
  21. package/core/actions/src/lint/fix.ts +1 -1
  22. package/core/ai/package.json +3 -3
  23. package/core/alias/package.json +1 -1
  24. package/core/analytics/package.json +1 -1
  25. package/core/api/package.json +1 -1
  26. package/core/arrays/package.json +1 -1
  27. package/core/auth/package.json +1 -1
  28. package/core/buddy/README.md +2 -2
  29. package/core/buddy/dist/{chunk-b75e6db0012faa55.js → chunk-0455e53fab4244b1.js} +1 -4
  30. package/core/buddy/dist/{chunk-fd47b42fb923a4be.js → chunk-61e8f138963fc709.js} +173 -137
  31. package/core/buddy/dist/cli.js +2 -2
  32. package/core/buddy/dist/index.js +2 -2
  33. package/core/buddy/package.json +1 -1
  34. package/core/buddy/src/commands/add.ts +4 -0
  35. package/core/buddy/src/commands/build.ts +12 -1
  36. package/core/buddy/src/commands/changelog.ts +2 -0
  37. package/core/buddy/src/commands/clean.ts +2 -0
  38. package/core/buddy/src/commands/cloud.ts +6 -0
  39. package/core/buddy/src/commands/commit.ts +2 -0
  40. package/core/buddy/src/commands/configure.ts +3 -0
  41. package/core/buddy/src/commands/create.ts +8 -8
  42. package/core/buddy/src/commands/deploy.ts +24 -23
  43. package/core/buddy/src/commands/dev.ts +50 -10
  44. package/core/buddy/src/commands/dns.ts +2 -0
  45. package/core/buddy/src/commands/domains.ts +2 -0
  46. package/core/buddy/src/commands/example.ts +4 -0
  47. package/core/buddy/src/commands/fresh.ts +2 -0
  48. package/core/buddy/src/commands/generate.ts +12 -3
  49. package/core/buddy/src/commands/http.ts +2 -0
  50. package/core/buddy/src/commands/install.ts +2 -0
  51. package/core/buddy/src/commands/key.ts +4 -2
  52. package/core/buddy/src/commands/lint.ts +3 -0
  53. package/core/buddy/src/commands/make.ts +11 -1
  54. package/core/buddy/src/commands/migrate.ts +3 -0
  55. package/core/buddy/src/commands/prepublish.ts +2 -0
  56. package/core/buddy/src/commands/release.ts +2 -0
  57. package/core/buddy/src/commands/seed.ts +2 -0
  58. package/core/buddy/src/commands/setup.ts +33 -34
  59. package/core/buddy/src/commands/test.ts +2 -0
  60. package/core/buddy/src/commands/tinker.ts +2 -0
  61. package/core/buddy/src/commands/types.ts +3 -0
  62. package/core/buddy/src/commands/upgrade.ts +6 -0
  63. package/core/build/package.json +1 -1
  64. package/core/bun-create/bud/package.json +3 -2
  65. package/core/bun-create/buddy/package.json +2 -2
  66. package/core/bun-create/stack/package.json +2 -2
  67. package/core/bun-create/stacks/package.json +2 -2
  68. package/core/bun-create/stx/package.json +2 -2
  69. package/core/bun-plugin-yaml/dist/index.js +1 -19
  70. package/core/bun-plugin-yaml/package.json +1 -1
  71. package/core/bun-plugin-yaml/src/index.ts +1 -1
  72. package/core/cache/package.json +2 -2
  73. package/core/chat/package.json +1 -1
  74. package/core/cli/dist/index.js +24 -23
  75. package/core/cli/package.json +1 -1
  76. package/core/cli/src/helpers.ts +21 -31
  77. package/core/cli/src/run.ts +9 -7
  78. package/core/cloud/buddy +2 -0
  79. package/core/cloud/package.json +33 -33
  80. package/core/cloud/src/cloud/ai.ts +45 -36
  81. package/core/cloud/src/cloud/aws-sdk-layer/nodejs/package-lock.json +10 -10
  82. package/core/cloud/src/cloud/aws-sdk-layer/nodejs/package.json +2 -2
  83. package/core/cloud/src/cloud/cdn.ts +44 -5
  84. package/core/cloud/src/cloud/cli.ts +45 -0
  85. package/core/cloud/src/cloud/compute.ts +1 -1
  86. package/core/cloud/src/cloud/index.ts +6 -1
  87. package/core/cloud/src/cloud/lambda/cli-setup/index.js +67 -0
  88. package/core/cloud/src/index.ts +1 -0
  89. package/core/cloud/src/runtime/example/lambda.ts +1 -1
  90. package/core/cloud/src/runtime/runtime.ts +1 -0
  91. package/core/cloud/src/runtime/server.ts +1 -1
  92. package/core/collections/package.json +1 -1
  93. package/core/config/dist/index.js +130 -122
  94. package/core/config/package.json +1 -1
  95. package/core/config/src/overrides.ts +0 -2
  96. package/core/database/package.json +2 -2
  97. package/core/database/src/kysely-bun-worker/driver.ts +1 -0
  98. package/core/database/src/kysely-bun-worker/worker.ts +1 -1
  99. package/core/datetime/package.json +1 -1
  100. package/core/desktop/dist/index.js +6 -0
  101. package/core/desktop/package.json +5 -5
  102. package/core/desktop/src/index.ts +2 -0
  103. package/core/desktop/src/system-tray.ts +1 -0
  104. package/core/development/package.json +1 -1
  105. package/core/dns/package.json +7 -7
  106. package/core/docs/build.ts +1 -1
  107. package/core/docs/dist/index.js +29878 -9995
  108. package/core/docs/package.json +6 -5
  109. package/core/docs/src/index.ts +7 -1
  110. package/core/docs/src/meta.ts +32 -0
  111. package/core/docs/src/scripts/pwa.ts +116 -0
  112. package/core/email/email-forwarder.cjs +1 -0
  113. package/core/email/package.json +3 -3
  114. package/core/email/src/server/converter.ts +1 -0
  115. package/core/email/src/server/inbound.ts +1 -0
  116. package/core/email/src/server/outbound.ts +6 -4
  117. package/core/enums/dist/index.js +4 -2
  118. package/core/enums/package.json +1 -1
  119. package/core/enums/src/index.ts +4 -2
  120. package/core/env/package.json +1 -1
  121. package/core/error-handling/package.json +1 -1
  122. package/core/eslint-config/package.json +18 -18
  123. package/core/eslint-config/src/configs/comments.ts +1 -1
  124. package/core/eslint-config/src/configs/imports.ts +9 -0
  125. package/core/eslint-config/src/configs/svelte.ts +1 -0
  126. package/core/eslint-config/src/configs/unicorn.ts +1 -1
  127. package/core/eslint-config/src/configs/vue.ts +0 -1
  128. package/core/eslint-config/src/factory.ts +2 -8
  129. package/core/events/package.json +1 -1
  130. package/core/faker/package.json +2 -2
  131. package/core/git/dist/index.js +40 -0
  132. package/core/git/package.json +3 -3
  133. package/core/git/src/index.ts +62 -0
  134. package/core/health/package.json +1 -1
  135. package/core/http/package.json +1 -1
  136. package/core/lint/package.json +1 -1
  137. package/core/logging/dist/index.js +0 -6
  138. package/core/logging/package.json +1 -1
  139. package/core/music/package.json +3 -3
  140. package/core/notifications/package.json +2 -2
  141. package/core/objects/package.json +1 -1
  142. package/core/orm/package.json +1 -1
  143. package/core/path/dist/index.js +6 -3
  144. package/core/path/package.json +1 -1
  145. package/core/path/src/index.ts +7 -3
  146. package/core/payments/dist/index.js +2 -5
  147. package/core/payments/package.json +3 -3
  148. package/core/push/package.json +1 -1
  149. package/core/query-builder/package.json +1 -1
  150. package/core/queue/package.json +1 -1
  151. package/core/raycast/package.json +5 -0
  152. package/core/raycast/src/index.ts +0 -0
  153. package/core/realtime/package.json +1 -1
  154. package/core/repl/package.json +1 -1
  155. package/core/router/dist/index.js +16 -496
  156. package/core/router/package.json +1 -1
  157. package/core/router/src/middleware.ts +16 -14
  158. package/core/scheduler/package.json +1 -1
  159. package/core/search-engine/package.json +2 -2
  160. package/core/security/dist/index.js +3 -6
  161. package/core/security/package.json +2 -2
  162. package/core/server/dist/index.js +6 -2
  163. package/core/server/package.json +2 -2
  164. package/core/server/src/config.ts +14 -9
  165. package/core/signals/package.json +1 -1
  166. package/core/slug/package.json +1 -1
  167. package/core/sms/package.json +1 -1
  168. package/core/storage/dist/index.js +28 -10
  169. package/core/storage/package.json +1 -1
  170. package/core/storage/src/files.ts +10 -2
  171. package/core/storage/src/zip.ts +1 -1
  172. package/core/strings/package.json +3 -3
  173. package/core/stx +2 -0
  174. package/core/tables/package.json +3 -3
  175. package/core/testing/package.json +3 -3
  176. package/core/tinker/package.json +1 -1
  177. package/core/tsconfig.json +4 -9
  178. package/core/tunnel/package.json +1 -1
  179. package/core/types/dist/index.js +3 -1
  180. package/core/types/package.json +13 -13
  181. package/core/types/src/cli.ts +3 -3
  182. package/core/types/src/cloud.ts +4 -1
  183. package/core/types/src/dns.ts +1 -1
  184. package/core/types/src/native.ts +0 -0
  185. package/core/types/src/stacks.ts +0 -10
  186. package/core/ui/package.json +8 -8
  187. package/core/utils/dist/index.js +1 -1
  188. package/core/utils/package.json +1 -1
  189. package/core/validation/package.json +1 -1
  190. package/core/vite/README.md +1 -1
  191. package/core/vite/build.ts +1 -1
  192. package/core/vite/dist/index.js +211 -203
  193. package/core/vite/package.json +5 -5
  194. package/core/vite/src/components.ts +1 -1
  195. package/core/vite/src/dashboard.ts +100 -0
  196. package/core/vite/src/desktop.ts +2 -58
  197. package/core/vite/src/example-vue.ts +1 -1
  198. package/core/vite/src/example-wc.ts +1 -1
  199. package/core/vite/src/functions.ts +1 -1
  200. package/core/vite/src/index.ts +9 -11
  201. package/core/vite/src/plugin/auto-imports.ts +1 -1
  202. package/core/vite/src/plugin/layouts.ts +6 -7
  203. package/core/vite/src/plugin/router.ts +3 -1
  204. package/core/vite/src/system-tray.ts +100 -0
  205. package/core/vite/src/views.ts +13 -2
  206. package/core/vite/src/web-components.ts +1 -1
  207. package/core/whois/dist/index.js +1 -2
  208. package/core/whois/package.json +1 -1
  209. package/core/whois/src/index.ts +10 -8
  210. package/core/x-ray/package.json +1 -1
  211. package/core/zsh-buddy/README.md +51 -0
  212. package/core/zsh-buddy/package.json +29 -0
  213. package/ide/dictionary.txt +12 -0
  214. package/ide/jetbrains/.idea/stacks.iml +0 -1
  215. package/ide/vscode/.vscode/settings.json +7 -2
  216. package/ide/vscode/package.json +2 -4
  217. package/package.json +12 -7
  218. package/core/cloud/src/server/.dockerignore +0 -15
  219. package/core/cloud/src/server/Dockerfile +0 -21
  220. package/core/cloud/src/server/index.ts +0 -3
  221. package/core/cloud/src/server/package.json +0 -12
  222. package/core/cloud/src/server/runtime.ts +0 -828
  223. package/core/scripts/build +0 -58
  224. package/core/scripts/ensure +0 -33
  225. package/core/scripts/fresh +0 -14
  226. package/core/scripts/link +0 -14
  227. package/core/scripts/move-built-src-files.ts +0 -28
  228. package/core/scripts/move-dts-files.ts +0 -35
  229. package/core/scripts/publish +0 -43
  230. package/core/scripts/setup +0 -176
  231. package/core/tables/vite.config.ts +0 -84
  232. package/core/vite/src/plugin/index.ts +0 -21
  233. /package/core/vite/src/{stacks.ts → plugins.ts} +0 -0
@@ -1,828 +0,0 @@
1
- /* eslint-disable */
2
- import type { Server, ServerWebSocket } from 'bun'
3
- import { AwsClient } from 'aws4fetch'
4
-
5
- interface Lambda {
6
- fetch: (request: Request, server: Server) => Promise<Response | undefined>
7
- error?: (error: unknown) => Promise<Response>
8
- websocket?: {
9
- open?: (ws: ServerWebSocket) => Promise<void>
10
- message?: (ws: ServerWebSocket, message: string) => Promise<void>
11
- close?: (ws: ServerWebSocket, code: number, reason: string) => Promise<void>
12
- }
13
- }
14
-
15
- let requestId: string | undefined
16
- let traceId: string | undefined
17
- let functionArn: string | undefined
18
- let aws: AwsClient | undefined
19
-
20
- const logger = console.log
21
-
22
- function log(level: string, ...args: any[]): void {
23
- if (!args.length)
24
- return
25
-
26
- const messages = args.map(arg => Bun.inspect(arg).replace(/\n/g, '\r'))
27
- if (requestId === undefined)
28
- logger(level, ...messages)
29
-
30
- else
31
- logger(level, `RequestId: ${requestId}`, ...messages)
32
- }
33
-
34
- console.log = (...args: any[]) => log('INFO', ...args)
35
- console.info = (...args: any[]) => log('INFO', ...args)
36
- console.warn = (...args: any[]) => log('WARN', ...args)
37
- console.error = (...args: any[]) => log('ERROR', ...args)
38
- console.debug = (...args: any[]) => log('DEBUG', ...args)
39
- console.trace = (...args: any[]) => log('TRACE', ...args)
40
-
41
- let warnings: Set<string> | undefined
42
-
43
- function warnOnce(message: string, ...args: any[]): void {
44
- if (warnings === undefined)
45
- warnings = new Set()
46
-
47
- if (warnings.has(message))
48
- return
49
-
50
- warnings.add(message)
51
- console.warn(message, ...args)
52
- }
53
-
54
- function reset(): void {
55
- requestId = undefined
56
- traceId = undefined
57
- warnings = undefined
58
- }
59
-
60
- function exit(...cause: any[]): never {
61
- console.error(...cause)
62
- process.exit(1)
63
- }
64
-
65
- function env(name: string, fallback?: string): string {
66
- const value = process.env[name] ?? fallback ?? null
67
- if (value === null)
68
- exit(`Runtime failed to find the '${name}' environment variable`)
69
-
70
- return value
71
- }
72
-
73
- const runtimeUrl = new URL(`http://${env('AWS_LAMBDA_RUNTIME_API')}/2018-06-01/`)
74
-
75
- async function fetch(url: string, options?: RequestInit): Promise<Response> {
76
- const { href } = new URL(url, runtimeUrl)
77
- const response = await globalThis.fetch(href, {
78
- ...options,
79
- timeout: false,
80
- })
81
- if (!response.ok)
82
- exit(`Runtime failed to send request to Lambda [status: ${response.status}]`)
83
-
84
- return response
85
- }
86
-
87
- async function fetchAws(url: string, options?: RequestInit): Promise<Response> {
88
- if (aws === undefined) {
89
- aws = new AwsClient({
90
- accessKeyId: env('AWS_ACCESS_KEY_ID'),
91
- secretAccessKey: env('AWS_SECRET_ACCESS_KEY'),
92
- sessionToken: env('AWS_SESSION_TOKEN'),
93
- region: env('AWS_REGION'),
94
- })
95
- }
96
- return aws.fetch(url, options)
97
- }
98
-
99
- interface LambdaError {
100
- readonly errorType: string
101
- readonly errorMessage: string
102
- readonly stackTrace?: string[]
103
- }
104
-
105
- function formatError(error: unknown): LambdaError {
106
- if (error instanceof Error) {
107
- return {
108
- errorType: error.name,
109
- errorMessage: error.message,
110
- stackTrace: error.stack?.split('\n').filter(line => !line.includes(' /opt/runtime.ts')),
111
- }
112
- }
113
- return {
114
- errorType: 'Error',
115
- errorMessage: Bun.inspect(error),
116
- }
117
- }
118
-
119
- async function sendError(type: string, cause: unknown): Promise<void> {
120
- console.error(cause)
121
- await fetch(requestId === undefined ? 'runtime/init/error' : `runtime/invocation/${requestId}/error`, {
122
- method: 'POST',
123
- headers: {
124
- 'Content-Type': 'application/vnd.aws.lambda.error+json',
125
- 'Lambda-Runtime-Function-Error-Type': `Bun.${type}`,
126
- },
127
- body: JSON.stringify(formatError(cause)),
128
- })
129
- }
130
-
131
- async function throwError(type: string, cause: unknown): Promise<never> {
132
- await sendError(type, cause)
133
- exit()
134
- }
135
-
136
- async function init(): Promise<Lambda> {
137
- const handlerName = env('_HANDLER')
138
- const index = handlerName.lastIndexOf('.')
139
- const fileName = handlerName.substring(0, index)
140
- const filePath = `${env('LAMBDA_TASK_ROOT')}/${fileName}`
141
- let file
142
- try {
143
- file = await import(filePath)
144
- }
145
- catch (cause) {
146
- if (cause instanceof Error && cause.message.startsWith('Cannot find module'))
147
- return throwError('FileDoesNotExist', `Did not find a file named '${fileName}'`)
148
-
149
- return throwError('InitError', cause)
150
- }
151
- const moduleName = handlerName.substring(index + 1) || 'fetch'
152
- let module = file.default ?? file[moduleName] ?? {}
153
- if (typeof module === 'function') {
154
- module = {
155
- fetch: module,
156
- }
157
- }
158
- else if (typeof module === 'object' && moduleName !== 'fetch') {
159
- module = {
160
- ...module,
161
- fetch: module[moduleName],
162
- }
163
- }
164
- const { fetch, websocket } = module
165
- if (typeof fetch !== 'function') {
166
- return throwError(
167
- fetch === undefined ? 'MethodDoesNotExist' : 'MethodIsNotAFunction',
168
- `${fileName} does not have a default export with a function named '${moduleName}'`,
169
- )
170
- }
171
- if (websocket === undefined)
172
- return module
173
-
174
- for (const name of ['open', 'message', 'close']) {
175
- const method = websocket[name]
176
- if (method === undefined)
177
- continue
178
-
179
- if (typeof method !== 'function') {
180
- return throwError(
181
- 'MethodIsNotAFunction',
182
- `${fileName} does not have a function named '${name}' on the default 'websocket' property`,
183
- )
184
- }
185
- }
186
- return module
187
- }
188
-
189
- interface LambdaRequest<E = any> {
190
- readonly requestId: string
191
- readonly traceId: string
192
- readonly functionArn: string
193
- readonly deadlineMs: number | null
194
- readonly event: E
195
- }
196
-
197
- async function receiveRequest(): Promise<LambdaRequest> {
198
- const response = await fetch('runtime/invocation/next')
199
- requestId = response.headers.get('Lambda-Runtime-Aws-Request-Id') ?? undefined
200
- if (requestId === undefined)
201
- exit('Runtime received a request without a request ID')
202
-
203
- traceId = response.headers.get('Lambda-Runtime-Trace-Id') ?? undefined
204
- if (traceId === undefined)
205
- exit('Runtime received a request without a trace ID')
206
-
207
- process.env._X_AMZN_TRACE_ID = traceId
208
- functionArn = response.headers.get('Lambda-Runtime-Invoked-Function-Arn') ?? undefined
209
- if (functionArn === undefined)
210
- exit('Runtime received a request without a function ARN')
211
-
212
- const deadlineMs = Number.parseInt(response.headers.get('Lambda-Runtime-Deadline-Ms') ?? '0') || null
213
- let event
214
- try {
215
- event = await response.json()
216
- }
217
- catch (cause) {
218
- exit('Runtime received a request with invalid JSON', cause)
219
- }
220
- return {
221
- requestId,
222
- traceId,
223
- functionArn,
224
- deadlineMs,
225
- event,
226
- }
227
- }
228
-
229
- interface LambdaResponse {
230
- readonly statusCode: number
231
- readonly headers?: Record<string, string>
232
- readonly isBase64Encoded?: boolean
233
- readonly body?: string
234
- readonly multiValueHeaders?: Record<string, string[]>
235
- readonly cookies?: string[]
236
- }
237
-
238
- async function formatResponse(response: Response): Promise<LambdaResponse> {
239
- const statusCode = response.status
240
- const headers = response.headers.toJSON()
241
- if (statusCode === 101) {
242
- const protocol = headers['sec-websocket-protocol']
243
- if (protocol === undefined) {
244
- return {
245
- statusCode: 200,
246
- }
247
- }
248
- return {
249
- statusCode: 200,
250
- headers: {
251
- 'Sec-WebSocket-Protocol': protocol,
252
- },
253
- }
254
- }
255
- const mime = headers['content-type']
256
- const isBase64Encoded = !mime || (!mime.startsWith('text/') && !mime.startsWith('application/json'))
257
- const body = isBase64Encoded ? Buffer.from(await response.arrayBuffer()).toString('base64') : await response.text()
258
- delete headers['set-cookie']
259
- const cookies = response.headers.getAll('Set-Cookie')
260
- if (cookies.length === 0) {
261
- return {
262
- statusCode,
263
- headers,
264
- isBase64Encoded,
265
- body,
266
- }
267
- }
268
- return {
269
- statusCode,
270
- headers,
271
- cookies,
272
- multiValueHeaders: {
273
- 'Set-Cookie': cookies,
274
- },
275
- isBase64Encoded,
276
- body,
277
- }
278
- }
279
-
280
- async function sendResponse(response: unknown): Promise<void> {
281
- if (requestId === undefined)
282
- exit('Runtime attempted to send a response without a request ID')
283
-
284
- await fetch(`runtime/invocation/${requestId}/response`, {
285
- method: 'POST',
286
- body: response === null ? null : (typeof response === 'string' ? response : JSON.stringify(response)),
287
- })
288
- }
289
-
290
- function formatBody(body?: string, isBase64Encoded?: boolean): string | null {
291
- if (body === undefined)
292
- return null
293
-
294
- if (!isBase64Encoded)
295
- return body
296
-
297
- return Buffer.from(body).toString('base64')
298
- }
299
-
300
- interface HttpEventV1 {
301
- readonly requestContext: {
302
- readonly requestId: string
303
- readonly domainName: string
304
- readonly httpMethod: string
305
- readonly path: string
306
- }
307
- readonly headers: Record<string, string>
308
- readonly multiValueHeaders?: Record<string, string[]>
309
- readonly queryStringParameters?: Record<string, string>
310
- readonly multiValueQueryStringParameters?: Record<string, string[]>
311
- readonly isBase64Encoded: boolean
312
- readonly body?: string
313
- }
314
-
315
- function isHttpEventV1(event: any): event is HttpEventV1 {
316
- return !event.Records && event.version !== '2.0' && event.version !== '0' && typeof event.requestContext === 'object'
317
- }
318
-
319
- function formatHttpEventV1(event: HttpEventV1): Request {
320
- const request = event.requestContext
321
- const headers = new Headers()
322
- for (const [name, values] of Object.entries(event.multiValueHeaders ?? {})) {
323
- for (const value of values)
324
- headers.append(name, value)
325
- }
326
- const hostname = headers.get('Host') ?? request.domainName
327
- const proto = headers.get('X-Forwarded-Proto') ?? 'http'
328
- const url = new URL(request.path, `${proto}://${hostname}/`)
329
- for (const [name, values] of Object.entries(event.multiValueQueryStringParameters ?? {})) {
330
- for (const value of values ?? [])
331
- url.searchParams.append(name, value)
332
- }
333
- return new Request(url.toString(), {
334
- method: request.httpMethod,
335
- headers,
336
- body: formatBody(event.body, event.isBase64Encoded),
337
- })
338
- }
339
-
340
- interface HttpEventV2 {
341
- readonly version: '2.0'
342
- readonly requestContext: {
343
- readonly requestId: string
344
- readonly domainName: string
345
- readonly http: {
346
- readonly method: string
347
- readonly path: string
348
- }
349
- }
350
- readonly headers: Record<string, string>
351
- readonly queryStringParameters?: Record<string, string>
352
- readonly cookies?: string[]
353
- readonly isBase64Encoded: boolean
354
- readonly body?: string
355
- }
356
-
357
- function isHttpEventV2(event: any): event is HttpEventV2 {
358
- return !event.Records && event.version === '2.0' && typeof event.requestContext === 'object'
359
- }
360
-
361
- function formatHttpEventV2(event: HttpEventV2): Request {
362
- const request = event.requestContext
363
- const headers = new Headers()
364
- for (const [name, values] of Object.entries(event.headers)) {
365
- for (const value of values.split(','))
366
- headers.append(name, value)
367
- }
368
- for (const [name, values] of Object.entries(event.queryStringParameters ?? {})) {
369
- for (const value of values.split(','))
370
- headers.append(name, value)
371
- }
372
- for (const cookie of event.cookies ?? [])
373
- headers.append('Set-Cookie', cookie)
374
-
375
- const hostname = headers.get('Host') ?? request.domainName
376
- const proto = headers.get('X-Forwarded-Proto') ?? 'http'
377
- const url = new URL(request.http.path, `${proto}://${hostname}/`)
378
- return new Request(url.toString(), {
379
- method: request.http.method,
380
- headers,
381
- body: formatBody(event.body, event.isBase64Encoded),
382
- })
383
- }
384
-
385
- function isHttpEvent(event: any): boolean {
386
- return isHttpEventV1(event) || isHttpEventV2(event)
387
- }
388
-
389
- interface WebSocketEvent {
390
- readonly headers: Record<string, string>
391
- readonly multiValueHeaders: Record<string, string[]>
392
- readonly isBase64Encoded: boolean
393
- readonly body?: string
394
- readonly requestContext: {
395
- readonly apiId: string
396
- readonly requestId: string
397
- readonly connectionId: string
398
- readonly domainName: string
399
- readonly stage: string
400
- readonly identity: {
401
- readonly sourceIp: string
402
- }
403
- } & (
404
- | {
405
- readonly eventType: 'CONNECT'
406
- }
407
- | {
408
- readonly eventType: 'MESSAGE'
409
- }
410
- | {
411
- readonly eventType: 'DISCONNECT'
412
- readonly disconnectStatusCode: number
413
- readonly disconnectReason: string
414
- }
415
- )
416
- }
417
-
418
- function isWebSocketEvent(event: any): event is WebSocketEvent {
419
- return typeof event.requestContext === 'object' && typeof event.requestContext.connectionId === 'string'
420
- }
421
-
422
- function isWebSocketUpgrade(event: any): event is WebSocketEvent {
423
- return isWebSocketEvent(event) && event.requestContext.eventType === 'CONNECT'
424
- }
425
-
426
- function formatWebSocketUpgrade(event: WebSocketEvent): Request {
427
- const request = event.requestContext
428
- const headers = new Headers()
429
- headers.set('Upgrade', 'websocket')
430
- headers.set('x-amzn-connection-id', request.connectionId)
431
- for (const [name, values] of Object.entries(event.multiValueHeaders as any)) {
432
- for (const value of (values as any) ?? [])
433
- headers.append(name, value)
434
- }
435
- const hostname = headers.get('Host') ?? request.domainName
436
- const proto = headers.get('X-Forwarded-Proto') ?? 'http'
437
- const url = new URL(`${proto}://${hostname}/${request.stage}`)
438
- return new Request(url.toString(), {
439
- headers,
440
- body: formatBody(event.body, event.isBase64Encoded),
441
- })
442
- }
443
-
444
- function formatUnknownEvent(event: unknown): Request {
445
- return new Request('https://lambda/', {
446
- method: 'POST',
447
- body: JSON.stringify(event),
448
- headers: {
449
- 'Content-Type': 'application/json;charset=utf-8',
450
- },
451
- })
452
- }
453
-
454
- function formatRequest(input: LambdaRequest): Request | undefined {
455
- const { event, requestId, traceId, functionArn, deadlineMs } = input
456
- let request: Request
457
- if (isHttpEventV2(event)) {
458
- request = formatHttpEventV2(event)
459
- }
460
- else if (isHttpEventV1(event)) {
461
- request = formatHttpEventV1(event)
462
- }
463
- else if (isWebSocketEvent(event)) {
464
- if (!isWebSocketUpgrade(event))
465
- return undefined
466
-
467
- request = formatWebSocketUpgrade(event)
468
- }
469
- else {
470
- request = formatUnknownEvent(input)
471
- }
472
- request.headers.set('x-amzn-requestid', requestId)
473
- request.headers.set('x-amzn-trace-id', traceId)
474
- request.headers.set('x-amzn-function-arn', functionArn)
475
- if (deadlineMs !== null)
476
- request.headers.set('x-amzn-deadline-ms', `${deadlineMs}`)
477
-
478
- // @ts-expect-error: Attach the original event to the Request
479
- request.aws = event
480
- return request
481
- }
482
-
483
- class LambdaServer implements Server {
484
- #lambda: Lambda
485
- #webSockets: Map<string, LambdaWebSocket>
486
- #upgrade: Response | null
487
- pendingRequests: number
488
- pendingWebSockets: number
489
- port: number
490
- hostname: string
491
- development: boolean
492
-
493
- constructor(lambda: Lambda) {
494
- this.#lambda = lambda
495
- this.#webSockets = new Map()
496
- this.#upgrade = null
497
- this.pendingRequests = 0
498
- this.pendingWebSockets = 0
499
- this.port = 80
500
- this.hostname = 'lambda'
501
- this.development = false
502
- }
503
-
504
- async accept(request: LambdaRequest): Promise<unknown> {
505
- const deadlineMs = request.deadlineMs === null ? Date.now() + 60_000 : request.deadlineMs
506
- const durationMs = Math.max(1, deadlineMs - Date.now())
507
- let response: unknown
508
- try {
509
- response = await Promise.race([
510
- new Promise<undefined>(resolve => setTimeout(resolve, durationMs)),
511
- this.#acceptRequest(request),
512
- ])
513
- }
514
- catch (cause) {
515
- await sendError('RequestError', cause)
516
- return
517
- }
518
- if (response === undefined) {
519
- await sendError('TimeoutError', 'Function timed out')
520
- return
521
- }
522
- return response
523
- }
524
-
525
- async #acceptRequest(event: LambdaRequest): Promise<unknown> {
526
- const request = formatRequest(event)
527
- let response: Response | undefined
528
- if (request === undefined) {
529
- await this.#acceptWebSocket(event.event)
530
- }
531
- else {
532
- response = await this.fetch(request)
533
- if (response.status === 101)
534
- await this.#acceptWebSocket(event.event)
535
- }
536
- if (response === undefined) {
537
- return {
538
- statusCode: 200,
539
- }
540
- }
541
- if (!isHttpEvent(event.event))
542
- return response.text()
543
-
544
- return formatResponse(response)
545
- }
546
-
547
- async #acceptWebSocket(event: WebSocketEvent): Promise<void> {
548
- const request = event.requestContext
549
- const { connectionId, eventType } = request
550
- const webSocket = this.#webSockets.get(connectionId)
551
- if (webSocket === undefined || this.#lambda.websocket === undefined)
552
- return
553
-
554
- const { open, message, close } = this.#lambda.websocket
555
- switch (eventType) {
556
- case 'CONNECT': {
557
- if (open)
558
- await open(webSocket)
559
-
560
- break
561
- }
562
- case 'MESSAGE': {
563
- if (message) {
564
- const body = formatBody(event.body, event.isBase64Encoded)
565
- if (body !== null)
566
- await message(webSocket, body)
567
- }
568
- break
569
- }
570
- case 'DISCONNECT': {
571
- try {
572
- if (close) {
573
- const { disconnectStatusCode: code, disconnectReason: reason } = request
574
- await close(webSocket, code, reason)
575
- }
576
- }
577
- finally {
578
- this.#webSockets.delete(connectionId)
579
- this.pendingWebSockets--
580
- }
581
- break
582
- }
583
- }
584
- }
585
-
586
- stop(): void {
587
- exit('Runtime exited because Server.stop() was called')
588
- }
589
-
590
- reload(options: any): void {
591
- this.#lambda = {
592
- fetch: options.fetch ?? this.#lambda.fetch,
593
- error: options.error ?? this.#lambda.error,
594
- websocket: options.websocket ?? this.#lambda.websocket,
595
- }
596
- this.port
597
- = typeof options.port === 'number'
598
- ? options.port
599
- : typeof options.port === 'string'
600
- ? Number.parseInt(options.port)
601
- : this.port
602
- this.hostname = options.hostname ?? this.hostname
603
- this.development = options.development ?? this.development
604
- }
605
-
606
- async fetch(request: Request): Promise<Response> {
607
- this.pendingRequests++
608
- try {
609
- const response = await this.#lambda.fetch(request, this)
610
- if (response instanceof Response)
611
- return response
612
-
613
- if (response === undefined && this.#upgrade !== null)
614
- return this.#upgrade
615
-
616
- throw new Error('fetch() did not return a Response')
617
- }
618
- catch (cause) {
619
- console.error(cause)
620
- if (this.#lambda.error !== undefined) {
621
- try {
622
- return await this.#lambda.error(cause)
623
- }
624
- catch (cause) {
625
- console.error(cause)
626
- }
627
- }
628
- return new Response(null, { status: 500 })
629
- }
630
- finally {
631
- this.pendingRequests--
632
- this.#upgrade = null
633
- }
634
- }
635
-
636
- upgrade<T = undefined>(
637
- request: Request,
638
- options?: {
639
- headers?: HeadersInit
640
- data?: T
641
- },
642
- ): boolean {
643
- if (request.method === 'GET' && request.headers.get('Upgrade')?.toLowerCase() === 'websocket') {
644
- this.#upgrade = new Response(null, {
645
- status: 101,
646
- headers: options?.headers,
647
- })
648
- if ('aws' in request && isWebSocketUpgrade(request.aws)) {
649
- const { connectionId } = request.aws.requestContext
650
- this.#webSockets.set(connectionId, new LambdaWebSocket(request.aws, options?.data))
651
- this.pendingWebSockets++
652
- }
653
- return true
654
- }
655
- return false
656
- }
657
-
658
- publish(topic: string, data: string | ArrayBuffer | ArrayBufferView, compress?: boolean): number {
659
- let count = 0
660
- for (const webSocket of this.#webSockets.values())
661
- count += webSocket.publish(topic, data, compress) ? 1 : 0
662
-
663
- return count
664
- }
665
- }
666
-
667
- class LambdaWebSocket implements ServerWebSocket {
668
- #connectionId: string
669
- #url: string
670
- #invokeArn: string
671
- #topics: Set<string> | null
672
- remoteAddress: string
673
- readyState: 0 | 2 | 1 | -1 | 3
674
- binaryType?: 'arraybuffer' | 'uint8array'
675
- data: any
676
-
677
- constructor(event: WebSocketEvent, data?: any) {
678
- const request = event.requestContext
679
- this.#connectionId = `${request.connectionId}`
680
- this.#url = `https://${request.domainName}/${request.stage}/@connections/${this.#connectionId}`
681
- const [region, accountId] = (functionArn ?? '').split(':').slice(3, 5)
682
- this.#invokeArn = `arn:aws:execute-api:${region}:${accountId}:${request.apiId}/${request.stage}/*`
683
- this.#topics = null
684
- this.remoteAddress = request.identity.sourceIp
685
- this.readyState = 1 // WebSocket.OPEN
686
- this.data = data
687
- }
688
-
689
- send(data: string | ArrayBuffer | ArrayBufferView, compress?: boolean): number {
690
- if (typeof data === 'string')
691
- return this.sendText(data, compress)
692
-
693
- if (data instanceof ArrayBuffer)
694
- return this.sendBinary(new Uint8Array(data), compress)
695
-
696
- const buffer = new Uint8Array(data.buffer, data.byteOffset, data.byteLength)
697
- return this.sendBinary(buffer, compress)
698
- }
699
-
700
- sendText(data: string, compress?: boolean): number {
701
- fetchAws(this.#url, {
702
- method: 'POST',
703
- body: data,
704
- })
705
- .then(({ status }) => {
706
- if (status === 403) {
707
- warnOnce(
708
- 'Failed to send WebSocket message due to insufficient IAM permissions',
709
- `Assign the following IAM policy to ${functionArn} to fix this issue:`,
710
- {
711
- Version: '2012-10-17',
712
- Statement: [
713
- {
714
- Effect: 'Allow',
715
- Action: ['execute-api:Invoke'],
716
- Resource: [this.#invokeArn],
717
- },
718
- ],
719
- },
720
- )
721
- }
722
- else {
723
- warnOnce(`Failed to send WebSocket message due to a ${status} error`)
724
- }
725
- })
726
- .catch((error) => {
727
- warnOnce('Failed to send WebSocket message', error)
728
- })
729
- return data.length
730
- }
731
-
732
- sendBinary(data: Uint8Array, compress?: boolean): number {
733
- warnOnce(
734
- 'Lambda does not support binary WebSocket messages',
735
- 'https://docs.aws.amazon.com/apigateway/latest/developerguide/websocket-api-develop-binary-media-types.html',
736
- )
737
- const base64 = Buffer.from(data).toString('base64')
738
- return this.sendText(base64, compress)
739
- }
740
-
741
- publish(topic: string, data: string | ArrayBuffer | ArrayBufferView, compress?: boolean): number {
742
- if (this.isSubscribed(topic))
743
- return this.send(data, compress)
744
-
745
- return -1
746
- }
747
-
748
- publishText(topic: string, data: string, compress?: boolean): number {
749
- if (this.isSubscribed(topic))
750
- return this.sendText(data, compress)
751
-
752
- return -1
753
- }
754
-
755
- publishBinary(topic: string, data: Uint8Array, compress?: boolean): number {
756
- if (this.isSubscribed(topic))
757
- return this.sendBinary(data, compress)
758
-
759
- return -1
760
- }
761
-
762
- close(code?: number, reason?: string): void {
763
- // TODO: code? reason?
764
- fetchAws(this.#url, {
765
- method: 'DELETE',
766
- })
767
- .then(({ status }) => {
768
- if (status === 403) {
769
- warnOnce(
770
- 'Failed to close WebSocket due to insufficient IAM permissions',
771
- `Assign the following IAM policy to ${functionArn} to fix this issue:`,
772
- {
773
- Version: '2012-10-17',
774
- Statement: [
775
- {
776
- Effect: 'Allow',
777
- Action: ['execute-api:Invoke'],
778
- Resource: [this.#invokeArn],
779
- },
780
- ],
781
- },
782
- )
783
- }
784
- else {
785
- warnOnce(`Failed to close WebSocket due to a ${status} error`)
786
- }
787
- })
788
- .catch((error) => {
789
- warnOnce('Failed to close WebSocket', error)
790
- })
791
- this.readyState = 3 // WebSocket.CLOSED;
792
- }
793
-
794
- subscribe(topic: string): void {
795
- if (this.#topics === null)
796
- this.#topics = new Set()
797
-
798
- this.#topics.add(topic)
799
- }
800
-
801
- unsubscribe(topic: string): void {
802
- if (this.#topics !== null)
803
- this.#topics.delete(topic)
804
- }
805
-
806
- isSubscribed(topic: string): boolean {
807
- return this.#topics !== null && this.#topics.has(topic)
808
- }
809
-
810
- cork(callback: (ws: ServerWebSocket<undefined>) => any): void | Promise<void> {
811
- // Lambda does not support sending multiple messages at a time.
812
- return callback(this)
813
- }
814
- }
815
-
816
- const lambda = await init()
817
- const server = new LambdaServer(lambda)
818
- while (true) {
819
- try {
820
- const request = await receiveRequest()
821
- const response = await server.accept(request)
822
- if (response !== undefined)
823
- await sendResponse(response)
824
- }
825
- finally {
826
- reset()
827
- }
828
- }