@jon49/sw 0.15.2 → 0.15.4

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.
@@ -2,240 +2,240 @@ import { SwResponse } from "./sw-framework.js"
2
2
  import { isHtml } from "./utils.js"
3
3
 
4
4
  let { links, globalDb } =
5
- // @ts-ignore
6
- self.sw as { links: { file: string, url: string }[], html: Function, db: any, globalDb: any }
5
+ // @ts-ignore
6
+ self.sw as { links: { file: string, url: string }[], html: Function, db: any, globalDb: any }
7
7
 
8
8
  if (!links) {
9
- console.error("Expecting links defined with `self.sw.links`, but found none.")
9
+ console.error("Expecting links defined with `self.sw.links`, but found none.")
10
10
  }
11
11
 
12
12
  function redirect(req: Request) {
13
- return Response.redirect(req.referrer, 303)
13
+ return Response.redirect(req.referrer, 303)
14
14
  }
15
15
 
16
16
  const searchParamsHandler = {
17
- get(obj: any, prop: string) {
18
- if (prop === "_url") {
19
- return obj
20
- }
21
- return obj.searchParams.get(prop)
17
+ get(obj: any, prop: string) {
18
+ if (prop === "_url") {
19
+ return obj
22
20
  }
21
+ return obj.searchParams.get(prop)
22
+ }
23
23
  }
24
24
 
25
25
  function searchParams<TReturn>(url: URL): TReturn & { _url: URL } {
26
- return new Proxy(url, searchParamsHandler)
27
- }
28
-
29
- interface ResponseOptions {
30
- handleErrors?: Function
26
+ return new Proxy(url, searchParamsHandler)
31
27
  }
32
28
 
33
- export let options: ResponseOptions = {}
34
-
35
29
  // @ts-ignore
36
30
  export async function useRoutes(req: Request, res: SwResponse, ctx: any): Promise<void> {
37
- try {
38
- const url = normalizeUrl(req.url)
39
- if (!url.pathname.startsWith("/web/")) {
40
- res.response = await fetch(req)
41
- } else {
42
- Object.assign(res, await executeHandler({ url, req, ctx }))
43
- }
44
- } catch (error) {
45
- console.error("Get Response Error", error)
46
- res.error = "Oops something happened which shouldn't have!"
31
+ try {
32
+ const url = normalizeUrl(req.url)
33
+ if (!url.pathname.startsWith("/web/")) {
34
+ res.response = await fetch(req)
35
+ } else {
36
+ Object.assign(res, await executeHandler({ url, req, ctx }))
47
37
  }
38
+ } catch (error) {
39
+ console.error("Get Response Error", error)
40
+ res.error = "Oops something happened which shouldn't have!"
41
+ }
48
42
  }
49
43
 
50
44
  function call(fn: Function | undefined, args: any) {
51
- return fn instanceof Function && fn(args)
45
+ return fn instanceof Function && fn(args)
52
46
  }
53
47
 
54
48
  const methodTypes = ['get', 'post'] as const
55
49
  type MethodTypes = typeof methodTypes[number] | null
56
50
 
57
51
  function isMethod(method: unknown) {
58
- if (typeof method === "string" && methodTypes.includes(<any>method)) {
59
- return method as MethodTypes
60
- }
61
- return null
52
+ if (typeof method === "string" && methodTypes.includes(<any>method)) {
53
+ return method as MethodTypes
54
+ }
55
+ return null
62
56
  }
63
57
 
64
58
  let cache = new Map<string, any>()
65
59
  export async function findRoute(url: URL, method: unknown) {
66
- let validMethod: MethodTypes = isMethod(method)
67
- if (validMethod) {
68
- // @ts-ignore
69
- if (!self.sw?.routes) {
70
- console.error("Expecting routes defined with `self.sw.routes`, but found none.")
60
+ let validMethod: MethodTypes = isMethod(method)
61
+ if (validMethod) {
62
+ // @ts-ignore
63
+ if (!self.sw?.routes) {
64
+ console.error("Expecting routes defined with `self.sw.routes`, but found none.")
65
+ return null
66
+ }
67
+ // @ts-ignore
68
+ for (const r of self.sw.routes) {
69
+ // @ts-ignore
70
+ if (r.file
71
+ && (r.route instanceof RegExp && r.route.test(url.pathname)
72
+ || (r.route instanceof Function && r.route(url)))) {
73
+ let file = links?.find(x => x.url === r.file)?.file
74
+ // Load file
75
+ if (!file) {
76
+ console.error(`"${r.file}" not found in links!`)
77
+ return null
78
+ }
79
+ if (!cache.has(file)) {
80
+ let cachedResponse = await cacheResponse(file)
81
+ if (!cacheResponse) {
82
+ console.error(`"${file}" not found in cache!`)
71
83
  return null
84
+ }
85
+ let text = await cachedResponse.text()
86
+ let func = new Function(text)()
87
+ cache.set(file, func)
72
88
  }
73
- // @ts-ignore
74
- for (const r of self.sw.routes) {
75
- // @ts-ignore
76
- if (r.file
77
- && (r.route instanceof RegExp && r.route.test(url.pathname)
78
- || (r.route instanceof Function && r.route(url)))) {
79
- let file = links?.find(x => x.url === r.file)?.file
80
- // Load file
81
- if (!file) {
82
- console.error(`"${r.file}" not found in links!`)
83
- return null
84
- }
85
- if (!cache.has(file)) {
86
- let cachedResponse = await cacheResponse(file)
87
- if (!cacheResponse) {
88
- console.error(`"${file}" not found in cache!`)
89
- return null
90
- }
91
- let text = await cachedResponse.text()
92
- let func = new Function(text)()
93
- cache.set(file, func)
94
- }
95
-
96
- let routeDef = cache.get(file)
97
- if (routeDef[validMethod]) {
98
- return routeDef[validMethod]
99
- }
100
- }
101
-
102
- if (r[validMethod]
103
- && (r.route instanceof RegExp && r.route.test(url.pathname)
104
- || (r.route instanceof Function && r.route(url)))) {
105
- return r[validMethod]
106
- }
89
+
90
+ let routeDef = cache.get(file)
91
+ if (routeDef[validMethod]) {
92
+ return routeDef[validMethod]
107
93
  }
94
+ }
95
+
96
+ if (r[validMethod]
97
+ && (r.route instanceof RegExp && r.route.test(url.pathname)
98
+ || (r.route instanceof Function && r.route(url)))) {
99
+ return r[validMethod]
100
+ }
108
101
  }
109
- return null
102
+ }
103
+ return null
110
104
  }
111
105
 
112
106
  interface ExectuteHandlerOptions {
113
- url: URL
114
- req: Request
115
- ctx: any
107
+ url: URL
108
+ req: Request
109
+ ctx: any
116
110
  }
117
111
  async function executeHandler({ url, req, ctx }: ExectuteHandlerOptions): Promise<SwResponse> {
118
- let method = req.method.toLowerCase()
119
- let isPost = method === "post"
120
- if (!isPost) {
121
- if (!url.pathname.endsWith("/")) {
122
- return { response: await cacheResponse(url.pathname, req) }
123
- }
112
+ let method = req.method.toLowerCase()
113
+ let isPost = method === "post"
114
+ if (!isPost) {
115
+ if (!url.pathname.endsWith("/")) {
116
+ return { response: await cacheResponse(url.pathname, req) }
117
+ }
124
118
 
125
- if (url.searchParams.get("login") === "success") {
126
- await globalDb.setLoggedIn(true)
127
- }
119
+ if (url.searchParams.get("login") === "success") {
120
+ await globalDb.setLoggedIn(true)
128
121
  }
122
+ }
129
123
 
130
- let handlers =
131
- <RouteHandler<RouteGetArgs | RoutePostArgs> | null>
132
- await findRoute(url, method)
124
+ let handlers =
125
+ <RouteHandler<RouteGetArgs | RoutePostArgs> | null>
126
+ await findRoute(url, method)
133
127
 
134
- // @ts-ignore
135
- if (handlers) {
136
- try {
137
- let messages: string[] = []
138
- const data = await getData(req)
139
- let query = searchParams<{ handler?: string }>(url)
140
- let args = { req, data, query }
141
- let result
142
- try {
143
- result = await (
144
- handlers instanceof Function
145
- ? handlers(args)
146
- : (call(handlers[query.handler ?? ""], args)
147
- || call(handlers[method], args)
148
- || Promise.reject("I'm sorry, I didn't understand where to route your request.")))
149
- } catch (e: any) {
150
- if (Array.isArray(e)) {
151
- ctx.messages = e
152
- return {
153
- status: 204,
154
- }
155
- } else {
156
- throw e
157
- }
158
- }
159
-
160
- if (!result) {
161
- return isPost
162
- ? { response: redirect(req) }
163
- : { error: "Not found!" }
164
- }
165
-
166
- if (isPost && result.message == null) {
167
- messages.push("Saved!")
168
- }
169
-
170
- if (result.message?.length > 0) {
171
- messages.push(result.message)
172
- } else if (result.messages?.length > 0) {
173
- messages.push(...result.messages)
174
- }
175
- ctx.messages = result.messages = messages
176
- ctx.events = result.events
177
-
178
- if (isHtml(result)) {
179
- return {
180
- body: result,
181
- }
182
- }
183
-
184
- if (isHtml(result.body)) {
185
- return {
186
- ...result,
187
- type: "text/html",
188
- }
189
- } else {
190
- if ("json" in result) {
191
- result.body = JSON.stringify(result.json)
192
- result.headers = {
193
- ...result.headers,
194
- }
195
- } else if (result.messages?.length > 0) {
196
- return {
197
- ...result,
198
- status: result.status || 204,
199
- }
200
- }
201
- return {
202
- ...result,
203
- type: "application/json"
204
- }
205
- }
206
- } catch (error) {
207
- console.error(`"${method}" error:`, error, "\nURL:", url);
208
- if (isPost) {
209
- return { error: "An error occurred while processing your request." }
210
- } else {
211
- return { error: "Not found!" }
212
- }
128
+ // @ts-ignore
129
+ if (handlers) {
130
+ try {
131
+ let messages: string[] = []
132
+ const data = await getData(req)
133
+ let query = searchParams<{ handler?: string }>(url)
134
+ let args = { req, data, query }
135
+ let result
136
+ try {
137
+ result = await (
138
+ handlers instanceof Function
139
+ ? handlers(args)
140
+ : (call(handlers[query.handler ?? ""], args)
141
+ || call(handlers[method], args)
142
+ || Promise.reject("I'm sorry, I didn't understand where to route your request.")))
143
+ } catch (e: any) {
144
+ if (typeof e === "string") {
145
+ e = [e]
146
+ }
147
+ if ("message" in e) {
148
+ e = [e.message]
149
+ }
150
+ if (Array.isArray(e)) {
151
+ ctx.messages = e
152
+ return {
153
+ status: 204,
154
+ }
155
+ } else {
156
+ throw e
213
157
  }
158
+ }
159
+
160
+ if (!result) {
161
+ return isPost
162
+ ? { response: redirect(req) }
163
+ : { error: "Not found!" }
164
+ }
165
+
166
+ if (isPost && result.message == null) {
167
+ messages.push("Saved!")
168
+ }
169
+
170
+ if (result.message?.length > 0) {
171
+ messages.push(result.message)
172
+ } else if (result.messages?.length > 0) {
173
+ messages.push(...result.messages)
174
+ }
175
+ ctx.messages = result.messages = messages
176
+ ctx.events = result.events
177
+
178
+ if (isHtml(result)) {
179
+ return {
180
+ body: result,
181
+ }
182
+ }
183
+
184
+ if (isHtml(result.body)) {
185
+ return {
186
+ ...result,
187
+ type: "text/html",
188
+ }
189
+ } else {
190
+ if ("json" in result) {
191
+ result.body = JSON.stringify(result.json)
192
+ result.headers = {
193
+ ...result.headers,
194
+ }
195
+ } else if (result.messages?.length > 0) {
196
+ return {
197
+ ...result,
198
+ status: result.status || 204,
199
+ }
200
+ }
201
+ return {
202
+ ...result,
203
+ type: "application/json"
204
+ }
205
+ }
206
+ } catch (error) {
207
+ console.error(`"${method}" error:`, error, "\nURL:", url);
208
+ if (isPost) {
209
+ return { error: "An error occurred while processing your request." }
210
+ } else {
211
+ return { error: "Not found!" }
212
+ }
214
213
  }
214
+ }
215
215
 
216
- return { error: "Not Found!" }
216
+ return { error: "Not Found!" }
217
217
  }
218
218
 
219
219
  async function getData(req: Request) {
220
- let o: any = {}
221
- if (req.headers.get("content-type")?.includes("application/x-www-form-urlencoded")) {
222
- const formData = await req.formData()
223
- for (let [key, val] of formData.entries()) {
224
- if (key.endsWith("[]")) {
225
- key = key.slice(0, -2)
226
- if (key in o) {
227
- o[key].push(val)
228
- } else {
229
- o[key] = [val]
230
- }
231
- } else {
232
- o[key] = val
233
- }
220
+ let o: any = {}
221
+ if (req.headers.get("content-type")?.includes("application/x-www-form-urlencoded")) {
222
+ const formData = await req.formData()
223
+ for (let [key, val] of formData.entries()) {
224
+ if (key.endsWith("[]")) {
225
+ key = key.slice(0, -2)
226
+ if (key in o) {
227
+ o[key].push(val)
228
+ } else {
229
+ o[key] = [val]
234
230
  }
235
- } else if (req.headers.get("Content-Type")?.includes("json")) {
236
- o = await req.json()
231
+ } else {
232
+ o[key] = val
233
+ }
237
234
  }
238
- return o
235
+ } else if (req.headers.get("Content-Type")?.includes("json")) {
236
+ o = await req.json()
237
+ }
238
+ return o
239
239
  }
240
240
 
241
241
  /**
@@ -243,88 +243,88 @@ async function getData(req: Request) {
243
243
  * /my/script.js -> /my/script.js
244
244
  */
245
245
  function normalizeUrl(url: string): URL {
246
- let uri = new URL(url)
247
- let path = uri.pathname
248
- !uri.pathname.endsWith("/") && (uri.pathname = isFile(path) ? path : path + "/")
249
- return uri
246
+ let uri = new URL(url)
247
+ let path = uri.pathname
248
+ !uri.pathname.endsWith("/") && (uri.pathname = isFile(path) ? path : path + "/")
249
+ return uri
250
250
  }
251
251
 
252
252
  function isFile(s: string) {
253
- return s.lastIndexOf("/") < s.lastIndexOf(".")
253
+ return s.lastIndexOf("/") < s.lastIndexOf(".")
254
254
  }
255
255
 
256
256
  async function cacheResponse(url: string, req?: Request | undefined): Promise<Response> {
257
- url = links?.find(x => x.url === url)?.file || url
258
- const match = await caches.match(url)
259
- if (match) return match
260
- const res = await fetch(req || url)
261
- if (!res || res.status !== 200 || res.type !== "basic") return res
262
- const responseToCache = res.clone()
263
- // @ts-ignore
264
- let version: string = self.sw?.version
265
- ?? (console.warn("The version number is not available, expected glboal value `self.sw.version`."), "")
266
- const cache = await caches.open(version)
267
- cache.put(url, responseToCache)
268
- return res
257
+ url = links?.find(x => x.url === url)?.file || url
258
+ const match = await caches.match(url)
259
+ if (match) return match
260
+ const res = await fetch(req || url)
261
+ if (!res || res.status !== 200 || res.type !== "basic") return res
262
+ const responseToCache = res.clone()
263
+ // @ts-ignore
264
+ let version: string = self.sw?.version
265
+ ?? (console.warn("The version number is not available, expected glboal value `self.sw.version`."), "")
266
+ const cache = await caches.open(version)
267
+ cache.put(url, responseToCache)
268
+ return res
269
269
  }
270
270
 
271
271
  export interface RouteGetArgs {
272
- req: Request
273
- query: any
272
+ req: Request
273
+ query: any
274
274
  }
275
275
 
276
276
  export interface RoutePostArgs {
277
- query: any
278
- data: any
279
- req: Request
277
+ query: any
278
+ data: any
279
+ req: Request
280
280
  }
281
281
 
282
282
  export interface RouteObjectReturn {
283
- body?: string | AsyncGenerator | null
284
- status?: number
285
- headers?: any
286
- events?: any
287
- message?: string
288
- messages?: string[]
289
- json?: any
283
+ body?: string | AsyncGenerator | null
284
+ status?: number
285
+ headers?: any
286
+ events?: any
287
+ message?: string
288
+ messages?: string[]
289
+ json?: any
290
290
  }
291
291
 
292
292
  export type RouteHandler<T> = {
293
- (options: T): Promise<
294
- AsyncGenerator
295
- | Response
296
- | RouteObjectReturn
297
- | undefined
298
- | null
299
- | void> | (
300
- AsyncGenerator
301
- | Response
302
- | RouteObjectReturn
303
- | undefined
304
- | null
305
- | void)
293
+ (options: T): Promise<
294
+ AsyncGenerator
295
+ | Response
296
+ | RouteObjectReturn
297
+ | undefined
298
+ | null
299
+ | void> | (
300
+ AsyncGenerator
301
+ | Response
302
+ | RouteObjectReturn
303
+ | undefined
304
+ | null
305
+ | void)
306
306
  }
307
307
 
308
308
  export interface RoutePostHandler {
309
- [handler: string]: RouteHandler<RoutePostArgs>
309
+ [handler: string]: RouteHandler<RoutePostArgs>
310
310
  }
311
311
 
312
312
  export interface RouteGetHandler {
313
- [handler: string]: RouteHandler<RouteGetArgs>
313
+ [handler: string]: RouteHandler<RouteGetArgs>
314
314
  }
315
315
 
316
316
  interface Route_ {
317
- route: RegExp | ((a: URL) => boolean)
318
- file?: string
319
- get?: RouteHandler<RouteGetArgs> | RouteGetHandler
320
- post?: RouteHandler<RoutePostArgs> | RoutePostHandler
317
+ route: RegExp | ((a: URL) => boolean)
318
+ file?: string
319
+ get?: RouteHandler<RouteGetArgs> | RouteGetHandler
320
+ post?: RouteHandler<RoutePostArgs> | RoutePostHandler
321
321
  }
322
322
 
323
323
  type RequireAtLeastOne<T, Keys extends keyof T = keyof T> =
324
- Pick<T, Exclude<keyof T, Keys>>
325
- & {
326
- [K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>>
327
- }[Keys]
324
+ Pick<T, Exclude<keyof T, Keys>>
325
+ & {
326
+ [K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>>
327
+ }[Keys]
328
328
 
329
329
  export type Route = RequireAtLeastOne<Route_, "file" | "get" | "post">
330
330
  export type RoutePage = Pick<Route_, "get" | "post">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jon49/sw",
3
- "version": "0.15.2",
3
+ "version": "0.15.4",
4
4
  "description": "Packages for MVC service workers.",
5
5
  "type": "module",
6
6
  "files": [
@@ -44,4 +44,4 @@
44
44
  "@types/node": "^24.0.10",
45
45
  "typescript": "^5.8.3"
46
46
  }
47
- }
47
+ }