diff --git a/drizzle/0009_married_smasher.sql b/drizzle/0009_married_smasher.sql new file mode 100644 index 0000000..c5a3b90 --- /dev/null +++ b/drizzle/0009_married_smasher.sql @@ -0,0 +1,14 @@ +ALTER TABLE "aliases" ALTER COLUMN "created_at" SET DATA TYPE timestamp with time zone;--> statement-breakpoint +ALTER TABLE "edge_embeddings" ALTER COLUMN "created_at" SET DATA TYPE timestamp with time zone;--> statement-breakpoint +ALTER TABLE "edges" ALTER COLUMN "created_at" SET DATA TYPE timestamp with time zone;--> statement-breakpoint +ALTER TABLE "node_embeddings" ALTER COLUMN "created_at" SET DATA TYPE timestamp with time zone;--> statement-breakpoint +ALTER TABLE "node_metadata" ALTER COLUMN "created_at" SET DATA TYPE timestamp with time zone;--> statement-breakpoint +ALTER TABLE "nodes" ALTER COLUMN "created_at" SET DATA TYPE timestamp with time zone;--> statement-breakpoint +ALTER TABLE "scratchpads" ALTER COLUMN "updated_at" SET DATA TYPE timestamp with time zone;--> statement-breakpoint +ALTER TABLE "scratchpads" ALTER COLUMN "created_at" SET DATA TYPE timestamp with time zone;--> statement-breakpoint +ALTER TABLE "source_links" ALTER COLUMN "created_at" SET DATA TYPE timestamp with time zone;--> statement-breakpoint +ALTER TABLE "sources" ALTER COLUMN "last_ingested_at" SET DATA TYPE timestamp with time zone;--> statement-breakpoint +ALTER TABLE "sources" ALTER COLUMN "created_at" SET DATA TYPE timestamp with time zone;--> statement-breakpoint +ALTER TABLE "sources" ALTER COLUMN "deleted_at" SET DATA TYPE timestamp with time zone;--> statement-breakpoint +ALTER TABLE "user_profiles" ALTER COLUMN "last_updated_at" SET DATA TYPE timestamp with time zone;--> statement-breakpoint +ALTER TABLE "user_profiles" ALTER COLUMN "created_at" SET DATA TYPE timestamp with time zone; \ No newline at end of file diff --git a/drizzle/meta/0009_snapshot.json b/drizzle/meta/0009_snapshot.json new file mode 100644 index 0000000..c8ae628 --- /dev/null +++ b/drizzle/meta/0009_snapshot.json @@ -0,0 +1,1059 @@ +{ + "id": "82ac1333-e0c0-4179-900f-35e92b5754af", + "prevId": "ec92429f-cdba-4dc8-a78d-eb69e7ea7a46", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.aliases": { + "name": "aliases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "alias_text": { + "name": "alias_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "canonical_node_id": { + "name": "canonical_node_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "aliases_user_id_users_id_fk": { + "name": "aliases_user_id_users_id_fk", + "tableFrom": "aliases", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "aliases_canonical_node_id_nodes_id_fk": { + "name": "aliases_canonical_node_id_nodes_id_fk", + "tableFrom": "aliases", + "tableTo": "nodes", + "columnsFrom": [ + "canonical_node_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.edge_embeddings": { + "name": "edge_embeddings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "edge_id": { + "name": "edge_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(1024)", + "primaryKey": false, + "notNull": true + }, + "model_name": { + "name": "model_name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "edge_embeddings_embedding_idx": { + "name": "edge_embeddings_embedding_idx", + "columns": [ + { + "expression": "embedding", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": {} + }, + "edge_embeddings_edge_id_idx": { + "name": "edge_embeddings_edge_id_idx", + "columns": [ + { + "expression": "edge_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "edge_embeddings_edge_id_edges_id_fk": { + "name": "edge_embeddings_edge_id_edges_id_fk", + "tableFrom": "edge_embeddings", + "tableTo": "edges", + "columnsFrom": [ + "edge_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.edges": { + "name": "edges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_node_id": { + "name": "source_node_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_node_id": { + "name": "target_node_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "edge_type": { + "name": "edge_type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "edges_user_id_source_node_id_idx": { + "name": "edges_user_id_source_node_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_node_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "edges_user_id_target_node_id_idx": { + "name": "edges_user_id_target_node_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_node_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "edges_user_id_edge_type_idx": { + "name": "edges_user_id_edge_type_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "edge_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "edges_user_id_users_id_fk": { + "name": "edges_user_id_users_id_fk", + "tableFrom": "edges", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "edges_source_node_id_nodes_id_fk": { + "name": "edges_source_node_id_nodes_id_fk", + "tableFrom": "edges", + "tableTo": "nodes", + "columnsFrom": [ + "source_node_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "edges_target_node_id_nodes_id_fk": { + "name": "edges_target_node_id_nodes_id_fk", + "tableFrom": "edges", + "tableTo": "nodes", + "columnsFrom": [ + "target_node_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "edges_sourceNodeId_targetNodeId_edge_type_unique": { + "name": "edges_sourceNodeId_targetNodeId_edge_type_unique", + "nullsNotDistinct": false, + "columns": [ + "source_node_id", + "target_node_id", + "edge_type" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.node_embeddings": { + "name": "node_embeddings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "node_id": { + "name": "node_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(1024)", + "primaryKey": false, + "notNull": true + }, + "model_name": { + "name": "model_name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "node_embeddings_embedding_idx": { + "name": "node_embeddings_embedding_idx", + "columns": [ + { + "expression": "embedding", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": {} + }, + "node_embeddings_node_id_idx": { + "name": "node_embeddings_node_id_idx", + "columns": [ + { + "expression": "node_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "node_embeddings_node_id_nodes_id_fk": { + "name": "node_embeddings_node_id_nodes_id_fk", + "tableFrom": "node_embeddings", + "tableTo": "nodes", + "columnsFrom": [ + "node_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.node_metadata": { + "name": "node_metadata", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "node_id": { + "name": "node_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "canonical_label": { + "name": "canonical_label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "additional_data": { + "name": "additional_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "node_metadata_node_id_idx": { + "name": "node_metadata_node_id_idx", + "columns": [ + { + "expression": "node_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "node_metadata_canonical_label_idx": { + "name": "node_metadata_canonical_label_idx", + "columns": [ + { + "expression": "canonical_label", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "node_metadata_node_id_nodes_id_fk": { + "name": "node_metadata_node_id_nodes_id_fk", + "tableFrom": "node_metadata", + "tableTo": "nodes", + "columnsFrom": [ + "node_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "node_metadata_nodeId_unique": { + "name": "node_metadata_nodeId_unique", + "nullsNotDistinct": false, + "columns": [ + "node_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.nodes": { + "name": "nodes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "node_type": { + "name": "node_type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "nodes_user_id_idx": { + "name": "nodes_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "nodes_user_id_node_type_idx": { + "name": "nodes_user_id_node_type_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "node_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "nodes_user_id_users_id_fk": { + "name": "nodes_user_id_users_id_fk", + "tableFrom": "nodes", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scratchpads": { + "name": "scratchpads", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scratchpads_user_id_idx": { + "name": "scratchpads_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scratchpads_user_id_users_id_fk": { + "name": "scratchpads_user_id_users_id_fk", + "tableFrom": "scratchpads", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "scratchpads_userId_unique": { + "name": "scratchpads_userId_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.source_links": { + "name": "source_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "node_id": { + "name": "node_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "specific_location": { + "name": "specific_location", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "source_links_source_id_idx": { + "name": "source_links_source_id_idx", + "columns": [ + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "source_links_node_id_idx": { + "name": "source_links_node_id_idx", + "columns": [ + { + "expression": "node_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "source_links_source_id_sources_id_fk": { + "name": "source_links_source_id_sources_id_fk", + "tableFrom": "source_links", + "tableTo": "sources", + "columnsFrom": [ + "source_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "source_links_node_id_nodes_id_fk": { + "name": "source_links_node_id_nodes_id_fk", + "tableFrom": "source_links", + "tableTo": "nodes", + "columnsFrom": [ + "node_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "source_links_sourceId_nodeId_unique": { + "name": "source_links_sourceId_nodeId_unique", + "nullsNotDistinct": false, + "columns": [ + "source_id", + "node_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sources": { + "name": "sources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_source": { + "name": "parent_source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "last_ingested_at": { + "name": "last_ingested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "content_type": { + "name": "content_type", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "content_length": { + "name": "content_length", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "sources_user_id_idx": { + "name": "sources_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sources_status_idx": { + "name": "sources_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sources_user_id_users_id_fk": { + "name": "sources_user_id_users_id_fk", + "tableFrom": "sources", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sources_userId_type_externalId_unique": { + "name": "sources_userId_type_externalId_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id", + "type", + "external_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_profiles": { + "name": "user_profiles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_updated_at": { + "name": "last_updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_profiles_user_id_users_id_fk": { + "name": "user_profiles_user_id_users_id_fk", + "tableFrom": "user_profiles", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 05af970..72b7a0d 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -64,6 +64,13 @@ "when": 1775920239473, "tag": "0008_worthless_bullseye", "breakpoints": true + }, + { + "idx": 9, + "version": "7", + "when": 1775973748566, + "tag": "0009_married_smasher", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/db/schema.ts b/src/db/schema.ts index 1b19ada..541d4ed 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -27,7 +27,7 @@ export const nodes = pgTable( .references(() => users.id) .notNull(), nodeType: varchar("node_type", { length: 50 }).notNull().$type(), - createdAt: timestamp().defaultNow().notNull(), + createdAt: timestamp({ withTimezone: true }).defaultNow().notNull(), // Index on (userId, nodeType) might be useful }, (table) => [ @@ -60,7 +60,9 @@ export const nodeMetadata = pgTable( canonicalLabel: text("canonical_label"), description: text(), additionalData: jsonb(), - createdAt: timestamp("created_at").defaultNow().notNull(), + createdAt: timestamp("created_at", { withTimezone: true }) + .defaultNow() + .notNull(), }, (table) => [ index("node_metadata_node_id_idx").on(table.nodeId), @@ -96,7 +98,7 @@ export const edges = pgTable( // Temporal aspect for relationships // validFrom: timestamp('valid_from'), // validTo: timestamp('valid_to'), - createdAt: timestamp().defaultNow().notNull(), + createdAt: timestamp({ withTimezone: true }).defaultNow().notNull(), // Indexes on (userId, sourceNodeId), (userId, targetNodeId), (userId, edgeType) }, (table) => [ @@ -139,7 +141,7 @@ export const nodeEmbeddings = pgTable( .notNull(), embedding: vector("embedding", { dimensions: 1024 }).notNull(), // Dimension depends on model modelName: varchar("model_name", { length: 100 }).notNull(), - createdAt: timestamp().defaultNow().notNull(), + createdAt: timestamp({ withTimezone: true }).defaultNow().notNull(), // Unique constraint on (nodeId, modelName)? Or allow multiple embeddings per node? Let's start with unique. }, (table) => [ @@ -167,7 +169,7 @@ export const edgeEmbeddings = pgTable( .notNull(), embedding: vector("embedding", { dimensions: 1024 }).notNull(), // Same dimension as node embeddings modelName: varchar("model_name", { length: 100 }).notNull(), - createdAt: timestamp().defaultNow().notNull(), + createdAt: timestamp({ withTimezone: true }).defaultNow().notNull(), }, (table) => [ index("edge_embeddings_embedding_idx").using( @@ -196,7 +198,7 @@ export const aliases = pgTable("aliases", { canonicalNodeId: typeId("node") .references(() => nodes.id, { onDelete: "cascade" }) .notNull(), // The node this alias refers to - createdAt: timestamp().defaultNow().notNull(), + createdAt: timestamp({ withTimezone: true }).defaultNow().notNull(), // Index on (userId, aliasText) for fast lookups // Index on (userId, canonicalNodeId) }); @@ -226,12 +228,12 @@ export const sources = pgTable( parentSource: typeId("source"), metadata: jsonb(), // e.g., Notion page title, chat participants - lastIngestedAt: timestamp(), + lastIngestedAt: timestamp({ withTimezone: true }), status: varchar("status", { length: 20 }) .default("pending") .$type(), // e.g., 'pending', 'processing', 'completed', 'failed', 'summarized' - createdAt: timestamp().defaultNow().notNull(), - deletedAt: timestamp("deleted_at"), + createdAt: timestamp({ withTimezone: true }).defaultNow().notNull(), + deletedAt: timestamp("deleted_at", { withTimezone: true }), contentType: varchar("content_type", { length: 100 }), contentLength: integer("content_length"), }, @@ -268,7 +270,7 @@ export const sourceLinks = pgTable( .notNull(), // The ID of the node or edge // Optional: more specific location within the source (e.g., block ID, line number, timestamp in audio) specificLocation: text(), - createdAt: timestamp().defaultNow().notNull(), + createdAt: timestamp({ withTimezone: true }).defaultNow().notNull(), }, (table) => [ unique().on(table.sourceId, table.nodeId), @@ -296,8 +298,8 @@ export const userProfiles = pgTable("user_profiles", { .references(() => users.id) .notNull(), content: text().notNull(), // The descriptive text - lastUpdatedAt: timestamp().defaultNow().notNull(), - createdAt: timestamp().defaultNow().notNull(), + lastUpdatedAt: timestamp({ withTimezone: true }).defaultNow().notNull(), + createdAt: timestamp({ withTimezone: true }).defaultNow().notNull(), // Index on (userId) }); @@ -316,8 +318,12 @@ export const scratchpads = pgTable( .references(() => users.id) .notNull(), content: text().notNull().default(""), - updatedAt: timestamp("updated_at").defaultNow().notNull(), - createdAt: timestamp("created_at").defaultNow().notNull(), + updatedAt: timestamp("updated_at", { withTimezone: true }) + .defaultNow() + .notNull(), + createdAt: timestamp("created_at", { withTimezone: true }) + .defaultNow() + .notNull(), }, (table) => [ unique().on(table.userId), diff --git a/src/lib/conversation-store.ts b/src/lib/conversation-store.ts index d81739b..7790e24 100644 --- a/src/lib/conversation-store.ts +++ b/src/lib/conversation-store.ts @@ -1,5 +1,6 @@ import { z } from "zod"; import { DrizzleDB } from "~/db"; +import { safeToDate } from "~/lib/safe-date"; import { TypeId } from "~/types/typeid"; // Schema to parse stored metadata for conversation messages @@ -57,7 +58,7 @@ export async function loadConversationTurns( role: meta.role, name: meta.name, content: meta.rawContent, - timestamp: new Date(meta.timestamp), + timestamp: safeToDate(meta.timestamp), }; }); } diff --git a/src/lib/extract-graph.ts b/src/lib/extract-graph.ts index 8ceb975..82eaa00 100644 --- a/src/lib/extract-graph.ts +++ b/src/lib/extract-graph.ts @@ -6,6 +6,7 @@ import { import { formatNodesForPrompt } from "./formatting"; import { findSimilarNodes, findOneHopNodes, findNodesByType } from "./graph"; import { normalizeLabel } from "./label"; +import { safeToISOString } from "./safe-date"; import { TemporaryIdMapper } from "./temporary-id-mapper"; import { and, eq, inArray } from "drizzle-orm"; import { zodResponseFormat } from "openai/helpers/zod.mjs"; @@ -105,7 +106,7 @@ export async function extractGraph({ type: node.type, label: node.label, description: node.description, - timestamp: node.timestamp.toISOString(), + timestamp: safeToISOString(node.timestamp), }); } diff --git a/src/lib/formatting.ts b/src/lib/formatting.ts index 7b2b697..f5c7381 100644 --- a/src/lib/formatting.ts +++ b/src/lib/formatting.ts @@ -1,9 +1,9 @@ -import { formatISO } from "date-fns"; import type { NodeSearchResult, EdgeSearchResult, OneHopNode, } from "~/lib/graph"; +import { safeFormatISO } from "~/lib/safe-date"; interface Message { content: string; @@ -19,7 +19,7 @@ export function formatConversationAsXml(messages: Message[]): string { return messages .map( (message, index) => - ` + ` ${message.content.replace(//g, ">")} `, ) @@ -102,7 +102,7 @@ export type SearchResults = SearchResultItem[]; // Helpers for formatting individual result items function formatSearchNode(node: NodeSearchResult): string { - return ` + return ` ${escapeXml(node.description ?? "")} `; @@ -111,7 +111,7 @@ function formatSearchNode(node: NodeSearchResult): string { function formatSearchEdge(edge: EdgeSearchResult): string { return ` + )}" type="${escapeXml(edge.edgeType)}" timestamp="${safeFormatISO(edge.timestamp)}"> ${escapeXml(edge.description ?? "")} `; } @@ -119,7 +119,7 @@ function formatSearchEdge(edge: EdgeSearchResult): string { function formatSearchConnection(conn: OneHopNode): string { return ` + )}" type="${escapeXml(conn.edgeType)}" timestamp="${safeFormatISO(conn.timestamp)}"> ${escapeXml(conn.description ?? "")} `; diff --git a/src/lib/jobs/dream.ts b/src/lib/jobs/dream.ts index dca8c13..92e8004 100644 --- a/src/lib/jobs/dream.ts +++ b/src/lib/jobs/dream.ts @@ -7,6 +7,7 @@ import { crateTextCompletion, performStructuredAnalysis } from "~/lib/ai"; import { generateEmbeddings } from "~/lib/embeddings"; import { formatNodesForPrompt } from "~/lib/formatting"; import { findSimilarNodes, type NodeSearchResult } from "~/lib/graph"; +import { safeToISOString } from "~/lib/safe-date"; import { NodeTypeEnum } from "~/types/graph"; import { TypeId } from "~/types/typeid"; import { useDatabase } from "~/utils/db"; @@ -91,7 +92,7 @@ async function handleTopic( label: n.label, description: n.description, tempId: n.id, - timestamp: n.timestamp.toISOString(), + timestamp: safeToISOString(n.timestamp), })); const context = formatNodesForPrompt(nodesForPrompt); const dream = await generateDreamContent( diff --git a/src/lib/jobs/ingest-conversation.ts b/src/lib/jobs/ingest-conversation.ts index 7d2251e..8a10ea2 100644 --- a/src/lib/jobs/ingest-conversation.ts +++ b/src/lib/jobs/ingest-conversation.ts @@ -3,6 +3,7 @@ import { formatConversationAsXml } from "../formatting"; import { ensureSourceNode } from "../ingestion/ensure-source-node"; import { ensureUser } from "../ingestion/ensure-user"; import { insertNewSources } from "../ingestion/insert-new-sources"; +import { safeToISOString } from "../safe-date"; import { z } from "zod"; import { DrizzleDB } from "~/db"; import { type ConversationTurn } from "~/lib/conversation-store"; @@ -106,7 +107,7 @@ async function initializeConversation( rawContent: m.content, role: m.role, name: m.name, - timestamp: m.timestamp.toISOString(), + timestamp: safeToISOString(m.timestamp), }, })), }); diff --git a/src/lib/safe-date.ts b/src/lib/safe-date.ts new file mode 100644 index 0000000..f7b95fb --- /dev/null +++ b/src/lib/safe-date.ts @@ -0,0 +1,29 @@ +import { formatISO } from "date-fns"; + +/** + * Converts a value to a valid Date, returning the current time if invalid. + */ +export function safeToDate(value: Date | string | number): Date { + const date = value instanceof Date ? value : new Date(value); + if (isNaN(date.getTime())) { + return new Date(); + } + return date; +} + +/** + * Safely formats a date value as ISO 8601. + * Falls back to the current time if the input is an invalid date, + * preventing RangeError: Invalid time value crashes. + */ +export function safeFormatISO(value: Date | string | number): string { + return formatISO(safeToDate(value)); +} + +/** + * Safely converts a date value to an ISO string. + * Falls back to the current time if the input is an invalid date. + */ +export function safeToISOString(value: Date | string | number): string { + return safeToDate(value).toISOString(); +}