saloe 0.0.1
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/README.md +19 -0
- package/demos/cloudflare-worker-actions/.wrangler/state/v3/cache/miniflare-CacheObject/9f458c07675338a7426a7b81ac4fb1baf92d034efbcaaf4336379640ed744ded.sqlite +0 -0
- package/demos/cloudflare-worker-actions/package.json +29 -0
- package/demos/cloudflare-worker-actions/public/input.js +5 -0
- package/demos/cloudflare-worker-actions/public/load.js +5 -0
- package/demos/cloudflare-worker-actions/public/submit.js +6 -0
- package/demos/cloudflare-worker-actions/server.js +93 -0
- package/demos/cloudflare-worker-actions/vite.config.js +25 -0
- package/demos/cloudflare-worker-actions/wrangler.toml +35 -0
- package/demos/cloudflare-worker-server/package.json +29 -0
- package/demos/cloudflare-worker-server/server.js +75 -0
- package/demos/cloudflare-worker-server/vite.config.js +25 -0
- package/demos/cloudflare-worker-server/wrangler.toml +32 -0
- package/dist/actions.cjs.js +280 -0
- package/dist/actions.es.js +280 -0
- package/dist/cloudflare-kv.cjs.js +29 -0
- package/dist/cloudflare-kv.es.js +29 -0
- package/dist/cloudflare-worker.cjs.js +19 -0
- package/dist/cloudflare-worker.es.js +19 -0
- package/dist/cookie.cjs.js +18 -0
- package/dist/cookie.es.js +18 -0
- package/dist/html.cjs.js +78 -0
- package/dist/html.es.js +78 -0
- package/dist/router.cjs.js +51 -0
- package/dist/router.es.js +51 -0
- package/dist/urlpattern.cjs.js +23 -0
- package/dist/urlpattern.es.js +6 -0
- package/dist/util.cjs.js +36 -0
- package/dist/util.es.js +36 -0
- package/dist/vite.cjs.js +102 -0
- package/dist/vite.es.js +101 -0
- package/dist/worker.cjs.js +43 -0
- package/dist/worker.es.js +43 -0
- package/package.json +63 -0
- package/src/actions.js +284 -0
- package/src/cloudflare-kv.js +36 -0
- package/src/cloudflare-worker.js +23 -0
- package/src/cookie.js +22 -0
- package/src/html.js +99 -0
- package/src/router.js +61 -0
- package/src/urlpattern.js +11 -0
- package/src/util.js +44 -0
- package/src/vite.js +127 -0
- package/src/worker.js +46 -0
- package/vite.config.js +36 -0
package/src/actions.js
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { html } from './html'
|
|
2
|
+
|
|
3
|
+
const LISTENER_SCRIPT = html`
|
|
4
|
+
<script defer>
|
|
5
|
+
(() => {
|
|
6
|
+
const EVENTS_PREVENT_DEFAULT_MANDATORY = [
|
|
7
|
+
'submit'
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
const EVENTS_FIRE_DOCUMENT_BODY_LISTENERS = [
|
|
11
|
+
'mouseover',
|
|
12
|
+
'click',
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
const EVENTS = [
|
|
16
|
+
'submit',
|
|
17
|
+
'input',
|
|
18
|
+
'blur',
|
|
19
|
+
'change',
|
|
20
|
+
'focus',
|
|
21
|
+
'invalid',
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
const addListener = ({ srcElement, event, listeners }) => {
|
|
26
|
+
srcElement?.addEventListener(event, (e) => {
|
|
27
|
+
executeListeners({ e, srcElement, listeners })
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const executeListeners = ({ e, srcElement, listeners }) => {
|
|
32
|
+
listeners?.forEach((listener) => {
|
|
33
|
+
if (listener) listener({ e, srcElement })
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const getListenerFromScript = ({ script, event }) => {
|
|
38
|
+
if (!script) return null
|
|
39
|
+
if (script[event]) return script[event]
|
|
40
|
+
const prev = Object.keys(script)?.find((key) => script[key][event])
|
|
41
|
+
if (!prev) return null
|
|
42
|
+
return script[prev][event]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const fetchListeners = async ({ srcElement, event, e }) => {
|
|
46
|
+
if (!srcElement?.getAttribute) return
|
|
47
|
+
|
|
48
|
+
const scriptNames = srcElement?.getAttribute('on-' + event)
|
|
49
|
+
if (!scriptNames) return
|
|
50
|
+
|
|
51
|
+
if (scriptNames && EVENTS_PREVENT_DEFAULT_MANDATORY.includes(event)) e.preventDefault()
|
|
52
|
+
|
|
53
|
+
const scripts = await Promise.all(
|
|
54
|
+
scriptNames?.split(',')?.map((scriptName) => {
|
|
55
|
+
const scriptToImport = '/' + scriptName?.trim() + '.js'
|
|
56
|
+
return import(scriptToImport)?.catch((err) => { })
|
|
57
|
+
})
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
const listeners = scripts?.map((script) => getListenerFromScript({ script, event }))
|
|
61
|
+
|
|
62
|
+
return listeners
|
|
63
|
+
|
|
64
|
+
// if (['load', 'click', 'submit', 'input', 'change'].includes(event)) executeListeners({ e, srcElement, listeners })
|
|
65
|
+
// if (['focus', 'blur', 'invalid', 'click', 'submit', 'input', 'change'].includes(event)) addListener({ srcElement, event, listeners })
|
|
66
|
+
|
|
67
|
+
// srcElement?.removeAttribute('on-' + event)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const addScripts = () => {
|
|
71
|
+
const scriptsToLoad = [...document.querySelectorAll('script[data-script-to-load]')]
|
|
72
|
+
return Promise.all(
|
|
73
|
+
scriptsToLoad?.map((scriptToLoad) => {
|
|
74
|
+
const id = scriptToLoad?.getAttribute('data-script-to-load')
|
|
75
|
+
scriptToLoad.removeAttribute('data-script-to-load')
|
|
76
|
+
|
|
77
|
+
const attrs = scriptToLoad?.getAttributeNames()?.reduce((acc, attrName) => {
|
|
78
|
+
const attrValue = scriptToLoad.getAttribute(attrName)
|
|
79
|
+
if (attrValue !== 'text/script-to-load') acc[attrName] = attrValue
|
|
80
|
+
return acc
|
|
81
|
+
}, {})
|
|
82
|
+
|
|
83
|
+
const content = scriptToLoad?.textContent
|
|
84
|
+
|
|
85
|
+
scriptToLoad?.remove()
|
|
86
|
+
|
|
87
|
+
return loadScript({ id, attrs, content }).catch((err) => {
|
|
88
|
+
console.error(err)
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const loadScript = ({ id, attrs, content }) => {
|
|
95
|
+
const script = document?.createElement('script')
|
|
96
|
+
|
|
97
|
+
Object.keys(attrs)?.forEach((attrKey) => script?.setAttribute(attrKey, attrs[attrKey]))
|
|
98
|
+
script.id = id
|
|
99
|
+
|
|
100
|
+
if (content) script?.insertAdjacentHTML('beforeend', content)
|
|
101
|
+
|
|
102
|
+
return new Promise((resolve, reject) => {
|
|
103
|
+
if (!attrs.src) {
|
|
104
|
+
resolve()
|
|
105
|
+
document?.body?.insertAdjacentElement('beforeend', script)
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
script.onload = script.onreadystatechange = function () {
|
|
110
|
+
if (!this.readyState || this.readyState === 'loaded' || this.readyState === 'complete') {
|
|
111
|
+
resolve()
|
|
112
|
+
script.onload = script.onreadystatechange = null
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
script.onerror = () => {
|
|
117
|
+
console.error('script failed to load')
|
|
118
|
+
reject(new Error('Failed to load script with src ' + script.src))
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
document?.body?.insertAdjacentElement('beforeend', script)
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// load
|
|
126
|
+
const fireLoadListener = () => {
|
|
127
|
+
const event = 'load'
|
|
128
|
+
const srcElements = document?.querySelectorAll('[on-' + event + ']')
|
|
129
|
+
|
|
130
|
+
srcElements?.forEach(async (srcElement) => {
|
|
131
|
+
const listeners = await fetchListeners({ srcElement, event, e: null })
|
|
132
|
+
executeListeners({ e: null, srcElement, listeners })
|
|
133
|
+
|
|
134
|
+
srcElement?.removeAttribute('on-' + event)
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// invalid
|
|
139
|
+
const fireInvalidListener = () => {
|
|
140
|
+
const event = 'invalid'
|
|
141
|
+
const srcElements = document?.querySelectorAll('[on-' + event + ']')
|
|
142
|
+
|
|
143
|
+
srcElements?.forEach(async (srcElement) => {
|
|
144
|
+
const listeners = await fetchListeners({ srcElement, event, e: null })
|
|
145
|
+
addListener({ srcElement, event, listeners })
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// blur
|
|
150
|
+
const fireBlurListener = () => {
|
|
151
|
+
const event = 'blur'
|
|
152
|
+
const srcElements = document?.querySelectorAll('[on-' + event + ']')
|
|
153
|
+
|
|
154
|
+
srcElements?.forEach(async (srcElement) => {
|
|
155
|
+
const listeners = await fetchListeners({ srcElement, event, e: null })
|
|
156
|
+
addListener({ srcElement, event, listeners })
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// focus
|
|
161
|
+
const fireFocusListener = () => {
|
|
162
|
+
const event = 'focus'
|
|
163
|
+
const srcElements = document?.querySelectorAll('[on-' + event + ']')
|
|
164
|
+
|
|
165
|
+
srcElements?.forEach(async (srcElement) => {
|
|
166
|
+
const listeners = await fetchListeners({ srcElement, event, e: null })
|
|
167
|
+
addListener({ srcElement, event, listeners })
|
|
168
|
+
})
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// observers
|
|
172
|
+
const fireObserverListeners = () => {
|
|
173
|
+
const srcElements = [...document.querySelectorAll('[on-observe]')]
|
|
174
|
+
|
|
175
|
+
const uniqueScriptNames = [...srcElements?.reduce((acc, srcElement) => {
|
|
176
|
+
const attribute = srcElement?.getAttribute('on-observe')
|
|
177
|
+
if (attribute === 'undefined') return acc
|
|
178
|
+
|
|
179
|
+
const scriptNames = attribute?.split(',')
|
|
180
|
+
scriptNames?.forEach((scriptName) => acc?.set(scriptName, 1))
|
|
181
|
+
|
|
182
|
+
return acc
|
|
183
|
+
}, new Map())?.keys()]
|
|
184
|
+
|
|
185
|
+
uniqueScriptNames?.forEach(async (scriptName) => {
|
|
186
|
+
const observedSrcElements = document.querySelectorAll('[on-observe*="' + scriptName + '"]')
|
|
187
|
+
|
|
188
|
+
const script = await import('/' + scriptName?.trim() + '.js')?.catch((err) => { })
|
|
189
|
+
const listener = getListenerFromScript({ script, event: 'observe' })
|
|
190
|
+
if (!listener) return
|
|
191
|
+
|
|
192
|
+
const observer = new IntersectionObserver((entries) => {
|
|
193
|
+
entries.forEach((entry) => listener({ entry, observer }))
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
observedSrcElements?.forEach((observerSrcElement) => {
|
|
197
|
+
observer.observe(observerSrcElement)
|
|
198
|
+
|
|
199
|
+
const observerAttr = observerSrcElement?.getAttribute('on-observe')
|
|
200
|
+
const updatedObserverAttr = observerAttr?.replaceAll(scriptName + ', ', '')?.replaceAll(', ' + scriptName, '')?.replaceAll(scriptName, '')
|
|
201
|
+
|
|
202
|
+
if (updatedObserverAttr === '') observerSrcElement.removeAttribute('on-observe')
|
|
203
|
+
else observerSrcElement.setAttribute('on-observe', updatedObserverAttr)
|
|
204
|
+
})
|
|
205
|
+
})
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const getSrcElement = ({ srcElement, event }) => {
|
|
209
|
+
if (!srcElement?.hasAttribute) return srcElement
|
|
210
|
+
const attribute = 'on-' + event
|
|
211
|
+
const hasScriptName = srcElement?.hasAttribute(attribute)
|
|
212
|
+
if (hasScriptName) return srcElement
|
|
213
|
+
|
|
214
|
+
const query = ':is(a, button, li)[' + attribute + ']'
|
|
215
|
+
const closestButton = srcElement?.closest(query)
|
|
216
|
+
if (closestButton) return closestButton
|
|
217
|
+
|
|
218
|
+
return srcElement
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const fireListeners = () => {
|
|
222
|
+
EVENTS_FIRE_DOCUMENT_BODY_LISTENERS?.forEach((event) => {
|
|
223
|
+
document.body['on' + event] = async (e) => {
|
|
224
|
+
await addScripts()
|
|
225
|
+
|
|
226
|
+
fireLoadListener()
|
|
227
|
+
fireObserverListeners()
|
|
228
|
+
}
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
EVENTS?.forEach((event) => {
|
|
232
|
+
document.body['on' + event] = async (e) => {
|
|
233
|
+
const srcElement = getSrcElement({ srcElement: e?.srcElement, event })
|
|
234
|
+
const listeners = await fetchListeners({ srcElement, event, e })
|
|
235
|
+
|
|
236
|
+
executeListeners({ e, srcElement, listeners })
|
|
237
|
+
addListener({ srcElement, event, listeners })
|
|
238
|
+
|
|
239
|
+
if (srcElement?.removeAttribute) srcElement.removeAttribute('on-' + event)
|
|
240
|
+
}
|
|
241
|
+
})
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
fireListeners()
|
|
245
|
+
|
|
246
|
+
window.onload = () => {
|
|
247
|
+
setTimeout(() => {
|
|
248
|
+
document?.body?.click()
|
|
249
|
+
}, 2_500)
|
|
250
|
+
}
|
|
251
|
+
})()
|
|
252
|
+
</script>
|
|
253
|
+
`
|
|
254
|
+
|
|
255
|
+
const SW_REGISTER_SCRIPT = html`
|
|
256
|
+
<script defer>
|
|
257
|
+
(async () => {
|
|
258
|
+
if (!navigator.serviceWorker) return
|
|
259
|
+
|
|
260
|
+
navigator.serviceWorker.register('/sw.worker.js', { scope: '/', type: 'module' })
|
|
261
|
+
|
|
262
|
+
let refreshing
|
|
263
|
+
// check to see if there is a current active service worker
|
|
264
|
+
const oldSw = (await navigator.serviceWorker.getRegistration())?.active?.state
|
|
265
|
+
navigator.serviceWorker.addEventListener('controllerchange', async () => {
|
|
266
|
+
if (refreshing) return
|
|
267
|
+
// when the controllerchange event has fired, we get the new service worker
|
|
268
|
+
const newSw = (await navigator.serviceWorker.getRegistration())?.active?.state
|
|
269
|
+
|
|
270
|
+
// if there was already an old activated service worker, and a new activating service worker, do notify update
|
|
271
|
+
if (oldSw === 'activated' && newSw === 'activating') {
|
|
272
|
+
refreshing = true
|
|
273
|
+
// notifyUpdate()
|
|
274
|
+
location.reload()
|
|
275
|
+
}
|
|
276
|
+
})
|
|
277
|
+
})()
|
|
278
|
+
</script>
|
|
279
|
+
`
|
|
280
|
+
|
|
281
|
+
export {
|
|
282
|
+
LISTENER_SCRIPT,
|
|
283
|
+
SW_REGISTER_SCRIPT,
|
|
284
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const getKV = ({ env, kv }) => env[kv]
|
|
2
|
+
|
|
3
|
+
const getKVResponse = async ({ env, kv, key }) => {
|
|
4
|
+
const result = await getFromKV({ env, kv, key })
|
|
5
|
+
if (result?.err) return result
|
|
6
|
+
|
|
7
|
+
const response = new Response(result?.data)
|
|
8
|
+
return { response }
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const getFromKV = async ({ env, kv, key }) => {
|
|
12
|
+
try {
|
|
13
|
+
const data = await getKV({ env, kv }).get(key)
|
|
14
|
+
return { data }
|
|
15
|
+
} catch (err) {
|
|
16
|
+
return { err }
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const putInKV = async ({ env, kv, key, data }) => {
|
|
21
|
+
try {
|
|
22
|
+
const result = await getKV({ env, kv }).put(key, data)
|
|
23
|
+
return result
|
|
24
|
+
} catch (err) {
|
|
25
|
+
return { err }
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export {
|
|
30
|
+
getFromKV,
|
|
31
|
+
putInKV,
|
|
32
|
+
|
|
33
|
+
getKVResponse,
|
|
34
|
+
|
|
35
|
+
getKV,
|
|
36
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { getAssetFromKV } from '@cloudflare/kv-asset-handler'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
const getStaticResponse = async ({ request, waitUntil, manifestJSON, env }) => {
|
|
5
|
+
try {
|
|
6
|
+
const ASSET_MANIFEST = JSON.parse(manifestJSON ?? {})
|
|
7
|
+
const response = await getAssetFromKV({
|
|
8
|
+
request,
|
|
9
|
+
waitUntil
|
|
10
|
+
}, {
|
|
11
|
+
ASSET_NAMESPACE: env.__STATIC_CONTENT,
|
|
12
|
+
ASSET_MANIFEST,
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
return { response }
|
|
16
|
+
} catch (err) {
|
|
17
|
+
return { err }
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export {
|
|
22
|
+
getStaticResponse,
|
|
23
|
+
}
|
package/src/cookie.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const setCookie = ({ key, value }) => {
|
|
2
|
+
return cookieStore?.set(key, value)
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
const getCookie = ({ key }) => {
|
|
6
|
+
return cookieStore?.get(key)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const getAllCookies = () => {
|
|
10
|
+
return cookieStore?.getAll()
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const removeCookie = ({ key }) => {
|
|
14
|
+
return cookieStore?.delete(key)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
setCookie,
|
|
19
|
+
getCookie,
|
|
20
|
+
getAllCookies,
|
|
21
|
+
removeCookie,
|
|
22
|
+
}
|
package/src/html.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { stream as streamAsWorker } from './worker'
|
|
2
|
+
import { addRoute, removeRoute } from './router'
|
|
3
|
+
import { getScope, getEnv } from './util'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
const html = (s, ...args) => {
|
|
7
|
+
return s?.map((ss, i) => `${ss}${args?.at(i) ?? ''}`)?.join('')
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const stream = ({ head, body, scripts, env, status, args }) => {
|
|
11
|
+
const headers = new Headers()
|
|
12
|
+
headers.append('Content-Type', 'text/html;charset=UTF-8')
|
|
13
|
+
|
|
14
|
+
const callbacks = [
|
|
15
|
+
() => html`
|
|
16
|
+
<!DOCTYPE html>
|
|
17
|
+
<html lang="${args?.lang ?? 'en'}">
|
|
18
|
+
<head>
|
|
19
|
+
`,
|
|
20
|
+
head,
|
|
21
|
+
() => html`
|
|
22
|
+
</head>
|
|
23
|
+
<body
|
|
24
|
+
data-scope="${getScope({ env })}"
|
|
25
|
+
data-env="${getEnv({ env })}"
|
|
26
|
+
${args?.isPublic ?? ''}
|
|
27
|
+
${args?.isLoading ?? ''}
|
|
28
|
+
>
|
|
29
|
+
`,
|
|
30
|
+
body,
|
|
31
|
+
scripts ?? (() => ''),
|
|
32
|
+
() => html`
|
|
33
|
+
</body>
|
|
34
|
+
</html>
|
|
35
|
+
`
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
return streamAsWorker({ callbacks, headers, status })
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const awaitHtml = async ({ pending, success, error }) => {
|
|
42
|
+
const id = Math.floor(Math.random() * 1_000_000_000)
|
|
43
|
+
|
|
44
|
+
const pendingId = `pending_${id}`
|
|
45
|
+
const pendingRoutePathname = `/~/components/${pendingId}`
|
|
46
|
+
|
|
47
|
+
const getPage = async () => {
|
|
48
|
+
const headers = {
|
|
49
|
+
'Content-Type': 'text/html;charset=utf-8',
|
|
50
|
+
'Transfer-Encoding': 'chunked',
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const streamResult = streamAsWorker({
|
|
54
|
+
callbacks: [
|
|
55
|
+
async () => html`
|
|
56
|
+
${
|
|
57
|
+
await success()
|
|
58
|
+
.then((template) => template)
|
|
59
|
+
.catch((err) => {
|
|
60
|
+
console.error(err?.stack)
|
|
61
|
+
return error ? error({ id, err }) : ''
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
`
|
|
65
|
+
],
|
|
66
|
+
headers,
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
removeRoute({ pathname: pendingRoutePathname })
|
|
70
|
+
|
|
71
|
+
return streamResult
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
addRoute({
|
|
75
|
+
pathname: pendingRoutePathname,
|
|
76
|
+
route: { getPage }
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
return html`
|
|
80
|
+
${await pending({ id: pendingId })}
|
|
81
|
+
<script
|
|
82
|
+
data-script-to-load="await-html_script-${id}"
|
|
83
|
+
type="text/script-to-load"
|
|
84
|
+
>
|
|
85
|
+
(async () => {
|
|
86
|
+
const pendingEl = document?.querySelector('[data-await-pending-template="${pendingId}"]')
|
|
87
|
+
const response = await fetch('${pendingRoutePathname}')
|
|
88
|
+
const templateString = await response.text()
|
|
89
|
+
pendingEl.outerHTML = templateString
|
|
90
|
+
})()
|
|
91
|
+
</script>
|
|
92
|
+
`
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export {
|
|
96
|
+
html,
|
|
97
|
+
stream,
|
|
98
|
+
awaitHtml,
|
|
99
|
+
}
|
package/src/router.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { getURLPatern } from './urlpattern'
|
|
2
|
+
import { fetch as fetchAsWorker } from './worker'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
const router = new Map()
|
|
6
|
+
|
|
7
|
+
const getRouter = () => router
|
|
8
|
+
const getRoute = ({ pathname }) => router.get(pathname)
|
|
9
|
+
const addRoute = ({ pathname, route }) => router.set(pathname, route)
|
|
10
|
+
const removeRoute = ({ pathname }) => router.delete(pathname)
|
|
11
|
+
|
|
12
|
+
const findPatternFromUrl = ({ url }) => {
|
|
13
|
+
const patternPathname = [...new Set(getRouter()?.keys())]
|
|
14
|
+
.find((patternPathname) => {
|
|
15
|
+
const pattern = getURLPatern({ pathname: patternPathname })
|
|
16
|
+
return pattern.test(url.href)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
return patternPathname ? getURLPatern({ pathname: patternPathname }) : null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const getRedirectResponse = ({ origin, pathname, isRedirectableCallback }) => {
|
|
23
|
+
if (origin !== self?.origin) return
|
|
24
|
+
const isRedirectable = isRedirectableCallback({ pathname })
|
|
25
|
+
const response = isRedirectable ? Response.redirect(pathname.slice(0, -1), 301) : null
|
|
26
|
+
return { response }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const getNotFoundResponse = async ({ request }) => {
|
|
30
|
+
const status = 404
|
|
31
|
+
const notFoundRoute = getRoute({ pathname: `/${status}` })
|
|
32
|
+
const response = notFoundRoute ? (await notFoundRoute({ request, status }))?.response : new Response('404', { status })
|
|
33
|
+
return { response }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const getForbiddenResponse = ({ origin, request, isForbiddenCallback }) => {
|
|
37
|
+
if (origin !== self?.origin) return
|
|
38
|
+
const isForbidden = isForbiddenCallback({ request })
|
|
39
|
+
if (!isForbidden) return
|
|
40
|
+
return { response: new Response(`${request?.url} is forbidden`, { status: 503 }) }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const getServerOnlyResponse = ({ origin, request, isServerOnlyCallback }) => {
|
|
44
|
+
if (origin !== self?.origin) return
|
|
45
|
+
const isServerOnly = isServerOnlyCallback({ request })
|
|
46
|
+
if (!isServerOnly) return
|
|
47
|
+
return fetchAsWorker({ request })
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export {
|
|
51
|
+
getRouter,
|
|
52
|
+
getRoute,
|
|
53
|
+
addRoute,
|
|
54
|
+
removeRoute,
|
|
55
|
+
|
|
56
|
+
findPatternFromUrl,
|
|
57
|
+
getRedirectResponse,
|
|
58
|
+
getNotFoundResponse,
|
|
59
|
+
getForbiddenResponse,
|
|
60
|
+
getServerOnlyResponse,
|
|
61
|
+
}
|
package/src/util.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const Scope = {
|
|
2
|
+
Cloudflare: 'cloudflare-worker',
|
|
3
|
+
ServiceWorker: 'service-worker',
|
|
4
|
+
Window: 'window',
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const Environment = {
|
|
8
|
+
Production: 'prod',
|
|
9
|
+
Development: 'dev',
|
|
10
|
+
Qa: 'qa',
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const isCloudflareWorker = ({ env }) => env?.IS_CLOUDFLARE_WORKER
|
|
14
|
+
const isServiceWorker = ({ env }) => env?.IS_SERVICE_WORKER
|
|
15
|
+
const isWindow = () => typeof window === 'object'
|
|
16
|
+
|
|
17
|
+
const getScope = ({ env }) => {
|
|
18
|
+
if (isCloudflareWorker({ env })) return Scope.Cloudflare
|
|
19
|
+
if (isServiceWorker({ env })) return Scope.ServiceWorker
|
|
20
|
+
if (isWindow()) return Scope.Window
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const getEnv = ({ env } = {}) => {
|
|
24
|
+
if (isCloudflareWorker({ env }) || isServiceWorker({ env })) return env.ENV
|
|
25
|
+
if (isWindow()) return document?.body?.getAttribute('data-env')
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const isProdEnv = ({ env }) => getEnv({ env }) === Environment.Production
|
|
29
|
+
const isDevEnv = ({ env }) => getEnv({ env }) === Environment.Development
|
|
30
|
+
const isQaEnv = ({ env }) => getEnv({ env }) === Environment.Qa
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
export {
|
|
34
|
+
getScope,
|
|
35
|
+
getEnv,
|
|
36
|
+
|
|
37
|
+
isProdEnv,
|
|
38
|
+
isDevEnv,
|
|
39
|
+
isQaEnv,
|
|
40
|
+
|
|
41
|
+
isCloudflareWorker,
|
|
42
|
+
isWindow,
|
|
43
|
+
isServiceWorker,
|
|
44
|
+
}
|