mirror of
https://github.com/nestriness/nestri.git
synced 2026-08-02 01:35:18 +03:00
fix: Use a better ID format
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import { prefixes } from "./utils/id.js";
|
||||
import { Identifier } from "./id/index.js";
|
||||
|
||||
export namespace Examples {
|
||||
export const Id = (prefix: keyof typeof prefixes) =>
|
||||
`${prefixes[prefix]}_XXXXXXXXXXXXXXXXXXXXXXXXX`;
|
||||
export const Id = (prefix: keyof typeof Identifier.prefixes) =>
|
||||
`${Identifier.prefixes[prefix]}_XXXXXXXXXXXXXXXXXXXXXXXXX`;
|
||||
|
||||
export const User = {
|
||||
id: Id("user"),
|
||||
|
||||
89
packages/core/src/id/index.ts
Normal file
89
packages/core/src/id/index.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { z } from "zod/v4";
|
||||
import { randomBytes } from "crypto";
|
||||
|
||||
export namespace Identifier {
|
||||
export const prefixes = {
|
||||
app: "app",
|
||||
user: "usr",
|
||||
steam: "stm",
|
||||
member: "mbr",
|
||||
session: "ses",
|
||||
apiSecret: "sec",
|
||||
apiPersonal: "pat",
|
||||
organisation: "org",
|
||||
} as const;
|
||||
|
||||
export function schema(prefix: keyof typeof prefixes) {
|
||||
return z.string().startsWith(prefixes[prefix]);
|
||||
}
|
||||
|
||||
const LENGTH = 26;
|
||||
|
||||
// State for monotonic ID generation
|
||||
let lastTimestamp = 0;
|
||||
let counter = 0;
|
||||
|
||||
export function ascending(prefix: keyof typeof prefixes, given?: string) {
|
||||
return generateID(prefix, false, given);
|
||||
}
|
||||
|
||||
export function descending(prefix: keyof typeof prefixes, given?: string) {
|
||||
return generateID(prefix, true, given);
|
||||
}
|
||||
|
||||
function generateID(
|
||||
prefix: keyof typeof prefixes,
|
||||
descending: boolean,
|
||||
given?: string,
|
||||
): string {
|
||||
if (!given) {
|
||||
return create(prefix, descending);
|
||||
}
|
||||
|
||||
if (!given.startsWith(prefixes[prefix])) {
|
||||
throw new Error(`ID ${given} does not start with ${prefixes[prefix]}`);
|
||||
}
|
||||
return given;
|
||||
}
|
||||
|
||||
function randomBase62(length: number): string {
|
||||
const chars =
|
||||
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
let result = "";
|
||||
const bytes = randomBytes(length);
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars[bytes[i]! % 62];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function create(
|
||||
prefix: keyof typeof prefixes,
|
||||
descending: boolean,
|
||||
timestamp?: number,
|
||||
): string {
|
||||
const currentTimestamp = timestamp ?? Date.now();
|
||||
|
||||
if (currentTimestamp !== lastTimestamp) {
|
||||
lastTimestamp = currentTimestamp;
|
||||
counter = 0;
|
||||
}
|
||||
counter++;
|
||||
|
||||
let now = BigInt(currentTimestamp) * BigInt(0x1000) + BigInt(counter);
|
||||
|
||||
now = descending ? ~now : now;
|
||||
|
||||
const timeBytes = Buffer.alloc(6);
|
||||
for (let i = 0; i < 6; i++) {
|
||||
timeBytes[i] = Number((now >> BigInt(40 - 8 * i)) & BigInt(0xff));
|
||||
}
|
||||
|
||||
return (
|
||||
prefixes[prefix] +
|
||||
"_" +
|
||||
timeBytes.toString("hex") +
|
||||
randomBase62(LENGTH - 12)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import { z } from "zod/v4";
|
||||
import { Examples } from "../examples.js";
|
||||
import { Identifier } from "../id/index.js";
|
||||
|
||||
export namespace Organisation {
|
||||
export const Info = z
|
||||
.object({
|
||||
id: z.string().meta({
|
||||
id: Identifier.schema("organisation").meta({
|
||||
description: "The unique identifier of the organisation",
|
||||
example: Examples.Organisation.id,
|
||||
}),
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { z } from "zod/v4";
|
||||
import { Examples } from "../examples.js";
|
||||
import { Limitations, steamTable } from "./steam.sql.js";
|
||||
import { fn } from "../utils/fn.js";
|
||||
import { Actor } from "../actor.js";
|
||||
import { Database } from "../drizzle/index.js";
|
||||
import { Examples } from "../examples.js";
|
||||
import { Identifier } from "../id/index.js";
|
||||
import { eq, isNull, and } from "drizzle-orm";
|
||||
import { Database } from "../drizzle/index.js";
|
||||
import { Limitations, steamTable } from "./steam.sql.js";
|
||||
|
||||
export namespace Steam {
|
||||
export const Info = z
|
||||
.object({
|
||||
id: z.string().meta({
|
||||
id: Identifier.schema("steam").meta({
|
||||
description: "The unique Steam ID of the account",
|
||||
example: Examples.SteamAccount.id,
|
||||
}),
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { z } from "zod/v4";
|
||||
import { fn } from "../utils/fn.js";
|
||||
import { userTable } from "./user.sql.js";
|
||||
import { createID } from "../utils/id.js";
|
||||
import { Examples } from "../examples.js";
|
||||
import { Polar } from "../polar/index.js";
|
||||
import { Identifier } from "../id/index.js";
|
||||
import { Database } from "../drizzle/index.js";
|
||||
import { eq, and, isNotNull } from "drizzle-orm";
|
||||
|
||||
export namespace User {
|
||||
export const Info = z
|
||||
.object({
|
||||
id: z.string().meta({
|
||||
id: Identifier.schema("user").meta({
|
||||
description: "The unique identifier of the user",
|
||||
example: Examples.User.id,
|
||||
}),
|
||||
@@ -38,7 +38,7 @@ export namespace User {
|
||||
export const create = fn(
|
||||
Info.omit({ customerID: true }).partial({ id: true }),
|
||||
async (input) => {
|
||||
const id = input.id ?? createID("user");
|
||||
const id = Identifier.ascending("user", input.id);
|
||||
|
||||
const customerID = await Polar.fromUserEmail(input.email).then(
|
||||
(z) => z.id,
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import { ulid } from "ulid";
|
||||
|
||||
export const prefixes = {
|
||||
user: "usr",
|
||||
steam: "stm",
|
||||
app: "app",
|
||||
member: "mbr",
|
||||
apiSecret: "sec",
|
||||
apiPersonal: "pat",
|
||||
organisation: "org",
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Generates a unique identifier by concatenating a predefined prefix with a ULID.
|
||||
*
|
||||
* Given a key from the predefined prefixes mapping (e.g.,"user", "member", "steam"),
|
||||
* this function retrieves the corresponding prefix and combines it with a ULID using an underscore
|
||||
* as a separator. The resulting identifier is formatted as "prefix_ulid".
|
||||
*
|
||||
* @param prefix - A key from the prefixes mapping.
|
||||
* @returns A unique identifier string.
|
||||
*/
|
||||
export function createID(prefix: keyof typeof prefixes): string {
|
||||
return [prefixes[prefix], ulid()].join("_");
|
||||
}
|
||||
Reference in New Issue
Block a user