@tiddlywiki/mws 0.0.12 → 0.0.13
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 +3 -1
- package/dist/server.d.ts +923 -0
- package/dist/server.js +1763 -1415
- package/dist/server.js.map +1 -1
- package/editions/mws-docs/tiddlers/$__StoryList.tid +3 -3
- package/editions/mws-docs/tiddlers/MWS and SQLite.md +11 -0
- package/editions/mws-docs/tiddlers/Request Server.md +20 -0
- package/editions/mws-docs/tiddlers/Request Server.md.meta +5 -0
- package/package.json +16 -10
- package/prisma/schema.prisma +1 -0
- package/react-user-mgmt/package-lock.json +76 -321
- package/react-user-mgmt/package.json +2 -5
- package/react-user-mgmt/public/chunk-YALWN5OT.js +2 -0
- package/react-user-mgmt/public/chunk-YALWN5OT.js.map +7 -0
- package/react-user-mgmt/public/esm-QKDFEP3B.js +3 -0
- package/react-user-mgmt/public/esm-QKDFEP3B.js.map +7 -0
- package/react-user-mgmt/public/index.html +1 -26
- package/react-user-mgmt/public/main.js +68 -109
- package/react-user-mgmt/public/main.js.map +4 -4
- package/react-user-mgmt/public/stats.css +597 -0
- package/react-user-mgmt/public/stats.html +59 -0
- package/react-user-mgmt/public/stats.js +2013 -0
- package/react-user-mgmt/public/stats.json +1 -0
- package/react-user-mgmt/public/stats.svg +4 -0
- package/react-user-mgmt/tsconfig.json +3 -0
- package/dist/server.cjs +0 -16677
- package/dist/server.cjs.map +0 -1
- package/src/StateObject.ts +0 -316
- package/src/commander.ts +0 -417
- package/src/commands/divider.ts +0 -20
- package/src/commands/index.ts +0 -24
- package/src/commands/mws-init-store.ts +0 -77
- package/src/commands/mws-listen.ts +0 -136
- package/src/commands/mws-load-archive.ts +0 -281
- package/src/commands/mws-load-plugin-bags.ts +0 -151
- package/src/commands/mws-load-tiddlers.ts +0 -37
- package/src/commands/mws-load-wiki-folder.ts +0 -124
- package/src/commands/mws-render-tiddlywiki5.ts +0 -55
- package/src/commands/mws-save-archive.ts +0 -111
- package/src/commands/mws-save-tiddler-text.ts +0 -33
- package/src/commands/tests-complete.ts +0 -21
- package/src/global.ts +0 -247
- package/src/routes/TiddlerStore.ts +0 -722
- package/src/routes/bag-file-server.ts +0 -340
- package/src/routes/cache.ts +0 -25
- package/src/routes/index.ts +0 -138
- package/src/routes/managers/index.ts +0 -17
- package/src/routes/managers/manager-recipes.ts +0 -438
- package/src/routes/managers/manager-users.ts +0 -201
- package/src/routes/managers/router-tiddlers.ts +0 -313
- package/src/routes/router.ts +0 -429
- package/src/routes/services/PasswordService.ts +0 -178
- package/src/routes/services/attachments.ts +0 -259
- package/src/routes/services/sessions.ts +0 -166
- package/src/routes/tw-test.ts +0 -65
- package/src/server.ts +0 -151
- package/src/setupDevServer.ts +0 -71
- package/src/streamer.ts +0 -430
- package/src/tiddlywiki.ts +0 -94
- package/src/utils/data-checks.ts +0 -172
- package/src/utils/index.ts +0 -4
- package/src/utils/rootRoute.ts +0 -237
- package/src/utils/utils.ts +0 -403
- package/src/utils/zodAssert.ts +0 -269
- /package/prisma/{migrations/notes.md → notes.md} +0 -0
package/src/utils/utils.ts
DELETED
|
@@ -1,403 +0,0 @@
|
|
|
1
|
-
import { IncomingHttpHeaders, OutgoingHttpHeaders } from "node:http";
|
|
2
|
-
import { Streamer } from "../streamer";
|
|
3
|
-
import { StateObject } from '../StateObject';
|
|
4
|
-
import { createHash } from "node:crypto";
|
|
5
|
-
import * as zlib from "node:zlib";
|
|
6
|
-
import { ok } from "node:assert";
|
|
7
|
-
import { promisify } from "node:util";
|
|
8
|
-
import { SiteConfig } from "../routes/router";
|
|
9
|
-
import * as path from "node:path";
|
|
10
|
-
import { createRequire } from "node:module";
|
|
11
|
-
import { fileURLToPath } from "node:url";
|
|
12
|
-
/**
|
|
13
|
-
Options include:
|
|
14
|
-
- `cbPartStart(headers,name,filename)` - invoked when a file starts being received
|
|
15
|
-
- `cbPartChunk(chunk)` - invoked when a chunk of a file is received
|
|
16
|
-
- `cbPartEnd()` - invoked when a file finishes being received
|
|
17
|
-
- `cbFinished(err)` - invoked when the all the form data has been processed
|
|
18
|
-
*/
|
|
19
|
-
export function readMultipartData(this: StateObject<any, any>, options: {
|
|
20
|
-
cbPartStart: (headers: IncomingHttpHeaders, name: string | null, filename: string | null) => void,
|
|
21
|
-
cbPartChunk: (chunk: Buffer) => void,
|
|
22
|
-
cbPartEnd: () => void,
|
|
23
|
-
cbFinished: (err: Error | string | null) => void
|
|
24
|
-
}) {
|
|
25
|
-
return new Promise<void>((resolve, reject) => {
|
|
26
|
-
// Check that the Content-Type is multipart/form-data
|
|
27
|
-
const contentType = this.headers['content-type'];
|
|
28
|
-
if (!contentType?.startsWith("multipart/form-data")) {
|
|
29
|
-
return reject("Expected multipart/form-data content type");
|
|
30
|
-
}
|
|
31
|
-
// Extract the boundary string from the Content-Type header
|
|
32
|
-
const boundaryMatch = contentType.match(/boundary=(.+)$/);
|
|
33
|
-
if (!boundaryMatch) {
|
|
34
|
-
return reject("Missing boundary in multipart/form-data");
|
|
35
|
-
}
|
|
36
|
-
const boundary = boundaryMatch[1];
|
|
37
|
-
const boundaryBuffer = Buffer.from("--" + boundary);
|
|
38
|
-
// Initialise
|
|
39
|
-
let buffer = Buffer.alloc(0);
|
|
40
|
-
let processingPart = false;
|
|
41
|
-
// Process incoming chunks
|
|
42
|
-
this.reader.on("data", (chunk) => {
|
|
43
|
-
// Accumulate the incoming data
|
|
44
|
-
buffer = Buffer.concat([buffer, chunk]);
|
|
45
|
-
// Loop through any parts within the current buffer
|
|
46
|
-
while (true) {
|
|
47
|
-
if (!processingPart) {
|
|
48
|
-
// If we're not processing a part then we try to find a boundary marker
|
|
49
|
-
const boundaryIndex = buffer.indexOf(boundaryBuffer);
|
|
50
|
-
if (boundaryIndex === -1) {
|
|
51
|
-
// Haven't reached the boundary marker yet, so we should wait for more data
|
|
52
|
-
break;
|
|
53
|
-
}
|
|
54
|
-
// Look for the end of the headers
|
|
55
|
-
const endOfHeaders = buffer.indexOf("\r\n\r\n", boundaryIndex + boundaryBuffer.length);
|
|
56
|
-
if (endOfHeaders === -1) {
|
|
57
|
-
// Haven't reached the end of the headers, so we should wait for more data
|
|
58
|
-
break;
|
|
59
|
-
}
|
|
60
|
-
// Extract and parse headers
|
|
61
|
-
const headersPart = Uint8Array.prototype.slice.call(buffer, boundaryIndex + boundaryBuffer.length, endOfHeaders).toString();
|
|
62
|
-
const currentHeaders: IncomingHttpHeaders = {};
|
|
63
|
-
headersPart.split("\r\n").forEach(headerLine => {
|
|
64
|
-
const [key, value] = headerLine.split(": ");
|
|
65
|
-
ok(typeof key === "string");
|
|
66
|
-
currentHeaders[key.toLowerCase()] = value;
|
|
67
|
-
});
|
|
68
|
-
// Parse the content disposition header
|
|
69
|
-
const contentDisposition: {
|
|
70
|
-
name: string | null,
|
|
71
|
-
filename: string | null
|
|
72
|
-
} = {
|
|
73
|
-
name: null,
|
|
74
|
-
filename: null
|
|
75
|
-
};
|
|
76
|
-
if (currentHeaders["content-disposition"]) {
|
|
77
|
-
// Split the content-disposition header into semicolon-delimited parts
|
|
78
|
-
const parts = currentHeaders["content-disposition"].split(";").map(part => part.trim());
|
|
79
|
-
// Iterate over each part to extract name and filename if they exist
|
|
80
|
-
parts.forEach(part => {
|
|
81
|
-
if (part.startsWith("name=")) {
|
|
82
|
-
// Remove "name=" and trim quotes
|
|
83
|
-
contentDisposition.name = part.substring(6, part.length - 1);
|
|
84
|
-
} else if (part.startsWith("filename=")) {
|
|
85
|
-
// Remove "filename=" and trim quotes
|
|
86
|
-
contentDisposition.filename = part.substring(10, part.length - 1);
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
processingPart = true;
|
|
91
|
-
options.cbPartStart(currentHeaders, contentDisposition.name, contentDisposition.filename);
|
|
92
|
-
// Slice the buffer to the next part
|
|
93
|
-
buffer = Buffer.from(buffer, endOfHeaders + 4);
|
|
94
|
-
} else {
|
|
95
|
-
const boundaryIndex = buffer.indexOf(boundaryBuffer);
|
|
96
|
-
if (boundaryIndex >= 0) {
|
|
97
|
-
// Return the part up to the boundary minus the terminating LF CR
|
|
98
|
-
options.cbPartChunk(Buffer.from(buffer, 0, boundaryIndex - 2));
|
|
99
|
-
options.cbPartEnd();
|
|
100
|
-
processingPart = false;
|
|
101
|
-
// this starts at the boundary marker, which gets picked up on the next loop
|
|
102
|
-
buffer = Buffer.from(buffer, boundaryIndex);
|
|
103
|
-
} else {
|
|
104
|
-
// Return the rest of the buffer
|
|
105
|
-
options.cbPartChunk(buffer);
|
|
106
|
-
// Reset the buffer and wait for more data
|
|
107
|
-
buffer = Buffer.alloc(0);
|
|
108
|
-
break;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
// All done
|
|
115
|
-
this.reader.on("end", () => {
|
|
116
|
-
resolve();
|
|
117
|
-
});
|
|
118
|
-
}).then(() => {
|
|
119
|
-
options.cbFinished(null);
|
|
120
|
-
}, (err) => {
|
|
121
|
-
options.cbFinished(err);
|
|
122
|
-
throw err;
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
/*
|
|
130
|
-
Send a response to the client. This method checks if the response must be sent
|
|
131
|
-
or if the client alrady has the data cached. If that's the case only a 304
|
|
132
|
-
response will be transmitted and the browser will use the cached data.
|
|
133
|
-
Only requests with status code 200 are considdered for caching.
|
|
134
|
-
request: request instance passed to the handler
|
|
135
|
-
response: response instance passed to the handler
|
|
136
|
-
statusCode: stauts code to send to the browser
|
|
137
|
-
headers: response headers (they will be augmented with an `Etag` header)
|
|
138
|
-
data: the data to send (passed to the end method of the response instance)
|
|
139
|
-
encoding: the encoding of the data to send (passed to the end method of the response instance)
|
|
140
|
-
*/
|
|
141
|
-
export async function sendResponse(config: SiteConfig, state: StateObject<any, any>, statusCode: number, headers: OutgoingHttpHeaders, data: string | Buffer, encoding?: NodeJS.BufferEncoding) {
|
|
142
|
-
// console.log("sendResponse");
|
|
143
|
-
if (config.enableBrowserCache && (statusCode == 200)) {
|
|
144
|
-
// console.log("enableBrowserCache");
|
|
145
|
-
var hash = createHash('md5');
|
|
146
|
-
// Put everything into the hash that could change and invalidate the data that
|
|
147
|
-
// the browser already stored. The headers the data and the encoding.
|
|
148
|
-
hash.update(data);
|
|
149
|
-
hash.update(JSON.stringify(headers));
|
|
150
|
-
if (encoding) {
|
|
151
|
-
hash.update(encoding);
|
|
152
|
-
}
|
|
153
|
-
var contentDigest = hash.digest("hex");
|
|
154
|
-
// RFC 7232 section 2.3 mandates for the etag to be enclosed in quotes
|
|
155
|
-
headers["Etag"] = '"' + contentDigest + '"';
|
|
156
|
-
headers["Cache-Control"] = "max-age=0, must-revalidate";
|
|
157
|
-
// Check if any of the hashes contained within the if-none-match header
|
|
158
|
-
// matches the current hash.
|
|
159
|
-
// If one matches, do not send the data but tell the browser to use the
|
|
160
|
-
// cached data.
|
|
161
|
-
// We do not implement "*" as it makes no sense here.
|
|
162
|
-
var ifNoneMatch = state.headers["if-none-match"];
|
|
163
|
-
if (ifNoneMatch) {
|
|
164
|
-
var matchParts = ifNoneMatch.split(",").map(function (etag) {
|
|
165
|
-
return etag.replace(/^[ "]+|[ "]+$/g, "");
|
|
166
|
-
});
|
|
167
|
-
if (matchParts.indexOf(contentDigest) != -1) {
|
|
168
|
-
return state.sendEmpty(304, headers);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
/*
|
|
173
|
-
If the gzip=yes is set, check if the user agent permits compression. If so,
|
|
174
|
-
compress our response if the raw data is bigger than 2k. Compressing less
|
|
175
|
-
data is inefficient.
|
|
176
|
-
*/
|
|
177
|
-
if (config.enableGzip && (data.length > 2048)) {
|
|
178
|
-
var acceptEncoding = state.headers["accept-encoding"] || "";
|
|
179
|
-
if (/\bdeflate\b/.test(acceptEncoding)) {
|
|
180
|
-
headers["Content-Encoding"] = "deflate";
|
|
181
|
-
data = await promisify(zlib.deflate)(data);
|
|
182
|
-
} else if (/\bgzip\b/.test(acceptEncoding)) {
|
|
183
|
-
headers["Content-Encoding"] = "gzip";
|
|
184
|
-
data = await promisify(zlib.gzip)(data);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
if (typeof data === "string") {
|
|
188
|
-
ok(encoding, "encoding must be set for string data");
|
|
189
|
-
return state.sendString(statusCode, headers, data, encoding);
|
|
190
|
-
} else {
|
|
191
|
-
return state.sendBuffer(statusCode, headers, data);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
interface Route {
|
|
197
|
-
useACL: any;
|
|
198
|
-
method: string;
|
|
199
|
-
entityName: any;
|
|
200
|
-
csrfDisable: any;
|
|
201
|
-
bodyFormat: string;
|
|
202
|
-
path: { source: string; };
|
|
203
|
-
handler: (streamer: Streamer) => void;
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
interface State {
|
|
207
|
-
wiki: any;
|
|
208
|
-
boot: any;
|
|
209
|
-
server: any;
|
|
210
|
-
urlInfo: any;
|
|
211
|
-
queryParameters: any;
|
|
212
|
-
pathPrefix: string;
|
|
213
|
-
sendResponse: any;
|
|
214
|
-
redirect: any;
|
|
215
|
-
streamMultipartData: any;
|
|
216
|
-
authenticatedUser: any;
|
|
217
|
-
authenticatedUsername: any;
|
|
218
|
-
authorizationType: string;
|
|
219
|
-
allowAnon: boolean;
|
|
220
|
-
anonAccessConfigured: boolean;
|
|
221
|
-
allowAnonReads: boolean;
|
|
222
|
-
allowAnonWrites: boolean;
|
|
223
|
-
showAnonConfig: boolean;
|
|
224
|
-
firstGuestUser: boolean;
|
|
225
|
-
data?: any;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
interface Server {
|
|
229
|
-
enableBrowserCache: boolean;
|
|
230
|
-
enableGzip: boolean;
|
|
231
|
-
get: (key: string) => any;
|
|
232
|
-
methodMappings: { [key: string]: string };
|
|
233
|
-
isAuthorized: (authorizationType: string, username: string) => boolean;
|
|
234
|
-
getAnonymousAccessConfig: () => { allowReads: boolean; allowWrites: boolean; isEnabled: boolean; showAnonConfig: boolean };
|
|
235
|
-
sqlTiddlerDatabase: any;
|
|
236
|
-
servername: string;
|
|
237
|
-
findMatchingRoute: (request: Streamer, state: State) => Route;
|
|
238
|
-
authenticateUser: (request: Streamer, response: Streamer) => any;
|
|
239
|
-
methodACLPermMappings: { [key: string]: string };
|
|
240
|
-
csrfDisable: boolean;
|
|
241
|
-
wiki: any;
|
|
242
|
-
boot: any;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
export function is<T>(a: any, b: boolean): a is T { return b; }
|
|
247
|
-
|
|
248
|
-
/** Initiates a timer that must get cancelled before the current turn of the event loop ends */
|
|
249
|
-
export class SyncCheck {
|
|
250
|
-
done;
|
|
251
|
-
constructor() {
|
|
252
|
-
let cancelled = false;
|
|
253
|
-
const error = new Error("SyncCheck was not completed before the current turn of the event loop ended");
|
|
254
|
-
process.nextTick(() => {
|
|
255
|
-
if (cancelled) return;
|
|
256
|
-
console.log(error);
|
|
257
|
-
process.exit(1);
|
|
258
|
-
});
|
|
259
|
-
this.done = () => { cancelled = true };
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* If any property returns a function, and that function returns a promise,
|
|
266
|
-
* the proxy will throw an error if the promise is not awaited before the next property access.
|
|
267
|
-
*
|
|
268
|
-
* The stack trace of the error will point to the line where the promise was returned.
|
|
269
|
-
*/
|
|
270
|
-
export function createStrictAwaitProxy<T extends object>(instance: T): T {
|
|
271
|
-
let outstandingPromiseError: Error | null = null;
|
|
272
|
-
|
|
273
|
-
function wrappedFunction(value: Function) {
|
|
274
|
-
return function (this: any, ...args: any[]) {
|
|
275
|
-
// Protect against calling any methods while an outstanding promise exists.
|
|
276
|
-
|
|
277
|
-
// Call the original function preserving the correct "this" context.
|
|
278
|
-
const result = Reflect.apply(value, this, args);
|
|
279
|
-
// If the result is a promise, wrap it to clear the outstanding lock on await.
|
|
280
|
-
if (result instanceof Promise) {
|
|
281
|
-
return new Proxy(result, {
|
|
282
|
-
get(promiseTarget, promiseProp, promiseReceiver) {
|
|
283
|
-
// When the promise's then property is accessed (i.e. when it's awaited),
|
|
284
|
-
// clear the outstanding promise so that subsequent method calls are allowed.
|
|
285
|
-
if (promiseProp === "then") {
|
|
286
|
-
outstandingPromiseError = null;
|
|
287
|
-
}
|
|
288
|
-
const inner = Reflect.get(promiseTarget, promiseProp, promiseReceiver).bind(promiseTarget);
|
|
289
|
-
if (promiseProp === "catch") {
|
|
290
|
-
return wrappedFunction(inner);
|
|
291
|
-
}
|
|
292
|
-
return inner;
|
|
293
|
-
}
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
|
-
return result;
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
return new Proxy(instance, {
|
|
301
|
-
get(target, prop, receiver) {
|
|
302
|
-
// throw an error if an outstanding promise exists.
|
|
303
|
-
if (outstandingPromiseError !== null) {
|
|
304
|
-
throw outstandingPromiseError;
|
|
305
|
-
}
|
|
306
|
-
// Use Reflect.get to get the property value.
|
|
307
|
-
const value = Reflect.get(target, prop, receiver);
|
|
308
|
-
|
|
309
|
-
// If the property is a function, return a wrapped function.
|
|
310
|
-
if (typeof value === "function") {
|
|
311
|
-
return wrappedFunction(value);
|
|
312
|
-
}
|
|
313
|
-
// For non-function properties, just return the value.
|
|
314
|
-
return value;
|
|
315
|
-
}
|
|
316
|
-
});
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
export function tryParseJSON<T>(json: string): T | undefined {
|
|
320
|
-
try {
|
|
321
|
-
return JSON.parse(json);
|
|
322
|
-
} catch {
|
|
323
|
-
return undefined;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
export class TypedGenerator<T extends [any, any][]> {
|
|
329
|
-
static checker<T extends [any, any][]>() {
|
|
330
|
-
return {
|
|
331
|
-
/** `const value1 = asV(1, yield asY(0, value0))` */
|
|
332
|
-
asV<I extends number>(index: I, args: T[I][0]): T[I][0] { return args; },
|
|
333
|
-
/** `const value1 = asV(1, yield asY(0, value0))` */
|
|
334
|
-
asY<I extends number>(index: I, ret: T[I][1]): T[I][1] { return ret; },
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
static wrapper<T extends [any, any][]>() {
|
|
338
|
-
return <A extends any[]>(factory: (...args: A) => Generator<any, any, any>) => {
|
|
339
|
-
return (...args: A) => new TypedGenerator<T>(factory(...args));
|
|
340
|
-
};
|
|
341
|
-
}
|
|
342
|
-
constructor(private inner: Generator<any, any, any>, private index = 0) { }
|
|
343
|
-
async next<I extends number>(index: I, ...args: T[I][0] extends void ? [] : [T[I][0]]): Promise<(
|
|
344
|
-
T extends [...any[], T[I]] ? IteratorReturnResult<T[I][1]> : IteratorYieldResult<T[I][1]>
|
|
345
|
-
)> {
|
|
346
|
-
if (index !== this.index) throw new Error("Invalid index");
|
|
347
|
-
this.index++;
|
|
348
|
-
return this.inner.next(...args) as any;
|
|
349
|
-
}
|
|
350
|
-
return(value: any): any { this.inner.return(value); }
|
|
351
|
-
throw(e: any): any { this.inner.throw(e); }
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
export async function mapAsync<T, U, V>(array: T[], callback: (this: V, value: T, index: number, array: T[]) => Promise<U>, thisArg?: any): Promise<U[]> {
|
|
355
|
-
const results = new Array(array.length);
|
|
356
|
-
for (let index = 0; index < array.length; index++) {
|
|
357
|
-
results[index] = await callback.call(thisArg, array[index] as T, index, array);
|
|
358
|
-
}
|
|
359
|
-
return results;
|
|
360
|
-
};
|
|
361
|
-
|
|
362
|
-
export async function filterAsync<T, V>(array: T[], callback: (this: V, value: T, index: number, array: T[]) => Promise<boolean>, thisArg?: any): Promise<T[]> {
|
|
363
|
-
const results = [];
|
|
364
|
-
for (let index = 0; index < array.length; index++) {
|
|
365
|
-
if (await callback.call(thisArg, array[index] as T, index, array)) {
|
|
366
|
-
results.push(array[index]);
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
return results as any;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
export function truthy<T>(
|
|
374
|
-
obj: T
|
|
375
|
-
): obj is Exclude<T, false | null | undefined | 0 | '' | void> {
|
|
376
|
-
return !!obj;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
export class UserError extends Error {
|
|
381
|
-
constructor(message: string) {
|
|
382
|
-
super(message);
|
|
383
|
-
this.name = "UserError";
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
/**
|
|
387
|
-
* This returns the resolved path relative to the executing code file,
|
|
388
|
-
* however it uses path.resolve, not require.resolve.
|
|
389
|
-
*/
|
|
390
|
-
|
|
391
|
-
export function dist_resolve(filepath: string) {
|
|
392
|
-
const filename = typeof module === "undefined" ? fileURLToPath(import.meta.url) : module.filename;
|
|
393
|
-
return path.resolve(path.dirname(filename), filepath);
|
|
394
|
-
}
|
|
395
|
-
export function dist_require_resolve(filepath: string) {
|
|
396
|
-
const filename = typeof module === "undefined" ? fileURLToPath(import.meta.url) : module.filename;
|
|
397
|
-
return createRequire(filename).resolve(filepath);
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
export declare interface JsonArray extends Array<JsonValue> { }
|
|
402
|
-
export declare type JsonObject = { [Key in string]?: JsonValue; };
|
|
403
|
-
export declare type JsonValue = string | number | boolean | JsonObject | JsonArray | null | Date;
|
package/src/utils/zodAssert.ts
DELETED
|
@@ -1,269 +0,0 @@
|
|
|
1
|
-
import { z, ZodEffects } from "zod";
|
|
2
|
-
import { StateObject } from "../StateObject";
|
|
3
|
-
import { Prisma } from "@prisma/client";
|
|
4
|
-
import { STREAM_ENDED } from "../streamer";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export namespace ZodAssert {
|
|
9
|
-
type Exactly<T, X> = T & Record<Exclude<keyof X, keyof T>, never>
|
|
10
|
-
/**
|
|
11
|
-
* assert zod on the request body (`state.data`).
|
|
12
|
-
*
|
|
13
|
-
* You cannot assert data on a stream or ignore body format.
|
|
14
|
-
*
|
|
15
|
-
* Attempting to do so will cause the schema argument to expect a string
|
|
16
|
-
* containing an error message informing the developer of this.
|
|
17
|
-
*/
|
|
18
|
-
export function data<T extends
|
|
19
|
-
S extends StateObject<"ignore" | "stream"> ? z.ZodUnknown :
|
|
20
|
-
S extends StateObject<"www-form-urlencoded"> ? z.ZodObject<any> :
|
|
21
|
-
z.ZodTypeAny, S extends StateObject
|
|
22
|
-
>(
|
|
23
|
-
state: S,
|
|
24
|
-
schema:
|
|
25
|
-
S extends StateObject<"ignore" | "stream">
|
|
26
|
-
? "it is invalid to assert data for this body format"
|
|
27
|
-
: (zod: S extends StateObject<"www-form-urlencoded"> ? Z2<"STRING"> : Z2<"JSON">) => T,
|
|
28
|
-
/**
|
|
29
|
-
* If the data does not match the schema, this function is called sync'ly with the zod error.
|
|
30
|
-
* Whatever it returns is sent as a utf8 string response body.
|
|
31
|
-
* @param error The zod error.
|
|
32
|
-
* @returns A string to send as the response body, or void to use the default error message.
|
|
33
|
-
*/
|
|
34
|
-
onError?: (error: z.ZodError<any>) => string | void
|
|
35
|
-
): asserts state is S & ({ data: z.infer<T> }) {
|
|
36
|
-
_zodAssert("data", state, schema as any, onError);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* assert zod on the route path match groups (`state.pathParams`).
|
|
41
|
-
*
|
|
42
|
-
* Sends a 404 response instead of 400.
|
|
43
|
-
*/
|
|
44
|
-
export function pathParams<
|
|
45
|
-
T extends Exactly<{ [k in keyof S["pathParams"]]: z.ZodTypeAny; }, T>,
|
|
46
|
-
S extends StateObject,
|
|
47
|
-
>(
|
|
48
|
-
state: S, schemaShape: (zod: Z2<"STRING">) => T,
|
|
49
|
-
/**
|
|
50
|
-
* If the data does not match the schema, this function is called sync'ly with the zod error.
|
|
51
|
-
* Whatever it returns is sent as a utf8 string response body.
|
|
52
|
-
* @param error The zod error.
|
|
53
|
-
* @returns A string to send as the response body, or void to use the default error message.
|
|
54
|
-
*/
|
|
55
|
-
onError?: (error: z.ZodError<any>) => string | void
|
|
56
|
-
): asserts state is S & ({ pathParams: z.infer<z.ZodObject<T>> }) {
|
|
57
|
-
_zodAssert("pathParams", state, schemaShape, onError);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* assert zod on the url query parameters (`state.queryParams`).
|
|
62
|
-
*
|
|
63
|
-
* This is the only place that the query parameters are declared, so the keys are not checked.
|
|
64
|
-
*
|
|
65
|
-
* If no value is specified, it defaults to an empty string
|
|
66
|
-
* (`?key&key=&key` becomes `key: ["", "", ""]`).
|
|
67
|
-
*
|
|
68
|
-
* In zod: `z.object({ key: z.array(z.string()).optional() })`
|
|
69
|
-
*/
|
|
70
|
-
export function queryParams<T extends Record<string, z.ZodTypeAny>, S extends StateObject>(
|
|
71
|
-
state: S, schemaShape: (zod: Z2<"STRING">) => T,
|
|
72
|
-
/**
|
|
73
|
-
* If the data does not match the schema, this function is called sync'ly with the zod error.
|
|
74
|
-
* Whatever it returns is sent as a utf8 string response body.
|
|
75
|
-
* @param error The zod error.
|
|
76
|
-
* @returns A string to send as the response body, or void to use the default error message.
|
|
77
|
-
*/
|
|
78
|
-
onError?: (error: z.ZodError<any>) => string | void
|
|
79
|
-
): asserts state is S & ({ queryParams: z.infer<z.ZodObject<T>> }) {
|
|
80
|
-
_zodAssert("queryParams", state, schemaShape, onError);
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* assert zod on the data passed to this function and returns the cleaned data.
|
|
84
|
-
* If onError is not specified, or does not return STREAM_ENDED, an error 500 is sent.
|
|
85
|
-
* Either way, zodAssert will throw STREAM_ENDED immediately.
|
|
86
|
-
*
|
|
87
|
-
* If onError is not specified, the error will be printed with console.log.
|
|
88
|
-
*/
|
|
89
|
-
export function any<T extends z.ZodTypeAny, S extends StateObject>(
|
|
90
|
-
state: S, schemaShape: (zod: Z2<"JSON">) => T, data: z.input<T>,
|
|
91
|
-
/**
|
|
92
|
-
* If the data does not match the schema, this function is called sync'ly with the zod error.
|
|
93
|
-
* If it does not return the STREAM_ENDED symbol indicating an appropriate error response was sent,
|
|
94
|
-
* zodAssert will throw with a 500 error.
|
|
95
|
-
*
|
|
96
|
-
* It is perfectly valid to throw an error from this function,
|
|
97
|
-
* as zodAssert will throw STREAM_ENDED immediately if it returns.
|
|
98
|
-
*
|
|
99
|
-
* @param error The zod error.
|
|
100
|
-
* @returns STREAM_ENDED to indicate an appropriate error response was sent, or undefined.
|
|
101
|
-
*/
|
|
102
|
-
onError?: (error: z.ZodError<any>) => void | symbol
|
|
103
|
-
): z.infer<T> { return _zodAssertAny("any", state, schemaShape, data, onError); }
|
|
104
|
-
/**
|
|
105
|
-
* assert zod on the data passed to this function and returns the cleaned data.
|
|
106
|
-
* If onError is not specified, or does not return STREAM_ENDED, an error 500 is sent.
|
|
107
|
-
* Either way, zodAssert will throw STREAM_ENDED immediately.
|
|
108
|
-
*
|
|
109
|
-
* If onError is not specified, the error will be printed with console.log.
|
|
110
|
-
*/
|
|
111
|
-
export function response<T extends z.ZodTypeAny, S extends StateObject>(
|
|
112
|
-
state: S, schemaShape: (zod: Z2<"JSON">) => T, data: z.input<T>,
|
|
113
|
-
/**
|
|
114
|
-
* If the data does not match the schema, this function is called sync'ly with the zod error.
|
|
115
|
-
* If it does not return the STREAM_ENDED symbol indicating an appropriate error response was sent,
|
|
116
|
-
* zodAssert will throw with a 500 error.
|
|
117
|
-
*
|
|
118
|
-
* It is perfectly valid to throw an error from this function,
|
|
119
|
-
* as zodAssert will throw STREAM_ENDED immediately if it returns.
|
|
120
|
-
*
|
|
121
|
-
* @param error The zod error.
|
|
122
|
-
* @returns STREAM_ENDED to indicate an appropriate error response was sent, or undefined.
|
|
123
|
-
*/
|
|
124
|
-
onError?: (error: z.ZodError<any>) => void | symbol
|
|
125
|
-
): z.infer<T> { return _zodAssertAny("response", state, schemaShape, data, onError); }
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
// type t = number extends PrismaPayloadScalars<"Roles">["role_id"] ? FieldTypeNumberSelector<"JSON"> : false;
|
|
130
|
-
export interface Z2<T extends FieldTypeGroups> extends _zod {
|
|
131
|
-
/**
|
|
132
|
-
* Tags the resulting value as being from the specified table field.
|
|
133
|
-
*
|
|
134
|
-
* The third argument is a string literal that specifies the field type based on the selected field.
|
|
135
|
-
*
|
|
136
|
-
* "string" and "parse-number"
|
|
137
|
-
* - will use decodeURIComponent on the value.
|
|
138
|
-
* - Require a string length at least one
|
|
139
|
-
*
|
|
140
|
-
* You can use .optional(), .nullable(), .nulish() after this to make the field optional.
|
|
141
|
-
*/
|
|
142
|
-
prismaField<Table extends Prisma.ModelName, Field extends keyof PrismaPayloadScalars<Table>>(
|
|
143
|
-
table: Table, field: Field,
|
|
144
|
-
fieldtype:
|
|
145
|
-
string extends (PrismaPayloadScalars<Table>[Field]) ? FieldTypeStringSelector<T> :
|
|
146
|
-
number extends (PrismaPayloadScalars<Table>[Field]) ? FieldTypeNumberSelector<T> :
|
|
147
|
-
boolean extends (PrismaPayloadScalars<Table>[Field]) ? FieldTypeBooleanSelector<T> :
|
|
148
|
-
never,
|
|
149
|
-
nullable?: null extends PrismaPayloadScalars<Table>[Field] ? true : false
|
|
150
|
-
): ZodEffects<any, PrismaField<Table, Field>, PrismaPayloadScalars<Table>[Field]>;
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
authUser(): z.ZodNullable<z.ZodObject<{
|
|
154
|
-
user_id: z.ZodNumber;
|
|
155
|
-
isAdmin: z.ZodBoolean;
|
|
156
|
-
username: z.ZodString;
|
|
157
|
-
sessionId: z.ZodNumber;
|
|
158
|
-
}>>;
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
export const _zodAssertAny = (
|
|
163
|
-
input: string,
|
|
164
|
-
state: StateObject,
|
|
165
|
-
schema: (z: Z2<any>) => z.ZodTypeAny | Record<string, z.ZodTypeAny>,
|
|
166
|
-
inputdata: any,
|
|
167
|
-
onError?: (error: z.ZodError<any>) => symbol | void,
|
|
168
|
-
) => {
|
|
169
|
-
const schema2: any = schema(makeZ2(input));
|
|
170
|
-
const { success, data, error } = z.any().pipe(schema2).safeParse(inputdata);
|
|
171
|
-
if (!success) {
|
|
172
|
-
if (!onError) error.issues.forEach(e => console.log(input, e.message, e.path));
|
|
173
|
-
if (onError?.(error) === STREAM_ENDED) throw STREAM_ENDED;
|
|
174
|
-
throw state.sendEmpty(500, { "x-reason": input });
|
|
175
|
-
}
|
|
176
|
-
return data;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const _zodAssert = (
|
|
180
|
-
input: "data" | "pathParams" | "queryParams",
|
|
181
|
-
state: StateObject,
|
|
182
|
-
schema: (z: Z2<any>) => z.ZodTypeAny | Record<string, z.ZodTypeAny>,
|
|
183
|
-
onError?: (error: z.ZodError<any>) => string | void,
|
|
184
|
-
) => {
|
|
185
|
-
|
|
186
|
-
// this checks if the schema is a string, since that is used to indicate invalid use cases of zodAssert,
|
|
187
|
-
// and if it's correctly typed, then this is the best way to find it.
|
|
188
|
-
if (typeof schema === "string") {
|
|
189
|
-
console.log(new Error(`schema is a string: ${schema}`));
|
|
190
|
-
throw state.sendEmpty(500);
|
|
191
|
-
}
|
|
192
|
-
const schema2: any = schema(makeZ2(input));
|
|
193
|
-
|
|
194
|
-
const { success, data, error } = z.any().pipe(
|
|
195
|
-
input === "data" ? schema2 : z.object(schema2)
|
|
196
|
-
).safeParse(state[input]);
|
|
197
|
-
if (!success) {
|
|
198
|
-
if (!onError) error.issues.forEach(e => console.log(input, e.message, e.path));
|
|
199
|
-
const status = input === "pathParams" ? 404 : 400;
|
|
200
|
-
const message = onError?.(error);
|
|
201
|
-
if (typeof message === "string")
|
|
202
|
-
throw state.sendString(status, { "x-reason": input }, message ?? "", "utf8");
|
|
203
|
-
else
|
|
204
|
-
throw state.sendEmpty(status, { "x-reason": input });
|
|
205
|
-
}
|
|
206
|
-
state[input] = data;
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
type _zod = typeof z;
|
|
210
|
-
type ExtraFieldType = "string" | "number" | "parse-number" | "boolean" | "parse-boolean";
|
|
211
|
-
type FieldTypeGroups = "STRING" | "JSON";
|
|
212
|
-
type FieldTypeStringSelector<T extends FieldTypeGroups> = T extends "STRING" ? "string" : "string";
|
|
213
|
-
type FieldTypeNumberSelector<T extends FieldTypeGroups> = T extends "STRING" ? "parse-number" : "number";
|
|
214
|
-
type FieldTypeBooleanSelector<T extends FieldTypeGroups> = T extends "STRING" ? "parse-boolean" : "boolean";
|
|
215
|
-
|
|
216
|
-
export const Z2 = makeZ2("any");
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
function makeZ2<T extends FieldTypeGroups>(input: "data" | "pathParams" | "queryParams" | "any" | "response" | string): Z2<T> {
|
|
220
|
-
const z2 = Object.create(z);
|
|
221
|
-
z2.prismaField = prismaField;
|
|
222
|
-
z2.authUser = () => z.object({
|
|
223
|
-
user_id: z.number(),
|
|
224
|
-
isAdmin: z.boolean(),
|
|
225
|
-
username: z.string(),
|
|
226
|
-
sessionId: z.number(),
|
|
227
|
-
}).nullable();
|
|
228
|
-
return z2;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
function prismaField(table: any, field: any, fieldtype: ExtraFieldType, nullable?: boolean): any {
|
|
233
|
-
const check = (() => {
|
|
234
|
-
switch (fieldtype) {
|
|
235
|
-
case "string":
|
|
236
|
-
return z.string()
|
|
237
|
-
.refine(x => x.length, { message: "String must have length" });
|
|
238
|
-
case "parse-number":
|
|
239
|
-
return z.string().min(1)
|
|
240
|
-
.pipe(z.bigint({ coerce: true }))
|
|
241
|
-
.pipe(z.number({ coerce: true }).finite())
|
|
242
|
-
.refine(x => !isNaN(x), { message: "Invalid number" });
|
|
243
|
-
case "parse-boolean":
|
|
244
|
-
return z.enum(["true", "false", "null"]).transform(x => x === "null" ? null : x === "true");
|
|
245
|
-
case "boolean":
|
|
246
|
-
return z.boolean();
|
|
247
|
-
case "number":
|
|
248
|
-
return z.number().finite().int();
|
|
249
|
-
default:
|
|
250
|
-
throw new Error("Invalid field type");
|
|
251
|
-
}
|
|
252
|
-
})();
|
|
253
|
-
return nullable ? check.nullable() : check;
|
|
254
|
-
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
// const zodAssertGlobal = {
|
|
259
|
-
// data: _zodAssert.bind(null, "data"),
|
|
260
|
-
// pathParams: _zodAssert.bind(null, "pathParams"),
|
|
261
|
-
// queryParams: _zodAssert.bind(null, "queryParams"),
|
|
262
|
-
// response: _zodAssertAny.bind(null, "response"),
|
|
263
|
-
// any: _zodAssertAny.bind(null, "any"),
|
|
264
|
-
// zod: makeZ2("any"),
|
|
265
|
-
// };
|
|
266
|
-
|
|
267
|
-
// declare global { type ZodAssert = typeof zodAssertGlobal; }
|
|
268
|
-
|
|
269
|
-
// type t = ZodAssert;
|
|
File without changes
|