acrylblends 0.7.3 → 0.7.5

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 (115) hide show
  1. package/dist/index.js +5 -1
  2. package/dist/index.js.map +1 -1
  3. package/docs/framework/README.md +6 -4
  4. package/docs/framework/UPSTREAM.md +1 -1
  5. package/docs/framework/getting-started/aws-lambda.md +115 -0
  6. package/docs/framework/getting-started/bun.md +1 -3
  7. package/docs/framework/getting-started/cloudflare-pages.md +53 -0
  8. package/docs/framework/getting-started/cloudflare-workers.md +67 -0
  9. package/docs/framework/getting-started/deploy.md +5 -5
  10. package/docs/framework/getting-started/lambda-edge.md +41 -0
  11. package/docs/framework/getting-started/netlify.md +25 -0
  12. package/docs/framework/getting-started/other-runtimes.md +20 -9
  13. package/docs/framework/getting-started/service-worker.md +33 -0
  14. package/docs/framework/getting-started/vercel.md +28 -0
  15. package/docs/framework/guides/create-acrylblends.md +40 -2
  16. package/docs/framework/guides/data.md +202 -0
  17. package/docs/framework/guides/faq.md +2 -2
  18. package/docs/framework/guides/forms-and-sessions.md +34 -30
  19. package/docs/framework/helpers/conninfo.md +1 -1
  20. package/docs/framework/helpers/ssg.md +1 -1
  21. package/docs/framework/helpers/streaming.md +3 -2
  22. package/docs/framework/helpers/websocket.md +2 -2
  23. package/docs/framework/middleware/builtin/ip-restriction.md +1 -1
  24. package/docs/framework/upstream.json +3 -3
  25. package/lib/aws-lambda.d.ts +324 -0
  26. package/lib/aws-lambda.js +357 -0
  27. package/lib/bun.d.ts +75 -0
  28. package/lib/bun.js +134 -0
  29. package/lib/cloudflare-pages.d.ts +58 -0
  30. package/lib/cloudflare-pages.js +78 -0
  31. package/lib/cloudflare-workers.d.ts +21 -0
  32. package/lib/cloudflare-workers.js +80 -0
  33. package/lib/db/sqlite.d.ts +27 -0
  34. package/lib/db/sqlite.js +197 -0
  35. package/lib/db.d.ts +2 -0
  36. package/lib/db.js +2 -0
  37. package/lib/index-CseujaYI.d.ts +31 -0
  38. package/lib/index-D4FAsBZu.d.ts +85 -0
  39. package/lib/index-DaJtL7ci.d.ts +156 -0
  40. package/lib/lambda-edge.d.ts +92 -0
  41. package/lib/lambda-edge.js +105 -0
  42. package/lib/netlify.d.ts +29 -0
  43. package/lib/netlify.js +30 -0
  44. package/lib/node-server/register.js +1 -1
  45. package/lib/node-server/serve-static.js +1 -1
  46. package/lib/node-server.js +3 -3
  47. package/lib/serve-static-Cp5adzGo.js +77 -0
  48. package/lib/serve-static.d.ts +1 -30
  49. package/lib/serve-static.js +1 -76
  50. package/lib/service-worker.d.ts +48 -0
  51. package/lib/service-worker.js +38 -0
  52. package/lib/src-MWcsMS8i.js +85 -0
  53. package/lib/ssg.d.ts +1 -155
  54. package/lib/vercel.d.ts +10 -0
  55. package/lib/vercel.js +9 -0
  56. package/package.json +41 -1
  57. package/plugins/acryl-db/LICENSE +47 -0
  58. package/plugins/acryl-db/plugin.json +12 -0
  59. package/plugins/acryl-db/src/cordis.ts +13 -0
  60. package/plugins/acryl-db/src/facade/database.ts +41 -0
  61. package/plugins/acryl-db/src/facade/migrations.ts +50 -0
  62. package/plugins/acryl-db/src/facade/schema.ts +9 -0
  63. package/plugins/acryl-db/src/index.ts +19 -0
  64. package/plugins/acryl-db/src/migration-files.ts +31 -0
  65. package/plugins/acryl-db/upstream.json +18 -0
  66. package/plugins/acryl-db-sqlite/LICENSE +47 -0
  67. package/plugins/acryl-db-sqlite/plugin.json +13 -0
  68. package/plugins/acryl-db-sqlite/src/facade/node.ts +199 -0
  69. package/plugins/acryl-db-sqlite/src/index.ts +88 -0
  70. package/plugins/acryl-db-sqlite/src/open.ts +61 -0
  71. package/plugins/acryl-db-sqlite/upstream.json +21 -0
  72. package/plugins/acryl-inspect/upstream.json +1 -1
  73. package/plugins/acryl-node-server/upstream.json +1 -1
  74. package/plugins/acryl-router/src/core/adapter/aws-lambda/conninfo.ts +73 -0
  75. package/plugins/acryl-router/src/core/adapter/aws-lambda/handler.ts +682 -0
  76. package/plugins/acryl-router/src/core/adapter/aws-lambda/index.ts +14 -0
  77. package/plugins/acryl-router/src/core/adapter/aws-lambda/types.ts +173 -0
  78. package/plugins/acryl-router/src/core/adapter/bun/conninfo.ts +43 -0
  79. package/plugins/acryl-router/src/core/adapter/bun/index.ts +11 -0
  80. package/plugins/acryl-router/src/core/adapter/bun/serve-static.ts +32 -0
  81. package/plugins/acryl-router/src/core/adapter/bun/server.ts +14 -0
  82. package/plugins/acryl-router/src/core/adapter/bun/ssg.ts +26 -0
  83. package/plugins/acryl-router/src/core/adapter/bun/websocket.ts +113 -0
  84. package/plugins/acryl-router/src/core/adapter/cloudflare-pages/conninfo.ts +26 -0
  85. package/plugins/acryl-router/src/core/adapter/cloudflare-pages/handler.ts +122 -0
  86. package/plugins/acryl-router/src/core/adapter/cloudflare-pages/index.ts +11 -0
  87. package/plugins/acryl-router/src/core/adapter/cloudflare-workers/conninfo.ts +7 -0
  88. package/plugins/acryl-router/src/core/adapter/cloudflare-workers/index.ts +8 -0
  89. package/plugins/acryl-router/src/core/adapter/cloudflare-workers/serve-static-module.ts +12 -0
  90. package/plugins/acryl-router/src/core/adapter/cloudflare-workers/serve-static.ts +41 -0
  91. package/plugins/acryl-router/src/core/adapter/cloudflare-workers/utils.ts +50 -0
  92. package/plugins/acryl-router/src/core/adapter/cloudflare-workers/websocket.ts +52 -0
  93. package/plugins/acryl-router/src/core/adapter/lambda-edge/conninfo.ts +15 -0
  94. package/plugins/acryl-router/src/core/adapter/lambda-edge/handler.ts +239 -0
  95. package/plugins/acryl-router/src/core/adapter/lambda-edge/index.ts +14 -0
  96. package/plugins/acryl-router/src/core/adapter/netlify/conninfo.ts +57 -0
  97. package/plugins/acryl-router/src/core/adapter/netlify/handler.ts +10 -0
  98. package/plugins/acryl-router/src/core/adapter/netlify/index.ts +6 -0
  99. package/plugins/acryl-router/src/core/adapter/netlify/mod.ts +2 -0
  100. package/plugins/acryl-router/src/core/adapter/service-worker/handler.ts +37 -0
  101. package/plugins/acryl-router/src/core/adapter/service-worker/index.ts +36 -0
  102. package/plugins/acryl-router/src/core/adapter/service-worker/types.ts +14 -0
  103. package/plugins/acryl-router/src/core/adapter/vercel/conninfo.ts +8 -0
  104. package/plugins/acryl-router/src/core/adapter/vercel/handler.ts +8 -0
  105. package/plugins/acryl-router/src/core/adapter/vercel/index.ts +7 -0
  106. package/plugins/acryl-router/upstream.json +2 -2
  107. package/plugins/acryl-router-http/upstream.json +1 -1
  108. package/plugins/acryl-state/upstream.json +1 -1
  109. package/plugins/desktop/upstream.json +1 -1
  110. package/plugins/desktop-web/main.ts +13 -3
  111. package/plugins/desktop-web/upstream.json +1 -1
  112. package/plugins/tsconfig.json +1 -0
  113. package/templates/todo/upstream.json +1 -1
  114. package/lib/{serve-static-B14MF1Va.js → serve-static-B849HlX2.js} +1 -1
  115. package/lib/{transpile-CTnWof4z.js → transpile-QUK9b3eG.js} +1 -1
@@ -0,0 +1,50 @@
1
+ // __STATIC_CONTENT is KVNamespace
2
+ declare const __STATIC_CONTENT: unknown
3
+ declare const __STATIC_CONTENT_MANIFEST: string
4
+
5
+ export type KVAssetOptions = {
6
+ manifest?: object | string
7
+ // namespace is KVNamespace
8
+ namespace?: unknown
9
+ }
10
+
11
+ export const getContentFromKVAsset = async (
12
+ path: string,
13
+ options?: KVAssetOptions
14
+ ): Promise<ReadableStream | null> => {
15
+ let ASSET_MANIFEST: Record<string, string>
16
+
17
+ if (options && options.manifest) {
18
+ if (typeof options.manifest === 'string') {
19
+ ASSET_MANIFEST = JSON.parse(options.manifest)
20
+ } else {
21
+ ASSET_MANIFEST = options.manifest as Record<string, string>
22
+ }
23
+ } else {
24
+ if (typeof __STATIC_CONTENT_MANIFEST === 'string') {
25
+ ASSET_MANIFEST = JSON.parse(__STATIC_CONTENT_MANIFEST)
26
+ } else {
27
+ ASSET_MANIFEST = __STATIC_CONTENT_MANIFEST
28
+ }
29
+ }
30
+
31
+ // ASSET_NAMESPACE is KVNamespace
32
+ let ASSET_NAMESPACE: unknown
33
+ if (options && options.namespace) {
34
+ ASSET_NAMESPACE = options.namespace
35
+ } else {
36
+ ASSET_NAMESPACE = __STATIC_CONTENT
37
+ }
38
+
39
+ const key = ASSET_MANIFEST[path]
40
+ if (!key) {
41
+ return null
42
+ }
43
+
44
+ // @ts-expect-error ASSET_NAMESPACE is not typed
45
+ const content = await ASSET_NAMESPACE.get(key, { type: 'stream' })
46
+ if (!content) {
47
+ return null
48
+ }
49
+ return content as unknown as ReadableStream
50
+ }
@@ -0,0 +1,52 @@
1
+ import { WSContext, defineWebSocketHelper } from '../../helper/websocket/index.ts'
2
+ import type { UpgradeWebSocket, WSEvents, WSReadyState } from '../../helper/websocket/index.ts'
3
+
4
+ export const upgradeWebSocket: UpgradeWebSocket<
5
+ WebSocket,
6
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
+ any,
8
+ Omit<WSEvents<WebSocket>, 'onOpen'>
9
+ > = defineWebSocketHelper(async (c, events) => {
10
+ const upgradeHeader = c.req.header('Upgrade')
11
+ if (upgradeHeader !== 'websocket') {
12
+ return
13
+ }
14
+
15
+ // @ts-expect-error WebSocketPair is not typed
16
+ const webSocketPair = new WebSocketPair()
17
+ const client: WebSocket = webSocketPair[0]
18
+ const server: WebSocket = webSocketPair[1]
19
+
20
+ const wsContext = new WSContext<WebSocket>({
21
+ close: (code, reason) => server.close(code, reason),
22
+ get protocol() {
23
+ return server.protocol
24
+ },
25
+ raw: server,
26
+ get readyState() {
27
+ return server.readyState as WSReadyState
28
+ },
29
+ url: server.url ? new URL(server.url) : null,
30
+ send: (source) => server.send(source),
31
+ })
32
+
33
+ // note: cloudflare workers doesn't support 'open' event
34
+
35
+ if (events.onClose) {
36
+ server.addEventListener('close', (evt: CloseEvent) => events.onClose?.(evt, wsContext))
37
+ }
38
+ if (events.onMessage) {
39
+ server.addEventListener('message', (evt: MessageEvent) => events.onMessage?.(evt, wsContext))
40
+ }
41
+ if (events.onError) {
42
+ server.addEventListener('error', (evt: Event) => events.onError?.(evt, wsContext))
43
+ }
44
+
45
+ // @ts-expect-error - server.accept is not typed
46
+ server.accept?.()
47
+ return new Response(null, {
48
+ status: 101,
49
+ // @ts-expect-error - webSocket is not typed
50
+ webSocket: client,
51
+ })
52
+ })
@@ -0,0 +1,15 @@
1
+ import type { Context } from '../../context.ts'
2
+ import type { GetConnInfo } from '../../helper/conninfo/index.ts'
3
+ import type { CloudFrontEdgeEvent } from './handler.ts'
4
+
5
+ type Env = {
6
+ Bindings: {
7
+ event: CloudFrontEdgeEvent
8
+ }
9
+ }
10
+
11
+ export const getConnInfo: GetConnInfo = (c: Context<Env>) => ({
12
+ remote: {
13
+ address: c.env.event.Records[0].cf.request.clientIp,
14
+ },
15
+ })
@@ -0,0 +1,239 @@
1
+ import crypto from 'node:crypto'
2
+ import type { Acryl } from '../../app.ts'
3
+ import { decodeBase64, encodeBase64 } from '../../utils/encode.ts'
4
+
5
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
6
+ // @ts-ignore
7
+ globalThis.crypto ??= crypto
8
+
9
+ interface CloudFrontHeader {
10
+ key: string
11
+ value: string
12
+ }
13
+
14
+ interface CloudFrontHeaders {
15
+ [name: string]: CloudFrontHeader[]
16
+ }
17
+
18
+ interface CloudFrontCustomOrigin {
19
+ customHeaders: CloudFrontHeaders
20
+ domainName: string
21
+ keepaliveTimeout: number
22
+ path: string
23
+ port: number
24
+ protocol: string
25
+ readTimeout: number
26
+ sslProtocols: string[]
27
+ }
28
+ // https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-event-structure.html
29
+ interface CloudFrontS3Origin {
30
+ authMethod: 'origin-access-identity' | 'none'
31
+ customHeaders: CloudFrontHeaders
32
+ domainName: string
33
+ path: string
34
+ region: string
35
+ }
36
+ type CloudFrontOrigin =
37
+ | { s3: CloudFrontS3Origin; custom?: never }
38
+ | { custom: CloudFrontCustomOrigin; s3?: never }
39
+
40
+ export interface CloudFrontRequest {
41
+ clientIp: string
42
+ headers: CloudFrontHeaders
43
+ method: string
44
+ querystring: string
45
+ uri: string
46
+ body?: {
47
+ inputTruncated: boolean
48
+ action: string
49
+ encoding: string
50
+ data: string
51
+ }
52
+ origin?: CloudFrontOrigin
53
+ }
54
+
55
+ export interface CloudFrontResponse {
56
+ headers: CloudFrontHeaders
57
+ status: string
58
+ statusDescription?: string
59
+ }
60
+
61
+ export interface CloudFrontConfig {
62
+ distributionDomainName: string
63
+ distributionId: string
64
+ eventType: string
65
+ requestId: string
66
+ }
67
+
68
+ interface CloudFrontEvent {
69
+ cf: {
70
+ config: CloudFrontConfig
71
+ request: CloudFrontRequest
72
+ response?: CloudFrontResponse
73
+ }
74
+ }
75
+
76
+ export interface CloudFrontEdgeEvent {
77
+ Records: CloudFrontEvent[]
78
+ }
79
+
80
+ type CloudFrontContext = {}
81
+
82
+ export interface Callback {
83
+ (err: Error | null, result?: CloudFrontRequest | CloudFrontResult): void
84
+ }
85
+
86
+ // https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-generating-http-responses-in-requests.html#lambda-generating-http-responses-programming-model
87
+ interface CloudFrontResult {
88
+ status: string
89
+ statusDescription?: string
90
+ headers?: {
91
+ [header: string]: {
92
+ key: string
93
+ value: string
94
+ }[]
95
+ }
96
+ body?: string
97
+ bodyEncoding?: 'text' | 'base64'
98
+ }
99
+
100
+ /**
101
+ * Accepts events from 'Lambda@Edge' event
102
+ * https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-event-structure.html
103
+ */
104
+ const convertHeaders = (headers: Headers): CloudFrontHeaders => {
105
+ const cfHeaders: CloudFrontHeaders = {}
106
+ headers.forEach((value, key) => {
107
+ cfHeaders[key.toLowerCase()] = [
108
+ ...(cfHeaders[key.toLowerCase()] || []),
109
+ { key: key.toLowerCase(), value },
110
+ ]
111
+ })
112
+ return cfHeaders
113
+ }
114
+
115
+ export const handle = (
116
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
117
+ app: Acryl<any>
118
+ ): ((
119
+ event: CloudFrontEdgeEvent,
120
+ context?: CloudFrontContext,
121
+ callback?: Callback
122
+ ) => Promise<CloudFrontResult | CloudFrontRequest>) => {
123
+ return async (event, ...args: [context?: CloudFrontContext, callback?: Callback]) => {
124
+ const [context, callback] = args
125
+ let callbackError: Error | null = null
126
+ let callbackResult: CloudFrontResult | CloudFrontRequest | undefined
127
+ const cf = getCloudFrontRecord(event)
128
+ const res = await app.fetch(createRequest(cf), {
129
+ event,
130
+ context,
131
+ callback: (err: Error | null, result?: CloudFrontResult | CloudFrontRequest) => {
132
+ if (!callbackError && !callbackResult) {
133
+ callbackError = err
134
+ callbackResult = result
135
+ }
136
+ callback?.(err, result)
137
+ },
138
+ config: cf.config,
139
+ request: cf.request,
140
+ response: cf.response,
141
+ })
142
+ if (callbackError) {
143
+ throw callbackError
144
+ }
145
+ return callbackResult ?? createResult(res)
146
+ }
147
+ }
148
+
149
+ const createResult = async (res: Response): Promise<CloudFrontResult> => {
150
+ const contentEncoding = res.headers.get('content-encoding')
151
+ const isBase64Encoded =
152
+ isContentTypeBinary(res.headers.get('content-type') || '') ||
153
+ (!!contentEncoding && !/^identity$/i.test(contentEncoding))
154
+ const body = isBase64Encoded ? encodeBase64(await res.arrayBuffer()) : await res.text()
155
+
156
+ return {
157
+ status: res.status.toString(),
158
+ headers: convertHeaders(res.headers),
159
+ body,
160
+ ...(isBase64Encoded && { bodyEncoding: 'base64' }),
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Reads the CloudFront record out of a Lambda@Edge event.
166
+ *
167
+ * The event is supplied by the runtime, so a malformed one means the function
168
+ * was invoked with something other than a Lambda@Edge event. Fail with a
169
+ * message naming the adapter and the missing field, rather than letting an
170
+ * unattributable property access error escape.
171
+ */
172
+ const getCloudFrontRecord = (event: CloudFrontEdgeEvent): CloudFrontEvent['cf'] => {
173
+ const cf = event?.Records?.[0]?.cf
174
+ if (!cf?.request) {
175
+ throw new TypeError(
176
+ 'Unable to map the CloudFront event to a Request: expected `Records[0].cf.request` in the Lambda@Edge event.'
177
+ )
178
+ }
179
+ return cf
180
+ }
181
+
182
+ const createRequest = (cf: CloudFrontEvent['cf']): Request => {
183
+ const request = cf.request
184
+ const queryString = request.querystring
185
+ const host = request.headers?.host?.[0]?.value || cf.config?.distributionDomainName
186
+ const urlPath = `https://${host}${request.uri}`
187
+ const url = queryString ? `${urlPath}?${queryString}` : urlPath
188
+
189
+ const headers = new Headers()
190
+ Object.entries(request.headers ?? {}).forEach(([k, v]) => {
191
+ v.forEach((header) => headers.append(k, header.value))
192
+ })
193
+
194
+ const requestBody = request.body
195
+ const method = request.method
196
+ const rawBody = createBody(method, requestBody)
197
+
198
+ let body: string | Uint8Array<ArrayBuffer> | undefined = rawBody
199
+ if (rawBody !== undefined) {
200
+ const bytes =
201
+ typeof rawBody === 'string'
202
+ ? (new TextEncoder().encode(rawBody) as Uint8Array<ArrayBuffer>)
203
+ : rawBody
204
+ body = bytes
205
+ headers.set('content-length', bytes.length.toString())
206
+ }
207
+
208
+ return new Request(url, {
209
+ headers,
210
+ method,
211
+ body,
212
+ })
213
+ }
214
+
215
+ export const createBody = (
216
+ method: string,
217
+ requestBody: CloudFrontRequest['body']
218
+ ): string | Uint8Array<ArrayBuffer> | undefined => {
219
+ if (!requestBody || !requestBody.data) {
220
+ return undefined
221
+ }
222
+ if (method === 'GET' || method === 'HEAD') {
223
+ return undefined
224
+ }
225
+ if (requestBody.encoding === 'base64') {
226
+ return decodeBase64(requestBody.data)
227
+ }
228
+ return requestBody.data
229
+ }
230
+
231
+ export const isContentTypeBinary = (contentType: string): boolean => {
232
+ if (/^application\/vnd\.(?:apple\.installer|mozilla\.xul)\+xml\s*(?:;|$)/i.test(contentType)) {
233
+ return true
234
+ }
235
+
236
+ return !/^text\/(?:plain|html|css|javascript|csv)|(?:\/|\+)(?:json|xml)\s*(?:;|$)/i.test(
237
+ contentType
238
+ )
239
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @module
3
+ * Lambda@Edge Adapter for Acryl.
4
+ */
5
+
6
+ export { handle } from './handler.ts'
7
+ export { getConnInfo } from './conninfo.ts'
8
+ export type {
9
+ Callback,
10
+ CloudFrontConfig,
11
+ CloudFrontRequest,
12
+ CloudFrontResponse,
13
+ CloudFrontEdgeEvent,
14
+ } from './handler.ts'
@@ -0,0 +1,57 @@
1
+ import type { Context } from '../../context.ts'
2
+ import type { GetConnInfo } from '../../helper/conninfo/index.ts'
3
+
4
+ /**
5
+ * Netlify context type
6
+ * @see https://docs.netlify.com/functions/api/
7
+ */
8
+ type NetlifyContext = {
9
+ ip?: string
10
+ geo?: {
11
+ city?: string
12
+ country?: {
13
+ code?: string
14
+ name?: string
15
+ }
16
+ subdivision?: {
17
+ code?: string
18
+ name?: string
19
+ }
20
+ latitude?: number
21
+ longitude?: number
22
+ timezone?: string
23
+ postalCode?: string
24
+ }
25
+ requestId?: string
26
+ }
27
+
28
+ type Env = {
29
+ Bindings: {
30
+ context: NetlifyContext
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Get connection information from Netlify
36
+ * @param c - Context
37
+ * @returns Connection information including remote address
38
+ * @example
39
+ * ```ts
40
+ * import { Acryl } from 'acrylblends'
41
+ * import { handle, getConnInfo } from 'acrylblends/netlify'
42
+ *
43
+ * const app = new Acryl()
44
+ *
45
+ * app.get('/', (c) => {
46
+ * const info = getConnInfo(c)
47
+ * return c.text(`Your IP: ${info.remote.address}`)
48
+ * })
49
+ *
50
+ * export default handle(app)
51
+ * ```
52
+ */
53
+ export const getConnInfo: GetConnInfo = (c: Context<Env>) => ({
54
+ remote: {
55
+ address: c.env.context?.ip,
56
+ },
57
+ })
@@ -0,0 +1,10 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import type { Acryl } from '../../app.ts'
3
+
4
+ export const handle = (
5
+ app: Acryl<any, any>
6
+ ): ((req: Request, context: any) => Response | Promise<Response>) => {
7
+ return (req: Request, context: any) => {
8
+ return app.fetch(req, { context })
9
+ }
10
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @module
3
+ * Netlify Adapter for Acryl.
4
+ */
5
+
6
+ export * from './mod.ts'
@@ -0,0 +1,2 @@
1
+ export { handle } from './handler.ts'
2
+ export { getConnInfo } from './conninfo.ts'
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Handler for Service Worker
3
+ * @module
4
+ */
5
+
6
+ import type { Acryl } from '../../app.ts'
7
+ import type { Env, Schema } from '../../types.ts'
8
+ import type { FetchEvent } from './types.ts'
9
+
10
+ type Handler = (evt: FetchEvent) => void
11
+ export type HandleOptions = {
12
+ fetch?: typeof fetch
13
+ }
14
+
15
+ /**
16
+ * Adapter for Service Worker
17
+ */
18
+ export const handle = <E extends Env, S extends Schema, BasePath extends string>(
19
+ app: Acryl<E, S, BasePath>,
20
+ opts: HandleOptions = {
21
+ // To use `fetch` on a Service Worker correctly, bind it to `globalThis`.
22
+ fetch: globalThis.fetch.bind(globalThis),
23
+ }
24
+ ): Handler => {
25
+ return (evt) => {
26
+ evt.respondWith(
27
+ (async () => {
28
+ // @ts-expect-error Passing FetchEvent but app.fetch expects ExecutionContext
29
+ const res = await app.fetch(evt.request, {}, evt)
30
+ if (opts.fetch && res.status === 404) {
31
+ return await opts.fetch(evt.request)
32
+ }
33
+ return res
34
+ })()
35
+ )
36
+ }
37
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Service Worker Adapter for Acryl.
3
+ * @module
4
+ */
5
+ import type { Acryl } from '../../app.ts'
6
+ import type { Env, Schema } from '../../types.ts'
7
+ import { handle } from './handler.ts'
8
+ import type { HandleOptions } from './handler.ts'
9
+
10
+ /**
11
+ * Registers a Acryl app to handle fetch events in a service worker.
12
+ * This sets up `addEventListener('fetch', handle(app, options))` for the provided app.
13
+ *
14
+ * @param app - The Acryl application instance
15
+ * @param options - Options for handling requests (fetch defaults to undefined)
16
+ * @example
17
+ * ```ts
18
+ * import { Acryl } from 'acrylblends'
19
+ * import { fire } from 'acrylblends/service-worker'
20
+ *
21
+ * const app = new Acryl()
22
+ *
23
+ * app.get('/', (c) => c.text('Hi'))
24
+ *
25
+ * fire(app)
26
+ * ```
27
+ */
28
+ const fire = <E extends Env, S extends Schema, BasePath extends string>(
29
+ app: Acryl<E, S, BasePath>,
30
+ options?: HandleOptions
31
+ ): void => {
32
+ // @ts-expect-error addEventListener is not typed well in ServiceWorker-like contexts, see: https://github.com/microsoft/TypeScript/issues/14877
33
+ addEventListener('fetch', handle(app, options))
34
+ }
35
+
36
+ export { handle, fire }
@@ -0,0 +1,14 @@
1
+ interface ExtendableEvent extends Event {
2
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3
+ waitUntil(f: Promise<any>): void
4
+ }
5
+
6
+ export interface FetchEvent extends ExtendableEvent {
7
+ readonly clientId: string
8
+ readonly handled: Promise<void>
9
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
+ readonly preloadResponse: Promise<any>
11
+ readonly request: Request
12
+ readonly resultingClientId: string
13
+ respondWith(r: Response | PromiseLike<Response>): void
14
+ }
@@ -0,0 +1,8 @@
1
+ import type { GetConnInfo } from '../../helper/conninfo/index.ts'
2
+
3
+ export const getConnInfo: GetConnInfo = (c) => ({
4
+ remote: {
5
+ // https://github.com/vercel/vercel/blob/b70bfb5fbf28a4650d4042ce68ca5c636d37cf44/packages/edge/src/edge-headers.ts#L10-L12C32
6
+ address: c.req.header('x-real-ip'),
7
+ },
8
+ })
@@ -0,0 +1,8 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import type { Acryl } from '../../app.ts'
3
+
4
+ export const handle =
5
+ (app: Acryl<any, any, any>) =>
6
+ (req: Request): Response | Promise<Response> => {
7
+ return app.fetch(req)
8
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @module
3
+ * Vercel Adapter for Acryl.
4
+ */
5
+
6
+ export { handle } from './handler.ts'
7
+ export { getConnInfo } from './conninfo.ts'
@@ -2,7 +2,7 @@
2
2
  "source": {
3
3
  "repo": "acryldev/acryl",
4
4
  "path": "plugins/acryl-router",
5
- "commit": "e79bf3de59c700667ae617f5d367cc1fd2f8e037"
5
+ "commit": "4721bb00960fa4e157e6070c330cf87c148739fc"
6
6
  },
7
7
  "vendored": [
8
8
  "src",
@@ -10,7 +10,7 @@
10
10
  "LICENSE"
11
11
  ],
12
12
  "rewrittenSpecifiers": 6,
13
- "digest": "sha256:1dd20cb7b5bb87e4368ce507dfe85de3e992cc90069a3b43e24af053b1186842",
13
+ "digest": "sha256:07772b7e7a2cfe416e4566bebdec7e85fb4abc30466bf104e80342b9fc4ab007",
14
14
  "testsNotRun": {
15
15
  "tests/loader.spec.ts": "loads the built lib/ entry through the real Loader; the library ships source and has no build"
16
16
  },
@@ -2,7 +2,7 @@
2
2
  "source": {
3
3
  "repo": "acryldev/acryl",
4
4
  "path": "plugins/acryl-router-http",
5
- "commit": "e79bf3de59c700667ae617f5d367cc1fd2f8e037"
5
+ "commit": "4721bb00960fa4e157e6070c330cf87c148739fc"
6
6
  },
7
7
  "vendored": [
8
8
  "src",
@@ -2,7 +2,7 @@
2
2
  "source": {
3
3
  "repo": "acryldev/acryl",
4
4
  "path": "plugins/acryl-state",
5
- "commit": "e79bf3de59c700667ae617f5d367cc1fd2f8e037"
5
+ "commit": "4721bb00960fa4e157e6070c330cf87c148739fc"
6
6
  },
7
7
  "vendored": [
8
8
  "src",
@@ -5,7 +5,7 @@
5
5
  "apps/acryl-desktop/src/shell/desktop-user-data.ts": "user-data.ts",
6
6
  "apps/acryl-desktop/src/shell/shutdown.ts": "shutdown.ts"
7
7
  },
8
- "commit": "e79bf3de59c700667ae617f5d367cc1fd2f8e037"
8
+ "commit": "4721bb00960fa4e157e6070c330cf87c148739fc"
9
9
  },
10
10
  "rewrittenSpecifiers": 0,
11
11
  "digest": "sha256:7dfe960a8bbb476c35540d2202a7aaa62b33492c2ac3e34314e2ecfca45f32f2"
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The desktop shell of a plain Acryl app: `npm run desktop` serves the app (`src/app.ts`, `export default app`) on a free loopback port from this Electron main process and
2
+ * The desktop shell of a plain Acryl app: `npm run desktop` serves the app (`src/app.ts`: `export default app`, and optionally `export const close` for a database to close) on a free loopback port from this Electron main process and
3
3
  * opens one window on it. Nothing in the app imports electron; every Electron call is in this file and `window.ts`, so a different shell is a change here only.
4
4
  *
5
5
  * Order matters: the user-data folder is set before anything else (and before the single-instance lock), so one app never shares window state, logs or the lock with
@@ -36,13 +36,19 @@ if (!app.requestSingleInstanceLock()) {
36
36
  } else {
37
37
  let server: ReturnType<typeof serve> | undefined
38
38
  let window: BrowserWindow | undefined
39
+ /** The app module's optional `close`, for an app that owns a database: called once the server has stopped. */
40
+ let closeApp: (() => Promise<void>) | undefined
39
41
 
40
42
  const shutdown = createDesktopShutdown(
41
43
  () =>
42
44
  new Promise<void>((done) => {
43
45
  if (server === undefined) return done()
44
46
  server.close(() => {
45
- done()
47
+ const closing = closeApp?.() ?? Promise.resolve()
48
+ closing.then(
49
+ () => done(),
50
+ () => done(),
51
+ )
46
52
  })
47
53
  // Open streams and keep-alive connections would hold close() open: end them.
48
54
  ;(server as { closeAllConnections?: () => void }).closeAllConnections?.()
@@ -67,7 +73,11 @@ if (!app.requestSingleInstanceLock()) {
67
73
 
68
74
  void app.whenReady().then(async () => {
69
75
  // The hook above is registered before the app's modules are loaded, so the app may have .tsx pages: they are loaded here, not as a static import.
70
- const { default: acrylApp } = await import('../../app.ts')
76
+ const { default: acrylApp, close } = (await import('../../app.ts')) as {
77
+ default: { fetch: Parameters<typeof serve>[0]['fetch'] }
78
+ close?: () => Promise<void>
79
+ }
80
+ closeApp = close
71
81
  const port = await new Promise<number>((resolve) => {
72
82
  server = serve(
73
83
  { fetch: acrylApp.fetch, port: 0, hostname: '127.0.0.1' },
@@ -5,7 +5,7 @@
5
5
  "apps/acryl-desktop/src/shell/desktop-user-data.ts": "user-data.ts",
6
6
  "apps/acryl-desktop/src/shell/shutdown.ts": "shutdown.ts"
7
7
  },
8
- "commit": "e79bf3de59c700667ae617f5d367cc1fd2f8e037"
8
+ "commit": "4721bb00960fa4e157e6070c330cf87c148739fc"
9
9
  },
10
10
  "rewrittenSpecifiers": 0,
11
11
  "digest": "sha256:7dfe960a8bbb476c35540d2202a7aaa62b33492c2ac3e34314e2ecfca45f32f2"
@@ -23,6 +23,7 @@
23
23
  "acryl-*/src/**/*.case.ts",
24
24
  "acryl-node-server/tests/adapter",
25
25
  "acryl-router/tests/loader.spec.ts",
26
+ "acryl-db-sqlite/tests/loader.spec.ts",
26
27
  "acryl-state/src/react.ts",
27
28
  "acryl-state/tests/react.spec.ts",
28
29
  "acryl-router-http/tests/adapter.spec.ts",