generated from AthennaIO/Template
-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #139 from AthennaIO/develop
feat(ulid): add support to ulid
- Loading branch information
Showing
9 changed files
with
1,382 additions
and
2,118 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
/** | ||
* @athenna/common | ||
* | ||
* (c) João Lenon <lenon@athenna.io> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
import { Exception } from '#src/helpers/Exception' | ||
|
||
export class InvalidUlidException extends Exception { | ||
public constructor(value: string) { | ||
super({ | ||
code: 'E_INVALID_ULID', | ||
help: 'Use a valid ULID instead.', | ||
message: `The value ${value} is not a valid ULID.` | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
/** | ||
* @athenna/common | ||
* | ||
* (c) João Lenon <lenon@athenna.io> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
import { ulid } from 'ulid' | ||
import { Options } from '#src/helpers/Options' | ||
import { InvalidUlidException } from '#src/exceptions/InvalidUlidException' | ||
|
||
const pattern = /^[0-9A-HJKMNP-TV-Z]{26}$/ | ||
|
||
export function validate(value: unknown): boolean { | ||
return typeof value === 'string' && pattern.test(value) | ||
} | ||
|
||
export class Ulid { | ||
/** | ||
* Verify if string is a valid ulid. | ||
*/ | ||
public static verify( | ||
token: string, | ||
options: { prefix?: string; ignorePrefix?: boolean } = {} | ||
): boolean { | ||
if (!token) { | ||
return false | ||
} | ||
|
||
options = Options.create(options, { ignorePrefix: true }) | ||
|
||
if (options.prefix) { | ||
const prefix = this.getPrefix(token) | ||
|
||
if (prefix !== options.prefix) { | ||
return false | ||
} | ||
|
||
return validate(this.getToken(token)) | ||
} | ||
|
||
if (options.ignorePrefix) { | ||
return validate(this.getToken(token)) | ||
} | ||
|
||
return validate(token) | ||
} | ||
|
||
/** | ||
* Generate an ulid token | ||
*/ | ||
public static generate(prefix?: string): string { | ||
if (prefix) { | ||
return `${prefix}::${ulid()}` | ||
} | ||
|
||
return ulid() | ||
} | ||
|
||
/** | ||
* Return the token without his prefix. | ||
*/ | ||
public static getToken(token: string): string { | ||
const prefix = Ulid.getPrefix(token) | ||
|
||
if (!prefix) { | ||
return token | ||
} | ||
|
||
return token.split(`${prefix}::`)[1] | ||
} | ||
|
||
/** | ||
* Return the prefix without his token. | ||
*/ | ||
public static getPrefix(token: string): string | null { | ||
const prefix = token.split('::')[0] | ||
|
||
/** | ||
* Means that the "::" char has not been | ||
* found. So there is no prefix in the token. | ||
*/ | ||
if (prefix === token) { | ||
return null | ||
} | ||
|
||
return prefix | ||
} | ||
|
||
/** | ||
* Inject a prefix in the ulid token. | ||
*/ | ||
public static injectPrefix(prefix: string, token: string): string { | ||
if (!this.verify(token)) { | ||
throw new InvalidUlidException(token) | ||
} | ||
|
||
return `${prefix}::${token}` | ||
} | ||
|
||
/** | ||
* Change the prefix of an ulid token | ||
*/ | ||
public static changePrefix(newPrefix: string, token: string): string { | ||
const ulid = this.getToken(token) | ||
|
||
if (!this.verify(ulid)) { | ||
throw new InvalidUlidException(ulid) | ||
} | ||
|
||
return `${newPrefix}::${ulid}` | ||
} | ||
|
||
/** | ||
* Change the token prefix or generate a new one | ||
*/ | ||
public static changeOrGenerate(prefix: string, token?: string): string { | ||
if (token) { | ||
return this.changePrefix(prefix, token) | ||
} | ||
|
||
return this.generate(prefix) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
/** | ||
* @athenna/common | ||
* | ||
* (c) João Lenon <lenon@athenna.io> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
import { ulid } from 'ulid' | ||
import { Ulid } from '#src' | ||
import { Test, type Context } from '@athenna/test' | ||
import { InvalidUlidException } from '#src/exceptions/InvalidUlidException' | ||
|
||
export default class UlidTest { | ||
private ulid = ulid() | ||
|
||
@Test() | ||
public shouldVerifyIfUlidIsAValidUlidEventIfItIsPrefixed({ assert }: Context) { | ||
const tokenPrefixed = Ulid.generate('tkn') | ||
|
||
const verify = Ulid.verify(this.ulid) | ||
const verifyError = Ulid.verify('falseUlid') | ||
const verifyPrefixed = Ulid.verify(tokenPrefixed) | ||
|
||
assert.isTrue(verify) | ||
assert.isFalse(verifyError) | ||
assert.isTrue(verifyPrefixed) | ||
} | ||
|
||
@Test() | ||
public shouldGetOnlyTheTokenFromPrefixedUlid({ assert }: Context) { | ||
const tokenUlid = Ulid.generate('tkn') | ||
|
||
assert.equal(Ulid.getToken(tokenUlid), tokenUlid.replace('tkn::', '')) | ||
} | ||
|
||
@Test() | ||
public shouldGetOnlyThePrefixFromPrefixedUlid({ assert }: Context) { | ||
const tokenUlid = Ulid.generate('tkn') | ||
|
||
assert.isNull(Ulid.getPrefix(this.ulid), null) | ||
assert.equal(Ulid.getPrefix(tokenUlid), 'tkn') | ||
} | ||
|
||
@Test() | ||
public shouldInjectThePrefixInTheToken({ assert }: Context) { | ||
const tokenUlid = Ulid.generate() | ||
const injectedPrefix = Ulid.injectPrefix('tkn', tokenUlid) | ||
const tokenPrefixedChange = Ulid.changePrefix('any', injectedPrefix) | ||
|
||
assert.equal(injectedPrefix, `tkn::${tokenUlid}`) | ||
assert.equal(tokenPrefixedChange, `any::${tokenUlid}`) | ||
|
||
const useCase = () => Ulid.injectPrefix('tkn', 'not-valid-ulid') | ||
|
||
assert.throws(useCase, InvalidUlidException) | ||
} | ||
|
||
@Test() | ||
public shouldChangeOrGenerateANewToken({ assert }: Context) { | ||
const tokenGenerated = Ulid.changeOrGenerate('tkn', undefined) | ||
const tokenChanged = Ulid.changeOrGenerate('tkn', `ooo::${this.ulid}`) | ||
|
||
assert.isDefined(tokenGenerated) | ||
assert.equal(tokenChanged, `tkn::${this.ulid}`) | ||
|
||
const useCase = () => Ulid.changePrefix('tkn', 'not-valid-ulid') | ||
|
||
assert.throws(useCase, InvalidUlidException) | ||
} | ||
} |