Skip to content

Conversation

@sanny-io
Copy link
Contributor

@sanny-io sanny-io commented Dec 17, 2025

Sample Usage

model User {
    id Int @id
    uuid7 String @default(uuid(7, 'user_uuid7_%s'))
    cuid String @default(cuid(2, 'user_cuid_%s'))
    nanoid String @default(nanoid(21, 'user_nanoid_%s'))
    ulid String @default(ulid('user_ulid_%s'))
}

There are arguments to be made that @default could be a better place to handle this, which would open up opportunities for default values to be composed of complex expressions.

For now, this seems OK.

Summary by CodeRabbit

  • New Features

    • ID generation functions (UUID, CUID, NanoID, ULID) now accept an optional format string; the format may include a "%s" placeholder to embed the generated value and produce custom identifier shapes.
  • Tests

    • Added end-to-end tests verifying formatted ID generation across multiple strategies and creation scenarios (top-level, nested, deeply nested) to ensure prefixes and formatting are applied consistently.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 17, 2025

Walkthrough

Public stdlib ID generator signatures were extended to accept an optional format parameter; ORM ID generation now applies a formatting helper that validates a %s placeholder and substitutes the generated value; and a new E2E test verifies formatted generated IDs across multiple generators and nested create scenarios.

Changes

Cohort / File(s) Summary
Schema: stdlib model
packages/language/res/stdlib.zmodel
Extended public function signatures to accept an optional format: String? parameter: uuid, cuid, nanoid now accept format, and ulid now accepts format: String?.
Runtime: ID generation formatting
packages/orm/src/client/crud/operations/base.ts
ID generation for cuid, uuid, nanoid, and ulid now routes generated values through a new private formatGeneratedValue(generated, expr?) helper which validates the format expression is a string containing %s and substitutes the first occurrence with the generated value.
Tests: e2e generated ID formats
tests/e2e/orm/client-api/generated-id-format-strings.test.ts
Added E2E tests that push a SQLite-backed schema exercising uuid/uuid7, cuid/cuid2, nanoid/nanoid8, and ulid strategies across User/Post/Comment models and assert formatted generated IDs for top-level, nested, and deeply nested creates.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Inspect formatGeneratedValue() error paths and handling of non-literal or non-string expressions.
  • Verify correct mapping of schema argument indices to generator parameters (length vs format).
  • Confirm tests cover both presence and absence of format and nested creation permutations.

Possibly related PRs

Poem

🐰 I hop through code and stitch a tag,
I find the %s and make it brag.
UUIDs, CUIDs, nanoids in line,
ULIDs sparkle with a formatted sign.
Hooray — new IDs all look divine! ✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly describes the main change: adding support for format strings in generated identifiers (id prefixing), which matches the core functionality across all modified files and the new test.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
tests/e2e/orm/client-api/generated-id-format-strings.test.ts (2)

7-1060: Consider refactoring verbose schema definition.

The schema definition spans over 1000 lines with significant duplication—each field's default value is defined identically in both attributes[0].args[0].value and the default property. While this may be required by the SchemaDef type structure, consider:

  1. Extract a helper function to generate ID field definitions:
function makeIdField(name: string, funcName: string, args: any[], format: string) {
  const callExpr = { kind: 'call', function: funcName, args };
  return {
    name,
    type: 'String',
    attributes: [{ name: '@default', args: [{ name: 'value', value: callExpr }] }],
    default: callExpr
  };
}
  1. This would reduce the ~1050-line schema to ~100-200 lines, improving readability and maintainability.

1062-1167: Solid E2E coverage for core scenarios.

The tests effectively validate ID formatting across top-level, nested, and deeply nested creation scenarios, covering all supported ID generation strategies (uuid, uuid7, cuid, cuid2, nanoid, nanoid8, ulid).

Consider adding test cases for edge cases:

  • Invalid format strings (missing "%s") to verify validation errors
  • Format strings with special characters or unusual patterns
  • Verification that the generated suffix is a valid ID (not just the prefix)
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dbe7257 and afe1e7d.

📒 Files selected for processing (3)
  • packages/language/res/stdlib.zmodel (2 hunks)
  • packages/orm/src/client/crud/operations/base.ts (2 hunks)
  • tests/e2e/orm/client-api/generated-id-format-strings.test.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.zmodel

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.zmodel: Always run zenstack generate after modifying ZModel schemas
ZModel schema files should define database structure and policies that compile to TypeScript via zenstack generate

Files:

  • packages/language/res/stdlib.zmodel
tests/e2e/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

E2E tests should validate real-world schema compatibility with established projects

Files:

  • tests/e2e/orm/client-api/generated-id-format-strings.test.ts
🧠 Learnings (7)
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to tests/e2e/**/*.{ts,tsx} : E2E tests should validate real-world schema compatibility with established projects

Applied to files:

  • tests/e2e/orm/client-api/generated-id-format-strings.test.ts
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to packages/zenstackhq/orm/**/*.test.{ts,tsx} : ORM package tests should include comprehensive client API tests and policy tests

Applied to files:

  • tests/e2e/orm/client-api/generated-id-format-strings.test.ts
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to packages/zenstackhq/orm/**/*.{ts,tsx} : Implement plugin hooks at ORM, Kysely, and entity mutation levels for query interception and customization

Applied to files:

  • tests/e2e/orm/client-api/generated-id-format-strings.test.ts
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to tests/**/type*.{ts,tsx} : Ensure TypeScript inference and type coverage are validated through type coverage tests

Applied to files:

  • tests/e2e/orm/client-api/generated-id-format-strings.test.ts
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to **/*.zmodel : ZModel schema files should define database structure and policies that compile to TypeScript via `zenstack generate`

Applied to files:

  • tests/e2e/orm/client-api/generated-id-format-strings.test.ts
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to packages/zenstackhq/orm/**/*.{ts,tsx} : Use Kysely as the query builder interface for low-level database queries, avoiding raw SQL when possible

Applied to files:

  • tests/e2e/orm/client-api/generated-id-format-strings.test.ts
📚 Learning: 2025-10-21T16:09:31.218Z
Learnt from: ymc9
Repo: zenstackhq/zenstack-v3 PR: 319
File: packages/runtime/src/client/executor/zenstack-query-executor.ts:63-72
Timestamp: 2025-10-21T16:09:31.218Z
Learning: In ZenStack, TypeDefs can be inherited by models. When a TypeDef contains fields with `map` attributes, those mapped field names need to be processed by the QueryNameMapper since they become part of the inheriting model's schema. Therefore, when checking if a schema has mapped names (e.g., in `schemaHasMappedNames`), both `schema.models` and `schema.typeDefs` must be inspected for `@map` and `map` attributes.

Applied to files:

  • tests/e2e/orm/client-api/generated-id-format-strings.test.ts
🧬 Code graph analysis (1)
packages/orm/src/client/crud/operations/base.ts (2)
packages/schema/src/expression-utils.ts (1)
  • ExpressionUtils (19-123)
packages/common-helpers/src/tiny-invariant.ts (1)
  • invariant (4-14)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build-test (20.x, postgresql)
  • GitHub Check: build-test (20.x, sqlite)
🔇 Additional comments (4)
packages/language/res/stdlib.zmodel (1)

86-105: LGTM! Consistent API extension for ID formatting.

The optional format parameter has been consistently added to all ID generation functions (uuid, cuid, nanoid, ulid), maintaining backward compatibility while enabling the new prefixing feature.

packages/orm/src/client/crud/operations/base.ts (3)

863-878: LGTM! Consistent integration of format string support.

The formatGeneratedValue helper is correctly integrated into all ID generators:

  • cuid, uuid, nanoid use args[1] for the format parameter (after version/length)
  • ulid correctly uses args[0] since it has no version parameter

The logic properly handles optional formatting while maintaining backward compatibility.


896-912: Clarify single vs. multiple placeholder handling.

The implementation replaces only the first occurrence of "%s" using replace(). The comment at line 910 questions whether replaceAll should be used instead.

Consider:

  1. Should multiple "%s" placeholders be supported (e.g., "prefix_%s_suffix_%s")?
  2. If multiple placeholders aren't intended, should the validation enforce a single "%s"?
  3. The current behavior will leave subsequent "%s" as literal strings, which may be unexpected.

Additionally, consider adding type validation that generated is a string before calling replace(), even though the generators should always return strings.

For design clarity, please confirm the intended behavior and consider either:

  • Using replaceAll('%s', generated) if multiple placeholders should be supported
  • Validating exactly one "%s" exists if single replacement is intended

907-908: Effective input validation.

The invariant checks properly validate that the format is a string and contains the required "%s" placeholder, providing clear error messages if the format is misconfigured.

@sanny-io
Copy link
Contributor Author

sanny-io commented Dec 17, 2025

Looking into the failed tests.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7b3f9f4 and 6ab3c72.

📒 Files selected for processing (1)
  • packages/language/res/stdlib.zmodel (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.zmodel

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.zmodel: Always run zenstack generate after modifying ZModel schemas
ZModel schema files should define database structure and policies that compile to TypeScript via zenstack generate

Files:

  • packages/language/res/stdlib.zmodel
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build-test (20.x, postgresql)
  • GitHub Check: build-test (20.x, sqlite)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6ab3c72 and 37c3182.

📒 Files selected for processing (1)
  • packages/orm/src/client/crud/operations/base.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/orm/src/client/crud/operations/base.ts (2)
packages/schema/src/expression-utils.ts (1)
  • ExpressionUtils (19-123)
packages/common-helpers/src/tiny-invariant.ts (1)
  • invariant (4-14)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build-test (20.x, postgresql)
  • GitHub Check: build-test (20.x, sqlite)
🔇 Additional comments (1)
packages/orm/src/client/crud/operations/base.ts (1)

863-890: Consider explicit version validation for UUID generation.

The implementation handles UUID v7 generation correctly, but silently defaults all other values (including v1, v3, v5, v6, or invalid inputs) to v4. Since the uuid library supports versions 1, 3, 4, 5, 6, and 7, consider either:

  • Supporting all available versions
  • Validating the version parameter and throwing an error for unsupported values
  • Explicitly documenting that only v7 and v4 are supported

@sanny-io
Copy link
Contributor Author

@ymc9 tests are passing and this should be good to go. I debated between replace and replaceAll because I didn't know why anyone would ever want the ID more than once, but opted to use replaceAll because it might be considered an oversight or gotcha.

Let me know what you think and if you'd like more test cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant