Skip to content

feat(store): add MySQL record resolution - #79

Merged
ian-pascoe merged 5 commits into
mainfrom
issue-61-mysql-record-resolution
Aug 10, 2026
Merged

feat(store): add MySQL record resolution#79
ian-pascoe merged 5 commits into
mainfrom
issue-61-mysql-record-resolution

Conversation

@ian-pascoe

@ian-pascoe ian-pascoe commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add opaque MySQL table, column, direct type, enum, and custom type helpers
  • add synchronous MySQL Record resolution with physical codecs, generated and automatic column assets, qualified references, Unicode collision checks, and aggregated definition failures
  • add runtime and compile-time contract coverage, public subpath exports, package documentation, and a changeset

Verification

  • pnpm run verify
  • pnpm --filter @commissary/store test
  • pnpm --filter @commissary/store typecheck
  • pnpm --filter @commissary/store build
  • pnpm --filter @commissary/store pack:check
  • built-package MySQL helper and resolver smoke test

Closes #61

Summary by CodeRabbit

  • New Features

    • Added MySQL schema definitions, metadata helpers, synchronous record resolution, validation, overrides, immutable metadata, and value codecs.
    • Added PostgreSQL record resolution with support for arrays, enums, custom types, identities, metadata validation, overrides, and runtime codecs.
    • Added shared SQL record cataloging, primary-key validation, schema selection, and immutable record references.
    • Added public exports for MySQL and PostgreSQL resolution APIs.
  • Documentation

    • Added guidance for refining SQL records and resolving MySQL records.
  • Tests

    • Added comprehensive coverage for MySQL validation, codecs, defaults, generated columns, references, overrides, and type contracts.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@ian-pascoe, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 29 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 10775a53-d234-4b77-bffd-45449191611f

📥 Commits

Reviewing files that changed from the base of the PR and between 83a2d1a and 5011022.

📒 Files selected for processing (10)
  • docs/specs/sql-store.md
  • packages/store/src/sql/mysql/metadata.ts
  • packages/store/src/sql/mysql/record-resolver.ts
  • packages/store/src/sql/postgres/column-codecs.ts
  • packages/store/src/sql/postgres/column-type-resolver.ts
  • packages/store/src/sql/postgres/metadata.ts
  • packages/store/src/sql/postgres/record-resolver.ts
  • packages/store/src/sql/record-catalog-resolver.ts
  • packages/store/test/sql/mysql/record.test.ts
  • packages/store/test/sql/postgres/record.test.ts
📝 Walkthrough

Walkthrough

The PR adds MySQL Record builders, validation, codecs, immutable resolution assets, and the synchronous resolveMysqlRecords() resolver. It also adds shared SQL catalog utilities, PostgreSQL resolution modules, tests, package exports, documentation, and a changeset.

Changes

SQL Record resolution

Layer / File(s) Summary
Shared SQL catalog traversal
packages/store/src/sql/record-catalog-resolver.ts
Adds catalog traversal, override lookup, literal and evidence validation, primary-key checks, issue paths, and immutable references.
MySQL metadata contracts and builders
packages/store/src/sql/mysql/record.ts, packages/store/src/sql/mysql/mysql-type-options.ts, packages/store/src/sql/mysql/metadata.ts, packages/store/src/sql/mysql/mysql-contract.ts, packages/store/src/sql/mysql/name-folding.ts, packages/store/src/sql/mysql/resolution-types.ts
Adds MySQL definitions, metadata validation, option checks, Unicode name folding, type contracts, and immutable resolution types.
MySQL physical types and resolver
packages/store/src/sql/mysql/column-codecs.ts, packages/store/src/sql/mysql/column-type-resolver.ts, packages/store/src/sql/mysql/column-resolution.ts, packages/store/src/sql/mysql/record-resolver.ts
Adds MySQL value codecs, physical type resolution, default and generated-column handling, automatic increment, update behavior, collision checks, and resolveMysqlRecords().
PostgreSQL resolution modules
packages/store/src/sql/postgres/*
Adds PostgreSQL resolution types, metadata, codecs, physical type resolution, record resolution, and adapter re-exports.
Validation and package publication
packages/store/test/sql/mysql/record.test.ts, packages/store/src/sql/mysql/index.ts, packages/store/src/sql/mysql/adapter.ts, packages/store/package.json, packages/store/README.md, .changeset/warm-jobs-resolve.md
Adds comprehensive MySQL tests, public exports, package subpaths, documentation, and a minor-release changeset.

Estimated code review effort: 5 (Critical) | ~90+ minutes

Possibly related issues

Possibly related PRs

Sequence Diagram(s)

sequenceDiagram
  participant RecordDefinitions
  participant resolveMysqlRecords
  participant MysqlColumnResolver
  participant MysqlRecordResolution
  RecordDefinitions->>resolveMysqlRecords: provide records and overrides
  resolveMysqlRecords->>MysqlColumnResolver: resolve types, defaults, and generated metadata
  MysqlColumnResolver->>MysqlRecordResolution: create immutable tables, columns, references, and codecs
  MysqlRecordResolution-->>RecordDefinitions: return resolved MySQL Records
Loading

Poem

A rabbit checks each field and name,
While codecs guard the value game.
Frozen tables line the way,
Resolvers build the schema day.
Tests thump: every bound is right.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.52% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely identifies the primary change: adding MySQL record resolution to the store package.
Description check ✅ Passed The description includes a clear summary, verification commands, test coverage, documentation, and changeset information, but omits the template checklist section.
Linked Issues check ✅ Passed The changes implement the linked issue objectives, including MySQL contracts, synchronous resolution, codecs, validation, immutable assets, exports, tests, documentation, and a changeset.
Out of Scope Changes check ✅ Passed The PostgreSQL refactor and shared catalog utilities are documented supporting changes, while the main implementation remains within the linked issue scope.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-61-mysql-record-resolution

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 11

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/store/src/sql/mysql/adapter.ts`:
- Around line 59-82: Extract sqlOpaqueFormatSymbol, the shared predicates
isRecordContainer, hasStatementStructure, and isValidEnumValue, plus the table,
column, and SQL column-type allowlists, into one package-internal module. Remove
the duplicate declarations from the MySQL adapter and record.ts, import the
shared symbols in both files, and preserve identical predicate behavior and
allowlist contents for authoring and resolution checks.
- Around line 746-756: Update packages/store/src/sql/mysql/adapter.ts:746-756 in
decimalFits to reject decimal values whose parts.fraction.length exceeds scale,
preserving the existing precision and unsigned checks. Also update
packages/store/src/sql/mysql/adapter.ts:685-709 in the encode path to validate
fractional seconds through the fsp-aware normalizer, so values beyond the
resolved fsp are rejected before writing.
- Around line 643-667: Update the hour capture in parseTime to accept one to
three digits instead of requiring exactly three, while preserving the existing
minute, second, fraction, range, and negative-zero validation. Keep
normalizeTime responsible for producing the canonical three-digit hour
representation.
- Around line 711-727: Update binaryCodec’s encode function to pad decoded
base64 bytes with 0x00 up to the declared length before returning them, matching
the existing char branch’s MySQL padding behavior while preserving rejection of
payloads exceeding length.
- Around line 2052-2086: Update the table collision key in the surrounding
table-name validation logic to apply the same locale-independent folding used by
foldMysqlDatabaseName to tableName before composing tableKey. Preserve database
qualification and duplicate-name reporting, and add coverage proving differently
cased table names in the same database conflict.

In `@packages/store/src/sql/mysql/mysql-type-options.ts`:
- Around line 30-40: Update isMysqlFloatPrecisionOption so undefined scale is
accepted only when type is "float"; reject undefined scale for "double" and
"real" while preserving the existing precision range checks and no-precision
behavior.

In `@packages/store/test/sql/mysql/record.test.ts`:
- Around line 303-305: Extend the integer column tests around
table.columns.integer.encode/decode to assert the safe upper boundary:
Number.MAX_SAFE_INTEGER must be accepted, while Number.MAX_SAFE_INTEGER + 2 must
be rejected according to the existing encode/decode validation policy. Preserve
the current normal-value and string-type assertions.
- Around line 246-249: Update the MySQL type assertions in the test around
mysql.enum and mysql.bigint to use Vitest’s supported toExtend matcher instead
of deprecated toMatchTypeOf, preserving the existing expected enum and JobId
subtype relationships.
- Around line 411-415: Add an assertion in the custom encoder test that invokes
resolution.tables.custom.columns.value.encode({ x: -1 }) and verifies it throws
the expected “encode failed” error, placing it before the existing call-count
assertion. Preserve the current encoder behavior and call-count checks.
- Around line 314-342: Extend the direct MySQL helper boundary tests for
mysql.smallint and mysql.mediumint alongside the existing tinyint and int cases.
Assert successful encoding at each type’s signed maximum (16-bit and 24-bit) and
assert one-beyond-maximum values overflow, using the existing integer
boundary-test structure and symbols.
- Around line 595-623: The test assertions around the two autoDefault entries
must distinguish their independent validation failures. Update the failure
assertions to verify the corresponding messages for each autoDefault issue,
ensuring both the nullable autoIncrement rule and the autoIncrement-with-default
rule are independently covered rather than relying only on identical codes and
paths.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7c9b1b14-a9e6-4526-8441-9f5b790e3ac4

📥 Commits

Reviewing files that changed from the base of the PR and between bd249e1 and f160cb7.

📒 Files selected for processing (8)
  • .changeset/warm-jobs-resolve.md
  • packages/store/README.md
  • packages/store/package.json
  • packages/store/src/sql/mysql/adapter.ts
  • packages/store/src/sql/mysql/index.ts
  • packages/store/src/sql/mysql/mysql-type-options.ts
  • packages/store/src/sql/mysql/record.ts
  • packages/store/test/sql/mysql/record.test.ts

Comment thread packages/store/src/sql/mysql/adapter.ts Outdated
Comment thread packages/store/src/sql/mysql/adapter.ts Outdated
Comment thread packages/store/src/sql/mysql/adapter.ts Outdated
Comment thread packages/store/src/sql/mysql/adapter.ts Outdated
Comment thread packages/store/src/sql/mysql/adapter.ts Outdated
Comment thread packages/store/test/sql/mysql/record.test.ts Outdated
Comment thread packages/store/test/sql/mysql/record.test.ts
Comment thread packages/store/test/sql/mysql/record.test.ts
Comment thread packages/store/test/sql/mysql/record.test.ts
Comment thread packages/store/test/sql/mysql/record.test.ts
@ian-pascoe

Copy link
Copy Markdown
Contributor Author

Follow-up refactor in 83a2d1a:

  • reduced each public adapter.ts to a stable façade
  • added a shared SQL Record catalog module for traversal, provenance, primary-key validation, reference construction, and freezing
  • split PostgreSQL and MySQL metadata, codecs, type resolution, and catalog resolution into focused internal modules
  • kept the public subpaths, resolver names, resolved asset types, and runtime contracts unchanged

Verification:

  • pnpm run verify passed
  • pnpm --filter @commissary/store pack:check passed
  • PostgreSQL/MySQL resolver contract tests passed (30 tests)
  • built public resolver façade smoke test passed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 17

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/store/src/sql/mysql/column-codecs.ts`:
- Around line 302-340: Update decimalFits in
packages/store/src/sql/mysql/column-codecs.ts:302-340 to reject values whose
fraction length exceeds scale, matching normalizeDecimal and ensuring
decimalCodec.encode enforces precision consistently. In
packages/store/src/sql/mysql/column-codecs.ts:241-265, derive validate from the
fsp normalizer so encode rejects excess fractional digits for datetime,
timestamp, and time codecs.

In `@packages/store/src/sql/mysql/metadata.ts`:
- Around line 71-83: Reorder the validation in validStatement so the
fragments.some parameter check runs before the
fragments.length/hasMysqlStatementStructure check. Preserve the existing
parameter error and structure validation, ensuring statements containing SQL
parameters report “must not contain SQL parameters” instead of the
nonempty-structure error.

In `@packages/store/src/sql/mysql/record-resolver.ts`:
- Around line 467-481: Update duplicate detection in the columnNameAssets loop
to key seenColumnNames by the MySQL-folded form of asset.name, while retaining
asset.name’s original spelling in the issue message. Reuse the existing
database/table name-folding behavior or helper used elsewhere in this resolver
so names differing only by case are treated as duplicates.

In `@packages/store/src/sql/postgres/column-codecs.ts`:
- Around line 292-294: Update validUuid to validate only the canonical UUID
hexadecimal shape and separators, removing the version and variant nibble
restrictions. Preserve validation for malformed values while allowing
PostgreSQL-supported nil, max, and non-RFC variant UUIDs through encoding and
decoding.
- Around line 270-284: Document in the PostgreSQL adapter contract, alongside
the MySQL UTC contract, that temporal date, time, and timestamp OIDs must use
text parsers when the driver returns objects such as Date instances. Reference
temporalCodec’s string-only decode behavior and clearly state that adapters must
register these parsers.
- Around line 300-314: Update numericCodec’s valid function to inspect the
fractional portion of the unsigned value and reject values whose fractional
digit count exceeds the configured scale. Preserve the existing precision
validation and behavior when scale is not configured, while ensuring numeric
values such as 1.2345 are rejected for numeric(10, 2).
- Around line 381-389: Update the "char" and "varchar" branches in the codec
resolver to reuse the validated length option from directResolved/options and
reject encoded strings whose Unicode code-point count exceeds it with the
established codec TypeError behavior. Preserve trailing-space handling for
"char", and count characters rather than UTF-8 bytes.
- Around line 129-151: Update parsePostgresDate to reject year zero and enforce
PostgreSQL’s supported date range of 4713-01-01 BC through 5874897-12-31,
including the relevant month/day boundaries. Keep valid in-range dates unchanged
while returning undefined for zero, out-of-range, and oversized years so
validDate inherits the same validation.

In `@packages/store/src/sql/postgres/column-type-resolver.ts`:
- Around line 301-308: Update the invalidValue calls in encodeValue and
decodeValue to pass grammatical type tokens, so the formatted PostgreSQL codec
error reads correctly for both custom encoder and decoder outputs.
- Around line 235-253: Update the PostgreSQL array handling in
resolvePhysicalType so element formats with the portable dialect are normalized
through portableType before validation, matching the top-level portable-type
contract. Preserve PostgreSQL element handling and the existing invalid-element
issue for unsupported formats; do not reject valid portable scalar elements
solely because they are not already dialect-specific.

In `@packages/store/src/sql/postgres/record-resolver.ts`:
- Around line 99-110: Update resolveIdentity’s sequence-option handling to
reject unknown keys instead of silently ignoring them, matching
validateDirectOptions. Validate the sequence record’s keys against the supported
options (name, startWith, incrementBy, minValue, maxValue, cache, and cycle),
append an invalid-database-options issue for any unknown key, and preserve the
existing resolution behavior for valid keys.
- Around line 276-315: Remove the raw postgres metadata pre-validation loop in
packages/store/src/sql/postgres/record-resolver.ts lines 276-315; the effective
values are already validated by tableNameValid and schemaValid. Also remove the
raw column.postgres.name validation block at lines 326-337; columnName
validation at lines 344-354 is sufficient. These are the only affected sites.
- Around line 382-395: Update the notNull override handling in the record
resolver to treat null from ownNullableOverride as an absent, cleared override:
exclude null from the invalid-option check and let the derived
selectedPresence/physical nullability determine notNull. Preserve explicit
boolean overrides and the existing explicitNotNull === false identity-conflict
behavior.
- Around line 134-149: Update the sequence-options loop around
normalizeExactInteger so cache is normalized without applying the column range;
retain its existing cache validation elsewhere. When normalization returns
undefined, report an invalid/non-integer option instead of the column-range
error, and reserve the range message for integer values outside range.

In `@packages/store/src/sql/postgres/resolution-types.ts`:
- Around line 35-39: Add a type guard in the arrayCodec handling path that
validates the presence and expected shapes of values and lowerBounds, and
narrows the result to PostgresArrayDriverValue. Replace the separate
Reflect.get/Object.hasOwn checks with this guard, importing and using the
exported interface so runtime validation remains tied to the public contract.

In `@packages/store/src/sql/record-catalog-resolver.ts`:
- Around line 171-210: Update hasNestedOwnValue to return false when the path is
empty and the supplied value is undefined, while preserving true for an existing
candidate with an empty path and the current nested-property traversal behavior.
This ensures sqlDefinitionSourcePath only selects an overrides path when the
candidate actually exists.

In `@packages/store/test/sql/mysql/record.test.ts`:
- Around line 306-309: Update the invalid integer assertions in the integer
encode/decode tests to use Number.MAX_SAFE_INTEGER + 1 instead of + 2, ensuring
both methods reject the first unsafe integer value while preserving the existing
TypeError expectations.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0eed0c63-319c-4016-8404-a3e0b8080c22

📥 Commits

Reviewing files that changed from the base of the PR and between f160cb7 and 83a2d1a.

📒 Files selected for processing (18)
  • packages/store/src/sql/mysql/adapter.ts
  • packages/store/src/sql/mysql/column-codecs.ts
  • packages/store/src/sql/mysql/column-resolution.ts
  • packages/store/src/sql/mysql/column-type-resolver.ts
  • packages/store/src/sql/mysql/metadata.ts
  • packages/store/src/sql/mysql/mysql-contract.ts
  • packages/store/src/sql/mysql/name-folding.ts
  • packages/store/src/sql/mysql/record-resolver.ts
  • packages/store/src/sql/mysql/record.ts
  • packages/store/src/sql/mysql/resolution-types.ts
  • packages/store/src/sql/postgres/adapter.ts
  • packages/store/src/sql/postgres/column-codecs.ts
  • packages/store/src/sql/postgres/column-type-resolver.ts
  • packages/store/src/sql/postgres/metadata.ts
  • packages/store/src/sql/postgres/record-resolver.ts
  • packages/store/src/sql/postgres/resolution-types.ts
  • packages/store/src/sql/record-catalog-resolver.ts
  • packages/store/test/sql/mysql/record.test.ts

Comment thread packages/store/src/sql/mysql/column-codecs.ts
Comment thread packages/store/src/sql/mysql/metadata.ts
Comment thread packages/store/src/sql/mysql/record-resolver.ts
Comment thread packages/store/src/sql/postgres/column-codecs.ts
Comment thread packages/store/src/sql/postgres/column-codecs.ts
Comment thread packages/store/src/sql/postgres/record-resolver.ts Outdated
Comment thread packages/store/src/sql/postgres/record-resolver.ts
Comment thread packages/store/src/sql/postgres/resolution-types.ts
Comment thread packages/store/src/sql/record-catalog-resolver.ts
Comment thread packages/store/test/sql/mysql/record.test.ts Outdated
@ian-pascoe
ian-pascoe merged commit 9484564 into main Aug 10, 2026
9 checks passed
@ian-pascoe
ian-pascoe deleted the issue-61-mysql-record-resolution branch August 10, 2026 17:26
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.

Implement MySQL Record resolution

1 participant