@gershy/clearing 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/clearing.ts +765 -0
  2. package/global.d.ts +197 -0
  3. package/package.json +11 -0
package/clearing.ts ADDED
@@ -0,0 +1,765 @@
1
+ 'use strict';
2
+
3
+ // version [0]
4
+
5
+ if (!global['~clearing']) {
6
+ global['~clearing'] = true;
7
+
8
+ Object.assign(global, {
9
+
10
+ AsyncFunction: (async () => {}).constructor,
11
+ GeneratorFunction: (function*(){})().constructor,
12
+ AsyncGeneratorFunction: (async function*(){})().constructor,
13
+ C: Object.freeze({
14
+ def: (obj, prop, value, opts={}) => Object.defineProperty(obj, prop, { value, configurable: true, ...opts }),
15
+ skip: undefined,
16
+ 'Promise.all': Promise.all.bind(Promise),
17
+ 'Error.prototype.toString': Error.prototype.toString
18
+ }),
19
+ skip: undefined,
20
+
21
+ // Flow controls, sync/async interoperability
22
+ onto: (val, fn) => (fn(val), val),
23
+ safe: (fn, onErr: (err: Error) => void) => {
24
+
25
+ // Returns `fn()` with error handling provided by `onErr` regardless
26
+ // of whether `fn()` results in a Promise or an immediate value
27
+
28
+ let p = new Promise(r => r('abc'));
29
+ p.catch()
30
+ try { let val = fn(); return isForm(val, Promise) ? val.fail(onErr) : val; }
31
+ catch (err: any) { return onErr(err); }
32
+
33
+ },
34
+ soon: fn => fn ? Promise.resolve().then(fn) : Promise.resolve(),
35
+ then: (val, rsv=Function.stub, rjc?: (...args: any[]) => any) => {
36
+
37
+ // Act on `val` regardless of whether it's a Promise or an immediate
38
+ // value; return `rsv(val)` either immediately or as a Promise;
39
+
40
+ // Promises are returned with `then`/`fail` handling
41
+ if (val instanceof Promise) return val.then(rsv).catch(rjc);
42
+
43
+ // No `rjc` means no `try`/`catch` handling
44
+ if (!rjc) return rsv(val);
45
+
46
+ try { return rsv(val); } catch (err) { return rjc(err); }
47
+
48
+ },
49
+ thenAll: (vals, ...args /* rsv, rjc */) => {
50
+ if (vals.seek(v => v instanceof Promise).found) vals = Promise.all(vals);
51
+ return (then as any)(vals, ...args);
52
+ },
53
+
54
+ // Forms
55
+ form: ({ name, has={}, pars=has, props=()=>({}) }: { name: string, has?: { [key: string]: any }, pars?: any, props?: any }) => {
56
+
57
+ let reservedFormProps = [ 'constructor', 'Form' ];
58
+
59
+ // Ensure every ParForm is truly a Form
60
+ for (let [ k, Form ] of pars) if (!Form || !Form['~forms']) throw Error(`Invalid Form: "${k}" (it's Form is ${getFormName(Form)})`);
61
+
62
+ // // TODO: This is definitely faster than `eval`, but it prevents the
63
+ // // names of Forms from displaying correctly in the console
64
+ // let Form = function(...p) { return (this && this.constructor === Form) ? this.init(...p) : new Form(...p); }
65
+ // Object.defineProperty(Form, 'name', { value: name, writable: false, enumerable: true });
66
+ let fName = name.replace(/[^a-zA-Z0-9]/g, '$');
67
+ let Form = eval(
68
+ `let ${fName} = function ${fName}(...p) { return (this && this.Form === Form) ? this.init(...p) : new Form(...p); }; ${fName};`
69
+ );
70
+
71
+ Form['~forms'] = Set([ Form ]);
72
+ Form.prototype = Object.create(null);
73
+
74
+ // We'll store a Set of inherited static props for each static key
75
+ let statics = Object.create(null);
76
+
77
+ // Loop over all ParentForms; for each:
78
+ // - add to `Form['~forms']` to enable `hasForm` testing
79
+ // - collect static properties (to apply to `name` later)
80
+ // - map to a representation of its prototype (to supply Parent
81
+ // prototype methods to overriding methods)
82
+ let protos = Object.assign({}, pars);
83
+ for (let parName in pars) {
84
+
85
+ let { '~forms': parForms, ...parProps } = pars[parName];
86
+
87
+ // Add all ParentForms to the ~forms Set (facilitates `hasType`)
88
+ for (let ParForm of parForms) Form['~forms'].add(ParForm);
89
+
90
+ // Collect the rest of the properties
91
+ for (let [ k, v ] of parProps) {
92
+ if (!statics[k]) statics[k] = Set();
93
+ statics[k].add(v);
94
+ }
95
+
96
+ // `protoDef` sets non-enumerable prototype properties
97
+ // Iterate non-enumerable props via `Object.getOwnPropertyNames`
98
+ let proto = pars[parName].prototype;
99
+ protos[parName] = Object.fromEntries(Object.getOwnPropertyNames(proto).map( n => [ n, proto[n] ] ));
100
+
101
+ }
102
+ pars = null;
103
+
104
+ // If `props` is a function it becomes the result of its call
105
+ if (isForm(props, Function)) {
106
+
107
+ // Apply any immediately-unambiguous static properties to `Form`
108
+ // so they are available to the function body of `props`
109
+ let immediateStatics: [ string, any ][] = [];
110
+ for (let k in statics) if (statics[k].size === 1) {
111
+ immediateStatics.push([ k, [ ...statics[k] ][0] ]);
112
+ }
113
+ Object.assign(Form, Object.fromEntries(immediateStatics));
114
+
115
+ props = (props as any)(protos, Form);
116
+
117
+ }
118
+
119
+ // Ensure we have valid "props", and all prop names are valid
120
+ if (!isForm(props, Object)) throw Error(`Couldn't resolve "props" to Object`);
121
+ for (let prop of reservedFormProps) if (({}).has.call(props, prop)) throw Error(`Used reserved "${prop}" key`);
122
+
123
+ // Iterate all props of ParForm prototypes; collect inherited ones
124
+ let propsByName = {};
125
+ for (let [ formName, proto ] of protos) for (let [ propName, prop ] of proto) {
126
+
127
+ // Skip reserved names (they certainly exist in `formProto`!)
128
+ if (reservedFormProps.has(propName)) continue;
129
+
130
+ // Store all props under the same name in the same Set
131
+ if (!({}).has.call(propsByName, propName)) propsByName[propName] = Set();
132
+ propsByName[propName].add(prop);
133
+
134
+ }
135
+
136
+ // `propsByName` already has all ParForm props; now add in the props
137
+ // unique to the Form being created!
138
+ // TODO: Allow classes to define getters + setters??? Should really
139
+ // be iterating `Object.getOwnPropertyDescriptors(props)` rather
140
+ // than `props` itself...
141
+ for (let [ propName, prop ] of props) {
142
+
143
+ if (prop === skip) throw Error(`Provided ${name} @ ${propName} as skip`);
144
+
145
+ // `propName` values iterated here will be unique; `props` is an
146
+ // object, and must have unique keys. Note `Set` is used to ignore
147
+ // duplicate properties with the same identity (these would mean
148
+ // that multiple ancestors define the property, but they define it
149
+ // to the exact same value!)
150
+ if (propName[0] === '$') statics[propName.slice(1)] = Set([ prop ]); // Guaranteed to be singular
151
+ else propsByName[propName] = Set([ prop ]); // Guaranteed to be singular
152
+
153
+ }
154
+
155
+ // At this point ambiguous static props should be resolved
156
+ for (let k in statics) if (statics[k].size > 1) throw Error(`Multiple static props named "${k}" inherited by Form ${name} (define ${name}.$${k}!)`);
157
+
158
+ if (!({}).has.call(propsByName, 'init')) throw Error('No "init" method available');
159
+
160
+ for (let [ propName, propsOfThatName ] of propsByName) {
161
+
162
+ // If there are collidable props under this name there can only be
163
+ // one! Multiple collidable props indicates the prop needs to be
164
+ // defined directly on `Form.prototype`, guaranteeing singularity.
165
+ // If *no* collidable props are set, use any non-collidable prop
166
+ let collisionProps = propsOfThatName.toArr(v => (v && v['~noFormCollision']) ? skip : v);
167
+
168
+ // If there are no collision props we still may be able to assign
169
+ // one of the "no form collision" props; this is useful as calling
170
+ // the "uncolliding" method gives useful feedback (e.g. "function
171
+ // not implemented") whereas not defining anything would result in
172
+ // trying to call `undefined` as a function
173
+ if (collisionProps.length === 0) {
174
+
175
+ let utilProp = propsOfThatName.seek(v => !!v).val;
176
+ if (utilProp) collisionProps = [ utilProp ];
177
+ else continue;
178
+
179
+ } else if (collisionProps.length > 1) {
180
+
181
+ let definingForms = collisionProps.map(prop => Form['~forms'].seek(ParForm => ParForm.prototype[propName] === prop).val);
182
+ throw Error([
183
+ `Form ${name} has ambiguous "${propName}" property `,
184
+ `from ${collisionProps.length} ParentForms `,
185
+ `(${definingForms.map(Form => Form ? Form.name : '???').join(', ')}). `,
186
+ `Define ${name}.prototype.${propName}.`
187
+ ].join(''));
188
+
189
+ }
190
+
191
+ C.def(Form.prototype, propName, collisionProps[0], { enumerable: false, writable: true });
192
+
193
+ }
194
+
195
+ C.def(Form.prototype, 'Form', Form, { enumerable: false, writable: true });
196
+ C.def(Form.prototype, 'constructor', Form, { enumerable: false, writable: true });
197
+ Object.freeze(Form.prototype);
198
+
199
+ // Would be very satisifying to freeze `Form`, but the current
200
+ // pattern of defining specialized subclasses:
201
+ // | FnSrc.Tmp1 = form(...);
202
+ // relies on `Form` being mutable :(
203
+ // TODO: MapSrc and MemSrc are being refactored... maybe do this??
204
+ for (let k in statics) statics[k] = [ ...statics[k] ][0];
205
+ Object.assign(Form, statics);
206
+ // Object.freeze(Form);
207
+
208
+ return Form;
209
+
210
+ },
211
+ getFormName: f => {
212
+ if (f === null) return 'Null';
213
+ if (f === undefined) return 'Undef';
214
+ if (f !== f) return 'UndefNum';
215
+ return Object.getPrototypeOf(f)?.constructor.name ?? 'Prototypeless'; // e.g. `getFormName(Object.plain()) === 'Prototypeless'`
216
+ },
217
+ isForm: (fact, Form) => {
218
+
219
+ // NaN only matches against the NaN primitive (not the Number Form)
220
+ if (fact !== fact) return Form !== Form;
221
+ if (fact == null) return false;
222
+
223
+ return Object.getPrototypeOf(fact).constructor === (Form.Native ?? Form);
224
+
225
+ },
226
+ hasForm: (fact, FormOrCls) => {
227
+
228
+ if (fact == null) return false;
229
+
230
+ // `fact` may either be a fact/Form, or an instance/Cls. In case a
231
+ // fact/instance was given, the "constructor" property points us to
232
+ // the appropriate Form/Cls. We name this value "Form", although it
233
+ // is ambiguously a Form/Cls.
234
+ let Form = (Object.getPrototypeOf(fact)?.constructor === Function) ? fact : fact.constructor;
235
+ if (Form === FormOrCls) return true;
236
+
237
+ // If a "~forms" property exists `FormOrCls` is specifically a Form
238
+ // and inheritance can be checked by existence in the set
239
+ if (Form?.['~forms']) return Form['~forms'].has(FormOrCls);
240
+
241
+ // No "forms" property; FormOrCls is specifically a Cls. Inheritance
242
+ // can be checked using `instanceof`; prefer to compare against a
243
+ // "Native" property (which facilitates "newless" instances)
244
+ return (fact instanceof (FormOrCls.Native || FormOrCls));
245
+
246
+ },
247
+
248
+ // Keep access
249
+ keep: () => null,
250
+
251
+ // Configuration
252
+ conf: () => null,
253
+
254
+ // Timing
255
+ getMs: Date.now,
256
+ getDate: (ms=getMs()) => {
257
+ let [ yr, mo, dy, hr, mn, sc, ap ] = (new Date(ms)).toLocaleString().split(/[^0-9apm]+/i); // TODO: Happens to work on local and remote, but should probably just provide locale opts??
258
+ hr = (parseInt(hr, 10) + (ap[0].lower() === 'p' ? 12 : 0)).toString().padHead(2, '0');
259
+ return `${yr}-${mo}-${dy} ${hr}:${mn}:${sc}`;
260
+ },
261
+
262
+ // Room loading
263
+ mapCmpToSrc: (file, row, col) => ({ file, row, col, context: null }),
264
+
265
+ // Urls
266
+ uriRaw: ({ path='', cacheBust, query }) => {
267
+ let url = '';
268
+ if (cacheBust) url += `/!${cacheBust}`;
269
+ if (path) url += `/${path}`;
270
+ if (query && !query.empty()) url += '?' + query.toArr((v, k) => `${k}=${v}`).join('&'); // Note: DON'T encode here!
271
+ return url;
272
+ },
273
+
274
+ // Util
275
+ denumerate: (obj, prop) => C.def(obj, prop, obj[prop], { enumerable: false }),
276
+ formatAnyValue: val => { try { return valToJson(val); } catch (err) { return '<unformattable>'; } },
277
+ valToJson: JSON.stringify,
278
+ jsonToVal: JSON.parse,
279
+ valToSer: JSON.stringify,
280
+ serToVal: JSON.parse
281
+
282
+ });
283
+
284
+ let protoDefs = (Cls, vals) => {
285
+
286
+ if ('$$' in vals) {
287
+
288
+ // Convert, e.g., 'padHead:padStart,padTail:padEnd' into:
289
+ // | {
290
+ // | padHead: String.prototype['padStart'],
291
+ // | padTail: String.prototype['padEnd']
292
+ // | }
293
+
294
+ let v = Object.fromEntries(vals['$$'].split(',').map(v => (v = v.split(':'), [ v[0], Cls.prototype[v[1]] ])));
295
+ delete vals['$$'];
296
+ Object.assign(vals, v);
297
+
298
+ }
299
+
300
+ let keys = Reflect.ownKeys(vals);
301
+ for (let key of keys) if (isForm(key, String) && key[0] === '$') Cls[key.slice(1)] = vals[key];
302
+
303
+ // Avoid making more properties available on `global` - if a typo
304
+ // winds up referring to a global property the bug which results
305
+ // can be highly unexpected!
306
+ if (Cls === global.constructor) for (let key of keys) if (key[0] !== '$') global[key] = skip;
307
+
308
+ Object.defineProperties(Cls.prototype, Object.fromEntries(
309
+ keys
310
+ .filter(key => key[0] !== '$')
311
+ .map(key => [ key, { enumerable: false, writable: true, value: vals[key] } ])
312
+ ));
313
+
314
+ };
315
+ protoDefs(Object, {
316
+
317
+ $stub: Object.freeze({}),
318
+ $plain: obj => obj ? Object.assign(Object.create(null), obj) : Object.create(null),
319
+
320
+ $$: 'has:hasOwnProperty',
321
+
322
+ at(k, def=skip) {
323
+ if (k?.constructor === Array && isForm(k, Array)) {
324
+ let ret = this;
325
+ for (let p of k) { if (!ret.has(p)) return def; ret = ret[p]; }
326
+ return ret;
327
+ }
328
+ return this.has(k) ? this[k] : def;
329
+ },
330
+ each(fn) { for (let [ k, v ] of this) fn(v, k); },
331
+ map(fn) { // Iterator: (val, key) => val
332
+ let ret = Object.assign({}, this);
333
+ for (let k in ret) { let v = fn(ret[k], k); if (v !== skip) ret[k] = v; else delete ret[k]; }
334
+ return ret;
335
+ },
336
+ mapk(fn) { // Iterator: (val, k) => [ k, v ]
337
+ let arr: any[] = [];
338
+ for (let k in this) { let v = fn(this[k], k); if (v !== skip) arr.push(v); }
339
+ return Object.fromEntries(arr);
340
+ },
341
+ toArr(fn) { // Iterator: (val, k) => [ k, v ]
342
+ let ret: any[] = [];
343
+ for (let k in this) { let v = fn(this[k], k); if (v !== skip) ret.push(v); }
344
+ return ret;
345
+ },
346
+ slice(p) { // TODO: Rename to "subset"?
347
+
348
+ // >> { a: 1, b: 2, c: 3, d: 4 }.slice([ 'b', 'd' ]);
349
+ // { b: 2, d: 4 }
350
+ return p.toObj(p => this.has(p) ? [ p, this[p] ] : skip);
351
+
352
+ },
353
+ omit(p) {
354
+
355
+ let obj = { ...this };
356
+ for (let k of p) delete obj[k];
357
+ return obj;
358
+
359
+ },
360
+ find(f) { // Iterator: (val, key) => bool; returns { found, val=null, key=null }
361
+ for (let k in this) if (f(this[k], k)) return { found: true, val: this[k], key: k };
362
+ return { found: false, val: null, k: null };
363
+ },
364
+ empty() { for (let k in this) return false; return true; },
365
+ gain(...objs) {
366
+ // Note for performance we combine all source Objects first, to
367
+ // reduce the number of items that need to be checked for skips -
368
+ // probably worth the overhead of calling `Object.assign` x2
369
+ let gain = Object.assign({}, ...objs);
370
+ for (let k in gain) if (gain[k] === skip) delete gain[k];
371
+ return Object.assign(this, gain);
372
+ },
373
+ merge(o) { // Modifies `this` in-place
374
+ for (let [ k, v ] of o) {
375
+ // `skip` can be passed to remove properties
376
+ if (v === skip) { delete this[k]; continue; }
377
+
378
+ // Incoming non-Object properties are simple
379
+ if (!isForm(v, Object)) { this[k] = v; continue; }
380
+
381
+ // `v` is an Object; existing non-Object replaced with `{}`
382
+ if (!isForm(this[k], Object) || !this.has(k)) this[k] = {};
383
+
384
+ // And simply recurse!
385
+ this[k].merge(v);
386
+ }
387
+ return this;
388
+ },
389
+ built() {
390
+ let result = {};
391
+ for (let [ k, v ] of this) {
392
+ let dive = k.split('.');
393
+ let last = dive.pop();
394
+ let ptr = result;
395
+ for (let cmp of dive) ptr = (ptr.has(cmp) && ptr[cmp] != null) ? ptr[cmp] : (ptr[cmp] = {});
396
+ ptr[last] = isForm(v, Object) ? v.built() : v;
397
+ }
398
+ return result;
399
+ },
400
+ * unbuilt(dive=[]) {
401
+ for (let [ k, val ] of this) {
402
+ if (isForm(val, Object)) yield* val.unbuilt([ ...dive, k ]);
403
+ else yield { dive: [ ...dive, k ], val };
404
+ }
405
+ },
406
+ count() { let c = 0; for (let k in this) c++; return c; },
407
+ categorize(fn) { // Iterator: (val, key) => '<categoryTerm>'
408
+
409
+ // { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10 }.categorize(n => {
410
+ // if (n < 4) return 'small';
411
+ // if (n < 8) return 'medium';
412
+ // return 'big';
413
+ // });
414
+ // >> { small: { a: 1, b: 2, c: 3 }, medium: { d: 4, e: 5, f: 6, g: 7 }, big: { h: 8, i: 9, j: 10 } }
415
+
416
+ let ret = {};
417
+ for (let [ k, v ] of this) {
418
+ let t = fn(v, k);
419
+ if (!ret.has(t)) ret[t] = {};
420
+ ret[t][k] = v;
421
+ }
422
+ return ret;
423
+
424
+ },
425
+ *[Symbol.iterator]() { for (let k in this) yield [ k, this[k] ]; }
426
+
427
+ });
428
+ protoDefs(Array, {
429
+
430
+ $stub: Object.freeze([]),
431
+ $from: Array.from, // (it, fn) => (isForm(it, Array) ? it : [ ...it ]).map(fn),
432
+
433
+ $$: 'each:forEach,has:includes',
434
+
435
+ map(it) { // Iterator: (val, ind) => val
436
+ let ret: any[] = [];
437
+ let len = this.length;
438
+ for (let i = 0; i < len; i++) { let v = it(this[i], i); if (v !== skip) ret.push(v); }
439
+ return ret;
440
+ },
441
+ toArr(it) { return this.map(it); }, // Can't inherit Object.prototype.toArr - it passes keys as Strings!
442
+ toObj(it) { // Iterator: (val, ind) => [ key0, val0 ]
443
+ let ret: any[] = [];
444
+ let len = this.length;
445
+ for (let i = 0; i < len; i++) { let v = it(this[i], i); if (v !== skip) ret.push(v); }
446
+ return Object.fromEntries(ret);
447
+ },
448
+
449
+ seek(f) { // Iterator: (val, ind) => bool; returns { found=false, val=null, ind=null }
450
+ let n = this.length;
451
+ for (let i = 0; i < n; i++) if (f(this[i], i)) return { found: true, val: this[i], ind: i };
452
+ return { found: false, val: null, ind: null };
453
+ },
454
+ all(fn=Boolean) { return this.every(fn); },
455
+ any(fn=Boolean) { return this.some(fn); },
456
+ sift(fn=Boolean) { return this.filter(fn); },
457
+ empty() { return !this.length; },
458
+ add(...args) { this.push(...args); return args[0]; },
459
+ rem(val) { let ind = this.indexOf(val); if (ind > -1) this.splice(ind, 1); },
460
+ gain(...arrs) { for (let arr of arrs) this.push(...arr); return this; },
461
+ count() { return this.length; },
462
+ valSort(fn) { return this.sort((a, b) => fn(a) - fn(b)); },
463
+ categorize(fn) { // Iterator: val => '<categoryTerm>'
464
+
465
+ // [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ].categorize(n => {
466
+ // if (n < 4) return 'small';
467
+ // if (n < 8) return 'medium';
468
+ // return 'big';
469
+ // });
470
+ // >> { small: [ 1, 2, 3 ], medium: [ 4, 5, 6, 7 ], big: [ 8, 9, 10 ] }
471
+
472
+ let ret = {};
473
+ for (let elem of this) { let t = fn(elem); if (!ret.has(t)) ret[t] = []; ret[t].push(elem); }
474
+ return ret;
475
+
476
+ },
477
+ equals(arr) {
478
+ if (this.length !== arr.length) return false;
479
+ for (let i = 0; i < this.length; i++)
480
+ if (this[i] !== arr[i]) return false;
481
+ return true;
482
+ }
483
+
484
+ });
485
+ protoDefs(String, {
486
+
487
+ $multiline: str => {
488
+
489
+ let lines = str.replace(/\r/g, '').split('\n');
490
+
491
+ // Trim any leading empty lines
492
+ while (lines.length && !lines[0].trim()) lines = lines.slice(1);
493
+
494
+ // Count leading whitespace chars on first line with content
495
+ let initSpace = 0;
496
+ while (lines[0][initSpace] === ' ') initSpace++;
497
+
498
+ let ret = lines.map(ln => ln.slice(initSpace)).join('\n');
499
+ return ret.trimTail();
500
+
501
+ },
502
+ $baseline: (str, seq='| ') => {
503
+
504
+ return str.split('\n').map(ln => {
505
+ ln = ln.trimHead();
506
+ if (!ln.startsWith(seq)) return skip; // After whitespace should come `seq`; ignore lines not containing `seq`!
507
+ return ln.slice(seq.length).trimTail(); // Trim off the baseline; remove tailing whitespace
508
+ }).join('\n');
509
+
510
+ },
511
+ $charset: str => {
512
+ let cache = Map<string, bigint>();
513
+ return {
514
+ str,
515
+ size: BigInt(str.length),
516
+ charVal: (c: string) => {
517
+ if (!cache.has(c)) {
518
+ let ind = str.indexOf(c);
519
+ if (ind < 0) throw Error('Char outside charset');
520
+ cache.set(c, BigInt(ind));
521
+ }
522
+ return cache.get(c);
523
+ },
524
+ valChar: (n: bigint) => {
525
+ if (n < 0 || n >= str.length) throw Error('Val outside charset');
526
+ return str[n as any as number];
527
+ }
528
+ };
529
+ },
530
+ $base32: '0123456789abcdefghijklmnopqrstuv',
531
+ $base36: '0123456789abcdefghijklmnopqrstuvwxyz',
532
+ $base62: '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
533
+ $base64: '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ=-',
534
+
535
+ $$: 'has:includes,hasHead:startsWith,hasTail:endsWith,padHead:padStart,padTail:padEnd,trimHead:trimStart,trimTail:trimEnd,upper:toUpperCase,lower:toLowerCase',
536
+
537
+ cut(delim, cuts=1) { // e.g. `cuts === 1` produces Array of length 2
538
+ // `cuts` defines # of cuts (resulting array length is `num + 1`)
539
+ let split = this.split(delim, cuts < Infinity ? cuts : skip);
540
+ let numDelimsSplit = split.length - 1;
541
+ let lenConsumed = 0
542
+ + split.reduce((a, s) => a + s.length, 0)
543
+ + delim.length * numDelimsSplit;
544
+ if (lenConsumed < this.length) split = [ ...split, this.slice(lenConsumed + delim.length) ];
545
+ return split;
546
+ },
547
+ code(ind=0) { return this.charCodeAt(0); },
548
+ count() { return this.length; },
549
+ indent(...args /* amt=2, char=' ' | indentStr=' '.repeat(2) */) {
550
+
551
+ if (!this) return this; // No-op on empty String (otherwise it would transform a 0-line string to a 1-line string)
552
+ let indentStr: string;
553
+ if (isForm(args[0], String)) { indentStr = args[0]; }
554
+ else { let [ amt=2, char=' ' ] = args; indentStr = char.repeat(amt); }
555
+ return this.split('\n').map(ln => `${indentStr}${ln}`).join('\n');
556
+
557
+ },
558
+ encodeInt(charset: string | CharSet=String.base62) {
559
+
560
+ if (isForm(charset, String)) charset = String.charset(charset);
561
+
562
+ let base = charset.size;
563
+ if (base === 1n) return this.count();
564
+
565
+ let sum = 0n;
566
+ let n = this.length;
567
+ for (let ind = 0; ind < n; ind++)
568
+ // Earlier values of `i` represent higher places same as with written numbers further left
569
+ // digits are more significant
570
+ // The value of the place `i` is `ind - 1`
571
+ sum += (base ** BigInt(n - ind - 1)) * charset.charVal(this[ind]);
572
+ return sum;
573
+
574
+ },
575
+ reencode(srcCharset: string | CharSet, trgCharset: string | CharSet) {
576
+ return this.encodeInt(srcCharset).encodeStr(trgCharset);
577
+ }
578
+
579
+ });
580
+ protoDefs(Number, {
581
+
582
+ $int32: Math.pow(2, 32),
583
+ $int64: Math.pow(2, 64),
584
+
585
+ char() { return String.fromCharCode(this); },
586
+ each(fn) { for (let i = 0; i < this; i++) fn(i); },
587
+ toArr(fn) { let arr = new Array(this || 0); for (let i = 0; i < this; i++) arr[i] = fn(i); return arr; },
588
+ toObj(fn) { // Iterator: n => [ key, val ]
589
+ let ret: [ string, any ][] = [];
590
+ for (let i = 0; i < this; i++) { let v = fn(i); if (v !== skip) ret.push(v); }
591
+ return Object.fromEntries(ret);
592
+ },
593
+ encodeStr(charset: string | CharSet, padLen=0) {
594
+
595
+ // Note that base-1 requires 0 to map to the empty string. This also
596
+ // means that, for `n >= 1`:
597
+ // | (n).encodeStr(singleChr)
598
+ // is always equivalent to
599
+ // | singleChr.repeat(n - 1)
600
+
601
+ if (isForm(charset, String)) charset = String.charset(charset);
602
+
603
+ let base = charset.size;
604
+ if (base === 1n && padLen) throw Error(`Can't pad when using base-1 encoding`);
605
+
606
+ if (this !== this) return (base === 1n) ? '' : charset[0].repeat(Math.max(padLen, 1));
607
+
608
+ let num = this.constructor === BigInt ? this : BigInt(Math.floor(this));
609
+ let digits: string[] = [];
610
+ while (num) { digits.push(charset.valChar(num % base)); num /= base; }
611
+ return digits.reverse().join('').padHead(padLen, charset.str[0]);
612
+
613
+ },
614
+ isInteger() { return this === Math.round(this); }, // No bitwise shortcut - it disrupts Infinity
615
+ * [Symbol.iterator]() { for (let i = 0; i < this; i++) yield i; },
616
+ * bits() { let n = this >= 0 ? this : -this; while (n) { yield n & 1; n = n >> 1; } },
617
+
618
+ map: undefined // Prevent `Number(...).map`
619
+
620
+ });
621
+ protoDefs(BigInt, {
622
+
623
+ encodeStr: Number.prototype.encodeStr
624
+
625
+ });
626
+ protoDefs(Function, {
627
+
628
+ $stub: v => v,
629
+ $createStub: v => () => v,
630
+ bound(...args) { return this.bind(null, ...args); }
631
+
632
+ });
633
+ protoDefs(Error, {
634
+
635
+ $stackTraceLimit: 150,
636
+
637
+ mod(props: any = {} /* { cause, msg, message, ...more } */) {
638
+
639
+ if (isForm(props, Function)) props = props(this.message, this);
640
+ if (isForm(props, String)) props = { message: props };
641
+
642
+ let { cause=null, msg=null, message=msg??this.message, ...moreProps } = props;
643
+
644
+ // - Assign `cause` to transfer props like fs "code" props, etc. - watch out, `cause` may be
645
+ // an Array or Object!
646
+ // - Assign `moreProps` to transfer any other properties
647
+ // - Add `message` prop
648
+ // - Only add `cause` prop if `cause` is non-null
649
+ return Object.assign(this, hasForm(cause, Error) ? cause : {}, moreProps, cause ? { message, cause } : { message });
650
+
651
+ },
652
+ propagate(props /* { cause, msg, message, ...more } */) { throw this.mod(props); },
653
+ suppress() {
654
+ this['~suppressed'] = true;
655
+
656
+ if (!this.cause) return;
657
+ let causes = (hasForm(this.cause, Error) ? [ this.cause ] : this.cause);
658
+ causes.each(err => err.suppress());
659
+ }
660
+
661
+ });
662
+
663
+ let newlessProtoDefs = (Cls, vals) => {
664
+
665
+ // Extend prototypes and allow instantiation without "new"
666
+
667
+ let name = Cls.name;
668
+ let Newless = global[name] = function(...args) { return new Cls(...args); } as any;
669
+ C.def(Newless, 'name', name);
670
+ Newless.Native = Cls;
671
+ Newless.prototype = Cls.prototype;
672
+
673
+ protoDefs(Newless, vals);
674
+
675
+ };
676
+ newlessProtoDefs(Promise, {
677
+
678
+ $all: (prms, mapFn=null) => {
679
+
680
+ if (mapFn) prms = prms.map(mapFn);
681
+
682
+ if (isForm(prms, Array)) return C['Promise.all'](prms).then(a => a.toArr(v => v)); // Remove any `skip` results
683
+
684
+ if (isForm(prms, Object)) {
685
+
686
+ // Need to get `keys` here in case `obj` mutates before resolution
687
+ let keys = Object.keys(prms);
688
+ return C['Promise.all'](Object.values(prms))
689
+ .then(vals => { let ret = {}; for (let [ i, k ] of keys.entries()) ret[k] = vals[i]; return ret; });
690
+
691
+ }
692
+
693
+ throw Error(`Unexpected parameter for Promise.all: ${getFormName(prms)}`);
694
+
695
+ },
696
+ $resolve: Promise.resolve,
697
+ $reject: Promise.reject,
698
+ $later: (resolve, reject) => {
699
+ let p = Promise((...a) => [ resolve, reject ] = a);
700
+ return Object.assign(p, { resolve, reject });
701
+ },
702
+
703
+ $$: 'route:then,fail:catch'
704
+
705
+ });
706
+ newlessProtoDefs(Set, {
707
+
708
+ $stub: { count: () => 0, add: Function.stub, rem: Function.stub, has: () => false, values: () => Array.stub },
709
+
710
+ $$: 'each:forEach,rem:delete',
711
+
712
+ map(fn) { // Iterator: (val, ind) => val0
713
+ let ret: any[] = [], ind = 0;
714
+ for (let v of this) { v = fn(v, ind++); if (v !== skip) ret.push(v); }
715
+ return ret;
716
+ },
717
+ find(fn) { // Iterator: (val) => bool; returns { found, val }
718
+ for (let val of this) if (fn(val)) return { found: true, val };
719
+ return { found: false, val: null };
720
+ },
721
+ count() { return this.size; },
722
+ empty() { return !this.size; },
723
+ toArr(...args) { return this.map(...args); },
724
+ toObj(fn) {
725
+ let ret: any[] = [];
726
+ for (let v of this) { v = fn(v); if (v !== skip) ret.push(v); }
727
+ return Object.fromEntries(ret);
728
+ }
729
+
730
+ });
731
+ newlessProtoDefs(Map, {
732
+
733
+ $stub: { count: () => 0, set: Function.stub, rem: Function.stub, has: () => false, values: () => Array.stub },
734
+
735
+ $$: 'each:forEach,add:set,rem:delete',
736
+
737
+ map(fn) { // Iterator: (val, key) => [ key0, val0 ]
738
+ let ret: any = [];
739
+ for (let [ k, v ] of this) { v = fn(v, k); if (v !== skip) ret.push(v); }
740
+ return Object.fromEntries(ret);
741
+ },
742
+ find(f) { // Iterator: (val, key) => bool; returns { found, val, key }
743
+ for (let [ k, v ] of this) if (f(v, k)) return { found: true, val: v, key: k };
744
+ return { found: false, val: null, key: null };
745
+ },
746
+ count() { return this.size; },
747
+ empty() { return !this.size; },
748
+ toObj(...args) { return this.map(...args); },
749
+ toArr(fn) { // Iterator: (val, key) => val0
750
+ let ret: any[] = [];
751
+ for (let [ k, v ] of this) { v = fn(v, k); if (v !== skip) ret.push(v); }
752
+ return ret;
753
+ }
754
+
755
+ });
756
+ newlessProtoDefs(GeneratorFunction, {
757
+ each(fn) { for (let v of this) fn(v); },
758
+ toArr(fn) { return [ ...this ].map(fn); },
759
+ toObj(fn) {
760
+ let ret = {};
761
+ for (let v of this) { v = fn(v); if (v !== skip) ret[v[0]] = v[1]; }
762
+ return ret;
763
+ }
764
+ });
765
+ }
package/global.d.ts ADDED
@@ -0,0 +1,197 @@
1
+ // Util
2
+ type fn = (...args: any[]) => any;
3
+ type obj = { [key: string]: any };
4
+ type maybeFn<T> = T | ((...args: any[]) => T);
5
+ type maybeFnResolved<T> = T extends (...args: any[]) => any ? ReturnType<T> : T;
6
+ type MaybePromise<T> = T | Promise<T>;
7
+
8
+ // Forms
9
+ type formConfig = {
10
+ name: string,
11
+ has: { [key: string]: any },
12
+ props: { [key: string]: any }
13
+ };
14
+ type Form<props extends {}, initArgs extends any[], state extends {}> = {
15
+ '~props': maybeFnResolved<props>,
16
+ '~initArgs': initArgs,
17
+ '~state': state,
18
+ (...a: initArgs): formInstance<Form<props, initArgs, state>>,
19
+ new (...a: initArgs): formInstance<Form<props, initArgs, state>>
20
+ };
21
+ type formInstance<MyForm extends Form> = { '~form': MyForm }
22
+ & { [ key in keyof MyForm['~props'] ]: MyForm['~props'][key] }
23
+ & { [ key in keyof MyForm['~state'] ]: MyForm['~state'][key] };
24
+
25
+ // Globals
26
+ declare const global: any;
27
+ declare const window: any;
28
+ declare const getFormName: (f: any) => string;
29
+ declare const denumerate: (val: any, name: string) => void;
30
+ declare const skip: never;
31
+ declare const isForm: {
32
+ (val: any, arr: FunctionConstructor): val is fn;
33
+ (val: any, obj: ObjectConstructor): val is { [key: string]: any };
34
+ (val: any, str: StringConstructor): val is string;
35
+ (val: any, num: NumberConstructor): val is number;
36
+ (val: any, arr: ArrayConstructor): val is any[];
37
+ <T>(val: any, prm: PromiseConstructor): val is Promise<T>;
38
+ <T>(val: any, t: T): val is T;
39
+ }
40
+ declare const hasForm: {
41
+ (val: any, t: any): boolean;
42
+ }
43
+ declare const form: <initArgs extends any[], state extends {}>(c: formConfig) => Form<formConfig['props'], initArgs, state>;
44
+ declare const formatAnyValue: (val: any) => string;
45
+ declare const C: {
46
+ def: fn
47
+ };
48
+ declare const getMs: () => number;
49
+ declare const getDate: () => string;
50
+ declare const then: (val: any, resolve: fn, reject?: fn) => any;
51
+ declare const valToJson: (val: any) => string;
52
+ declare const valToSer: (val: any) => string;
53
+ declare const jsonToVal: (val: string | Buffer | null) => any;
54
+ declare const GeneratorFunction: any;
55
+ declare const global: { [key: string|symbol]: any } & {
56
+ skip: typeof skip,
57
+ GeneratorFunction: typeof GeneratorFunction,
58
+ isForm: typeof isForm,
59
+ hasForm: typeof hasForm,
60
+ formatAnyValue: typeof formatAnyValue,
61
+ then: typeof then,
62
+ getMs: typeof getMs,
63
+ getDate: typeof getDate,
64
+ valToJson: typeof valToJson,
65
+ jsonToVal: typeof jsonToVal,
66
+ C: typeof C,
67
+ denumerate: typeof denumerate
68
+ };
69
+
70
+ interface JSON {
71
+ parse: (val: Buffer) => any
72
+ }
73
+
74
+ interface Array<T> {
75
+ any: (fn: fn) => boolean,
76
+ has: (val: T) => boolean,
77
+ add: (val: T) => void,
78
+ count: () => number,
79
+ valSort: (fn: (val: T) => number) => Array<T>,
80
+ find: (fn: fn) => ({ found: true, val: T } | { found: false }),
81
+ each: (fn: (val: T) => void) => void,
82
+ empty: () => boolean,
83
+ equals: <Z>(arr: Array<T>) => Z extends T ? boolean : false,
84
+ toObj: (fn: fn) => any,
85
+ seek: (fn: (val: T) => any) => { found: boolean, val: T | undefined, ind: number },
86
+ }
87
+ interface ArrayConstructor {
88
+ stub: any[]
89
+ }
90
+
91
+ interface Error {
92
+ mod: (props: { [key: string]: any }) => Error
93
+ propagate: (props?: { [key: string]: any }) => never
94
+ }
95
+
96
+ interface FunctionConstructor {
97
+ stub: (...args: any[]) => any
98
+ }
99
+ interface Function {
100
+ bound: fn
101
+ }
102
+
103
+ interface GeneratorFunctionConstructor {
104
+ }
105
+
106
+ interface Number {
107
+ encodeStr: (str: string | CharSet, len?: number) => string,
108
+ each: (fn: (n: number) => void) => void,
109
+ toArr: <T>(fn: (n: number) => T) => T[]
110
+ }
111
+ interface NumberConstructor {
112
+ int32: number
113
+ }
114
+
115
+ interface BigInt {
116
+ encodeStr: Number[encodeStr]
117
+ }
118
+
119
+ interface ObjectConstructor {
120
+ }
121
+ interface Object {
122
+ empty: () => boolean,
123
+ has: (k: string) => boolean,
124
+ map: (fn: (val: any, key: string) => any) => any,
125
+ mapk: (fn: (val: any, key: string) => [ string, any ]) => any,
126
+ at: (k: string | string[], def?: any) => any,
127
+ plain: (obj?: any) => any,
128
+ slice: <T>(this: T, keys: (keyof T)[]) => Partial<T>,
129
+ omit: <T>(this: T, keys: (keyof T)[]) => Partial<T>,
130
+ toArr: <T extends (v: any, k: string) => any>(fn: T) => ReturnType<T>[],
131
+ built: () => Object,
132
+ merge: <T>(val: T) => Object & T,
133
+ gain: (...args: any[]) => any,
134
+ [Symbol.iterator]: () => Iterator<[ string, any]>
135
+ }
136
+
137
+ interface Promise<T> {
138
+ fail: Promise<T>['catch'],
139
+ }
140
+
141
+ interface PromiseLater<T=void> extends Promise<T> {
142
+ resolve: T extends void ? () => void : (v: T) => void,
143
+ reject: (err: any) => void
144
+ }
145
+ interface PromiseConstructor {
146
+ <T>(fn: (resolve: (v: T) => void, reject: (err: any) => void) => any): Promise<T>,
147
+ later: <T=void>() => PromiseLater<T>,
148
+ all: {
149
+ <T>(values: Iterable<T | PromiseLike<T>>): Promise<Awaited<T>[]>;
150
+
151
+ //<T>(arr: Promise<T>[]): Promise<T[]>,
152
+ //<T>(obj: { [key: string]: Promise<T> }): Promise<{ [key: string]: T }>
153
+ }
154
+ }
155
+
156
+ interface SetConstructor {
157
+ <T>(arr?: T[]): Set<T>
158
+ }
159
+ interface Set<T> {
160
+ toArr: (fn: (val: T, ind: number) => void) => T[],
161
+ rem: (val: T) => void
162
+ }
163
+
164
+ interface MapConstructor {
165
+ <K, V>(arr?: [ K, V ][]): Map<K, V>
166
+ }
167
+ interface Map<K, V> {
168
+ add: (k: K, v: V) => void,
169
+ toArr: <T>(fn: (val: V, key: K) => T) => T[],
170
+ rem: (key: K) => void
171
+ }
172
+
173
+ type CharSet = { str: string, size: bigint, charVal: (c: string) => bigint, valChar: (n: bigint) => string };
174
+ interface String {
175
+ count(): number,
176
+ padHead(n: number, s?: string): string,
177
+ padTail(n: number, s?: string): string,
178
+ encodeInt: (chrs: string | CharSet) => bigint,
179
+ reencode: (src: string | CharSet, trg: string | CharSet) => string,
180
+ trimHead(): string,
181
+ trimTail(): string,
182
+ hasHead(head: string): boolean,
183
+ upper(): string,
184
+ lower(): string,
185
+ cut(str: string, cuts?: number): string[],
186
+ indent: {
187
+ (amount: number, char?: string): string,
188
+ (str: string): string
189
+ }
190
+ }
191
+ interface StringConstructor {
192
+ charset: (str: string) => CharSet,
193
+ base32: string,
194
+ base62: string,
195
+ baseline: (str: string) => str,
196
+ multiline: (str: string) => str,
197
+ }
package/package.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "@gershy/clearing",
3
+ "version": "0.0.1",
4
+ "description": "Simplified js basics",
5
+ "main": "main.ts",
6
+ "scripts": {
7
+ "test": "node ./test/main.ts"
8
+ },
9
+ "author": "Gershom Maes",
10
+ "license": "ISC"
11
+ }