Skip to content
This repository was archived by the owner on Mar 1, 2026. It is now read-only.
8 changes: 4 additions & 4 deletions packages/language/res/stdlib.zmodel
Original file line number Diff line number Diff line change
Expand Up @@ -83,25 +83,25 @@ function now(): DateTime {
/**
* Generates a globally unique identifier based on the UUID specs.
*/
function uuid(version: Int?): String {
function uuid(version: Int?, format: String?): String {
} @@@expressionContext([DefaultValue])
Comment thread
sanny-io marked this conversation as resolved.

/**
* Generates a globally unique identifier based on the CUID spec.
*/
function cuid(version: Int?): String {
function cuid(version: Int?, format: String?): String {
} @@@expressionContext([DefaultValue])
Comment thread
sanny-io marked this conversation as resolved.

/**
* Generates an identifier based on the nanoid spec.
*/
function nanoid(length: Int?): String {
function nanoid(length: Int?, format: String?): String {
} @@@expressionContext([DefaultValue])
Comment thread
sanny-io marked this conversation as resolved.

/**
* Generates an identifier based on the ulid spec.
*/
function ulid(): String {
function ulid(format: String?): String {
} @@@expressionContext([DefaultValue])
Comment thread
sanny-io marked this conversation as resolved.

/**
Expand Down
56 changes: 41 additions & 15 deletions packages/orm/src/client/crud/operations/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -860,22 +860,34 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {
private evalGenerator(defaultValue: Expression) {
if (ExpressionUtils.isCall(defaultValue)) {
return match(defaultValue.function)
.with('cuid', () => createId())
.with('uuid', () =>
defaultValue.args?.[0] &&
ExpressionUtils.isLiteral(defaultValue.args?.[0]) &&
defaultValue.args[0].value === 7
.with('cuid', () => this.formatGeneratedValue(createId(), defaultValue.args?.[1]))
.with('uuid', () => {
const version = defaultValue.args?.[0] && ExpressionUtils.isLiteral(defaultValue.args[0])
? defaultValue.args[0].value
: undefined;

const generated = version === 7
? uuid.v7()
: uuid.v4(),
)
.with('nanoid', () =>
defaultValue.args?.[0] &&
ExpressionUtils.isLiteral(defaultValue.args[0]) &&
typeof defaultValue.args[0].value === 'number'
? nanoid(defaultValue.args[0].value)
: nanoid(),
)
.with('ulid', () => ulid())
: uuid.v4();

const format = defaultValue.args?.[1];

return this.formatGeneratedValue(generated, format);
})
.with('nanoid', () => {
const length = defaultValue.args?.[0] && ExpressionUtils.isLiteral(defaultValue.args[0])
? defaultValue.args[0].value
: undefined;

const generated = typeof length === 'number'
? nanoid(length)
: nanoid();

const format = defaultValue.args?.[1];

return this.formatGeneratedValue(generated, format);
})
.with('ulid', () => this.formatGeneratedValue(ulid(), defaultValue.args?.[0]))
.otherwise(() => undefined);
} else if (
ExpressionUtils.isMember(defaultValue) &&
Expand All @@ -893,6 +905,20 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {
}
}

private formatGeneratedValue(generated: string, expr?: Expression) {
if (!expr || !ExpressionUtils.isLiteral(expr)) {
return generated;
}

const format = expr.value;

invariant(typeof format === 'string', 'generated identifier format value must be a string');
invariant(format.includes('%s'), 'generated identifier format strings must include "%s"');

// replaceAll instead? Does anyone even need that...?
return format.replace('%s', generated);
}

protected async update(
kysely: AnyKysely,
model: string,
Expand Down
Loading
Loading