htmx-router 0.2.0 → 1.0.0-alpha.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/bin/cli/config.d.ts +10 -0
- package/bin/cli/config.js +4 -0
- package/bin/cli/index.js +54 -10
- package/bin/client/index.d.ts +4 -0
- package/bin/client/index.js +100 -0
- package/bin/client/mount.d.ts +2 -0
- package/bin/client/mount.js +74 -0
- package/bin/client/watch.d.ts +1 -0
- package/bin/client/watch.js +19 -0
- package/bin/helper.d.ts +1 -2
- package/bin/helper.js +25 -14
- package/bin/index.d.ts +8 -6
- package/bin/index.js +7 -16
- package/bin/request/http.d.ts +10 -0
- package/bin/request/http.js +46 -0
- package/bin/request/index.d.ts +16 -0
- package/bin/request/index.js +6 -0
- package/bin/request/native.d.ts +9 -0
- package/bin/request/native.js +56 -0
- package/bin/router.d.ts +41 -16
- package/bin/router.js +176 -236
- package/bin/types.d.ts +10 -0
- package/bin/types.js +1 -0
- package/bin/util/cookies.d.ts +22 -0
- package/bin/util/cookies.js +57 -0
- package/bin/util/css.d.ts +9 -0
- package/bin/util/css.js +47 -0
- package/bin/util/dynamic.d.ts +5 -0
- package/bin/util/dynamic.js +26 -0
- package/bin/util/endpoint.d.ts +9 -0
- package/bin/util/endpoint.js +28 -0
- package/bin/util/event-source.d.ts +16 -0
- package/bin/util/event-source.js +85 -0
- package/bin/util/hash.d.ts +1 -0
- package/bin/util/hash.js +7 -0
- package/bin/util/index.d.ts +1 -0
- package/bin/util/index.js +7 -0
- package/bin/util/parameters.d.ts +7 -0
- package/bin/util/parameters.js +14 -0
- package/bin/util/shell.d.ts +32 -0
- package/bin/util/shell.js +1 -0
- package/package.json +9 -7
- package/readme.md +149 -213
- package/bin/404-route.d.ts +0 -2
- package/bin/404-route.js +0 -8
- package/bin/cli/dynamic.d.ts +0 -2
- package/bin/cli/dynamic.js +0 -47
- package/bin/cli/static.d.ts +0 -2
- package/bin/cli/static.js +0 -49
- package/bin/components.d.ts +0 -8
- package/bin/components.js +0 -11
- package/bin/render-args.d.ts +0 -35
- package/bin/render-args.js +0 -120
- package/bin/shared.d.ts +0 -28
- package/bin/shared.js +0 -28
package/bin/router.js
CHANGED
|
@@ -1,281 +1,221 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import * as endpoint from './util/endpoint.js';
|
|
2
|
+
import * as dynamic from './util/dynamic.js';
|
|
3
|
+
import * as mount from './client/mount.js';
|
|
4
|
+
import * as css from './util/css.js';
|
|
5
|
+
import { Parameterize } from './util/parameters.js';
|
|
6
|
+
import { Cookies } from './util/cookies.js';
|
|
7
|
+
export class GenericContext {
|
|
8
|
+
request;
|
|
9
|
+
headers; // response headers
|
|
10
|
+
cookie;
|
|
11
|
+
params;
|
|
12
|
+
url;
|
|
13
|
+
render;
|
|
14
|
+
constructor(request, url, renderer) {
|
|
15
|
+
this.cookie = new Cookies(request.headers);
|
|
16
|
+
this.headers = new Headers();
|
|
17
|
+
this.request = request;
|
|
18
|
+
this.params = {};
|
|
19
|
+
this.url = url;
|
|
20
|
+
this.render = renderer;
|
|
21
|
+
this.headers.set("x-powered-by", "htmx-router");
|
|
22
|
+
}
|
|
23
|
+
shape(shape) {
|
|
24
|
+
return new RouteContext(this, shape);
|
|
7
25
|
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
-
if (mod && mod.__esModule) return mod;
|
|
20
|
-
var result = {};
|
|
21
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
-
__setModuleDefault(result, mod);
|
|
23
|
-
return result;
|
|
24
|
-
};
|
|
25
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
-
exports.RouteTree = exports.RouteLeaf = exports.IsAllowedExt = void 0;
|
|
27
|
-
const shared_1 = require("./shared");
|
|
28
|
-
const render_args_1 = require("./render-args");
|
|
29
|
-
const BlankRoute = __importStar(require("./404-route"));
|
|
30
|
-
function IsAllowedExt(ext) {
|
|
31
|
-
if (ext[0] !== ".")
|
|
32
|
-
return false;
|
|
33
|
-
// js, jsx, tsx, ts
|
|
34
|
-
if (ext[2] !== "s")
|
|
35
|
-
return false;
|
|
36
|
-
if (ext[1] !== "j" && ext[1] !== "t")
|
|
37
|
-
return false;
|
|
38
|
-
if (ext.length == 3)
|
|
39
|
-
return true;
|
|
40
|
-
if (ext.length != 4)
|
|
41
|
-
return false;
|
|
42
|
-
if (ext[3] !== "x")
|
|
43
|
-
return false;
|
|
44
|
-
return true;
|
|
45
26
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
27
|
+
export class RouteContext {
|
|
28
|
+
request;
|
|
29
|
+
headers; // response headers
|
|
30
|
+
cookie;
|
|
31
|
+
params;
|
|
32
|
+
url;
|
|
33
|
+
render;
|
|
34
|
+
constructor(base, shape) {
|
|
35
|
+
this.params = Parameterize(base.params, shape);
|
|
36
|
+
this.cookie = base.cookie;
|
|
37
|
+
this.headers = base.headers;
|
|
38
|
+
this.request = base.request;
|
|
39
|
+
this.render = base.render;
|
|
40
|
+
this.url = base.url;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export class RouteLeaf {
|
|
44
|
+
module;
|
|
45
|
+
constructor(module) {
|
|
49
46
|
this.module = module;
|
|
50
|
-
this.mask = mask;
|
|
51
47
|
}
|
|
52
|
-
async
|
|
48
|
+
async resolve(ctx) {
|
|
49
|
+
const res = await this.renderWrapper(ctx);
|
|
50
|
+
if (res === null)
|
|
51
|
+
return null;
|
|
52
|
+
if (res instanceof Response)
|
|
53
|
+
return res;
|
|
54
|
+
return ctx.render(res);
|
|
55
|
+
}
|
|
56
|
+
async error(ctx, e) {
|
|
57
|
+
if (!this.module.error)
|
|
58
|
+
return null;
|
|
59
|
+
const res = await this.module.error(ctx, e);
|
|
60
|
+
if (res === null)
|
|
61
|
+
return null;
|
|
62
|
+
if (res instanceof Response)
|
|
63
|
+
return res;
|
|
64
|
+
return ctx.render(res);
|
|
65
|
+
}
|
|
66
|
+
async renderWrapper(ctx) {
|
|
53
67
|
try {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
else {
|
|
63
|
-
return await args.Outlet();
|
|
68
|
+
if (!this.module.loader && !this.module.action)
|
|
69
|
+
return null;
|
|
70
|
+
const context = ctx.shape(this.module.parameters || {});
|
|
71
|
+
if (ctx.request.method === "HEAD" || ctx.request.method === "GET") {
|
|
72
|
+
if (this.module.loader)
|
|
73
|
+
return await this.module.loader(context);
|
|
74
|
+
else
|
|
75
|
+
return null;
|
|
64
76
|
}
|
|
77
|
+
if (this.module.action)
|
|
78
|
+
return await this.module.action(context);
|
|
79
|
+
throw new Response("Method not Allowed", { status: 405, statusText: "Method not Allowed", headers: ctx.headers });
|
|
65
80
|
}
|
|
66
81
|
catch (e) {
|
|
67
|
-
if (
|
|
82
|
+
if (this.module.error)
|
|
83
|
+
return await this.module.error(ctx, e);
|
|
84
|
+
else
|
|
68
85
|
throw e;
|
|
69
|
-
const err = (e instanceof shared_1.ErrorResponse) ? e :
|
|
70
|
-
new shared_1.ErrorResponse(500, "Runtime Error", e);
|
|
71
|
-
if (this.module.CatchError)
|
|
72
|
-
return await this.module.CatchError(routeName, args, err);
|
|
73
|
-
throw err;
|
|
74
86
|
}
|
|
75
|
-
return
|
|
87
|
+
return null;
|
|
76
88
|
}
|
|
77
89
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
90
|
+
export class RouteTree {
|
|
91
|
+
root;
|
|
92
|
+
nested;
|
|
93
|
+
// Leaf nodes
|
|
94
|
+
index; // about._index
|
|
95
|
+
// Wild card route
|
|
96
|
+
slug; // $
|
|
97
|
+
wild; // e.g. $userID
|
|
98
|
+
wildCard;
|
|
99
|
+
constructor(root = true) {
|
|
100
|
+
this.root = root;
|
|
82
101
|
this.nested = new Map();
|
|
83
102
|
this.wildCard = "";
|
|
103
|
+
this.slug = null;
|
|
84
104
|
this.wild = null;
|
|
85
|
-
this.
|
|
86
|
-
this.route = null;
|
|
105
|
+
this.index = null;
|
|
87
106
|
}
|
|
88
|
-
|
|
89
|
-
if (!
|
|
90
|
-
|
|
91
|
-
if (
|
|
92
|
-
|
|
93
|
-
this.route = new RouteLeaf(module, []);
|
|
94
|
-
}
|
|
95
|
-
ingest(path, module, override) {
|
|
96
|
-
if (!Array.isArray(path)) {
|
|
97
|
-
path = path.split(/[\./\\]/g);
|
|
98
|
-
}
|
|
99
|
-
if (path.length === 0) {
|
|
100
|
-
override.push(false);
|
|
101
|
-
this.route = new RouteLeaf(module, override);
|
|
107
|
+
ingest(path, module) {
|
|
108
|
+
if (!Array.isArray(path))
|
|
109
|
+
path = path.split("/");
|
|
110
|
+
if (path.length === 0 || (path.length == 1 && path[0] === "_index")) {
|
|
111
|
+
this.index = new RouteLeaf(module);
|
|
102
112
|
return;
|
|
103
113
|
}
|
|
104
|
-
if (path
|
|
105
|
-
|
|
106
|
-
this.default = new RouteLeaf(module, override);
|
|
114
|
+
if (path[0] === "$") {
|
|
115
|
+
this.slug = new RouteLeaf(module);
|
|
107
116
|
return;
|
|
108
117
|
}
|
|
109
|
-
if (path[0].endsWith("_")) {
|
|
110
|
-
path[0] = path[0].slice(0, -1);
|
|
111
|
-
override.push(true);
|
|
112
|
-
}
|
|
113
|
-
else {
|
|
114
|
-
override.push(false);
|
|
115
|
-
}
|
|
116
118
|
if (path[0][0] === "$") {
|
|
117
119
|
const wildCard = path[0].slice(1);
|
|
118
120
|
// Check wildcard isn't being changed
|
|
119
121
|
if (!this.wild) {
|
|
120
122
|
this.wildCard = wildCard;
|
|
121
|
-
this.wild = new RouteTree();
|
|
123
|
+
this.wild = new RouteTree(false);
|
|
122
124
|
}
|
|
123
125
|
else if (wildCard !== this.wildCard) {
|
|
124
126
|
throw new Error(`Redefinition of wild card ${this.wildCard} to ${wildCard}`);
|
|
125
127
|
}
|
|
126
128
|
path.splice(0, 1);
|
|
127
|
-
this.wild.ingest(path, module
|
|
129
|
+
this.wild.ingest(path, module);
|
|
128
130
|
return;
|
|
129
131
|
}
|
|
130
132
|
let next = this.nested.get(path[0]);
|
|
131
133
|
if (!next) {
|
|
132
|
-
next = new RouteTree();
|
|
134
|
+
next = new RouteTree(false);
|
|
133
135
|
this.nested.set(path[0], next);
|
|
134
136
|
}
|
|
135
137
|
path.splice(0, 1);
|
|
136
|
-
next.ingest(path, module
|
|
138
|
+
next.ingest(path, module);
|
|
137
139
|
}
|
|
138
|
-
|
|
139
|
-
let
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const segmentB = to.splice(0, 1)[0];
|
|
146
|
-
const subRoute = this.nested.get(segmentA);
|
|
147
|
-
if (subRoute && segmentA === segmentB) {
|
|
148
|
-
depth = subRoute.calculateDepth(from, to);
|
|
149
|
-
}
|
|
150
|
-
else if (this.wild) {
|
|
151
|
-
depth = this.wild.calculateDepth(from, to);
|
|
152
|
-
}
|
|
153
|
-
else {
|
|
154
|
-
return 1;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
depth++;
|
|
158
|
-
return depth;
|
|
140
|
+
async resolve(fragments, ctx) {
|
|
141
|
+
let res = await this.resolveNative(fragments, ctx)
|
|
142
|
+
|| await this.resolveIndex(fragments, ctx)
|
|
143
|
+
|| await this.resolveNext(fragments, ctx)
|
|
144
|
+
|| await this.resolveWild(fragments, ctx)
|
|
145
|
+
|| await this.resolveSlug(fragments, ctx);
|
|
146
|
+
return this.unwrap(ctx, res);
|
|
159
147
|
}
|
|
160
|
-
async
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
res.setHeader('Vary', "hx-current-url");
|
|
167
|
-
const from = req.headers['hx-current-url'] ?
|
|
168
|
-
new URL(((_a = req.headers['hx-current-url']) === null || _a === void 0 ? void 0 : _a.toString()) || "/").pathname :
|
|
169
|
-
"";
|
|
170
|
-
try {
|
|
171
|
-
const depth = BuildOutlet(this, args, from);
|
|
172
|
-
if (from) {
|
|
173
|
-
res.setHeader('HX-Push-Url', req.url || "/");
|
|
174
|
-
if (depth > 0) {
|
|
175
|
-
res.setHeader('HX-Retarget', `#hx-route-${depth.toString(16)}`);
|
|
176
|
-
}
|
|
177
|
-
res.setHeader('HX-Reswap', "outerHTML");
|
|
178
|
-
}
|
|
179
|
-
const out = await args.Outlet();
|
|
180
|
-
if (args.title) {
|
|
181
|
-
const trigger = res.getHeader('HX-Trigger');
|
|
182
|
-
const entry = `{"setTitle":"${encodeURIComponent(args.title)}"}`;
|
|
183
|
-
if (Array.isArray(trigger)) {
|
|
184
|
-
res.setHeader('HX-Trigger', [...trigger, entry]);
|
|
185
|
-
}
|
|
186
|
-
else if (trigger) {
|
|
187
|
-
res.setHeader('HX-Trigger', [trigger.toString(), entry]);
|
|
188
|
-
}
|
|
189
|
-
else {
|
|
190
|
-
res.setHeader('HX-Trigger', [entry]);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
return out;
|
|
194
|
-
}
|
|
195
|
-
catch (e) {
|
|
196
|
-
if (e instanceof shared_1.Redirect)
|
|
197
|
-
return e;
|
|
198
|
-
if (e instanceof shared_1.Override)
|
|
199
|
-
return e;
|
|
200
|
-
console.error(e);
|
|
201
|
-
throw new Error(`Unhandled boil up type ${typeof (e)}: ${e}`);
|
|
202
|
-
}
|
|
203
|
-
;
|
|
148
|
+
async resolveIndex(fragments, ctx) {
|
|
149
|
+
if (fragments.length > 0)
|
|
150
|
+
return null;
|
|
151
|
+
if (!this.index)
|
|
152
|
+
return null;
|
|
153
|
+
return await this.index.resolve(ctx);
|
|
204
154
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
155
|
+
async resolveNext(fragments, ctx) {
|
|
156
|
+
if (fragments.length < 1)
|
|
157
|
+
return null;
|
|
158
|
+
const next = this.nested.get(fragments[0]);
|
|
159
|
+
if (!next)
|
|
160
|
+
return null;
|
|
161
|
+
return await next.resolve(fragments.slice(1), ctx);
|
|
211
162
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
163
|
+
async resolveWild(fragments, ctx) {
|
|
164
|
+
if (!this.wild)
|
|
165
|
+
return null;
|
|
166
|
+
if (fragments.length < 1)
|
|
167
|
+
return null;
|
|
168
|
+
ctx.params[this.wildCard] = fragments[0];
|
|
169
|
+
return this.wild.resolve(fragments.slice(1), ctx);
|
|
215
170
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}
|
|
234
|
-
else {
|
|
235
|
-
args._addOutlet(blankLeaf);
|
|
236
|
-
mask = [];
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
else {
|
|
240
|
-
if (matching && from.length === 0) {
|
|
241
|
-
depth = args._outletChain.length + stack.length;
|
|
242
|
-
matching = false;
|
|
243
|
-
}
|
|
244
|
-
const segment = frags.splice(0, 1)[0];
|
|
245
|
-
const other = from.splice(0, 1)[0];
|
|
246
|
-
const subRoute = cursor.nested.get(segment);
|
|
247
|
-
if (subRoute) {
|
|
248
|
-
if (matching && segment !== other) {
|
|
249
|
-
depth = args._outletChain.length + stack.length;
|
|
250
|
-
matching = false;
|
|
251
|
-
}
|
|
252
|
-
;
|
|
253
|
-
stack.push(subRoute);
|
|
254
|
-
}
|
|
255
|
-
else if (cursor.wild) {
|
|
256
|
-
if (matching && cursor.nested.has(other)) {
|
|
257
|
-
depth = args._outletChain.length + stack.length;
|
|
258
|
-
matching = false;
|
|
259
|
-
}
|
|
260
|
-
;
|
|
261
|
-
args.params[cursor.wildCard] = segment;
|
|
262
|
-
stack.push(cursor.wild);
|
|
263
|
-
}
|
|
264
|
-
else {
|
|
265
|
-
args._addOutlet(blankLeaf);
|
|
266
|
-
mask = [];
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
else {
|
|
271
|
-
if (cursor.route) {
|
|
272
|
-
args._addOutlet(cursor.route);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
171
|
+
async resolveSlug(fragments, ctx) {
|
|
172
|
+
if (!this.slug)
|
|
173
|
+
return null;
|
|
174
|
+
ctx.params["$"] = fragments.join("/");
|
|
175
|
+
const res = this.slug.resolve
|
|
176
|
+
? await this.slug.resolve(ctx)
|
|
177
|
+
: null;
|
|
178
|
+
return res;
|
|
179
|
+
}
|
|
180
|
+
async resolveNative(fragments, ctx) {
|
|
181
|
+
if (!this.root)
|
|
182
|
+
return null;
|
|
183
|
+
if (fragments.length < 2)
|
|
184
|
+
return null;
|
|
185
|
+
if (fragments[0] != "_")
|
|
186
|
+
return null;
|
|
187
|
+
return await ResolveNatively(fragments, ctx);
|
|
275
188
|
}
|
|
276
|
-
|
|
277
|
-
|
|
189
|
+
async unwrap(ctx, res) {
|
|
190
|
+
if (!BadResponse(res))
|
|
191
|
+
return res;
|
|
192
|
+
if (!this.slug)
|
|
193
|
+
return res;
|
|
194
|
+
if (res === null)
|
|
195
|
+
res = new Response("Not Found", { status: 404, statusText: "Not Found", headers: ctx.headers });
|
|
196
|
+
if (res.headers.has("X-Caught"))
|
|
197
|
+
return res;
|
|
198
|
+
const caught = await this.slug.error(ctx, res);
|
|
199
|
+
if (!caught)
|
|
200
|
+
return res;
|
|
201
|
+
caught.headers.set("X-Caught", "true");
|
|
202
|
+
return caught;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function BadResponse(res) {
|
|
206
|
+
if (res === null)
|
|
207
|
+
return true;
|
|
208
|
+
if (res.status < 200)
|
|
209
|
+
return true;
|
|
210
|
+
if (res.status > 299)
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
async function ResolveNatively(fragments, ctx) {
|
|
214
|
+
switch (fragments[1]) {
|
|
215
|
+
case "dynamic": return dynamic._resolve(fragments, ctx);
|
|
216
|
+
case "endpoint": return endpoint._resolve(fragments, ctx);
|
|
217
|
+
case "mount": return mount._resolve(fragments);
|
|
218
|
+
case "style": return css._resolve(fragments);
|
|
278
219
|
}
|
|
279
|
-
|
|
280
|
-
return depth;
|
|
220
|
+
return null;
|
|
281
221
|
}
|
package/bin/types.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ParameterShaper } from "./util/parameters.js";
|
|
2
|
+
import { RouteContext } from "./router.js";
|
|
3
|
+
export type CatchFunction<T> = (args: T, err: unknown) => Promise<Response | JSX.Element | null>;
|
|
4
|
+
export type RenderFunction<T> = (args: T) => Promise<Response | JSX.Element | null>;
|
|
5
|
+
export type RouteModule<T extends ParameterShaper> = {
|
|
6
|
+
parameters?: T;
|
|
7
|
+
loader?: RenderFunction<RouteContext<T>>;
|
|
8
|
+
action?: RenderFunction<RouteContext<T>>;
|
|
9
|
+
error?: CatchFunction<RouteContext<T>>;
|
|
10
|
+
};
|
package/bin/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface CookieOptions {
|
|
2
|
+
domain?: string | undefined;
|
|
3
|
+
expires?: Date;
|
|
4
|
+
httpOnly?: boolean;
|
|
5
|
+
maxAge?: number;
|
|
6
|
+
partitioned?: boolean;
|
|
7
|
+
path?: string;
|
|
8
|
+
priority?: "low" | "medium" | "high";
|
|
9
|
+
sameSite?: "lax" | "strict" | "none";
|
|
10
|
+
secure?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare class Cookies {
|
|
13
|
+
private map;
|
|
14
|
+
private config;
|
|
15
|
+
constructor(headers: Headers);
|
|
16
|
+
get(name: string): string | null;
|
|
17
|
+
has(name: string): boolean;
|
|
18
|
+
set(name: string, value: string, options?: CookieOptions): void;
|
|
19
|
+
flash(name: string, value: string): void;
|
|
20
|
+
unset(name: string): void;
|
|
21
|
+
export(): string[];
|
|
22
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export class Cookies {
|
|
2
|
+
map;
|
|
3
|
+
config;
|
|
4
|
+
constructor(headers) {
|
|
5
|
+
this.config = {};
|
|
6
|
+
this.map = {};
|
|
7
|
+
const cookie = headers.get("Cookie");
|
|
8
|
+
if (!cookie)
|
|
9
|
+
return;
|
|
10
|
+
for (const line of cookie.split("; ")) {
|
|
11
|
+
const [name, value] = line.split("=");
|
|
12
|
+
this.map[name] = value;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
get(name) {
|
|
16
|
+
return this.map[name] || null;
|
|
17
|
+
}
|
|
18
|
+
has(name) {
|
|
19
|
+
return name in this.map;
|
|
20
|
+
}
|
|
21
|
+
set(name, value, options = {}) {
|
|
22
|
+
if (!options['path'])
|
|
23
|
+
options['path'] = "/";
|
|
24
|
+
this.config[name] = options;
|
|
25
|
+
this.map[name] = value;
|
|
26
|
+
}
|
|
27
|
+
flash(name, value) {
|
|
28
|
+
return this.set(name, value, { maxAge: 0 });
|
|
29
|
+
}
|
|
30
|
+
unset(name) {
|
|
31
|
+
return this.set(name, "", { maxAge: 0 });
|
|
32
|
+
}
|
|
33
|
+
export() {
|
|
34
|
+
const headers = new Array();
|
|
35
|
+
for (const name in this.config) {
|
|
36
|
+
let config = "";
|
|
37
|
+
for (const opt in this.config[name]) {
|
|
38
|
+
const prop = opt === "maxAge"
|
|
39
|
+
? "Max-Age"
|
|
40
|
+
: opt[0].toUpperCase() + opt.slice(1);
|
|
41
|
+
const raw = this.config[name][opt];
|
|
42
|
+
if (raw === true) {
|
|
43
|
+
config += `; ${prop}`;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (raw === false)
|
|
47
|
+
continue;
|
|
48
|
+
let value = String(raw);
|
|
49
|
+
value = value[0].toUpperCase() + value.slice(1);
|
|
50
|
+
config += `; ${prop}=${value}`;
|
|
51
|
+
}
|
|
52
|
+
const cookie = name + "=" + this.map[name] + config + ";";
|
|
53
|
+
headers.push(cookie);
|
|
54
|
+
}
|
|
55
|
+
return headers;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare class StyleClass {
|
|
2
|
+
readonly name: string;
|
|
3
|
+
readonly style: string;
|
|
4
|
+
readonly hash: string;
|
|
5
|
+
constructor(name: string, style: string);
|
|
6
|
+
toString(): string;
|
|
7
|
+
}
|
|
8
|
+
export declare function GetSheetUrl(): string;
|
|
9
|
+
export declare function _resolve(fragments: string[]): Response | null;
|
package/bin/util/css.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { QuickHash } from "../util/hash.js";
|
|
2
|
+
const classNamePattern = /^[a-zA-Z_][a-zA-Z0-9_-]*$/;
|
|
3
|
+
const registry = new Array();
|
|
4
|
+
let cache = null;
|
|
5
|
+
export class StyleClass {
|
|
6
|
+
name;
|
|
7
|
+
style;
|
|
8
|
+
hash;
|
|
9
|
+
constructor(name, style) {
|
|
10
|
+
if (!name.match(classNamePattern))
|
|
11
|
+
throw new Error("Cannot use given name for CSS class");
|
|
12
|
+
this.hash = QuickHash(style);
|
|
13
|
+
this.name = `${name}-${this.hash}`;
|
|
14
|
+
style = style.replaceAll(".this", "." + this.name);
|
|
15
|
+
this.style = style;
|
|
16
|
+
registry.push(this);
|
|
17
|
+
cache = null;
|
|
18
|
+
}
|
|
19
|
+
toString() {
|
|
20
|
+
return this.name;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function GetSheet() {
|
|
24
|
+
return cache || BuildSheet();
|
|
25
|
+
}
|
|
26
|
+
export function GetSheetUrl() {
|
|
27
|
+
const sheet = GetSheet();
|
|
28
|
+
return `/_/style/${sheet.hash}.css`;
|
|
29
|
+
}
|
|
30
|
+
export function _resolve(fragments) {
|
|
31
|
+
if (!fragments[2])
|
|
32
|
+
return null;
|
|
33
|
+
const build = GetSheet();
|
|
34
|
+
if (!fragments[2].startsWith(build.hash))
|
|
35
|
+
return null;
|
|
36
|
+
const headers = new Headers();
|
|
37
|
+
headers.set("Content-Type", "text/css");
|
|
38
|
+
headers.set("Cache-Control", "public, max-age=604800");
|
|
39
|
+
return new Response(build.sheet, { headers });
|
|
40
|
+
}
|
|
41
|
+
function BuildSheet() {
|
|
42
|
+
const key = registry.map(x => x.hash).join("");
|
|
43
|
+
const hash = QuickHash(key);
|
|
44
|
+
const sheet = registry.map(x => x.style).join("");
|
|
45
|
+
cache = { hash, sheet };
|
|
46
|
+
return cache;
|
|
47
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { GenericContext } from "../router.js";
|
|
2
|
+
export declare function RegisterDynamic<T>(load: Loader<T>): string;
|
|
3
|
+
type Loader<T> = (params: T, ctx: GenericContext) => Promise<JSX.Element>;
|
|
4
|
+
export declare function _resolve(fragments: string[], ctx: GenericContext): Promise<Response | null>;
|
|
5
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { QuickHash } from "../util/hash.js";
|
|
2
|
+
const registry = new Map();
|
|
3
|
+
const index = new Map();
|
|
4
|
+
export function RegisterDynamic(load) {
|
|
5
|
+
const existing = index.get(load);
|
|
6
|
+
if (existing)
|
|
7
|
+
return existing;
|
|
8
|
+
const hash = QuickHash(String(load));
|
|
9
|
+
const name = `${encodeURIComponent(load.name)}-${hash}`;
|
|
10
|
+
registry.set(name, load);
|
|
11
|
+
const url = `/_/dynamic/${name}?`;
|
|
12
|
+
index.set(load, url);
|
|
13
|
+
return url;
|
|
14
|
+
}
|
|
15
|
+
export async function _resolve(fragments, ctx) {
|
|
16
|
+
if (!fragments[2])
|
|
17
|
+
return null;
|
|
18
|
+
const endpoint = registry.get(fragments[2]);
|
|
19
|
+
if (!endpoint)
|
|
20
|
+
return null;
|
|
21
|
+
const props = {};
|
|
22
|
+
for (const [key, value] of ctx.url.searchParams)
|
|
23
|
+
props[key] = value;
|
|
24
|
+
ctx.headers.set("X-Partial", "true");
|
|
25
|
+
return ctx.render(await endpoint(props, ctx));
|
|
26
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { RenderFunction } from "../types.js";
|
|
2
|
+
import { GenericContext } from "../router.js";
|
|
3
|
+
export declare class Endpoint {
|
|
4
|
+
readonly render: RenderFunction<GenericContext>;
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly url: string;
|
|
7
|
+
constructor(render: RenderFunction<GenericContext>, name?: string);
|
|
8
|
+
}
|
|
9
|
+
export declare function _resolve(fragments: string[], ctx: GenericContext): Promise<Response | null>;
|