Skip to content

Configurable Monorepo: User-zone config, route hooks, and config validation - #125

Merged
thinkdj merged 7 commits into
copilot/make-monorepo-flexiblefrom
claude/shareable-monorepo-BvhnT
Feb 26, 2026
Merged

thinkdj merged 7 commits into
copilot/make-monorepo-flexiblefrom
claude/shareable-monorepo-BvhnT

Conversation

@thinkdj

@thinkdj thinkdj commented Feb 26, 2026

Copy link
Copy Markdown
Owner
  • User-zone file separation: ottabase.config.ts, wrangler.jsonc, and ottabase/ are now gitignored with tracked .example/.template counterparts, so framework updates never overwrite user customizations
  • Route registration hook: Custom/premium packages can register API routes via ottabase/config.routes.ts without editing framework-owned router.ts — mirrors the existing config.migrations.ts pattern for tables
  • Config validation at startup: defineOttabaseConfig() now throws on missing required fields (appId, appName) and warns on unrecognized keys at all nesting levels (catches typos like packges or features.authBehaviour that previously fell silently to defaults)
  • Config pipeline fixes: Email from/sesRegion and auth behavior flags (sessionMaxAge, requireEmailVerified, disableCredentials, verbose) moved from env vars to ottabase.config.ts with env var override kept for backward compat
  • Package route guards: Built-in package routes (ottablog, shortlinks, referrals, brandEngine) return 404 with PACKAGE_DISABLED code when toggled off in config

Key files

File Change
ottabase.template/config.routes.ts New — user-zone route registration scaffold
worker/routes/types.ts New — shared ApiRouteContext type (avoids circular imports)
worker/routes/router.ts Wires custom routes as last fallback in resolveApiRoute()
packages/config/src/createAppConfig.ts Adds validateOttabaseConfig() with 3-level-deep key checking
ottabase.config.example.ts Replaces ottabase.config.ts as tracked template
wrangler.example.jsonc Replaces wrangler.jsonc as tracked template
.gitignore Ignores user working copies

Test plan

  • 34 config tests pass (12 new validation tests)
  • pnpm dev:worker starts without config validation errors
  • Typo in ottabase.config.ts (e.g. packges) produces [ottabase] Unknown key warning
  • Missing appId throws clear error at startup
  • Custom route in ottabase/config.routes.ts responds correctly
  • Disabled package routes return 404 PACKAGE_DISABLED

claude and others added 6 commits February 26, 2026 18:56
…ute guards

The shareable monorepo config migration was incomplete in two key areas:

1. Config pipeline gap: email and authBehavior settings defined in
   ottabase.config.ts were silently ignored because AppConfig, ConfigOptions,
   createAppConfig(), and userConfigToOptions() all lacked support for these
   fields. Now the full chain works: ottabase.config.ts → userConfigToOptions →
   createAppConfig → AppConfig with env var overrides for backward compat.

2. Package route guarding: disabling a package via packages.ottablog: false
   only removed its DB tables from migrations but left API routes active,
   causing 500 errors on access. Now all package-specific routes in the
   worker router check PACKAGES toggles and return a clear 404 with
   PACKAGE_DISABLED code when the package is off.

Additional fixes:
- Migrate queue/handlers.ts from raw env vars to worker-config imports
- Update bootstrap wizard to reflect EMAIL_FROM moving to ottabase.config.ts
- Update email route provider info to reference config instead of env vars
- Add DEFAULT_EMAIL_CONFIG, DEFAULT_AUTH_BEHAVIOR_CONFIG constants
- Export AUTH_BEHAVIOR_CONFIG and EMAIL_CONFIG from app.config.ts
- Add PACKAGES toggle export to worker-config.ts

https://claude.ai/code/session_01VrsgTM282BW4TFws7ZfnkE
…uthBehavior/email config tests (#124)

* fix: address PR review comments - unused imports, email API backward compat, add config tests
Separate framework-tracked files from user-owned files so `git pull`
never overwrites user customisations:

- ottabase.config.ts → ottabase.config.example.ts (tracked template)
- wrangler.jsonc → wrangler.example.jsonc (tracked; CI uses this)
- ottabase/ → ottabase.template/ (tracked reference copy)

User working copies (ottabase.config.ts, wrangler.jsonc, ottabase/)
are gitignored. First-time setup: copy from .example/.template.

Updates CI workflows (deploy.yml, pr-preview.yml), cloudflare-config.json,
and README with new setup instructions and directory structure.

https://claude.ai/code/session_01VrsgTM282BW4TFws7ZfnkE
Custom packages can now register API routes without editing the
framework-owned router.ts. Users add handlers in the user-zone
file `ottabase/config.routes.ts` (same pattern as config.migrations.ts).

- Extract ApiRouteContext to worker/routes/types.ts (shared type)
- Add ottabase.template/config.routes.ts with scaffold + docs
- Wire handleCustomRoutes() into resolveApiRoute() as fallback
  after all built-in routes
- Update README with route registration in premium package workflow

https://claude.ai/code/session_01VrsgTM282BW4TFws7ZfnkE
- types.ts: add field-level JSDoc on ApiRouteContext with usage example
- ottabase.config.example.ts: mention config.routes.ts in premium package steps
- config.migrations.ts: cross-reference config.routes.ts in setup steps
- README: add dedicated "Custom Routes" section with example + resolution order
- README: update File Ownership table to include routes

https://claude.ai/code/session_01VrsgTM282BW4TFws7ZfnkE
Typos in ottabase.config.ts no longer silently fall to defaults.
defineOttabaseConfig() now validates at startup:
- Throws on missing required fields (appId, appName)
- Warns on unrecognised keys at all nesting levels (top-level,
  packages, features, features.authBehavior, email, etc.)

Adds validateOttabaseConfig() as a standalone export for testing.
12 new tests covering required fields, typo detection at every
nesting level, multiple warnings, and integration with defineOttabaseConfig.

https://claude.ai/code/session_01VrsgTM282BW4TFws7ZfnkE
Copilot AI review requested due to automatic review settings February 26, 2026 19:23

Copilot AI 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.

Pull request overview

This pull request introduces a configurable monorepo architecture with user-zone file separation, custom route registration, and config validation. The key innovation is gitignoring ottabase.config.ts, wrangler.jsonc, and the ottabase/ directory (which contain user customizations) while providing tracked .example and .template counterparts, ensuring framework updates never overwrite user code.

Changes:

  • User-zone file separation: Three user-owned areas (ottabase.config.ts, wrangler.jsonc, ottabase/) are now gitignored with tracked templates, preventing framework updates from overwriting customizations
  • Config validation: defineOttabaseConfig() validates required fields and warns on typos via recursive key checking (3 levels deep)
  • Route registration hook: Custom packages can register API routes via ottabase/config.routes.ts using the new ApiRouteContext type, called as final fallback in the router
  • Template scaffolding: Queue handlers, Todo model, migration system, and helper utilities provided in ottabase.template/ as starting point
  • CI/CD updates: Workflows updated to use wrangler.example.jsonc (tracked) instead of wrangler.jsonc (gitignored)

Reviewed changes

Copilot reviewed 15 out of 26 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
packages/config/src/createAppConfig.ts Adds validateOttabaseConfig() with 3-level recursive key validation; warns on typos, throws on missing required fields
packages/config/src/__tests__/config.test.ts 12 new validation tests covering required fields, typos, and nested key warnings
packages/config/src/index.ts Exports validateOttabaseConfig for external use
worker/routes/types.ts New file defining shared ApiRouteContext interface to avoid circular imports
worker/routes/router.ts Imports custom routes handler and calls it as final fallback after built-in routes
ottabase.template/config.routes.ts User-zone scaffold for custom route registration with documentation
ottabase.template/config.migrations.ts Updated comments referencing route registration in config.routes.ts
ottabase.template/queue/index.ts Queue handler registry with DLQ support, stats tracking, and KV persistence
ottabase.template/queue/handlers.ts Email, order processing, report generation, and sync job handlers
ottabase.template/models/Todo.ts Example app-specific fat model with helper methods
ottabase.template/models/Todo.schema.ts Drizzle schema for todos table
ottabase.template/migrations/index.ts Migration registry combining core, app, and package migrations
ottabase.template/migrations/README.md Comprehensive migration system documentation (337 lines)
ottabase.template/helpers/referral-attribution.ts Server-side referral attribution helper with context capture
ottabase.template/helpers/__tests__/referral-attribution.test.ts Test coverage for referral attribution edge cases
ottabase.template/db/schemas-helper.ts Collects schemas from core, app, and package sources for autoInit
ottabase.template/db/schema.ts Re-exports all table schemas for drizzle-kit
ottabase.config.example.ts Updated template with validation documentation and route registration instructions
wrangler.example.jsonc Updated header comments explaining CI usage and local workflow
cloudflare-config.json Changed wranglerConfig to point to wrangler.example.jsonc
.gitignore Adds gitignore entries for ottabase.config.ts, wrangler.jsonc, and ottabase/ directory
README.md Major updates: first-time setup section, file ownership table, custom routes documentation, directory structure
.github/workflows/pr-preview.yml Updates wrangler config references from wrangler.jsonc to wrangler.example.jsonc
.github/workflows/deploy.yml Updates wrangler config references and comments
.github/README.md Documentation updates for new wrangler config pattern

Comment on lines +309 to +332
describe('defineOttabaseConfig – validation integration', () => {
it('should log warnings for unknown keys', () => {
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
defineOttabaseConfig({
appId: 'test',
appName: 'Test',
packges: { ottablog: true }, // typo
} as any);
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('Unknown key "packges"'));
warnSpy.mockRestore();
});

it('should throw on missing required fields', () => {
expect(() => defineOttabaseConfig({ appId: '', appName: 'Test' } as any)).toThrow('"appId" is required');
});

it('should still return the config object on success', () => {
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
const cfg = defineOttabaseConfig({ appId: 'ok', appName: 'OK' });
expect(cfg.appId).toBe('ok');
expect(cfg.appName).toBe('OK');
warnSpy.mockRestore();
});
});

Copilot AI Feb 26, 2026

Copy link

Choose a reason for hiding this comment

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

The console.warn spy is created and restored within individual test cases, but if a test fails or throws an error before reaching warnSpy.mockRestore(), the spy will leak into subsequent tests. According to the stored memory about this codebase, "Global vitest setup does not call vi.restoreAllMocks()/vi.resetAllMocks() between tests; individual tests must restore spies/mocks they create."

Consider using afterEach to ensure cleanup happens even when tests fail:

describe('defineOttabaseConfig – validation integration', () => {
    let warnSpy: ReturnType<typeof vi.spyOn>;

    beforeEach(() => {
        warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
    });

    afterEach(() => {
        warnSpy?.mockRestore();
    });

    // tests...
});

This follows the same pattern needed elsewhere in the codebase due to the global setup not handling mock restoration.

Copilot uses AI. Check for mistakes.
Comment thread apps/ottabase-template-app-tanstack/worker/routes/router.ts Outdated
Comment thread packages/config/src/createAppConfig.ts Outdated
Comment thread packages/config/src/createAppConfig.ts Outdated
Add a setup script and npm hooks to ensure user-zone files exist before dev/build. Introduce scripts/setup-user-zone.js which copies ottabase.template → ottabase and wrangler.example.jsonc → wrangler.jsonc when targets are missing, and wire it into package.json (setup, predev, prebuild). Make worker/router load user custom routes via dynamic import with a no-op fallback to avoid crashes when the user-zone is not present. Tighten createAppConfig validation to require non-empty (trimmed) appId and appName. Update tests to centralize console.warn spying with beforeEach/afterEach and adjust import ordering.
@thinkdj
thinkdj merged commit 6e73f78 into copilot/make-monorepo-flexible Feb 26, 2026
4 of 5 checks passed
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.

4 participants