raiutils 8.7.11 → 9.0.0
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 +110 -118
- package/dist/router.d.ts +25 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +1 -0
- package/dist/router.js.map +1 -0
- package/dist/schema.d.ts +113 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +1 -0
- package/dist/schema.js.map +1 -0
- package/dist/utils.d.ts +288 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +1 -0
- package/dist/utils.js.map +1 -0
- package/dist/uuid.d.ts +29 -0
- package/dist/uuid.d.ts.map +1 -0
- package/dist/uuid.js +1 -0
- package/dist/uuid.js.map +1 -0
- package/package.json +22 -7
- package/src/router.ts +131 -0
- package/src/schema.ts +259 -0
- package/src/utils.ts +996 -0
- package/src/uuid.ts +102 -0
- package/router.js +0 -109
- package/schema.js +0 -148
- package/utils.js +0 -911
- package/utils.min.js +0 -2
- package/uuid.js +0 -69
package/src/uuid.ts
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
//Chu ID v1.5.1, Pecacheu 2026. GNU GPL v3
|
|
2
|
+
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import fs from 'fs/promises';
|
|
5
|
+
import crypto from 'crypto';
|
|
6
|
+
import { promisify } from 'util';
|
|
7
|
+
import utils from 'raiutils';
|
|
8
|
+
|
|
9
|
+
const ID_FN = import.meta.dirname+'/uuid';
|
|
10
|
+
let Cnt: number, CLT: number, LT: number,
|
|
11
|
+
LD: number, UT: NodeJS.Timeout | boolean;
|
|
12
|
+
|
|
13
|
+
let mdb: any;
|
|
14
|
+
//@ts-ignore
|
|
15
|
+
try {mdb=await import('mongodb')} catch(e) {}
|
|
16
|
+
|
|
17
|
+
namespace mdb {export interface Long {
|
|
18
|
+
unsigned: boolean; toString(r: number): string;
|
|
19
|
+
}}
|
|
20
|
+
|
|
21
|
+
//64-bit UUID Format
|
|
22
|
+
//<U8 Uptime><U8 Magic><U8 CryptoRand><U8 Counter><U32 Date>
|
|
23
|
+
|
|
24
|
+
function swapHex(h: string) {return h.match(/.{2}/g)!.reverse().join('')}
|
|
25
|
+
|
|
26
|
+
async function loadId() {
|
|
27
|
+
//Prevent race condition
|
|
28
|
+
if(UT === true) {
|
|
29
|
+
while(UT === true) await utils.delay(10);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
UT=true;
|
|
33
|
+
try {Cnt = Number(await fs.readFile(ID_FN, {encoding:'utf8'}))} catch(e) {}
|
|
34
|
+
if(!(Cnt >= 0 && Cnt <= 255)) console.error("[ChuID] IDCount error, resetting"), Cnt=0;
|
|
35
|
+
UT=false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default class UUID {
|
|
39
|
+
static readonly LEN = 11;
|
|
40
|
+
static readonly BYTES = 8;
|
|
41
|
+
/** Delay before writing ID file to disk */
|
|
42
|
+
static ID_Delay = 10000;
|
|
43
|
+
id: Buffer;
|
|
44
|
+
|
|
45
|
+
constructor(id: string | Buffer | mdb.Long) {
|
|
46
|
+
if(id instanceof Buffer && id.length === UUID.BYTES) {}
|
|
47
|
+
else if(typeof id === 'string' && id.length === UUID.LEN) id=Buffer.from(id,'base64');
|
|
48
|
+
else if(mdb && id instanceof mdb.Long) {
|
|
49
|
+
(id as mdb.Long).unsigned=true;
|
|
50
|
+
id=Buffer.from((id as mdb.Long).toString(16),'hex');
|
|
51
|
+
} else throw `Unknown UUID format ${id}`;
|
|
52
|
+
this.id = id;
|
|
53
|
+
}
|
|
54
|
+
toString(f?: BufferEncoding) {return this.id.toString(f||'base64url')}
|
|
55
|
+
toHexLE() {return swapHex(this.id.toString('hex'))}
|
|
56
|
+
toLong() {return mdb.Long.fromString(this.id.toString('hex'),16)}
|
|
57
|
+
getMagic() {return this.id.readUInt8(1)}
|
|
58
|
+
getDate() {
|
|
59
|
+
let d=this.id.readUInt32LE(4)*10000;
|
|
60
|
+
return new Date(d<1621543800000?0:d);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Convenience method for async `crypto.randomBytes` */
|
|
64
|
+
static randBytes = promisify(crypto.randomBytes);
|
|
65
|
+
|
|
66
|
+
/** Generate new random UUID
|
|
67
|
+
@param date Optional Date or Unix ms timestamp; default is current time
|
|
68
|
+
@param magic User-defined 8-bit value that can be retrieved later; default is random
|
|
69
|
+
*/
|
|
70
|
+
static genUUID = async (date?: Date | number, magic?: number) => {
|
|
71
|
+
let ts = (os.uptime()*10)&255;
|
|
72
|
+
const ds = (date instanceof Date?
|
|
73
|
+
date.getTime() : date||Date.now())/10000,
|
|
74
|
+
rb = await UUID.randBytes(magic!=null?1:2),
|
|
75
|
+
u = Buffer.allocUnsafe(8);
|
|
76
|
+
|
|
77
|
+
if(Cnt == null) await loadId();
|
|
78
|
+
const ct = Cnt;
|
|
79
|
+
if(++Cnt > 255) Cnt=0;
|
|
80
|
+
|
|
81
|
+
//Prevent collision
|
|
82
|
+
if(LT === ts && LD === ds) {
|
|
83
|
+
if(CLT === ct) {
|
|
84
|
+
await utils.delay(50);
|
|
85
|
+
LT = ts = (ts+1)&255;
|
|
86
|
+
}
|
|
87
|
+
} else LT=ts, LD=ds, CLT=ct;
|
|
88
|
+
|
|
89
|
+
u.writeUInt8(ts);
|
|
90
|
+
if(magic != null) {
|
|
91
|
+
u.writeUInt8(magic&255, 1);
|
|
92
|
+
u.writeUInt8(rb.readUInt8(), 2);
|
|
93
|
+
} else u.writeUInt16LE(rb.readUInt16LE(), 1);
|
|
94
|
+
u.writeUInt8(ct, 3);
|
|
95
|
+
u.writeUInt32LE(ds, 4);
|
|
96
|
+
|
|
97
|
+
if(!UT) UT = setTimeout(() => {
|
|
98
|
+
UT=false, fs.writeFile(ID_FN, Cnt.toString());
|
|
99
|
+
}, UUID.ID_Delay);
|
|
100
|
+
return new UUID(u);
|
|
101
|
+
}
|
|
102
|
+
}
|
package/router.js
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
//Node Webserver v3.4.2, Pecacheu 2025. GNU GPL v3
|
|
2
|
-
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import fs from 'fs/promises';
|
|
5
|
-
import crypto from 'crypto';
|
|
6
|
-
import chalk from 'chalk';
|
|
7
|
-
let debug;
|
|
8
|
-
|
|
9
|
-
const types = {
|
|
10
|
-
'.html': "text/html",
|
|
11
|
-
'.php': "text/html",
|
|
12
|
-
'.css': "text/css",
|
|
13
|
-
'.png': "image/png",
|
|
14
|
-
'.svg': "image/svg+xml",
|
|
15
|
-
'.js': "application/javascript",
|
|
16
|
-
'.pdf': "application/pdf",
|
|
17
|
-
'.mp4': "video/mp4",
|
|
18
|
-
'.ogg': "video/ogg",
|
|
19
|
-
'.webm': "video/webm"
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
//etag: If number, max file size to calc hash, else if true, use fast etag mode (modified date)
|
|
23
|
-
async function handle(dir, req, res, virtualDirs, etag=true) {
|
|
24
|
-
let f;
|
|
25
|
-
try {
|
|
26
|
-
let fn=await resolve(dir, new URL(req.url,'http://a').pathname, virtualDirs),
|
|
27
|
-
hdr={}, stat=200, ext=path.extname(fn), ct=types[ext], rng=req.headers.range, str;
|
|
28
|
-
if(ct) hdr["content-type"] = ct;
|
|
29
|
-
f=await fs.open(fn);
|
|
30
|
-
let st=await f.stat(), dl=st.size;
|
|
31
|
-
if(rng) { //Range
|
|
32
|
-
if(!rng.startsWith('bytes=') || (rng=rng.slice(6).split('-'))
|
|
33
|
-
.length !== 2 || !rng[0]) return await rngErr(f,dl,rng,res);
|
|
34
|
-
let rs=Number(rng[0]), re=Number(rng[1]);
|
|
35
|
-
if(!re) re=dl-1;
|
|
36
|
-
if(rs>=dl || re>=dl || rs>=re) return await rngErr(f,dl,rng,res);
|
|
37
|
-
str=f.createReadStream({start:rs, end:re});
|
|
38
|
-
hdr["accept-ranges"] = 'bytes';
|
|
39
|
-
hdr["content-range"] = `bytes ${rs}-${re}/${dl}`;
|
|
40
|
-
stat=206;
|
|
41
|
-
} else if(typeof etag==='number') {if(dl <= MAX_ETAG) {
|
|
42
|
-
str=await f.readFile();
|
|
43
|
-
let h=crypto.createHash('sha1');
|
|
44
|
-
h.update(str);
|
|
45
|
-
hdr.etag=h.digest('base64url');
|
|
46
|
-
}} else if(etag) hdr.etag=st.mtime.toISOString();
|
|
47
|
-
|
|
48
|
-
if(hdr.etag && hdr.etag === req.headers['if-none-match'])
|
|
49
|
-
return res.writeHead(304,''),res.end(),f.close();
|
|
50
|
-
if(!str) str=f.createReadStream();
|
|
51
|
-
|
|
52
|
-
res.writeHead(stat,'',hdr);
|
|
53
|
-
if(str instanceof Buffer) res.write(str),res.end(); else str.pipe(res);
|
|
54
|
-
res.on('close', () => f.close());
|
|
55
|
-
if(debug) log(fn.startsWith(dir)?fn.slice(dir.length):fn, ct);
|
|
56
|
-
} catch(e) {
|
|
57
|
-
let nf=e.code==='ENOENT';
|
|
58
|
-
if(debug||!nf) nf?err("-- Not found"):err("-- Read",e);
|
|
59
|
-
sendCode(res, nf?404:500, nf?"Resource Not Found":e);
|
|
60
|
-
if(f) f.close();
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async function serve(fn, req, res, etag=true) {
|
|
65
|
-
let u=req.url; req.url='/';
|
|
66
|
-
return handle(fn, req, res, null, etag).finally(() => req.url=u);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async function rngErr(f,fl,rng,res) {
|
|
70
|
-
if(debug) err("-- Bad Range",rng);
|
|
71
|
-
let h={"content-range": 'bytes */'+fl};
|
|
72
|
-
res.writeHead(416,'',h); res.end();
|
|
73
|
-
f.close();
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function sendCode(res, code, msg) {
|
|
77
|
-
res.writeHead(code,''), res.write(`<pre style='font-size:16pt'>${msg}</pre>`), res.end();
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function err(m,e) {console.error(chalk.red(m,e))}
|
|
81
|
-
function log(name, ct) {console.log(chalk.dim("-- Served "+name+(ct?" with type "+ct:'')))}
|
|
82
|
-
|
|
83
|
-
async function resolve(dir, uri, vDir) {
|
|
84
|
-
if(uri.indexOf('..') !== -1) throw "Bad path";
|
|
85
|
-
let fn = parseUri(dir, uri, vDir);
|
|
86
|
-
if(fn.endsWith('/')) fn=fn.slice(0,-1);
|
|
87
|
-
try {
|
|
88
|
-
let stat = await fs.stat(fn);
|
|
89
|
-
if(stat.isDirectory()) return path.join(fn,'/index.html'); //Try index
|
|
90
|
-
return fn;
|
|
91
|
-
} catch(e) {
|
|
92
|
-
if(!path.extname(fn)) return fn+'.html'; //Try with ext
|
|
93
|
-
throw e;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function parseUri(root, uri, vDir) {
|
|
98
|
-
if(vDir) {
|
|
99
|
-
let v,vs; for(v in vDir) {
|
|
100
|
-
vs=v.startsWith('/')?v:'/'+v;
|
|
101
|
-
if(uri.startsWith(vs)) return path.join(vDir[v], uri.slice(vs.length));
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
return root+uri;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const ex={handle, serve, types};
|
|
108
|
-
Object.defineProperty(ex, 'debug', {set:d => debug=d});
|
|
109
|
-
export default ex;
|
package/schema.js
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
//ChuSchema v1.3.1, Pecacheu 2025. GNU GPL v3
|
|
2
|
-
|
|
3
|
-
class SubError extends Error {}
|
|
4
|
-
|
|
5
|
-
function errAt(k,e,l) {
|
|
6
|
-
let s=e instanceof SubError, es=e.message||e;
|
|
7
|
-
k=(l?`[${k}]`:k)+(s ? es.startsWith('[')?'':'.' : ': ');
|
|
8
|
-
if(s) e.message=k+es;
|
|
9
|
-
else e=new SubError(k+es, e instanceof Error?{cause:e}:null);
|
|
10
|
-
return e;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function isDictOrArr(d) {return typeof d==='object' && d!==null}
|
|
14
|
-
function isDict(d) {return isDictOrArr(d) && !Array.isArray(d)}
|
|
15
|
-
function oobStr(s) {return ` out-of-bounds (${s.min==null?'*':s.min}-${s.max==null?'*':s.max})`}
|
|
16
|
-
function dictFmt(s) {
|
|
17
|
-
if(typeof s.c==='string') s.c={t:s.c};
|
|
18
|
-
let dt=isDictOrArr(s.f)?2:isDictOrArr(s.c)?1:0;
|
|
19
|
-
if(!dt) throw "Schema lacks format or childType";
|
|
20
|
-
if(dt===2 && s.c) throw "Cannot require both format and childType";
|
|
21
|
-
return dt===2;
|
|
22
|
-
}
|
|
23
|
-
function tryAll(a,fn) {
|
|
24
|
-
if(Array.isArray(a)) {
|
|
25
|
-
let el=[];
|
|
26
|
-
a.forEach(s => {try {fn(s)} catch(e) {el.push(e)}});
|
|
27
|
-
if(el.length >= a.length) throw "[Failed all cases] "+el.map(e => e.message||e).join(', ');
|
|
28
|
-
} else fn(a);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function checkType(d,sr) {
|
|
32
|
-
let tt,el,sl,s,l,k,n,ds,dt,
|
|
33
|
-
run=(t,i) => {
|
|
34
|
-
//Get prop cache
|
|
35
|
-
if(!(s=sl[k='$'+i])) {
|
|
36
|
-
s=sl[k]={};
|
|
37
|
-
for(k in sl) s[k]=Array.isArray(sl[k])?sl[k][i]:sl[k];
|
|
38
|
-
}
|
|
39
|
-
//Check type
|
|
40
|
-
try {switch(t) {
|
|
41
|
-
case 'str':
|
|
42
|
-
if(typeof d!=='string') throw -1;
|
|
43
|
-
l=d.length;
|
|
44
|
-
if(l<s.min || l>s.max) throw "Str len "+l+oobStr(s);
|
|
45
|
-
if(s.len!=null && l!==s.len) throw "Str len must be "+s.len;
|
|
46
|
-
if(typeof s.f==='string') s.f=new RegExp(`^(?:${s.f})$`);
|
|
47
|
-
if(s.f instanceof RegExp && !s.f.test(d)) throw `Str '${d}' does not match format ${s.f}`;
|
|
48
|
-
break; case 'int': case 'float':
|
|
49
|
-
if(typeof d!=='number' || !(t==='int'?Number.isSafeInteger(d):Number.isFinite(d))) throw -1;
|
|
50
|
-
if(s.val!=null && d!==s.val) throw "Num != "+s.val;
|
|
51
|
-
if(d<s.min || d>s.max) throw "Num "+d+oobStr(s);
|
|
52
|
-
break; case 'bool':
|
|
53
|
-
if(typeof d!=='boolean') throw -1;
|
|
54
|
-
break; case 'list':
|
|
55
|
-
if(!Array.isArray(d)) throw -1;
|
|
56
|
-
l=d.length; if(!l && s.min!==0) throw "Empty list";
|
|
57
|
-
if(s.len!=null && l!==s.len) throw "Array size must be "+s.len;
|
|
58
|
-
if(l<s.min || l>s.max) throw "Array size "+l+oobStr(s);
|
|
59
|
-
n=0, ds=tt.length>1?s:sl, dt=dictFmt(ds);
|
|
60
|
-
for(; n<l; ++n) try {dt?checkSchema(d[n],ds.f):checkType(d[n],ds.c)}
|
|
61
|
-
catch(e) {throw errAt(n,e,1)}
|
|
62
|
-
break; case 'dict':
|
|
63
|
-
if(!isDict(d)) throw -1;
|
|
64
|
-
k=Object.keys(d);
|
|
65
|
-
if(!k.length && s.min!==0) throw "Empty dict";
|
|
66
|
-
if(typeof s.kf==='string') s.kf=new RegExp(`^(?:${s.kf})$`);
|
|
67
|
-
ds=tt.length>1?s:sl, dt=dictFmt(ds);
|
|
68
|
-
for(n of k) try {
|
|
69
|
-
if(n.startsWith('$')) throw "Key cannot start with $";
|
|
70
|
-
if(s.kf instanceof RegExp && !s.kf.test(n)) throw `Key '${n}' does not match format ${s.kf}`;
|
|
71
|
-
dt?checkSchema(d[n],ds.f):checkType(d[n],ds.c);
|
|
72
|
-
} catch(e) {throw errAt(n,e,1)}
|
|
73
|
-
break; case 'obj':
|
|
74
|
-
if(typeof s.c !== 'function') throw "Obj schema requires class";
|
|
75
|
-
if(!(d instanceof s.c)) throw `Obj must be of type ${s.c.name}`;
|
|
76
|
-
break; default:
|
|
77
|
-
throw `Unknown type ${s.t} in schema`;
|
|
78
|
-
}} catch(e) {el.push(e)}
|
|
79
|
-
}
|
|
80
|
-
tryAll(sr, s => {
|
|
81
|
-
if(typeof s.t!=='string') throw "Missing type";
|
|
82
|
-
sl=s, el=[], tt=s.t.split('|'), tt.forEach(run);
|
|
83
|
-
if(el.length >= tt.length) {
|
|
84
|
-
let e,m;
|
|
85
|
-
for(e of el) if(e!==-1) m=e;
|
|
86
|
-
if(!m) m="Must be of type "+sr.t;
|
|
87
|
-
throw m;
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const R_FN=/\W+|(\w+)/g;
|
|
93
|
-
|
|
94
|
-
function checkSchema(data, schema, ignoreReq) {
|
|
95
|
-
if(!isDict(data)) throw "Data must be dict";
|
|
96
|
-
if(!isDictOrArr(schema)) throw "Schema must be dict|list[dict]";
|
|
97
|
-
let k,d,s,r,n,m;
|
|
98
|
-
tryAll(schema, sch => {
|
|
99
|
-
for(k in data) try {
|
|
100
|
-
d=data[k], s=sch[k];
|
|
101
|
-
if(!s) throw "Not in schema";
|
|
102
|
-
if(k.startsWith('$')) throw "Key cannot start with $";
|
|
103
|
-
checkType(d,s);
|
|
104
|
-
} catch(e) {throw errAt(k,e)}
|
|
105
|
-
if(ignoreReq) return;
|
|
106
|
-
//Check missing
|
|
107
|
-
for(k in sch) if((s=sch[k]).req != null) {
|
|
108
|
-
d=s.req, r=typeof d==='string';
|
|
109
|
-
if(r) { //Conditional req
|
|
110
|
-
n='';
|
|
111
|
-
while(m=R_FN.exec(d)) n+=m[1] ? m[1] in data : m[0];
|
|
112
|
-
d=eval(n);
|
|
113
|
-
}
|
|
114
|
-
if(d) {
|
|
115
|
-
n=k in data;
|
|
116
|
-
if(d===-1 ? n : !n) throw k+": Required"+(r?" if "+s.req:'');
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function isNumArr(a) {
|
|
123
|
-
for(let v of a) if(typeof v!=='number') return;
|
|
124
|
-
return 1;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
//Custom JSON implementation w/ better line-breaks
|
|
128
|
-
function prettyJSON(val, d=0) {
|
|
129
|
-
let t=typeof val;
|
|
130
|
-
if(t==='number' || t==='string' || t==='boolean') return JSON.stringify(val);
|
|
131
|
-
else if(Array.isArray(val)) {
|
|
132
|
-
if(isNumArr(val)) return JSON.stringify(val);
|
|
133
|
-
let s='[',d1=d+1,i=0,l=val.length;
|
|
134
|
-
for(; i<l; ++i) s += (i?',\n':'\n')+('\t'.repeat(d1))+prettyJSON(val[i],d1);
|
|
135
|
-
return s+'\n'+'\t'.repeat(d)+']';
|
|
136
|
-
} else if(t==='object') {
|
|
137
|
-
let k,s='{',ln=!d,f,d1=d+1;
|
|
138
|
-
for(k in val) {
|
|
139
|
-
if(Array.isArray(val[k]) && !isNumArr(val[k])) ln=1;
|
|
140
|
-
s += (ln?f?',\n':'\n':f?', ':'')+'\t'.repeat(ln?d1:0)+JSON.stringify(k)+":"+prettyJSON(val[k],d1);
|
|
141
|
-
f=1,ln=0;
|
|
142
|
-
}
|
|
143
|
-
if(!d) s+='\n';
|
|
144
|
-
return s+'}';
|
|
145
|
-
} else throw "Unknown type "+t;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
export default {checkSchema, checkType, prettyJSON, errAt};
|