@c9up/ream 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/container/Container.d.ts +18 -0
- package/dist/container/Container.d.ts.map +1 -1
- package/dist/container/Container.js +29 -0
- package/dist/container/Container.js.map +1 -1
- package/dist/env/Env.d.ts +11 -0
- package/dist/env/Env.d.ts.map +1 -1
- package/dist/env/Env.js +33 -17
- package/dist/env/Env.js.map +1 -1
- package/dist/env/Secret.d.ts +17 -0
- package/dist/env/Secret.d.ts.map +1 -1
- package/dist/env/Secret.js +23 -0
- package/dist/env/Secret.js.map +1 -1
- package/dist/errors/aggregate.d.ts +3 -0
- package/dist/errors/aggregate.d.ts.map +1 -1
- package/dist/errors/aggregate.js +3 -0
- package/dist/errors/aggregate.js.map +1 -1
- package/dist/events/Emitter.d.ts +20 -1
- package/dist/events/Emitter.d.ts.map +1 -1
- package/dist/events/Emitter.js +28 -6
- package/dist/events/Emitter.js.map +1 -1
- package/dist/helpers/MessageBuilder.d.ts +29 -0
- package/dist/helpers/MessageBuilder.d.ts.map +1 -0
- package/dist/helpers/MessageBuilder.js +63 -0
- package/dist/helpers/MessageBuilder.js.map +1 -0
- package/dist/helpers/VerificationToken.d.ts +56 -0
- package/dist/helpers/VerificationToken.d.ts.map +1 -0
- package/dist/helpers/VerificationToken.js +70 -0
- package/dist/helpers/VerificationToken.js.map +1 -0
- package/dist/helpers/defineStaticProperty.d.ts +15 -0
- package/dist/helpers/defineStaticProperty.d.ts.map +1 -0
- package/dist/helpers/defineStaticProperty.js +47 -0
- package/dist/helpers/defineStaticProperty.js.map +1 -0
- package/dist/helpers/duration.d.ts +20 -0
- package/dist/helpers/duration.d.ts.map +1 -1
- package/dist/helpers/duration.js +71 -0
- package/dist/helpers/duration.js.map +1 -1
- package/dist/helpers/fs.d.ts +39 -0
- package/dist/helpers/fs.d.ts.map +1 -0
- package/dist/helpers/fs.js +70 -0
- package/dist/helpers/fs.js.map +1 -0
- package/dist/helpers/index.d.ts +8 -0
- package/dist/helpers/index.d.ts.map +1 -1
- package/dist/helpers/index.js +7 -0
- package/dist/helpers/index.js.map +1 -1
- package/dist/helpers/safeTiming.d.ts +14 -0
- package/dist/helpers/safeTiming.d.ts.map +1 -0
- package/dist/helpers/safeTiming.js +42 -0
- package/dist/helpers/safeTiming.js.map +1 -0
- package/dist/helpers/string.d.ts +85 -0
- package/dist/helpers/string.d.ts.map +1 -1
- package/dist/helpers/string.js +127 -1
- package/dist/helpers/string.js.map +1 -1
- package/dist/http/HttpContext.d.ts +14 -1
- package/dist/http/HttpContext.d.ts.map +1 -1
- package/dist/http/HttpContext.js.map +1 -1
- package/dist/http/RedirectBuilder.d.ts +16 -0
- package/dist/http/RedirectBuilder.d.ts.map +1 -1
- package/dist/http/RedirectBuilder.js +34 -9
- package/dist/http/RedirectBuilder.js.map +1 -1
- package/dist/http/Request.d.ts.map +1 -1
- package/dist/http/Request.js +5 -30
- package/dist/http/Request.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/router/Router.d.ts +54 -0
- package/dist/router/Router.d.ts.map +1 -1
- package/dist/router/Router.js +72 -3
- package/dist/router/Router.js.map +1 -1
- package/dist/session/ReadOnlyValuesStore.d.ts +11 -0
- package/dist/session/ReadOnlyValuesStore.d.ts.map +1 -1
- package/dist/session/ReadOnlyValuesStore.js +17 -0
- package/dist/session/ReadOnlyValuesStore.js.map +1 -1
- package/dist/session/Session.d.ts +11 -3
- package/dist/session/Session.d.ts.map +1 -1
- package/dist/session/Session.js +15 -6
- package/dist/session/Session.js.map +1 -1
- package/dist/session/SessionMiddleware.js +2 -1
- package/dist/session/SessionMiddleware.js.map +1 -1
- package/dist/session/errors.d.ts +25 -0
- package/dist/session/errors.d.ts.map +1 -0
- package/dist/session/errors.js +32 -0
- package/dist/session/errors.js.map +1 -0
- package/dist/types/{container.d.ts → index.d.ts} +47 -18
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +14 -0
- package/dist/types/index.js.map +1 -0
- package/events.darwin-arm64.node +0 -0
- package/events.darwin-x64.node +0 -0
- package/events.linux-arm64-gnu.node +0 -0
- package/events.linux-x64-gnu.node +0 -0
- package/events.win32-x64-msvc.node +0 -0
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +7 -3
- package/scheduler.win32-x64-msvc.node +0 -0
- package/src/container/Container.ts +31 -0
- package/src/env/Env.ts +34 -15
- package/src/env/Secret.ts +26 -0
- package/src/errors/aggregate.ts +3 -0
- package/src/events/Emitter.ts +33 -5
- package/src/helpers/MessageBuilder.ts +69 -0
- package/src/helpers/VerificationToken.ts +93 -0
- package/src/helpers/defineStaticProperty.ts +51 -0
- package/src/helpers/duration.ts +75 -0
- package/src/helpers/fs.ts +104 -0
- package/src/helpers/index.ts +8 -0
- package/src/helpers/safeTiming.ts +45 -0
- package/src/helpers/string.ts +145 -1
- package/src/http/HttpContext.ts +14 -1
- package/src/http/RedirectBuilder.ts +38 -8
- package/src/http/Request.ts +5 -29
- package/src/index.ts +11 -2
- package/src/router/Router.ts +86 -3
- package/src/session/ReadOnlyValuesStore.ts +20 -0
- package/src/session/Session.ts +16 -11
- package/src/session/SessionMiddleware.ts +2 -1
- package/src/session/errors.ts +39 -0
- package/src/types/{container.ts → index.ts} +48 -17
- package/dist/types/container.d.ts.map +0 -1
- package/dist/types/container.js +0 -23
- package/dist/types/container.js.map +0 -1
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wrap a value with an expiry and a purpose before it is signed or encrypted.
|
|
3
|
+
*
|
|
4
|
+
* The signature on a token proves it was issued by this application. It does
|
|
5
|
+
* NOT prove the token is still valid, nor that it was issued for the job it is
|
|
6
|
+
* being presented for — a signed password-reset token would otherwise pass as a
|
|
7
|
+
* signed email-verification token. Those two claims travel inside the payload,
|
|
8
|
+
* which is why they are added before signing rather than checked after.
|
|
9
|
+
*
|
|
10
|
+
* Ported from `@poppinss/utils`; `CookieSigner` and `SignedUrl` sign what this
|
|
11
|
+
* produces.
|
|
12
|
+
*/
|
|
13
|
+
import { milliseconds } from './duration.js';
|
|
14
|
+
export class MessageBuilder {
|
|
15
|
+
/**
|
|
16
|
+
* Serialise `message`, optionally stamped with an expiry and a purpose.
|
|
17
|
+
*
|
|
18
|
+
* @param expiresIn - a duration (`'2 hours'`) or a number of milliseconds.
|
|
19
|
+
*/
|
|
20
|
+
build(message, expiresIn, purpose) {
|
|
21
|
+
const envelope = { message };
|
|
22
|
+
if (purpose !== undefined)
|
|
23
|
+
envelope.purpose = purpose;
|
|
24
|
+
if (expiresIn !== undefined) {
|
|
25
|
+
envelope.expiryDate = new Date(Date.now() + milliseconds.parse(expiresIn)).toISOString();
|
|
26
|
+
}
|
|
27
|
+
return JSON.stringify(envelope);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Read an envelope back, or `null` when it is malformed, expired, or was
|
|
31
|
+
* issued for a different purpose.
|
|
32
|
+
*
|
|
33
|
+
* Never throws: every failure is the same `null`, so a caller cannot
|
|
34
|
+
* accidentally tell "tampered" from "expired" and leak which it was.
|
|
35
|
+
*/
|
|
36
|
+
verify(payload, purpose) {
|
|
37
|
+
let parsed;
|
|
38
|
+
try {
|
|
39
|
+
parsed = JSON.parse(payload);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
if (parsed === null || typeof parsed !== 'object')
|
|
45
|
+
return null;
|
|
46
|
+
if (!('message' in parsed) || parsed.message === undefined)
|
|
47
|
+
return null;
|
|
48
|
+
const envelopePurpose = 'purpose' in parsed ? parsed.purpose : undefined;
|
|
49
|
+
if (envelopePurpose !== purpose)
|
|
50
|
+
return null;
|
|
51
|
+
if ('expiryDate' in parsed && typeof parsed.expiryDate === 'string') {
|
|
52
|
+
const expiresAt = new Date(parsed.expiryDate);
|
|
53
|
+
// An unparseable date counts as expired: a token whose expiry cannot be
|
|
54
|
+
// read is a token whose expiry cannot be enforced.
|
|
55
|
+
if (Number.isNaN(expiresAt.getTime()) || expiresAt <= new Date())
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
// The caller declares the shape it put in — the same contract as
|
|
59
|
+
// `session.get<T>()`, over a payload that is untyped by nature.
|
|
60
|
+
return parsed.message;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=MessageBuilder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MessageBuilder.js","sourceRoot":"","sources":["../../src/helpers/MessageBuilder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAS5C,MAAM,OAAO,cAAc;IACzB;;;;OAIG;IACH,KAAK,CAAC,OAAgB,EAAE,SAA2B,EAAE,OAAgB;QACnE,MAAM,QAAQ,GAAa,EAAE,OAAO,EAAE,CAAA;QACtC,IAAI,OAAO,KAAK,SAAS;YAAE,QAAQ,CAAC,OAAO,GAAG,OAAO,CAAA;QACrD,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YAC5B,QAAQ,CAAC,UAAU,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,YAAY,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,EAAE,CAAA;QAC1F,CAAC;QACD,OAAO,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAA;IACjC,CAAC;IAED;;;;;;OAMG;IACH,MAAM,CAAc,OAAe,EAAE,OAAgB;QACnD,IAAI,MAAe,CAAA;QACnB,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;QAC9B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAA;QACb,CAAC;QACD,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAA;QAC9D,IAAI,CAAC,CAAC,SAAS,IAAI,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS;YAAE,OAAO,IAAI,CAAA;QAEvE,MAAM,eAAe,GAAG,SAAS,IAAI,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAA;QACxE,IAAI,eAAe,KAAK,OAAO;YAAE,OAAO,IAAI,CAAA;QAE5C,IAAI,YAAY,IAAI,MAAM,IAAI,OAAO,MAAM,CAAC,UAAU,KAAK,QAAQ,EAAE,CAAC;YACpE,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAA;YAC7C,wEAAwE;YACxE,mDAAmD;YACnD,IAAI,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,IAAI,SAAS,IAAI,IAAI,IAAI,EAAE;gBAAE,OAAO,IAAI,CAAA;QAC/E,CAAC;QAED,iEAAiE;QACjE,gEAAgE;QAChE,OAAO,MAAM,CAAC,OAAY,CAAA;IAC5B,CAAC;CACF"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The base class for opaque, single-use tokens — password resets, email
|
|
3
|
+
* verification, magic links.
|
|
4
|
+
*
|
|
5
|
+
* The shape it enforces is the point. The token handed to the user is
|
|
6
|
+
* `<identifier>.<secret>`, and only the SHA-256 of the secret is stored. A
|
|
7
|
+
* database leak therefore yields hashes, not usable tokens, and the identifier
|
|
8
|
+
* travels alongside so verification is one indexed lookup rather than a scan.
|
|
9
|
+
*
|
|
10
|
+
* Comparison goes through a constant-time equality: comparing hashes with `===`
|
|
11
|
+
* leaks their common prefix through timing, which is enough to forge one.
|
|
12
|
+
*
|
|
13
|
+
* Ported from `@adonisjs/core/helpers` — subclass it and add the persistence.
|
|
14
|
+
*/
|
|
15
|
+
import { Secret } from '../env/Secret.js';
|
|
16
|
+
export declare abstract class VerificationToken {
|
|
17
|
+
/** Row identifier, as stored. */
|
|
18
|
+
identifier: string | number | bigint;
|
|
19
|
+
/** Who the token belongs to. */
|
|
20
|
+
tokenableId: string | number | bigint;
|
|
21
|
+
/** SHA-256 of the secret — the only half that is persisted. */
|
|
22
|
+
hash: string;
|
|
23
|
+
/** When the token stops being accepted. */
|
|
24
|
+
expiresAt: Date;
|
|
25
|
+
/** The full token, present only on the instance that just created it. */
|
|
26
|
+
value?: Secret<string>;
|
|
27
|
+
/**
|
|
28
|
+
* Split a presented token back into its identifier and secret.
|
|
29
|
+
*
|
|
30
|
+
* Returns `null` for anything malformed rather than throwing: the input is
|
|
31
|
+
* attacker-controlled, and every rejection should look the same.
|
|
32
|
+
*/
|
|
33
|
+
static decode(value: string): {
|
|
34
|
+
identifier: string;
|
|
35
|
+
secret: Secret<string>;
|
|
36
|
+
} | null;
|
|
37
|
+
/** A fresh secret and its hash. */
|
|
38
|
+
static seed(size: number): {
|
|
39
|
+
secret: Secret<string>;
|
|
40
|
+
hash: string;
|
|
41
|
+
};
|
|
42
|
+
/** A seed plus the owner and an expiry, ready to be persisted. */
|
|
43
|
+
static createTransientToken(userId: string | number | bigint, size: number, expiresIn: string | number): {
|
|
44
|
+
userId: string | number | bigint;
|
|
45
|
+
expiresAt: Date;
|
|
46
|
+
secret: Secret<string>;
|
|
47
|
+
hash: string;
|
|
48
|
+
};
|
|
49
|
+
/** Build the public `<identifier>.<secret>` value, after the row has an id. */
|
|
50
|
+
protected computeValue(secret: Secret<string>): void;
|
|
51
|
+
/** Whether the expiry has passed. */
|
|
52
|
+
isExpired(): boolean;
|
|
53
|
+
/** Whether `secret` hashes to the stored hash, compared in constant time. */
|
|
54
|
+
verify(secret: Secret<string>): boolean;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=VerificationToken.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VerificationToken.d.ts","sourceRoot":"","sources":["../../src/helpers/VerificationToken.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAMzC,8BAAsB,iBAAiB;IACrC,iCAAiC;IACzB,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAA;IAC5C,gCAAgC;IACxB,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAA;IAC7C,+DAA+D;IACvD,IAAI,EAAE,MAAM,CAAA;IACpB,2CAA2C;IACnC,SAAS,EAAE,IAAI,CAAA;IACvB,yEAAyE;IACzE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,CAAA;IAEtB;;;;;OAKG;IACH,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAA;KAAE,GAAG,IAAI;IAUnF,mCAAmC;IACnC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG;QAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE;IAQnE,kEAAkE;IAClE,MAAM,CAAC,oBAAoB,CACzB,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,EAChC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,GAAG,MAAM,GACzB;QACD,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAA;QAChC,SAAS,EAAE,IAAI,CAAA;QACf,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAA;QACtB,IAAI,EAAE,MAAM,CAAA;KACb;IAMD,+EAA+E;IAC/E,SAAS,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,IAAI;IAMpD,qCAAqC;IACrC,SAAS,IAAI,OAAO;IAIpB,6EAA6E;IAC7E,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,OAAO;CAIxC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The base class for opaque, single-use tokens — password resets, email
|
|
3
|
+
* verification, magic links.
|
|
4
|
+
*
|
|
5
|
+
* The shape it enforces is the point. The token handed to the user is
|
|
6
|
+
* `<identifier>.<secret>`, and only the SHA-256 of the secret is stored. A
|
|
7
|
+
* database leak therefore yields hashes, not usable tokens, and the identifier
|
|
8
|
+
* travels alongside so verification is one indexed lookup rather than a scan.
|
|
9
|
+
*
|
|
10
|
+
* Comparison goes through a constant-time equality: comparing hashes with `===`
|
|
11
|
+
* leaks their common prefix through timing, which is enough to forge one.
|
|
12
|
+
*
|
|
13
|
+
* Ported from `@adonisjs/core/helpers` — subclass it and add the persistence.
|
|
14
|
+
*/
|
|
15
|
+
import { createHash } from 'node:crypto';
|
|
16
|
+
import { Secret } from '../env/Secret.js';
|
|
17
|
+
import { base64 } from './base64.js';
|
|
18
|
+
import { seconds } from './duration.js';
|
|
19
|
+
import { safeEqual } from './safeEqual.js';
|
|
20
|
+
import { random } from './string.js';
|
|
21
|
+
export class VerificationToken {
|
|
22
|
+
/** The full token, present only on the instance that just created it. */
|
|
23
|
+
value;
|
|
24
|
+
/**
|
|
25
|
+
* Split a presented token back into its identifier and secret.
|
|
26
|
+
*
|
|
27
|
+
* Returns `null` for anything malformed rather than throwing: the input is
|
|
28
|
+
* attacker-controlled, and every rejection should look the same.
|
|
29
|
+
*/
|
|
30
|
+
static decode(value) {
|
|
31
|
+
if (typeof value !== 'string' || value.length === 0)
|
|
32
|
+
return null;
|
|
33
|
+
const [identifier, ...rest] = value.split('.');
|
|
34
|
+
if (!identifier || rest.length === 0)
|
|
35
|
+
return null;
|
|
36
|
+
const decodedIdentifier = base64.urlDecode(identifier);
|
|
37
|
+
const decodedSecret = base64.urlDecode(rest.join('.'));
|
|
38
|
+
if (!decodedIdentifier || !decodedSecret)
|
|
39
|
+
return null;
|
|
40
|
+
return { identifier: decodedIdentifier, secret: new Secret(decodedSecret) };
|
|
41
|
+
}
|
|
42
|
+
/** A fresh secret and its hash. */
|
|
43
|
+
static seed(size) {
|
|
44
|
+
const secret = new Secret(random(size));
|
|
45
|
+
return {
|
|
46
|
+
secret,
|
|
47
|
+
hash: createHash('sha256').update(secret.release()).digest('hex'),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** A seed plus the owner and an expiry, ready to be persisted. */
|
|
51
|
+
static createTransientToken(userId, size, expiresIn) {
|
|
52
|
+
const expiresAt = new Date();
|
|
53
|
+
expiresAt.setSeconds(expiresAt.getSeconds() + seconds.parse(expiresIn));
|
|
54
|
+
return { userId, expiresAt, ...VerificationToken.seed(size) };
|
|
55
|
+
}
|
|
56
|
+
/** Build the public `<identifier>.<secret>` value, after the row has an id. */
|
|
57
|
+
computeValue(secret) {
|
|
58
|
+
this.value = new Secret(`${base64.urlEncode(String(this.identifier))}.${base64.urlEncode(secret.release())}`);
|
|
59
|
+
}
|
|
60
|
+
/** Whether the expiry has passed. */
|
|
61
|
+
isExpired() {
|
|
62
|
+
return this.expiresAt < new Date();
|
|
63
|
+
}
|
|
64
|
+
/** Whether `secret` hashes to the stored hash, compared in constant time. */
|
|
65
|
+
verify(secret) {
|
|
66
|
+
const candidate = createHash('sha256').update(secret.release()).digest('hex');
|
|
67
|
+
return safeEqual(this.hash, candidate);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=VerificationToken.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VerificationToken.js","sourceRoot":"","sources":["../../src/helpers/VerificationToken.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEpC,MAAM,OAAgB,iBAAiB;IASrC,yEAAyE;IACzE,KAAK,CAAiB;IAEtB;;;;;OAKG;IACH,MAAM,CAAC,MAAM,CAAC,KAAa;QACzB,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAA;QAChE,MAAM,CAAC,UAAU,EAAE,GAAG,IAAI,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QAC9C,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAA;QACjD,MAAM,iBAAiB,GAAG,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,CAAA;QACtD,MAAM,aAAa,GAAG,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;QACtD,IAAI,CAAC,iBAAiB,IAAI,CAAC,aAAa;YAAE,OAAO,IAAI,CAAA;QACrD,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,EAAE,IAAI,MAAM,CAAC,aAAa,CAAC,EAAE,CAAA;IAC7E,CAAC;IAED,mCAAmC;IACnC,MAAM,CAAC,IAAI,CAAC,IAAY;QACtB,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAA;QACvC,OAAO;YACL,MAAM;YACN,IAAI,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;SAClE,CAAA;IACH,CAAC;IAED,kEAAkE;IAClE,MAAM,CAAC,oBAAoB,CACzB,MAAgC,EAChC,IAAY,EACZ,SAA0B;QAO1B,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAA;QAC5B,SAAS,CAAC,UAAU,CAAC,SAAS,CAAC,UAAU,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAA;QACvE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAA;IAC/D,CAAC;IAED,+EAA+E;IACrE,YAAY,CAAC,MAAsB;QAC3C,IAAI,CAAC,KAAK,GAAG,IAAI,MAAM,CACrB,GAAG,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,EAAE,CACrF,CAAA;IACH,CAAC;IAED,qCAAqC;IACrC,SAAS;QACP,OAAO,IAAI,CAAC,SAAS,GAAG,IAAI,IAAI,EAAE,CAAA;IACpC,CAAC;IAED,6EAA6E;IAC7E,MAAM,CAAC,MAAsB;QAC3B,MAAM,SAAS,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QAC7E,OAAO,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;IACxC,CAAC;CACF"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Give a subclass its own copy of an inherited static property.
|
|
3
|
+
*
|
|
4
|
+
* A static declared on a base class is SHARED: every subclass mutating
|
|
5
|
+
* `Model.booted` or `Model.$columns` writes into the same object. This walks
|
|
6
|
+
* that back by defining an own property on the subclass, seeded either from a
|
|
7
|
+
* fresh value or from a copy of what it inherited.
|
|
8
|
+
*
|
|
9
|
+
* Ported from `@poppinss/utils` — the mechanism ORM-style base classes rely on.
|
|
10
|
+
*/
|
|
11
|
+
export declare function defineStaticProperty<T>(target: object, propertyName: string, options: {
|
|
12
|
+
initialValue: T;
|
|
13
|
+
strategy: 'inherit' | 'define' | ((value: T) => T);
|
|
14
|
+
}): void;
|
|
15
|
+
//# sourceMappingURL=defineStaticProperty.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defineStaticProperty.d.ts","sourceRoot":"","sources":["../../src/helpers/defineStaticProperty.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EACpC,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE;IAAE,YAAY,EAAE,CAAC,CAAC;IAAC,QAAQ,EAAE,SAAS,GAAG,QAAQ,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC,CAAA;CAAE,GAC/E,IAAI,CAmBN"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Give a subclass its own copy of an inherited static property.
|
|
3
|
+
*
|
|
4
|
+
* A static declared on a base class is SHARED: every subclass mutating
|
|
5
|
+
* `Model.booted` or `Model.$columns` writes into the same object. This walks
|
|
6
|
+
* that back by defining an own property on the subclass, seeded either from a
|
|
7
|
+
* fresh value or from a copy of what it inherited.
|
|
8
|
+
*
|
|
9
|
+
* Ported from `@poppinss/utils` — the mechanism ORM-style base classes rely on.
|
|
10
|
+
*/
|
|
11
|
+
export function defineStaticProperty(target, propertyName, options) {
|
|
12
|
+
if (Object.hasOwn(target, propertyName))
|
|
13
|
+
return;
|
|
14
|
+
const inherited = Reflect.get(target, propertyName);
|
|
15
|
+
const { strategy, initialValue } = options;
|
|
16
|
+
const value = strategy === 'define' || inherited === undefined
|
|
17
|
+
? initialValue
|
|
18
|
+
: typeof strategy === 'function'
|
|
19
|
+
? strategy(inherited)
|
|
20
|
+
: structuredCloneOrCopy(inherited);
|
|
21
|
+
Object.defineProperty(target, propertyName, {
|
|
22
|
+
value,
|
|
23
|
+
configurable: true,
|
|
24
|
+
enumerable: true,
|
|
25
|
+
writable: true,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* A deep copy where one is possible, a shallow one otherwise.
|
|
30
|
+
*
|
|
31
|
+
* `structuredClone` throws on functions and class instances, which a static
|
|
32
|
+
* bag can legitimately hold — falling back keeps the inherit strategy usable
|
|
33
|
+
* instead of failing at class-definition time.
|
|
34
|
+
*/
|
|
35
|
+
function structuredCloneOrCopy(value) {
|
|
36
|
+
try {
|
|
37
|
+
return structuredClone(value);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
if (Array.isArray(value))
|
|
41
|
+
return [...value];
|
|
42
|
+
if (value !== null && typeof value === 'object')
|
|
43
|
+
return { ...value };
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=defineStaticProperty.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defineStaticProperty.js","sourceRoot":"","sources":["../../src/helpers/defineStaticProperty.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,UAAU,oBAAoB,CAClC,MAAc,EACd,YAAoB,EACpB,OAAgF;IAEhF,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC;QAAE,OAAM;IAE/C,MAAM,SAAS,GAAY,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;IAC5D,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,GAAG,OAAO,CAAA;IAE1C,MAAM,KAAK,GACT,QAAQ,KAAK,QAAQ,IAAI,SAAS,KAAK,SAAS;QAC9C,CAAC,CAAC,YAAY;QACd,CAAC,CAAC,OAAO,QAAQ,KAAK,UAAU;YAC9B,CAAC,CAAC,QAAQ,CAAC,SAAc,CAAC;YAC1B,CAAC,CAAC,qBAAqB,CAAC,SAAS,CAAC,CAAA;IAExC,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,YAAY,EAAE;QAC1C,KAAK;QACL,YAAY,EAAE,IAAI;QAClB,UAAU,EAAE,IAAI;QAChB,QAAQ,EAAE,IAAI;KACf,CAAC,CAAA;AACJ,CAAC;AAED;;;;;;GAMG;AACH,SAAS,qBAAqB,CAAC,KAAc;IAC3C,IAAI,CAAC;QACH,OAAO,eAAe,CAAC,KAAK,CAAC,CAAA;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;YAAE,OAAO,CAAC,GAAG,KAAK,CAAC,CAAA;QAC3C,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,OAAO,EAAE,GAAG,KAAK,EAAE,CAAA;QACpE,OAAO,KAAK,CAAA;IACd,CAAC;AACH,CAAC"}
|
|
@@ -29,4 +29,24 @@ export declare function parseDurationMs(value: string): number | undefined;
|
|
|
29
29
|
* @param label - what is being parsed, for the error message.
|
|
30
30
|
*/
|
|
31
31
|
export declare function durationToSeconds(value: number | string, label: string): number;
|
|
32
|
+
/**
|
|
33
|
+
* `string.milliseconds` — parse and format durations in milliseconds.
|
|
34
|
+
*
|
|
35
|
+
* A number passes through untouched, matching `@poppinss/string`.
|
|
36
|
+
*/
|
|
37
|
+
export declare const milliseconds: {
|
|
38
|
+
parse(duration: string | number): number;
|
|
39
|
+
format(value: number, long?: boolean): string;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* `string.seconds` — the same, in seconds.
|
|
43
|
+
*
|
|
44
|
+
* `parse` keeps the AdonisJS quirk its own `durationToSeconds` documents: a
|
|
45
|
+
* NUMBER is already seconds and passes through, while a unit-less STRING is
|
|
46
|
+
* milliseconds and gets scaled down.
|
|
47
|
+
*/
|
|
48
|
+
export declare const seconds: {
|
|
49
|
+
parse(duration: string | number): number;
|
|
50
|
+
format(value: number, long?: boolean): string;
|
|
51
|
+
};
|
|
32
52
|
//# sourceMappingURL=duration.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"duration.d.ts","sourceRoot":"","sources":["../../src/helpers/duration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAgBH;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAejE;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAS/E"}
|
|
1
|
+
{"version":3,"file":"duration.d.ts","sourceRoot":"","sources":["../../src/helpers/duration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAgBH;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAejE;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAS/E;AA8CD;;;;GAIG;AACH,eAAO,MAAM,YAAY;oBACP,MAAM,GAAG,MAAM,GAAG,MAAM;kBAG1B,MAAM,mBAAiB,MAAM;CAG5C,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,OAAO;oBACF,MAAM,GAAG,MAAM,GAAG,MAAM;kBAI1B,MAAM,mBAAiB,MAAM;CAG5C,CAAA"}
|
package/dist/helpers/duration.js
CHANGED
|
@@ -71,4 +71,75 @@ export function durationToSeconds(value, label) {
|
|
|
71
71
|
}
|
|
72
72
|
return Math.floor(milliseconds / SECOND);
|
|
73
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Format a millisecond duration the way `@poppinss/string` does.
|
|
76
|
+
*
|
|
77
|
+
* Short form by default (`'2h'`), long form on request (`'2 hours'`).
|
|
78
|
+
*/
|
|
79
|
+
function formatMs(value, long) {
|
|
80
|
+
const table = long
|
|
81
|
+
? [
|
|
82
|
+
[YEAR, 'year'],
|
|
83
|
+
[DAY, 'day'],
|
|
84
|
+
[HOUR, 'hour'],
|
|
85
|
+
[MINUTE, 'minute'],
|
|
86
|
+
[SECOND, 'second'],
|
|
87
|
+
[1, 'millisecond'],
|
|
88
|
+
]
|
|
89
|
+
: [
|
|
90
|
+
[DAY, 'd'],
|
|
91
|
+
[HOUR, 'h'],
|
|
92
|
+
[MINUTE, 'm'],
|
|
93
|
+
[SECOND, 's'],
|
|
94
|
+
[1, 'ms'],
|
|
95
|
+
];
|
|
96
|
+
const magnitude = Math.abs(value);
|
|
97
|
+
for (const [scale, label] of table) {
|
|
98
|
+
if (magnitude >= scale) {
|
|
99
|
+
const count = Math.round(value / scale);
|
|
100
|
+
return long ? `${count} ${label}${Math.abs(count) === 1 ? '' : 's'}` : `${count}${label}`;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return long ? `${value} milliseconds` : `${value}ms`;
|
|
104
|
+
}
|
|
105
|
+
/** The duration that could not be read, as one message shape for both objects. */
|
|
106
|
+
function parseOrThrow(duration, unitLabel) {
|
|
107
|
+
if (typeof duration === 'number')
|
|
108
|
+
return duration;
|
|
109
|
+
const parsed = parseDurationMs(duration);
|
|
110
|
+
if (parsed === undefined) {
|
|
111
|
+
throw new Error(`Cannot read "${duration}" as ${unitLabel}. Use a number, or a duration such as '30m', '2 hours' or '7 days'.`);
|
|
112
|
+
}
|
|
113
|
+
return parsed;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* `string.milliseconds` — parse and format durations in milliseconds.
|
|
117
|
+
*
|
|
118
|
+
* A number passes through untouched, matching `@poppinss/string`.
|
|
119
|
+
*/
|
|
120
|
+
export const milliseconds = {
|
|
121
|
+
parse(duration) {
|
|
122
|
+
return parseOrThrow(duration, 'milliseconds');
|
|
123
|
+
},
|
|
124
|
+
format(value, long = false) {
|
|
125
|
+
return formatMs(value, long);
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
/**
|
|
129
|
+
* `string.seconds` — the same, in seconds.
|
|
130
|
+
*
|
|
131
|
+
* `parse` keeps the AdonisJS quirk its own `durationToSeconds` documents: a
|
|
132
|
+
* NUMBER is already seconds and passes through, while a unit-less STRING is
|
|
133
|
+
* milliseconds and gets scaled down.
|
|
134
|
+
*/
|
|
135
|
+
export const seconds = {
|
|
136
|
+
parse(duration) {
|
|
137
|
+
if (typeof duration === 'number')
|
|
138
|
+
return duration;
|
|
139
|
+
return parseOrThrow(duration, 'seconds') / SECOND;
|
|
140
|
+
},
|
|
141
|
+
format(value, long = false) {
|
|
142
|
+
return formatMs(value * SECOND, long);
|
|
143
|
+
},
|
|
144
|
+
};
|
|
74
145
|
//# sourceMappingURL=duration.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"duration.js","sourceRoot":"","sources":["../../src/helpers/duration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH;;;;GAIG;AACH,MAAM,gBAAgB,GACpB,wKAAwK,CAAA;AAE1K,MAAM,MAAM,GAAG,IAAI,CAAA;AACnB,MAAM,MAAM,GAAG,MAAM,GAAG,EAAE,CAAA;AAC1B,MAAM,IAAI,GAAG,MAAM,GAAG,EAAE,CAAA;AACxB,MAAM,GAAG,GAAG,IAAI,GAAG,EAAE,CAAA;AACrB,MAAM,IAAI,GAAG,GAAG,GAAG,MAAM,CAAA;AAEzB;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,MAAM,OAAO,GAAG,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAA;IAC1D,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,SAAS,CAAA;IACtC,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAA;IAClD,wEAAwE;IACxE,yEAAyE;IACzE,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAA;IAC7B,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,SAAS;QAAE,OAAO,MAAM,GAAG,MAAM,CAAA;IACpD,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,SAAS;QAAE,OAAO,MAAM,GAAG,MAAM,CAAA;IACpD,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,SAAS;QAAE,OAAO,MAAM,GAAG,IAAI,CAAA;IAClD,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,SAAS;QAAE,OAAO,MAAM,GAAG,GAAG,CAAA;IACjD,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,SAAS;QAAE,OAAO,MAAM,GAAG,GAAG,GAAG,CAAC,CAAA;IACrD,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,SAAS;QAAE,OAAO,MAAM,GAAG,IAAI,CAAA;IAClD,gEAAgE;IAChE,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAsB,EAAE,KAAa;IACrE,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;IACvD,MAAM,YAAY,GAAG,eAAe,CAAC,KAAK,CAAC,CAAA;IAC3C,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CACb,gBAAgB,KAAK,QAAQ,KAAK,gFAAgF,CACnH,CAAA;IACH,CAAC;IACD,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,MAAM,CAAC,CAAA;AAC1C,CAAC"}
|
|
1
|
+
{"version":3,"file":"duration.js","sourceRoot":"","sources":["../../src/helpers/duration.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recursive directory reads, as `fsReadAll` / `fsImportAll` in AdonisJS.
|
|
3
|
+
*
|
|
4
|
+
* What the framework itself uses to discover config files, preloads and
|
|
5
|
+
* commands, and what an app reaches for to load a directory of modules.
|
|
6
|
+
*/
|
|
7
|
+
export interface ReadAllFilesOptions {
|
|
8
|
+
/** Keep only files this returns true for. Defaults to `.js`/`.ts` modules. */
|
|
9
|
+
filter?: (filePath: string, index: number) => boolean;
|
|
10
|
+
/** Sort the collected paths. Defaults to a stable alphabetical order. */
|
|
11
|
+
sort?: (current: string, next: string) => number;
|
|
12
|
+
/** Ignore directories whose name this returns true for. */
|
|
13
|
+
ignoreMissingRoot?: boolean;
|
|
14
|
+
/** Return absolute paths instead of paths relative to the root. */
|
|
15
|
+
absolute?: boolean;
|
|
16
|
+
/** Also descend into subdirectories. Defaults to true. */
|
|
17
|
+
recursive?: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Every file under `location`, relative to it and alphabetically sorted.
|
|
21
|
+
*
|
|
22
|
+
* A missing root throws unless `ignoreMissingRoot` is set — an app that
|
|
23
|
+
* declares no `config/` directory is legitimate, a typo in the path is not,
|
|
24
|
+
* and only the caller can tell them apart.
|
|
25
|
+
*/
|
|
26
|
+
export declare function fsReadAll(location: string | URL, options?: ReadAllFilesOptions): Promise<string[]>;
|
|
27
|
+
export interface ImportAllFilesOptions extends ReadAllFilesOptions {
|
|
28
|
+
/** Turn a file path into the key it lands under. Defaults to the path minus its extension. */
|
|
29
|
+
transformKeys?: (key: string) => string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Import every file under `location`, keyed by its path minus the extension.
|
|
33
|
+
*
|
|
34
|
+
* A module with a default export contributes that export; one without
|
|
35
|
+
* contributes its named exports as a plain object — the same rule the config
|
|
36
|
+
* loader follows, and for the same reason.
|
|
37
|
+
*/
|
|
38
|
+
export declare function fsImportAll(location: string | URL, options?: ImportAllFilesOptions): Promise<Record<string, unknown>>;
|
|
39
|
+
//# sourceMappingURL=fs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../../src/helpers/fs.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,MAAM,WAAW,mBAAmB;IAClC,8EAA8E;IAC9E,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAA;IACrD,yEAAyE;IACzE,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IAChD,2DAA2D;IAC3D,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,mEAAmE;IACnE,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,0DAA0D;IAC1D,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAKD;;;;;;GAMG;AACH,wBAAsB,SAAS,CAC7B,QAAQ,EAAE,MAAM,GAAG,GAAG,EACtB,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,MAAM,EAAE,CAAC,CAwBnB;AAED,MAAM,WAAW,qBAAsB,SAAQ,mBAAmB;IAChE,8FAA8F;IAC9F,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAA;CACxC;AAED;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,MAAM,GAAG,GAAG,EACtB,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAWlC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recursive directory reads, as `fsReadAll` / `fsImportAll` in AdonisJS.
|
|
3
|
+
*
|
|
4
|
+
* What the framework itself uses to discover config files, preloads and
|
|
5
|
+
* commands, and what an app reaches for to load a directory of modules.
|
|
6
|
+
*/
|
|
7
|
+
import { readdir } from 'node:fs/promises';
|
|
8
|
+
import { join, relative, sep } from 'node:path';
|
|
9
|
+
import { pathToFileURL } from 'node:url';
|
|
10
|
+
const DEFAULT_FILTER = (filePath) => /\.(js|ts|mjs|cjs|mts|cts)$/.test(filePath) && !/\.d\.ts$/.test(filePath);
|
|
11
|
+
/**
|
|
12
|
+
* Every file under `location`, relative to it and alphabetically sorted.
|
|
13
|
+
*
|
|
14
|
+
* A missing root throws unless `ignoreMissingRoot` is set — an app that
|
|
15
|
+
* declares no `config/` directory is legitimate, a typo in the path is not,
|
|
16
|
+
* and only the caller can tell them apart.
|
|
17
|
+
*/
|
|
18
|
+
export async function fsReadAll(location, options = {}) {
|
|
19
|
+
const root = location instanceof URL ? location.pathname : location;
|
|
20
|
+
const filter = options.filter ?? DEFAULT_FILTER;
|
|
21
|
+
const recursive = options.recursive ?? true;
|
|
22
|
+
let entries;
|
|
23
|
+
try {
|
|
24
|
+
entries = await readdir(root, { withFileTypes: true, recursive });
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
if (options.ignoreMissingRoot && isMissing(error))
|
|
28
|
+
return [];
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
const files = [];
|
|
32
|
+
for (const entry of entries) {
|
|
33
|
+
if (!entry.isFile())
|
|
34
|
+
continue;
|
|
35
|
+
// `parentPath` is where recursive readdir puts the containing directory.
|
|
36
|
+
const absolute = join(entry.parentPath ?? root, entry.name);
|
|
37
|
+
const value = options.absolute ? absolute : relative(root, absolute).split(sep).join('/');
|
|
38
|
+
files.push(value);
|
|
39
|
+
}
|
|
40
|
+
const kept = files.filter((file, index) => filter(file, index));
|
|
41
|
+
return options.sort ? kept.sort(options.sort) : kept.sort();
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Import every file under `location`, keyed by its path minus the extension.
|
|
45
|
+
*
|
|
46
|
+
* A module with a default export contributes that export; one without
|
|
47
|
+
* contributes its named exports as a plain object — the same rule the config
|
|
48
|
+
* loader follows, and for the same reason.
|
|
49
|
+
*/
|
|
50
|
+
export async function fsImportAll(location, options = {}) {
|
|
51
|
+
const root = location instanceof URL ? location.pathname : location;
|
|
52
|
+
const files = await fsReadAll(root, options);
|
|
53
|
+
const collected = {};
|
|
54
|
+
for (const file of files) {
|
|
55
|
+
const key = (options.transformKeys ?? stripExtension)(file);
|
|
56
|
+
const module = await import(pathToFileURL(join(root, file)).href);
|
|
57
|
+
collected[key] = 'default' in module ? module.default : { ...module };
|
|
58
|
+
}
|
|
59
|
+
return collected;
|
|
60
|
+
}
|
|
61
|
+
function stripExtension(file) {
|
|
62
|
+
return file.replace(/\.(js|ts|mjs|cjs|mts|cts)$/, '');
|
|
63
|
+
}
|
|
64
|
+
function isMissing(error) {
|
|
65
|
+
return (error !== null &&
|
|
66
|
+
typeof error === 'object' &&
|
|
67
|
+
'code' in error &&
|
|
68
|
+
(error.code === 'ENOENT' || error.code === 'ENOTDIR'));
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=fs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs.js","sourceRoot":"","sources":["../../src/helpers/fs.ts"],"names":[],"mappings":"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"}
|
package/dist/helpers/index.d.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
export { Secret } from '../env/Secret.js';
|
|
1
2
|
export { base64 } from './base64.js';
|
|
3
|
+
export { defineStaticProperty } from './defineStaticProperty.js';
|
|
4
|
+
export { milliseconds, seconds } from './duration.js';
|
|
5
|
+
export type { ImportAllFilesOptions, ReadAllFilesOptions } from './fs.js';
|
|
6
|
+
export { fsImportAll, fsReadAll } from './fs.js';
|
|
2
7
|
export type * from './is.js';
|
|
3
8
|
export { default as is } from './is.js';
|
|
9
|
+
export { MessageBuilder } from './MessageBuilder.js';
|
|
4
10
|
export { safeEqual } from './safeEqual.js';
|
|
11
|
+
export { safeTiming } from './safeTiming.js';
|
|
5
12
|
export { default as string } from './string.js';
|
|
6
13
|
export type * from './types.js';
|
|
14
|
+
export { VerificationToken } from './VerificationToken.js';
|
|
7
15
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/helpers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,mBAAmB,SAAS,CAAA;AAC5B,OAAO,EAAE,OAAO,IAAI,EAAE,EAAE,MAAM,SAAS,CAAA;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,aAAa,CAAA;AAC/C,mBAAmB,YAAY,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/helpers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AAChE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACrD,YAAY,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAA;AACzE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAChD,mBAAmB,SAAS,CAAA;AAC5B,OAAO,EAAE,OAAO,IAAI,EAAE,EAAE,MAAM,SAAS,CAAA;AACvC,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC5C,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,aAAa,CAAA;AAC/C,mBAAmB,YAAY,CAAA;AAC/B,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA"}
|
package/dist/helpers/index.js
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
export { Secret } from '../env/Secret.js';
|
|
1
2
|
export { base64 } from './base64.js';
|
|
3
|
+
export { defineStaticProperty } from './defineStaticProperty.js';
|
|
4
|
+
export { milliseconds, seconds } from './duration.js';
|
|
5
|
+
export { fsImportAll, fsReadAll } from './fs.js';
|
|
2
6
|
export { default as is } from './is.js';
|
|
7
|
+
export { MessageBuilder } from './MessageBuilder.js';
|
|
3
8
|
export { safeEqual } from './safeEqual.js';
|
|
9
|
+
export { safeTiming } from './safeTiming.js';
|
|
4
10
|
export { default as string } from './string.js';
|
|
11
|
+
export { VerificationToken } from './VerificationToken.js';
|
|
5
12
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/helpers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/helpers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AAChE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAErD,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAEhD,OAAO,EAAE,OAAO,IAAI,EAAE,EAAE,MAAM,SAAS,CAAA;AACvC,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC5C,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,aAAa,CAAA;AAE/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run `callback`, never returning before `minimumMs` has elapsed.
|
|
3
|
+
*
|
|
4
|
+
* A login that answers in 3ms for an unknown address and 80ms for a known one
|
|
5
|
+
* tells an attacker which addresses exist, whatever the response body says.
|
|
6
|
+
* Holding every path to the same floor removes that signal.
|
|
7
|
+
*
|
|
8
|
+
* The callback can call `returnEarly()` to opt out — on a path where the timing
|
|
9
|
+
* carries nothing, waiting is pure latency.
|
|
10
|
+
*/
|
|
11
|
+
export declare function safeTiming<T>(minimumMs: number, callback: (timing: {
|
|
12
|
+
returnEarly(): void;
|
|
13
|
+
}) => Promise<T>): Promise<T>;
|
|
14
|
+
//# sourceMappingURL=safeTiming.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safeTiming.d.ts","sourceRoot":"","sources":["../../src/helpers/safeTiming.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,wBAAsB,UAAU,CAAC,CAAC,EAChC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,CAAC,MAAM,EAAE;IAAE,WAAW,IAAI,IAAI,CAAA;CAAE,KAAK,OAAO,CAAC,CAAC,CAAC,GACxD,OAAO,CAAC,CAAC,CAAC,CA+BZ"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run `callback`, never returning before `minimumMs` has elapsed.
|
|
3
|
+
*
|
|
4
|
+
* A login that answers in 3ms for an unknown address and 80ms for a known one
|
|
5
|
+
* tells an attacker which addresses exist, whatever the response body says.
|
|
6
|
+
* Holding every path to the same floor removes that signal.
|
|
7
|
+
*
|
|
8
|
+
* The callback can call `returnEarly()` to opt out — on a path where the timing
|
|
9
|
+
* carries nothing, waiting is pure latency.
|
|
10
|
+
*/
|
|
11
|
+
export async function safeTiming(minimumMs, callback) {
|
|
12
|
+
const startedAt = performance.now();
|
|
13
|
+
let returnEarly = false;
|
|
14
|
+
let result;
|
|
15
|
+
let caught;
|
|
16
|
+
let threw = false;
|
|
17
|
+
try {
|
|
18
|
+
result = await callback({
|
|
19
|
+
returnEarly() {
|
|
20
|
+
returnEarly = true;
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
caught = error;
|
|
26
|
+
threw = true;
|
|
27
|
+
}
|
|
28
|
+
// The floor applies to the failing path too: an error that comes back faster
|
|
29
|
+
// than a success is the same leak in the other direction.
|
|
30
|
+
if (!returnEarly) {
|
|
31
|
+
const remaining = minimumMs - (performance.now() - startedAt);
|
|
32
|
+
if (remaining > 0) {
|
|
33
|
+
await new Promise((resolve) => setTimeout(resolve, remaining));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (threw)
|
|
37
|
+
throw caught;
|
|
38
|
+
// `result` is assigned whenever the callback returned, and we rethrew above
|
|
39
|
+
// when it did not.
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=safeTiming.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safeTiming.js","sourceRoot":"","sources":["../../src/helpers/safeTiming.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,SAAiB,EACjB,QAAyD;IAEzD,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE,CAAA;IACnC,IAAI,WAAW,GAAG,KAAK,CAAA;IAEvB,IAAI,MAAqB,CAAA;IACzB,IAAI,MAAe,CAAA;IACnB,IAAI,KAAK,GAAG,KAAK,CAAA;IACjB,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,QAAQ,CAAC;YACtB,WAAW;gBACT,WAAW,GAAG,IAAI,CAAA;YACpB,CAAC;SACF,CAAC,CAAA;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,GAAG,KAAK,CAAA;QACd,KAAK,GAAG,IAAI,CAAA;IACd,CAAC;IAED,6EAA6E;IAC7E,0DAA0D;IAC1D,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,SAAS,GAAG,SAAS,GAAG,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,CAAA;QAC7D,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;YAClB,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAA;QAChE,CAAC;IACH,CAAC;IAED,IAAI,KAAK;QAAE,MAAM,MAAM,CAAA;IACvB,4EAA4E;IAC5E,mBAAmB;IACnB,OAAO,MAAW,CAAA;AACpB,CAAC"}
|