Abstract
This stack gives Naly a typed publication control plane: Drizzle ORM captures article, prediction, source, social-post, reward, and cron records as relational entities in Neon Postgres, instead of scattered runtime state. Because each workflow step is stored as explicit rows and enum states, Naly can re-run cron pipelines safely, recover from partial failures, and keep editor-facing UI data aligned with API-visible state. As of June 26, 2026, this is the project’s operationally durable contract for prediction publishing.
Where it sits in Naly
In the current stack, publishing behavior is shared across several Next.js app paths and cron jobs, all of which resolve to the same database contract. The package set already in play—next@16.0.7, react@19.2.1, drizzle-orm@^0.44.7, @neondatabase/serverless@^1.0.2, tsx@^4.21.0, typescript@^5.9.3—matches a serverful-ish ORM layer running inside server-side routes/actions and scheduled workers.
Naly stores these domains as first-class tables:
- articles and prediction records
- source URLs and provenance snapshots
- social posts + distribution metadata
- reward scores, calibration metadata, and audit fields
- cron-run metadata and publication checkpoints
The value is not just persistence; it is shared semantics. Every renderer, worker, and API action reads the same publication state model and can coordinate without hidden cross-process flags.
Technical mechanism
Drizzle provides a schema-first path for this kind of shared semantics. The Drizzle guides show the canonical flow: define schema in TypeScript, configure a Neon's DATABASE_URL, initialize drizzle, and use generated query types for inserts/reads/updates (overview docs, Neon tutorial).
A minimal Naly-style driver initialization pattern looks like this:
import { drizzle } from 'drizzle-orm/neon-http';
import { pgTable, text, timestamp, pgEnum, integer } from 'drizzle-orm/pg-core';
export const publicationState = pgEnum('publication_state', [
'queued', 'draft_ready', 'published', 'failed',
]);
export const publications = pgTable('publications', {
id: text('id').primaryKey(),
slug: text('slug').notNull().unique(),
state: publicationState('state').notNull(),
stateVersion: integer('state_version').notNull().default(1),
stateChangedAt: timestamp('state_changed_at').notNull().defaultNow(),
});
const db = drizzle(process.env.DATABASE_URL!);
This mirrors Drizzle docs that support drizzle-orm/neon-http and drizzle-orm/neon-serverless as transport choices for Neon, with HTTP for one-shot workloads and WebSocket-like session behavior for interactive transaction work when needed. Drizzle’s pg-core definitions also enable typed inference ($inferInsert, $inferSelect) so publication payloads are validated by TypeScript at compile time while preserving flexible JSON for non-critical metadata.
For Naly, the crucial architectural pattern is this:
- define state transitions (
queued -> drafted -> approved -> published -> archived) as explicit rows, - keep transition logic in one server-only module,
- log each mutation with idempotency keys (job id + state hash),
- run critical transitions inside database transactions where atomicity matters,
- generate immutable artifacts (e.g., blob URLs, social copy, schema snapshots) only after state is advanced.
The effect is similar to a tiny finite-state machine persisted in Postgres with strict schema contracts rather than implicit behavior inside cron workers.
What the literature says
Primary documentation frames this as a practical design choice for serverless systems:
- Drizzle presents itself as SQL-like by design and serverless-ready, reducing ORM abstraction overhead for teams that want direct SQL semantics while keeping type safety (Drizzle overview).
- The Drizzle/Neon tutorials explicitly support native Neon HTTP/WebSocket driver combinations and typed schema-first modeling, including
@neondatabase/serverlessintegration and type inference examples (Drizzle with Neon, Get Started with Drizzle + Neon). - Drizzle’s connection matrix shows explicit runtime-driver separation, so teams can match execution mode to workload and runtime constraints (Database connection docs).
- Neon’s own driver documentation and edge guidance emphasize that serverless/postgres access is often achieved via HTTP or websocket-based proxying, which is precisely why edge execution decisions must be explicit per workload (Neon serverless driver, How to use Postgres at the edge).
On the schema side, research on schema evolution shows why explicit migration and state models matter. Tesseract argues schema evolution can be treated as a first-class transactional operation and that robust systems should minimize downtime by design (online schema evolution). EvoSchema shows schema changes—especially table-level perturbations—can destabilize downstream behavior, which is a strong warning against casual ad-hoc additions to publication/state tables (EvoSchema).
Design trade-offs
Naly’s choice is effectively a trade between strictness and friction. Strongly typed schemas and explicit state enums improve observability and reliability, but they also increase upfront modeling cost and require migration discipline. The trade curve is favorable when publication logic becomes shared across cron workers, AI pipelines, and public page renderers.
- Driver choice:
neon-httpis simpler and often faster for one-shot operations;neon-serverlessis better when interactive sessions are required. - Schema-first design: compile-time safety reduces runtime errors, but schema changes require migration planning and can surface as blocked deploys if tests do not cover state transitions.
- Runtime portability: Neon's edge-friendly driver model expands deployment options, but transport mode affects session behavior, latency profile, and authentication/TLS path.
- Type fidelity vs operational convenience: explicit enums prevent invalid states but can slow late-night hotfixes if migration scripts are not pre-approved.
Failure modes
- Wrong driver for workload: using one-shot HTTP queries for multi-step state transitions can lose atomic guarantees and produce partial publication states.
- Schema drift between workers and deploys: if
publication_statevalues change without coordinated migrations, old cron code may write invalid states. - Non-idempotent retries: cron restarts can duplicate social writes unless checkpoints are idempotent and unique by run-id.
- Migration lag: scheduled jobs running against stale schema snapshots can fail, especially during rolling deploys.
- Edge cold-start + auth overhead: in constrained edge tiers, repeated connection setup can increase latency and produce false timeouts unless timeout budgets and job fanout are tuned.
For this topic, failure analysis should treat publication state as a correctness artifact, not implementation detail. Every state transition must be replay-safe.
Implementation notes
- Keep schema files central and versioned; regenerate migration artifacts from a single source of truth.
- Separate mutable domain tables from immutable artifact tables.
- Model publication transitions as a transaction boundary and enforce invariants (e.g., no direct
queued -> publishedjump). - Store scheduler metadata (
last_run,next_run_at,error_count) in its own table for alerting and audit. - Prefer server-only modules for DB initialization (
DATABASE_URLfrom.env.localin cron/runtime environments). - Use structured queries over raw SQL for most operations, and reserve raw SQL for migration seeds or reporting.
- Treat
stateVersionand event logs as compatibility bridges when schema evolves.
References
- Drizzle ORM - Drizzle with Neon Postgres
- Drizzle ORM Overview
- Drizzle ORM - Database connection
- Get Started with Drizzle and Neon
- Neon serverless driver (Neon Docs)
- Neon blog: How to use Postgres at the Edge
- Online Schema Evolution is (Almost) Free for Snapshot Databases
- EvoSchema: Towards Text-to-SQL Robustness Against Schema Evolution