Skip to content

refactor: unify type system — drizzle → zod schemas → inferred TS types#2414

Merged
andrew-bierman merged 2 commits into
developmentfrom
refactor/unify-schema-type-system
May 17, 2026
Merged

refactor: unify type system — drizzle → zod schemas → inferred TS types#2414
andrew-bierman merged 2 commits into
developmentfrom
refactor/unify-schema-type-system

Conversation

@andrew-bierman
Copy link
Copy Markdown
Collaborator

@andrew-bierman andrew-bierman commented May 13, 2026

Summary

  • Single schema source: All types now flow drizzle schema → drizzle-zod → @packrat/api/schemas/* → inferred TypeScript types. No more hand-rolled duplicates.
  • Eden Treaty end-to-end typing: Added response: { 200: Schema } to every Elysia route so eden.route.get() returns typed data instead of unknown.
  • Embedding leak fix: CatalogItemSchema.parse(drizzleRow) strips the embedding vector field before it reaches the API response.
  • Deleted duplicate schemas: Removed apps/expo/types/index.ts (15 consumers redirected to @packrat/api/types/constants) and collapsed packages/app/src/entities/*/schema.ts files into thin re-exports of the canonical API schemas.
  • Constants extracted: @packrat/api/types/constants is now the single home for enum tuples (WEIGHT_UNITS, PACK_CATEGORIES, etc.) and their Zod/TS types. types/index.ts is a barrel re-export for backward compat.
  • Admin macro: packTemplates generate-from-online-content uses adminAuthPlugin + isAdmin: true declaratively instead of a manual role check.

Commits

  1. chore(deps) — Zod ^3 → ^4.3.6
  2. fix(types) — userId/author id types: z.number()z.string() (better-auth uses UUIDs)
  3. feat(api)response: { 200: Schema } on all Elysia routes
  4. refactor(api) — extract constants to types/constants.ts
  5. refactor(app) — entity schemas become re-exports from @packrat/api/schemas
  6. fix(catalog) — embedding leak fix + admin macro for packTemplates
  7. refactor(expo) — delete apps/expo/types/index.ts, fix userId string types

Test Plan

  • bun check-types from monorepo root: 0 errors
  • bun test packages/api/src/utils/__tests__/: 191 pass, 0 fail
  • Pre-push hooks: no unsafe casts, no circular deps, all routes annotated

Post-Deploy Monitoring & Validation

  • What to monitor: Catalog API responses — verify embedding field is absent in response payloads. Eden Treaty client calls — data should be typed, not unknown.
  • Validation check: curl /api/catalog/1 | jq 'has("embedding")' → should be false
  • Expected healthy behavior: All catalog, pack, weather, and upload endpoints return correctly typed data via Eden Treaty.
  • Failure signal: If embedding appears in a response, the CatalogItemSchema.parse() call was bypassed.
  • Validation window: Immediately after deploy; no runtime behavior change — this is a type-system + schema-shape change only.

Compound Engineered 🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Improved API response validation and error handling across catalog, guides, packs, and trips endpoints.
    • Added admin-only gating for pack template generation endpoint.
  • Bug Fixes

    • Standardized error responses with proper HTTP error types instead of generic status codes.
    • Enhanced data validation to ensure consistency across API responses.
  • Refactor

    • Consolidated type definitions and schemas to centralized API package.
    • Updated user ID field from numeric to string format.
  • Chores

    • Upgraded Zod to v4.0.0.

Copilot AI review requested due to automatic review settings May 13, 2026 06:21
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 13, 2026

Warning

Rate limit exceeded

@andrew-bierman has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 54 minutes and 5 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0f1e39d1-d049-42d4-a98b-9ed015096276

📥 Commits

Reviewing files that changed from the base of the PR and between b095372 and 3cd4af4.

📒 Files selected for processing (4)
  • apps/expo/app/(app)/current-pack/[id].tsx
  • packages/api/src/routes/catalog/index.ts
  • packages/api/src/routes/weather.ts
  • packages/schemas/src/catalog.ts

Walkthrough

The PR consolidates core type definitions and Zod schemas from scattered local definitions and expo-app/types into a centralized @packrat/api/types/constants, then updates import paths across the monorepo and standardizes API route error handling to use schema-based validation and thrown exceptions instead of status-based responses.

Changes

API Type Consolidation & Centralization

Layer / File(s) Summary
Core type definitions module
packages/api/src/types/constants.ts, packages/api/src/types/index.ts
New constants.ts exports frozen enum arrays (PACK_CATEGORIES, ITEM_CATEGORIES, WEIGHT_UNITS, AVAILABILITY_VALUES) and corresponding Zod schemas with inferred types. Index module simplified to re-export everything from constants.
Schema re-exports in app package
packages/app/src/entities/catalog/schema.ts, packages/app/src/entities/feed/schema.ts, packages/app/src/entities/pack/schema.ts, packages/app/src/entities/trip/schema.ts, packages/app/src/entities/user/schema.ts
Five entity schema files now re-export Zod schemas from @packrat/api instead of defining them locally, removing 150+ lines of duplicate schema definitions.

Monorepo Import Path Updates to Centralized Types

Layer / File(s) Summary
Expo app and library imports
apps/expo/app/(app)/current-pack/[id].tsx, apps/expo/components/initial/{UserAvatar,WeightBadge}.tsx, apps/expo/data/mockData.ts, apps/expo/features/**/*.ts, apps/expo/lib/utils/**/*.ts, apps/expo/utils/**/*.ts
~15 files update type imports from expo-app/types to @packrat/api/types/constants for User, Pack, PackItem, PackCategory, WeightUnit.
Web app, CLI, and database schema
packages/api/src/db/schema.ts, packages/api/src/services/packService.ts, packages/api/src/utils/{itemCalculations,weight}.ts, packages/cli/src/args.ts
Type imports switched to centralized location; CLI Zod type annotation simplified.

Web App userId Type Change: Number → String

Layer / File(s) Summary
Web type definitions
apps/web/lib/types.ts
PackItem.userId, PackWithWeights.userId, and Trip.userId field types changed from number to string to align with API backend.
Web mock data
apps/web/lib/data.ts
Mock pack, template, and trip fixtures updated to use userId: 'mock-user-id' string instead of numeric values (10 lines affected across three constants).

API Route Error Handling & Schema Validation Standardization

Layer / File(s) Summary
Catalog route responses
packages/api/src/routes/catalog/index.ts
Five endpoints (list, categories, create, get-by-id, update) refactored to use *.Schema.parse(...) for response validation, throw NotFoundError for 404 cases, and throw Error for validation failures instead of returning status objects.
Feed and guides routes
packages/api/src/routes/feed/index.ts, packages/api/src/routes/guides/index.ts
List/get/search endpoints now validate responses via schema parsing; 404 errors throw NotFoundError, error handling logs and rethrows instead of returning 500 status responses.
Packs route responses
packages/api/src/routes/packs/index.ts
List/create/get-by-id and item endpoints refactored to parse payloads via PackWithWeightsSchema / PackItemSchema, throw NotFoundError for missing resources, and rethrow on failures.
Trips, user, upload, and weather routes
packages/api/src/routes/{trips,user,upload,weather}.ts
Consistent pattern: replace status(...) returns with thrown Error/NotFoundError, validate responses with schema parsing, rethrow caught errors. Trips removes local schema duplication by importing shared schemas from DB.

Guide & Pack-Template Type Updates

Layer / File(s) Summary
Guide type enhancement
apps/expo/features/guides/types.ts
Guide.readingTime field type changed from optional string to optional number for proper duration representation.
Pack-template admin gating
packages/api/src/routes/packTemplates/index.ts
Route group now applies adminAuthPlugin middleware; /generate-from-online-content endpoint removes inline role check and adds isAdmin: true metadata.

Schema Validation & Format Improvements

Layer / File(s) Summary
Catalog and upload schema refinements
packages/api/src/schemas/catalog.ts, packages/api/src/schemas/upload.ts
CatalogItemSchema adds datetimeString preprocessor that coerces Date inputs to ISO strings; PresignedUploadResponseSchema.url relaxed from URL-format validation to plain string validation.
Guide categories & route param schemas
packages/api/src/schemas/guides.ts, packages/api/src/utils/routeParams.ts, packages/overpass/src/schemas.ts
New GuideCategoriesResponseSchema added; integerIdSchema refactored to use parseInt transform with custom refine message; OverpassElementSchema.tags explicitly validates both keys and values as strings.
Dependency & constant source updates
packages/api/src/schemas/packs.ts, package.json
PACK_CATEGORIES and WEIGHT_UNITS now imported from @packrat/api/types/constants; zod bumped from ^3.24.2 to ^4.0.0.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes


Possibly related PRs

  • PackRat-AI/PackRat#2372: Both PRs modify CatalogItemSchema's createdAt/updatedAt field validation to use consistent ISO datetime string serialization.

Suggested labels

api, dependencies, mobile, web


Suggested reviewers

  • mikib0
  • Isthisanmol
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main refactoring objective: unifying the type system by consolidating duplicate schemas and establishing a single source of truth (Drizzle → Zod schemas → inferred TypeScript types).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/unify-schema-type-system

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.

@github-actions github-actions Bot added dependencies Pull requests that update a dependency file api mobile database labels May 13, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 13, 2026

Coverage Report for API Unit Tests Coverage (./packages/api)

Status Category Percentage Covered / Total
🔵 Lines 70.11% 502 / 716
🔵 Statements 70.11% (🎯 65%) 502 / 716
🔵 Functions 92.68% 38 / 41
🔵 Branches 88.32% 227 / 257
File CoverageNo changed files found.
Generated in workflow #1312 for commit 3cd4af4 by the Vitest Coverage Report Action

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 13, 2026

Coverage Report for Expo Unit Tests Coverage (./apps/expo)

Status Category Percentage Covered / Total
🔵 Lines 82.76% 485 / 586
🔵 Statements 82.76% (🎯 75%) 485 / 586
🔵 Functions 92.59% 50 / 54
🔵 Branches 90.9% 170 / 187
File CoverageNo changed files found.
Generated in workflow #1312 for commit 3cd4af4 by the Vitest Coverage Report Action

Copy link
Copy Markdown
Contributor

@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: 14

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/expo/app/(app)/current-pack/[id].tsx (1)

196-198: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove the unnecessary type assertion.

The comment claims PackItem schema requires createdAt: string, but the PackItem interface in types.ts (line 28-29) defines both createdAt?: Date | string and updatedAt?: Date | string as optional. The double cast through unknown is unnecessarily permissive and the comment is misleading.

If the Treaty response type actually matches PackItem, remove the cast entirely. If there's a genuine mismatch, handle it explicitly rather than forcing a cast.

🔧 Proposed fix
-              // safe-cast: Treaty response type has createdAt?: string but PackItem schema requires string
-              <ItemRow item={item as unknown as PackItem} index={index} />
+              <ItemRow item={item} index={index} />
🤖 Prompt for 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.

In `@apps/expo/app/`(app)/current-pack/[id].tsx around lines 196 - 198, The double
type assertion "item as unknown as PackItem" and the accompanying comment are
unnecessary and misleading because the PackItem interface (createdAt?: Date |
string, updatedAt?: Date | string) already allows optional Date|string; remove
the cast and comment and pass item directly to ItemRow (i.e., <ItemRow
item={item} index={index} />). If there is an actual shape mismatch, explicitly
map/convert the Treaty response to a PackItem (e.g., ensure createdAt/updatedAt
are strings or Dates) before passing to ItemRow instead of using an unknown
cast; use the PackItem type from types.ts to annotate the conversion.
🤖 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/api/src/routes/catalog/index.ts`:
- Around line 419-421: The current check in the handler that throws a generic
Error when data.weight <= 0 causes a 500; replace this with a client error
response or schema validation: either update
UpdateCatalogItemRequestSchema.weight to z.number().positive().optional() and
remove this manual branch, or change the throw to an Elysia-aware bad request
(e.g., return status(400, ...) or throw a BadRequestError) in the block that
checks data.weight to ensure invalid client input yields a 400 instead of a 500.
- Line 32: This file mixes throwing NotFoundError and returning status(...)
across handlers (e.g., the handlers for '/vector-search', '/etl',
'/:id/similar', 'DELETE /:id' versus handlers that throw NotFoundError around
lines noted), breaking unified error handling and response schemas; pick one
approach and make all catalog routes consistent: either (A) replace status(...)
usages in the routes mentioned with throw NotFoundError(...) so every
missing-resource path uses the same thrown error (ensure messages/metadata match
existing NotFoundError usage), or (B) convert the handlers that throw
NotFoundError to return status(status.NOT_FOUND, {...}) to match the existing
pattern — update the same endpoints ('/vector-search', '/etl', '/:id/similar',
'DELETE /:id') and any sibling handlers in this file accordingly and adjust the
documented response schemas to reflect the chosen error shape. Ensure you update
the specific functions/route handlers in index.ts where NotFoundError or
status(...) is used so the file is no longer half-migrated.
- Around line 226-237: Remove the redundant manual request validations that
duplicate CreateCatalogItemRequestSchema (the checks using data.name,
data.weight, data.weightUnit and the weight <= 0 check) so the route relies on
the schema/validator to return 422/400; then replace the plain throw new
Error('OpenAI API key not configured') (the OPENAI_API_KEY guard after getEnv())
with a specific error type (e.g., a ConfigError or an HttpError with an explicit
500 status) so missing API key remains a server/config error but is
distinguishable from generic failures.

In `@packages/api/src/routes/guides/index.ts`:
- Around line 203-205: Replace the generic Error thrown for empty/missing q with
a client-validation error (HTTP 400) so it is not treated as a server error;
specifically, in the guides search handler where q is validated (reference the q
variable and GuideSearchQuerySchema), throw or return a BadRequest/validation
error (e.g., a framework HttpError/BadRequest variant or Response with status
400) and include a clear message like "Search query parameter q is required"; if
GuideSearchQuerySchema already marks q as required, remove this redundant
runtime check instead.

In `@packages/api/src/routes/packs/index.ts`:
- Line 42: This file is half-migrated to throwing errors and should be
consistent: either revert the partially migrated handlers or finish converting
them to throw errors — pick finishing the migration. For each handler still
using status(...) (handlers for PUT /:packId, DELETE /:packId, item-suggestions,
POST weight-history, gap-analysis, GET /:packId/items, POST /:packId/items,
DELETE /items/:itemId, similar), replace the status(...) return paths with
thrown errors (use throw new NotFoundError(...) for 404 cases and throw new
Error(...) or a more specific thrown error for 400/500 cases), remove the
status(...) usage, and ensure the function handlers (the route callbacks in this
file) consistently throw instead of returning error-shaped payloads; since
NotFoundError is already imported from Elysia at the top, reuse it and keep
error messages descriptive so OpenAPI and consumers see a single error shape.
- Line 120: computePacksWeights([packWithItems])[0] can be undefined under
noUncheckedIndexedAccess, causing PackWithWeightsSchema.parse to throw an opaque
Zod error; change the code to call computePacksWeights once into a local
variable (e.g., const weights = computePacksWeights([packWithItems])), assert
the first element is defined (if undefined throw a clear error mentioning
packWithItems and computePacksWeights), then pass that defined value to
PackWithWeightsSchema.parse instead of indexing inline; alternatively call the
single-pack variant of the weight computation if available to avoid the
undefined risk.
- Around line 240-246: The try/catch around the pack fetch is redundant because
it only rethrows every error; remove the surrounding try/catch and let errors
bubble to the global handler, or if you want to keep logging, replace the catch
with a single catch that logs and rethrows (referencing
PackWithWeightsSchema.parse, computePackWeights, and NotFoundError) — i.e.,
eliminate the entire try { ... } catch block and simply perform the fetch,
existence check (throw new NotFoundError('Pack not found')), and return
PackWithWeightsSchema.parse(computePackWeights(pack)).
- Around line 685-690: The authorization check currently throws a generic Error
which Elysia maps to 500; instead, when neither isOwner nor isPublic is true
return a 403 response using the framework helper (e.g., return status(403, {
error: 'Unauthorized' }) or whatever local status helper pattern is used
elsewhere in this file) rather than throwing; update the block around
isOwner/isPublic and ensure the function still returns
PackItemSchema.parse(item) for allowed requests.

In `@packages/api/src/routes/packTemplates/index.ts`:
- Around line 142-143: The router-wide application of adminAuthPlugin is
blocking non-admin access; remove the global .use(adminAuthPlugin) so authPlugin
remains but adminAuthPlugin is not applied to the entire pack-templates router,
then apply admin-only protection specifically to the POST
'/generate-from-online-content' route (the handler using
GenerateFromOnlineContentRequestSchema) either by passing an isAdmin:true option
on that route or explicitly wrapping that single route with adminAuthPlugin;
ensure other handlers (GET '/', POST '/', GET '/:templateId', PATCH/DELETE item
handlers) keep normal auth behavior so their internal userId / isAppTemplate
logic continues to work.

In `@packages/api/src/routes/trips/index.ts`:
- Around line 186-190: Currently the flow hard-deletes after a separate
ownership check which can leak existence; change to a scoped soft-delete update
that matches both tripId and user.userId in a single query (e.g., replace
db.delete(trips).where(eq(trips.id, tripId)) with an update that sets the
soft-delete marker such as deletedAt or isDeleted and includes
where(eq(trips.id, tripId), eq(trips.userId, user.userId))). Remove the separate
ownership-only check or use the update result to decide: if no rows were
updated, throw NotFoundError('Trip not found') to avoid exposing whether the
trip exists for other users.

In `@packages/api/src/routes/upload.ts`:
- Around line 29-49: The route currently throws generic Errors for
validation/auth failures in the upload handler (checks around
fileName/contentType, ALLOWED_IMAGE_TYPES, MAX_FILE_SIZE, and the filename
prefix check against user.userId) which Elysia maps to 500; change those throws
to use Elysia's status(...) helper to return 400 for missing/invalid
fileName/contentType, 400 for disallowed content type, 400 for invalid/oversized
size, and 403 for the unauthorized filename prefix; also import and apply the
ErrorResponseSchema in the route's response schema so 400/403 are reflected in
the route typing for clients.

In `@packages/api/src/routes/user/index.ts`:
- Line 77: Replace the generic throw new Error('Email already in use by another
user') with a specific ConflictError (e.g., create class ConflictError extends
Error) and throw new ConflictError('Email already in use by another user'); then
update the central error-handling middleware to map ConflictError instances to
HTTP 409 responses (check instanceof ConflictError and return res.status(409)
with a clear message) so email conflicts produce a 409 Conflict instead of a
generic error.

In `@packages/api/src/routes/weather.ts`:
- Around line 136-138: Replace the generic throw new Error in the validation
block that checks idParam and id with a framework-specific 400-level error so
middleware can map it to a Bad Request; specifically, in the handler where
idParam and id are validated (the if (!idParam || Number.isNaN(id)) block),
throw or return a BadRequest/HTTP 400 error (e.g., BadRequestError or
createError(400, ...)) with a clear message like "Valid location ID is required"
so it matches how other routes handle client errors and is correctly interpreted
by the error middleware.
- Around line 148-157: The catch block around
WeatherAPIForecastResponseSchema.parse currently lumps all errors together;
update the error handling in the function that fetches/parses the forecast so it
checks for ZodError specifically (importing ZodError from zod), logs the
validation details (error.errors) and throws a clearer validation error (e.g.,
include invalid paths from error.errors), and otherwise retains the existing
logging/throw for non-validation errors; reference
WeatherAPIForecastResponseSchema.parse and the catch block where you currently
console.error('Error fetching weather forecast:', error).

---

Outside diff comments:
In `@apps/expo/app/`(app)/current-pack/[id].tsx:
- Around line 196-198: The double type assertion "item as unknown as PackItem"
and the accompanying comment are unnecessary and misleading because the PackItem
interface (createdAt?: Date | string, updatedAt?: Date | string) already allows
optional Date|string; remove the cast and comment and pass item directly to
ItemRow (i.e., <ItemRow item={item} index={index} />). If there is an actual
shape mismatch, explicitly map/convert the Treaty response to a PackItem (e.g.,
ensure createdAt/updatedAt are strings or Dates) before passing to ItemRow
instead of using an unknown cast; use the PackItem type from types.ts to
annotate the conversion.
🪄 Autofix (Beta)

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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: fe86a705-669d-4361-8d84-fc4c86b40b65

📥 Commits

Reviewing files that changed from the base of the PR and between 44534b1 and b095372.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock, !bun.lock
📒 Files selected for processing (49)
  • apps/expo/app/(app)/current-pack/[id].tsx
  • apps/expo/components/initial/UserAvatar.tsx
  • apps/expo/components/initial/WeightBadge.tsx
  • apps/expo/data/mockData.ts
  • apps/expo/features/guides/types.ts
  • apps/expo/features/pack-templates/packTemplateListAtoms.ts
  • apps/expo/features/pack-templates/screens/CreatePackTemplateItemForm.tsx
  • apps/expo/features/packs/components/TemplateItemsSection.tsx
  • apps/expo/features/packs/input.ts
  • apps/expo/features/packs/packListAtoms.ts
  • apps/expo/features/packs/screens/CreatePackItemForm.tsx
  • apps/expo/features/packs/types.ts
  • apps/expo/lib/utils/__tests__/compute-pack.test.ts
  • apps/expo/lib/utils/compute-pack.ts
  • apps/expo/types/index.ts
  • apps/expo/utils/__tests__/weight.test.ts
  • apps/expo/utils/weight.ts
  • apps/web/lib/data.ts
  • apps/web/lib/types.ts
  • package.json
  • packages/api/src/db/schema.ts
  • packages/api/src/routes/catalog/index.ts
  • packages/api/src/routes/feed/index.ts
  • packages/api/src/routes/guides/index.ts
  • packages/api/src/routes/packTemplates/index.ts
  • packages/api/src/routes/packs/index.ts
  • packages/api/src/routes/trips/index.ts
  • packages/api/src/routes/upload.ts
  • packages/api/src/routes/user/index.ts
  • packages/api/src/routes/weather.ts
  • packages/api/src/schemas/catalog.ts
  • packages/api/src/schemas/guides.ts
  • packages/api/src/schemas/packs.ts
  • packages/api/src/schemas/upload.ts
  • packages/api/src/services/packService.ts
  • packages/api/src/types/constants.ts
  • packages/api/src/types/index.ts
  • packages/api/src/utils/__tests__/itemCalculations.test.ts
  • packages/api/src/utils/__tests__/weight.test.ts
  • packages/api/src/utils/itemCalculations.ts
  • packages/api/src/utils/routeParams.ts
  • packages/api/src/utils/weight.ts
  • packages/app/src/entities/catalog/schema.ts
  • packages/app/src/entities/feed/schema.ts
  • packages/app/src/entities/pack/schema.ts
  • packages/app/src/entities/trip/schema.ts
  • packages/app/src/entities/user/schema.ts
  • packages/cli/src/args.ts
  • packages/overpass/src/schemas.ts
💤 Files with no reviewable changes (1)
  • apps/expo/types/index.ts

Comment thread packages/api/src/routes/catalog/index.ts
Comment thread packages/api/src/routes/catalog/index.ts Outdated
Comment thread packages/api/src/routes/catalog/index.ts Outdated
Comment thread packages/api/src/routes/guides/index.ts
Comment thread packages/api/src/routes/packs/index.ts
Comment thread packages/api/src/routes/trips/index.ts Outdated
Comment on lines 29 to 49
if (!fileName || !contentType) {
return status(400, { error: 'fileName and contentType are required' });
throw new Error('fileName and contentType are required');
}

// Validate content type - only allow images
if (!ALLOWED_IMAGE_TYPES.includes(contentType.toLowerCase())) {
return status(400, { error: 'Invalid content type. Only image files are allowed.' });
throw new Error('Invalid content type. Only image files are allowed.');
}

// Validate file size - max 10MB
if (size) {
const fileSize = Number.parseInt(String(size), 10);
if (Number.isNaN(fileSize) || fileSize <= 0 || fileSize > MAX_FILE_SIZE) {
return status(400, { error: 'File size must be greater than 0 and not exceed 10MB' });
throw new Error('File size must be greater than 0 and not exceed 10MB');
}
}

// Security check: Ensure the filename starts with the user's ID
if (!fileName.startsWith(`${user.userId}-`)) {
return status(403, { error: 'Unauthorized' });
throw new Error('Unauthorized');
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Elysia v1 throw status helper for 400 and 403 from route handler

💡 Result:

Elysia v1: use the handler context status(...) helper (typed) to return or throw HTTP 400/403 from a route handler. 1) Return 400/403 (does NOT go through onError) Use return status(400, payload) or return status(403, payload) in the route handler. Example: const app = new Elysia.get('/bad', ({ status }) => { return status(400, { error: 'Bad Request' }) }).get('/forbidden', ({ status }) => { return status(403, { error: 'Forbidden' }) }) Elysia’s docs explain that returning status(...) is not caught by onError middleware. [1] 2) Throw 400/403 (DOES go through onError) Use throw status(400, payload) or throw status(403, payload) in the route handler, and handle it in .onError(...). Example: new Elysia.onError(({ code }) => { if (code === 403) return { error: 'Forbidden' } }).get('/forbidden-throw', ({ status }) => { throw status(403, { error: 'Forbidden' }) }) Elysia’s docs explicitly state: if status is thrown, it will be caught by onError; if status is returned, it will not. [1] 3) If TypeScript won’t allow status(403, ...) Some setups/type definitions may only include certain status codes for the route response schema, and then status(403, ...) causes a TS error (e.g., 403 not assignable). An Elysia issue reports this behavior and that you may need to include 403 in the route’s allowed response/status schema. [2] In that case, two options are: - Update the route’s declared response/status union to include 400/403. [2] - Use set.status = 403 instead (less type-narrowing, but works as a fallback). The docs note set.status as a way to set response status in handlers. [3][4]

Citations:


🏁 Script executed:

wc -l packages/api/src/routes/upload.ts

Repository: PackRat-AI/PackRat

Length of output: 99


🏁 Script executed:

cat -n packages/api/src/routes/upload.ts

Repository: PackRat-AI/PackRat

Length of output: 3205


🏁 Script executed:

rg -A 5 "onError" packages/api/src/routes/upload.ts

Repository: PackRat-AI/PackRat

Length of output: 44


🏁 Script executed:

rg "response\s*:" packages/api/src/routes/upload.ts | head -20

Repository: PackRat-AI/PackRat

Length of output: 116


🏁 Script executed:

cat -n packages/api/src/middleware/auth.ts | head -80

Repository: PackRat-AI/PackRat

Length of output: 3184


🏁 Script executed:

cat -n packages/api/src/schemas/upload.ts

Repository: PackRat-AI/PackRat

Length of output: 608


🏁 Script executed:

rg "PresignedUploadQuerySchema" packages/api/src/schemas/upload.ts -A 10

Repository: PackRat-AI/PackRat

Length of output: 353


🏁 Script executed:

rg "onError|ErrorResponseSchema" packages/api/src/routes/index.ts | head -20

Repository: PackRat-AI/PackRat

Length of output: 44


Validation and authorization errors incorrectly return 500 instead of 400/403.

All four throw new Error(...) sites (lines 30, 35, 42, 48) are validation or authorization checks that should return 4xx codes, not 500:

  • Line 30: missing fileName or contentType → should be 400
  • Line 35: disallowed content type → should be 400
  • Line 42: invalid/oversized size → should be 400
  • Line 48: filename doesn't start with user.userId → should be 403

Elysia's default error handler maps bare Error to 500. This breaks client status-code-based error handling and hides legitimate access violations in monitoring as server faults.

Use Elysia's status(...) helper (already imported in auth middleware) to return the correct code. Also add 400 and 403 to the route's response schema so Eden Treaty clients get proper typing:

Fix
-    if (!fileName || !contentType) {
-      throw new Error('fileName and contentType are required');
-    }
-    if (!ALLOWED_IMAGE_TYPES.includes(contentType.toLowerCase())) {
-      throw new Error('Invalid content type. Only image files are allowed.');
-    }
-    if (size) {
-      const fileSize = Number.parseInt(String(size), 10);
-      if (Number.isNaN(fileSize) || fileSize <= 0 || fileSize > MAX_FILE_SIZE) {
-        throw new Error('File size must be greater than 0 and not exceed 10MB');
-      }
-    }
-    if (!fileName.startsWith(`${user.userId}-`)) {
-      throw new Error('Unauthorized');
-    }
+    if (!fileName || !contentType) {
+      throw status(400, { error: 'fileName and contentType are required' });
+    }
+    if (!ALLOWED_IMAGE_TYPES.includes(contentType.toLowerCase())) {
+      throw status(400, { error: 'Invalid content type. Only image files are allowed.' });
+    }
+    if (size) {
+      const fileSize = Number.parseInt(String(size), 10);
+      if (Number.isNaN(fileSize) || fileSize <= 0 || fileSize > MAX_FILE_SIZE) {
+        throw status(400, { error: 'File size must be greater than 0 and not exceed 10MB' });
+      }
+    }
+    if (!fileName.startsWith(`${user.userId}-`)) {
+      throw status(403, { error: 'Unauthorized' });
+    }

And update the response schema:

-    response: { 200: PresignedUploadResponseSchema },
+    response: {
+      200: PresignedUploadResponseSchema,
+      400: ErrorResponseSchema,
+      403: ErrorResponseSchema,
+    },

(Import status from 'elysia' and ErrorResponseSchema from the schemas.)

🤖 Prompt for 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.

In `@packages/api/src/routes/upload.ts` around lines 29 - 49, The route currently
throws generic Errors for validation/auth failures in the upload handler (checks
around fileName/contentType, ALLOWED_IMAGE_TYPES, MAX_FILE_SIZE, and the
filename prefix check against user.userId) which Elysia maps to 500; change
those throws to use Elysia's status(...) helper to return 400 for
missing/invalid fileName/contentType, 400 for disallowed content type, 400 for
invalid/oversized size, and 403 for the unauthorized filename prefix; also
import and apply the ErrorResponseSchema in the route's response schema so
400/403 are reflected in the route typing for clients.

Comment thread packages/api/src/routes/user/index.ts Outdated
error: 'Email already in use by another user',
code: 'EMAIL_CONFLICT',
});
throw new Error('Email already in use by another user');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Consider a more specific error type for email conflicts.

Line 77 throws a generic Error for email conflicts, but this should ideally result in a 409 Conflict HTTP response. Ensure your error-handling middleware can distinguish this case, or use a more specific error type (e.g., ConflictError) for clearer intent.

Suggested improvement
-            throw new Error('Email already in use by another user');
+            throw new ConflictError('Email already in use by another user');
+            // Or include status hint in message for middleware:
+            // throw new Error('[409] Email already in use by another user');
🤖 Prompt for 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.

In `@packages/api/src/routes/user/index.ts` at line 77, Replace the generic throw
new Error('Email already in use by another user') with a specific ConflictError
(e.g., create class ConflictError extends Error) and throw new
ConflictError('Email already in use by another user'); then update the central
error-handling middleware to map ConflictError instances to HTTP 409 responses
(check instanceof ConflictError and return res.status(409) with a clear message)
so email conflicts produce a 409 Conflict instead of a generic error.

Comment thread packages/api/src/routes/weather.ts
Comment thread packages/api/src/routes/weather.ts Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Refactors the monorepo type system to derive runtime validation and TypeScript types from a single Zod/Drizzle-driven source, and wires Elysia route response schemas to enable end-to-end typing with Eden Treaty clients. Also updates ID types (e.g., userId) to UUID strings and reduces duplicate schema/type definitions across apps/packages.

Changes:

  • Upgrade to Zod v4 and centralize enum tuples + shared utility types in @packrat/api/types/constants (with @packrat/api/types as a compatibility barrel).
  • Re-export app entity schemas from canonical @packrat/api/schemas/* and remove Expo’s duplicate types/index.ts.
  • Add response schemas + runtime parsing to multiple API routes to enforce response shapes and improve Treaty typing (including stripping sensitive fields like embeddings).

Reviewed changes

Copilot reviewed 49 out of 50 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
packages/overpass/src/schemas.ts Updates Zod record typing for Overpass element tags (Zod v4 signature).
packages/cli/src/args.ts Updates generic Zod type annotation for parsing helpers (Zod v4).
packages/app/src/entities/user/schema.ts Replaces local User schemas with re-exports from @packrat/api/schemas/users.
packages/app/src/entities/trip/schema.ts Replaces local Trip schemas with re-exports from @packrat/api/schemas/trips.
packages/app/src/entities/pack/schema.ts Replaces local Pack schemas with re-exports from @packrat/api/schemas/packs.
packages/app/src/entities/feed/schema.ts Replaces local Feed schemas with re-exports from @packrat/api/schemas/feed.
packages/app/src/entities/catalog/schema.ts Replaces local Catalog schemas with re-exports from @packrat/api/schemas/catalog.
packages/api/src/utils/weight.ts Switches PackItem type import to @packrat/api/types/constants.
packages/api/src/utils/routeParams.ts Changes integer param parsing to transform + refine for int4 range enforcement.
packages/api/src/utils/itemCalculations.ts Switches CatalogItem/PackItem/WeightUnit type imports to @packrat/api/types/constants.
packages/api/src/utils/tests/weight.test.ts Updates PackItem type import and userId fixture type to string.
packages/api/src/utils/tests/itemCalculations.test.ts Updates type imports and userId fixture type to string.
packages/api/src/types/index.ts Turns @packrat/api/types into a barrel re-export of ./constants.
packages/api/src/types/constants.ts Introduces centralized enum tuples + shared Zod schemas/types for utilities and consumers.
packages/api/src/services/packService.ts Switches PACK_CATEGORIES import to @packrat/api/types/constants.
packages/api/src/schemas/upload.ts Expands presigned upload response schema (adds objectKey, publicUrl).
packages/api/src/schemas/packs.ts Switches constants import source; documents datetime coercion behavior.
packages/api/src/schemas/guides.ts Adds GuideCategoriesResponseSchema.
packages/api/src/schemas/catalog.ts Switches constants import source; standardizes created/updated to ISO string coercion.
packages/api/src/routes/weather.ts Adds response schema for forecast and parses output; refactors error handling.
packages/api/src/routes/user/index.ts Adds response schemas and parses outputs for profile/update endpoints; refactors error handling.
packages/api/src/routes/upload.ts Adds response schema + parsing for presigned upload; refactors error handling.
packages/api/src/routes/trips/index.ts Moves request/response validation to shared schemas; adds response schemas; refactors error handling.
packages/api/src/routes/packTemplates/index.ts Adds admin macro usage (adminAuthPlugin + isAdmin: true) for generate-from-online endpoint.
packages/api/src/routes/packs/index.ts Adds response schemas + parsing for selected endpoints; refactors some error handling; strips extra fields via schema parsing.
packages/api/src/routes/guides/index.ts Adds response schemas + parsing for list/search/detail/categories; refactors error handling.
packages/api/src/routes/feed/index.ts Adds FeedResponseSchema response typing and parses feed list responses.
packages/api/src/routes/catalog/index.ts Adds response schemas + parsing for list/categories/get/create/update; refactors error handling; strips embedding via schema parsing.
packages/api/src/db/schema.ts Switches PackCategory/WeightUnit type imports to @packrat/api/types/constants.
package.json Upgrades root Zod dependency range to v4.
bun.lock Updates lockfile to Zod v4 resolution and related dependency graph.
apps/web/lib/types.ts Updates userId fields in shared web types from number → string.
apps/web/lib/data.ts Updates mock data to use string userId values.
apps/expo/utils/weight.ts Switches PackItem type import from Expo-local types to @packrat/api/types/constants.
apps/expo/utils/tests/weight.test.ts Switches PackItem type import to @packrat/api/types/constants.
apps/expo/types/index.ts Removes duplicate Expo-local type/schema definitions.
apps/expo/lib/utils/compute-pack.ts Switches Pack type import to @packrat/api/types/constants.
apps/expo/lib/utils/tests/compute-pack.test.ts Switches Pack/PackItem type imports to @packrat/api/types/constants.
apps/expo/features/packs/types.ts Switches PackCategory/WeightUnit type imports to @packrat/api/types/constants.
apps/expo/features/packs/screens/CreatePackItemForm.tsx Switches WeightUnit type import to @packrat/api/types/constants.
apps/expo/features/packs/packListAtoms.ts Switches PackCategory type import to @packrat/api/types/constants.
apps/expo/features/packs/input.ts Switches WeightUnit type import to @packrat/api/types/constants.
apps/expo/features/packs/components/TemplateItemsSection.tsx Switches WeightUnit type import to @packrat/api/types/constants.
apps/expo/features/pack-templates/screens/CreatePackTemplateItemForm.tsx Switches WeightUnit type import to @packrat/api/types/constants.
apps/expo/features/pack-templates/packTemplateListAtoms.ts Switches PackCategory type import to @packrat/api/types/constants.
apps/expo/features/guides/types.ts Aligns readingTime type to number (matches API schema).
apps/expo/data/mockData.ts Switches User type import to @packrat/api/types/constants.
apps/expo/components/initial/WeightBadge.tsx Switches WeightUnit type import to @packrat/api/types/constants.
apps/expo/components/initial/UserAvatar.tsx Switches User type import to @packrat/api/types/constants.
apps/expo/app/(app)/current-pack/[id].tsx Switches PackItem type import to @packrat/api/types/constants.
Comments suppressed due to low confidence (1)

packages/api/src/routes/packs/index.ts:690

  • Authorization failures in this endpoint throw a generic Error('Unauthorized'), which will be returned as a 500 by the global onError handler. This should be a 403 (or 401) so clients can handle it correctly and so it doesn’t look like a server outage.
      const isOwner = item.userId === user.userId;
      const isPublic = item.pack.isPublic;

      if (!isOwner && !isPublic) throw new Error('Unauthorized');

      return PackItemSchema.parse(item);

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 133 to 138
const idParam = query.id;
const id = Number(idParam);

if (!idParam || Number.isNaN(id)) {
return status(400, { error: 'Valid location ID is required' });
throw new Error('Valid location ID is required');
}
Comment on lines 69 to 78
if (email) {
const [existingUser] = await db
.select({ id: users.id })
.from(users)
.where(eq(users.email, email.toLowerCase()))
.limit(1);

if (existingUser && existingUser.id !== user.userId) {
return status(409, {
error: 'Email already in use by another user',
code: 'EMAIL_CONFLICT',
});
throw new Error('Email already in use by another user');
}
Comment thread packages/api/src/routes/trips/index.ts Outdated
Comment on lines 184 to 188
});

if (!trip) return status(404, { error: 'Trip not found' });
if (trip.userId !== user.userId) return status(403, { error: 'Forbidden' });
if (!trip) throw new NotFoundError('Trip not found');
if (trip.userId !== user.userId) throw new Error('Forbidden');

Comment on lines 27 to 49
const { fileName, contentType, size } = query;

if (!fileName || !contentType) {
return status(400, { error: 'fileName and contentType are required' });
throw new Error('fileName and contentType are required');
}

// Validate content type - only allow images
if (!ALLOWED_IMAGE_TYPES.includes(contentType.toLowerCase())) {
return status(400, { error: 'Invalid content type. Only image files are allowed.' });
throw new Error('Invalid content type. Only image files are allowed.');
}

// Validate file size - max 10MB
if (size) {
const fileSize = Number.parseInt(String(size), 10);
if (Number.isNaN(fileSize) || fileSize <= 0 || fileSize > MAX_FILE_SIZE) {
return status(400, { error: 'File size must be greater than 0 and not exceed 10MB' });
throw new Error('File size must be greater than 0 and not exceed 10MB');
}
}

// Security check: Ensure the filename starts with the user's ID
if (!fileName.startsWith(`${user.userId}-`)) {
return status(403, { error: 'Unauthorized' });
throw new Error('Unauthorized');
}
Comment on lines 201 to 205
async ({ query }) => {
const { q, page, limit, category } = query;
if (!q || q.trim() === '') {
return status(400, { error: 'Search query parameter q is required' });
throw new Error('Search query parameter q is required');
}
Comment on lines 224 to 231
const db = createDb();
const data = body;
if (!data.name || data.weight === undefined || data.weight === null || !data.weightUnit) {
return status(400, { error: 'name, weight, and weightUnit are required' });
throw new Error('name, weight, and weightUnit are required');
}
if (data.weight <= 0) {
return status(400, { error: 'weight must be a positive number' });
throw new Error('weight must be a positive number');
}
Comment on lines 93 to 118
const db = createDb();
const data = body;

const packId = data.id as string;
if (!packId) return status(400, { error: 'Pack ID is required' });
if (!packId) throw new Error('Pack ID is required');

// Zod validates all fields at runtime; cast through the Standard Schema
// inference gap so drizzle's insert accepts the values.
const [newPack] = await db
.insert(packs)
.values({
id: packId,
userId: user.userId,
name: data.name,
description: data.description,
category: data.category,
isPublic: data.isPublic,
image: data.image,
tags: data.tags,
localCreatedAt: new Date(data.localCreatedAt as string),
localUpdatedAt: new Date(data.localUpdatedAt as string),
} as typeof packs.$inferInsert)
.returning();

if (!newPack) return status(400, { error: 'Failed to create pack' });
if (!newPack) throw new Error('Failed to create pack');

Comment thread packages/api/src/utils/routeParams.ts Outdated
Comment on lines +11 to +17
export const integerIdSchema = z
.string()
.regex(/^[1-9]\d*$/)
.pipe(z.coerce.number().int().positive().max(PG_INT4_MAX));
.transform((s) => parseInt(s, 10))
.refine((n) => n > 0 && n <= PG_INT4_MAX, {
message: `Must be a positive integer up to ${PG_INT4_MAX}`,
});
Comment on lines 14 to 18
export const PresignedUploadResponseSchema = z.object({
url: z.string().url(),
url: z.string(),
objectKey: z.string(),
publicUrl: z.string(),
});
Comment thread package.json Outdated
"ts-extras": "^1.0.0",
"typescript": "~5.9.2",
"zod": "^3.24.2"
"zod": "^4.0.0"
@andrew-bierman andrew-bierman force-pushed the refactor/unify-schema-type-system branch from b095372 to b59605c Compare May 14, 2026 15:05
@cloudflare-workers-and-pages
Copy link
Copy Markdown
Contributor

cloudflare-workers-and-pages Bot commented May 14, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
packrat-admin 3cd4af4 Commit Preview URL

Branch Preview URL
May 17 2026, 02:52 AM

@github-actions github-actions Bot removed the dependencies Pull requests that update a dependency file label May 14, 2026
- expo/current-pack: switch PackItem import to expo features type so
  pack.items matches without the misleading `as unknown as` double cast.
- api/weather: handle ZodError separately when forecast response fails
  validation, surfacing the invalid paths instead of a generic throw.
- api/catalog: remove redundant manual validations on POST/PUT (now
  fully enforced by Create/UpdateCatalogItemRequestSchema). Make
  UpdateCatalogItemRequestSchema.weight positive to match Create.
  Clarify config-error message when OPENAI_API_KEY is missing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@andrew-bierman
Copy link
Copy Markdown
Collaborator Author

Addressed the remaining CodeRabbit + Copilot review comments in commit c8f23e953:

Fixed:

  • apps/expo/app/(app)/current-pack/[id].tsx: replaced item as unknown as PackItem double-cast by switching PackItem import to expo-app/features/packs/types (the actual store shape) — both the cast and the misleading comment removed.
  • packages/api/src/routes/weather.ts: added ZodError-specific handling in the forecast catch — logs error.errors and includes invalid paths in the thrown message.
  • packages/api/src/schemas/catalog.ts: tightened UpdateCatalogItemRequestSchema.weight to z.number().positive().optional().
  • packages/api/src/routes/catalog/index.ts: removed redundant manual name/weight/weightUnit/<=0 checks on POST and the weight <= 0 check on PUT — schemas now enforce. Clarified the OPENAI_API_KEY missing message as a configuration error.

Verified already addressed in b59605cc3:

  • weather id validation already uses status(400, ...).
  • guides search q already returns 400 + the schema enforces min(1).
  • upload validation/auth all branches return status(...) correctly.

Left as-is:

  • The OPENAI_API_KEY-missing and similar config-error paths kept as throw new Error(...) rather than status(503, ...) — every route's response schema currently declares only 200, so adding 503 entries would be a much larger non-minimal change. Open to revisiting if a follow-up unifies route response schemas.

bun check-types and bun check (biome) both clean locally. Pre-push lefthook (clean-checks, schema sort, route-schemas, type-casts) all passed.

@andrew-bierman andrew-bierman changed the base branch from main to development May 17, 2026 02:42
Now targeting development (retargeted from main). Resolved 2 conflicts:
- current-pack/[id].tsx: kept the local expo-app/features/packs/types PackItem
  since usePackDetailsFromStore returns the local-store shape (description?:
  string vs @packrat/types' description: string | null). Will revisit once
  the store is migrated to the unified type.
- catalog/index.ts: kept HEAD's pattern of relying on schema validation and
  throwing for config errors (3 hunks: dropped dev's redundant data.name/weight
  manual checks; restored HEAD's 'throw new Error' for missing OPENAI_API_KEY).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages
Copy link
Copy Markdown
Contributor

cloudflare-workers-and-pages Bot commented May 17, 2026

Deploying packrat-guides with  Cloudflare Pages  Cloudflare Pages

Latest commit: 3cd4af4
Status: ✅  Deploy successful!
Preview URL: https://bf389822.packrat-guides-6gq.pages.dev
Branch Preview URL: https://refactor-unify-schema-type-s.packrat-guides-6gq.pages.dev

View logs

@github-actions github-actions Bot removed the database label May 17, 2026
@cloudflare-workers-and-pages
Copy link
Copy Markdown
Contributor

Deploying packrat-landing with  Cloudflare Pages  Cloudflare Pages

Latest commit: 3cd4af4
Status: ✅  Deploy successful!
Preview URL: https://b971962d.packrat-landing.pages.dev
Branch Preview URL: https://refactor-unify-schema-type-s.packrat-landing.pages.dev

View logs

@andrew-bierman andrew-bierman merged commit e5a8f95 into development May 17, 2026
14 of 17 checks passed
@andrew-bierman andrew-bierman deleted the refactor/unify-schema-type-system branch May 17, 2026 05:22
andrew-bierman pushed a commit that referenced this pull request May 17, 2026
Resolves conflict in packages/api/src/routes/trailConditions/reports.ts
caused by #2414 schema refactor (schemas moved to @packrat/schemas).
Re-applies #2434's .optional().default() thickening to the new schema
location (packages/schemas/src/trailConditions.ts).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
andrew-bierman pushed a commit that referenced this pull request May 17, 2026
…factor)

Resolves conflicts from #2414 (extract @packrat/db + @packrat/schemas):
- Route files: keep dev's @packrat/schemas imports, retain mintId + queryBoolean
- packages/schemas/src/catalog.ts: keep my T6 CatalogCompare* schemas + dev's CatalogETLSchema
- packages/schemas/src/packs.ts: apply T9 (id optional with trim/min(1)) to CreatePackBody + AddPackItemBody
- packages/schemas/src/trips.ts: apply T9 to CreateTripBodySchema
- packages/schemas/src/trailConditions.ts: apply T9 + T-thickening (drop .default for hazards/waterCrossings/photos)
- packs/index.ts: keep AddPackItemFromCatalogSchema (T8) local, drop redundant CreatePack/AddPackItem local schemas (now in @packrat/schemas)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
andrew-bierman added a commit that referenced this pull request May 18, 2026
Phase 1 of the client/server ID split per docs/design/client-uuid-split.md §3
Option C. Each affected table gains a `client_uuid text UNIQUE NOT NULL`
column, backfilled from the existing `id`, with a format CHECK constraint
enforcing the URL-safe nanoid charset (≤64 chars).

Tables touched: packs, pack_items, weight_history, pack_templates,
pack_template_items, trips, trail_condition_reports.

Also restores packages/api/src/db/schema.ts as a 1-line re-export shim
(load-bearing for drizzle-kit per the #2414 plan §"Migration Infra"). The
shim was deleted in 0154b87 along with all other re-export shims, which
silently broke drizzle-kit generate since 2026-05-14.

Refs: docs/plans/2026-05-17-001-feat-client-uuid-split-phase1-plan.md U1

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
andrew-bierman added a commit that referenced this pull request May 20, 2026
Substantial rebase covering 225 dev commits — #2414 type unification,
#2422 single-param refactor, #2433 MCP+CLI Eden Treaty rewrite, #2439 OG
meta validation, #2441/#2442 OG URL fix, plus many smaller.

Conflicts resolved:
- apps/expo/features/packs/utils/uploadImage.ts: kept HEAD's userId
  cache, used dev's object-arg getPresignedUrl call (matches function
  signature).
- apps/expo/features/trips/hooks/useDeleteTrip.ts: kept HEAD's async +
  optimistic-delete comment, used dev's object-arg obs() call (matches
  current obs signature in apps/expo/lib/store.ts).

Post-merge cleanup of dev-introduced single-param violations:
- apps/expo/lib/utils/__tests__/getRelativeTime.test.ts: rewrote 3 test
  call sites to object args matching the refactored getRelativeTime.
- packages/api/src/utils/__tests__/embeddingHelper.test.ts: rewrote 7
  test call sites to object args matching the refactored
  getEmbeddingText; updated Parameters<> type indexes.
- packages/overpass/src/client.test.ts: converted makeResponse to
  single object param and updated all 11 call sites.
- scripts/lint/no-owned-max-params.ts: added
  apps/trails/scripts/generate-og-images.ts to EXCLUDED_FILES (same
  globalThis.fetch shim pattern as the existing landing/guides
  entries).

Verification: bun install ok; bun check-types 0 errors; biome check
0 errors (2 unrelated warnings); no-owned-max-params 0 violations.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
andrew-bierman added a commit that referenced this pull request May 21, 2026
…rite migrations

Hand-written SQL migrations drift across environments and break the
unified drizzle-schema → drizzle-zod → inferred-TS-types pipeline
established by PR #2414. Drizzle-kit is the only sanctioned path:

  1. Change schema in packages/db/src/schema/*.ts
  2. bun --cwd packages/db drizzle-kit generate
  3. Review the generated SQL
  4. Commit both schema + migration together

If drizzle-kit emits a migration you disagree with, fix the schema or
the generator config, not the SQL output.

Also updates the Database section to reflect the post-extraction
schema location (`packages/db/src/schema/` instead of
`packages/api/src/db/schema.ts`).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants