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.
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/docs/framework/README.md +6 -4
- package/docs/framework/UPSTREAM.md +1 -1
- package/docs/framework/getting-started/aws-lambda.md +115 -0
- package/docs/framework/getting-started/bun.md +1 -3
- package/docs/framework/getting-started/cloudflare-pages.md +53 -0
- package/docs/framework/getting-started/cloudflare-workers.md +67 -0
- package/docs/framework/getting-started/deploy.md +5 -5
- package/docs/framework/getting-started/lambda-edge.md +41 -0
- package/docs/framework/getting-started/netlify.md +25 -0
- package/docs/framework/getting-started/other-runtimes.md +20 -9
- package/docs/framework/getting-started/service-worker.md +33 -0
- package/docs/framework/getting-started/vercel.md +28 -0
- package/docs/framework/guides/create-acrylblends.md +40 -2
- package/docs/framework/guides/data.md +202 -0
- package/docs/framework/guides/faq.md +2 -2
- package/docs/framework/guides/forms-and-sessions.md +34 -30
- package/docs/framework/helpers/conninfo.md +1 -1
- package/docs/framework/helpers/ssg.md +1 -1
- package/docs/framework/helpers/streaming.md +3 -2
- package/docs/framework/helpers/websocket.md +2 -2
- package/docs/framework/middleware/builtin/ip-restriction.md +1 -1
- package/docs/framework/upstream.json +3 -3
- package/lib/aws-lambda.d.ts +324 -0
- package/lib/aws-lambda.js +357 -0
- package/lib/bun.d.ts +75 -0
- package/lib/bun.js +134 -0
- package/lib/cloudflare-pages.d.ts +58 -0
- package/lib/cloudflare-pages.js +78 -0
- package/lib/cloudflare-workers.d.ts +21 -0
- package/lib/cloudflare-workers.js +80 -0
- package/lib/db/sqlite.d.ts +27 -0
- package/lib/db/sqlite.js +197 -0
- package/lib/db.d.ts +2 -0
- package/lib/db.js +2 -0
- package/lib/index-CseujaYI.d.ts +31 -0
- package/lib/index-D4FAsBZu.d.ts +85 -0
- package/lib/index-DaJtL7ci.d.ts +156 -0
- package/lib/lambda-edge.d.ts +92 -0
- package/lib/lambda-edge.js +105 -0
- package/lib/netlify.d.ts +29 -0
- package/lib/netlify.js +30 -0
- package/lib/node-server/register.js +1 -1
- package/lib/node-server/serve-static.js +1 -1
- package/lib/node-server.js +3 -3
- package/lib/serve-static-Cp5adzGo.js +77 -0
- package/lib/serve-static.d.ts +1 -30
- package/lib/serve-static.js +1 -76
- package/lib/service-worker.d.ts +48 -0
- package/lib/service-worker.js +38 -0
- package/lib/src-MWcsMS8i.js +85 -0
- package/lib/ssg.d.ts +1 -155
- package/lib/vercel.d.ts +10 -0
- package/lib/vercel.js +9 -0
- package/package.json +41 -1
- package/plugins/acryl-db/LICENSE +47 -0
- package/plugins/acryl-db/plugin.json +12 -0
- package/plugins/acryl-db/src/cordis.ts +13 -0
- package/plugins/acryl-db/src/facade/database.ts +41 -0
- package/plugins/acryl-db/src/facade/migrations.ts +50 -0
- package/plugins/acryl-db/src/facade/schema.ts +9 -0
- package/plugins/acryl-db/src/index.ts +19 -0
- package/plugins/acryl-db/src/migration-files.ts +31 -0
- package/plugins/acryl-db/upstream.json +18 -0
- package/plugins/acryl-db-sqlite/LICENSE +47 -0
- package/plugins/acryl-db-sqlite/plugin.json +13 -0
- package/plugins/acryl-db-sqlite/src/facade/node.ts +199 -0
- package/plugins/acryl-db-sqlite/src/index.ts +88 -0
- package/plugins/acryl-db-sqlite/src/open.ts +61 -0
- package/plugins/acryl-db-sqlite/upstream.json +21 -0
- package/plugins/acryl-inspect/upstream.json +1 -1
- package/plugins/acryl-node-server/upstream.json +1 -1
- package/plugins/acryl-router/src/core/adapter/aws-lambda/conninfo.ts +73 -0
- package/plugins/acryl-router/src/core/adapter/aws-lambda/handler.ts +682 -0
- package/plugins/acryl-router/src/core/adapter/aws-lambda/index.ts +14 -0
- package/plugins/acryl-router/src/core/adapter/aws-lambda/types.ts +173 -0
- package/plugins/acryl-router/src/core/adapter/bun/conninfo.ts +43 -0
- package/plugins/acryl-router/src/core/adapter/bun/index.ts +11 -0
- package/plugins/acryl-router/src/core/adapter/bun/serve-static.ts +32 -0
- package/plugins/acryl-router/src/core/adapter/bun/server.ts +14 -0
- package/plugins/acryl-router/src/core/adapter/bun/ssg.ts +26 -0
- package/plugins/acryl-router/src/core/adapter/bun/websocket.ts +113 -0
- package/plugins/acryl-router/src/core/adapter/cloudflare-pages/conninfo.ts +26 -0
- package/plugins/acryl-router/src/core/adapter/cloudflare-pages/handler.ts +122 -0
- package/plugins/acryl-router/src/core/adapter/cloudflare-pages/index.ts +11 -0
- package/plugins/acryl-router/src/core/adapter/cloudflare-workers/conninfo.ts +7 -0
- package/plugins/acryl-router/src/core/adapter/cloudflare-workers/index.ts +8 -0
- package/plugins/acryl-router/src/core/adapter/cloudflare-workers/serve-static-module.ts +12 -0
- package/plugins/acryl-router/src/core/adapter/cloudflare-workers/serve-static.ts +41 -0
- package/plugins/acryl-router/src/core/adapter/cloudflare-workers/utils.ts +50 -0
- package/plugins/acryl-router/src/core/adapter/cloudflare-workers/websocket.ts +52 -0
- package/plugins/acryl-router/src/core/adapter/lambda-edge/conninfo.ts +15 -0
- package/plugins/acryl-router/src/core/adapter/lambda-edge/handler.ts +239 -0
- package/plugins/acryl-router/src/core/adapter/lambda-edge/index.ts +14 -0
- package/plugins/acryl-router/src/core/adapter/netlify/conninfo.ts +57 -0
- package/plugins/acryl-router/src/core/adapter/netlify/handler.ts +10 -0
- package/plugins/acryl-router/src/core/adapter/netlify/index.ts +6 -0
- package/plugins/acryl-router/src/core/adapter/netlify/mod.ts +2 -0
- package/plugins/acryl-router/src/core/adapter/service-worker/handler.ts +37 -0
- package/plugins/acryl-router/src/core/adapter/service-worker/index.ts +36 -0
- package/plugins/acryl-router/src/core/adapter/service-worker/types.ts +14 -0
- package/plugins/acryl-router/src/core/adapter/vercel/conninfo.ts +8 -0
- package/plugins/acryl-router/src/core/adapter/vercel/handler.ts +8 -0
- package/plugins/acryl-router/src/core/adapter/vercel/index.ts +7 -0
- package/plugins/acryl-router/upstream.json +2 -2
- package/plugins/acryl-router-http/upstream.json +1 -1
- package/plugins/acryl-state/upstream.json +1 -1
- package/plugins/desktop/upstream.json +1 -1
- package/plugins/desktop-web/main.ts +13 -3
- package/plugins/desktop-web/upstream.json +1 -1
- package/plugins/tsconfig.json +1 -0
- package/templates/todo/upstream.json +1 -1
- package/lib/{serve-static-B14MF1Va.js → serve-static-B849HlX2.js} +1 -1
- 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,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
|
+
})
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"source": {
|
|
3
3
|
"repo": "acryldev/acryl",
|
|
4
4
|
"path": "plugins/acryl-router",
|
|
5
|
-
"commit": "
|
|
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:
|
|
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
|
},
|
|
@@ -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": "
|
|
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
|
|
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
|
-
|
|
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": "
|
|
8
|
+
"commit": "4721bb00960fa4e157e6070c330cf87c148739fc"
|
|
9
9
|
},
|
|
10
10
|
"rewrittenSpecifiers": 0,
|
|
11
11
|
"digest": "sha256:7dfe960a8bbb476c35540d2202a7aaa62b33492c2ac3e34314e2ecfca45f32f2"
|
package/plugins/tsconfig.json
CHANGED
|
@@ -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",
|