@bluefields/db 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/LICENSE +202 -0
  2. package/dist/client.d.ts +6 -0
  3. package/dist/client.js +13 -0
  4. package/dist/client.js.map +1 -0
  5. package/dist/index.d.ts +19 -0
  6. package/dist/index.js +20 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/migrate.d.ts +11 -0
  9. package/dist/migrate.js +48 -0
  10. package/dist/migrate.js.map +1 -0
  11. package/dist/schema/api-keys.d.ts +145 -0
  12. package/dist/schema/api-keys.js +18 -0
  13. package/dist/schema/api-keys.js.map +1 -0
  14. package/dist/schema/audit-log.d.ts +228 -0
  15. package/dist/schema/audit-log.js +49 -0
  16. package/dist/schema/audit-log.js.map +1 -0
  17. package/dist/schema/cost-records.d.ts +190 -0
  18. package/dist/schema/cost-records.js +55 -0
  19. package/dist/schema/cost-records.js.map +1 -0
  20. package/dist/schema/crawl-job-pages.d.ts +186 -0
  21. package/dist/schema/crawl-job-pages.js +31 -0
  22. package/dist/schema/crawl-job-pages.js.map +1 -0
  23. package/dist/schema/crawl-jobs.d.ts +271 -0
  24. package/dist/schema/crawl-jobs.js +40 -0
  25. package/dist/schema/crawl-jobs.js.map +1 -0
  26. package/dist/schema/customers.d.ts +145 -0
  27. package/dist/schema/customers.js +19 -0
  28. package/dist/schema/customers.js.map +1 -0
  29. package/dist/schema/event-feedback.d.ts +170 -0
  30. package/dist/schema/event-feedback.js +35 -0
  31. package/dist/schema/event-feedback.js.map +1 -0
  32. package/dist/schema/events.d.ts +291 -0
  33. package/dist/schema/events.js +58 -0
  34. package/dist/schema/events.js.map +1 -0
  35. package/dist/schema/extractions.d.ts +327 -0
  36. package/dist/schema/extractions.js +56 -0
  37. package/dist/schema/extractions.js.map +1 -0
  38. package/dist/schema/host-extractors.d.ts +131 -0
  39. package/dist/schema/host-extractors.js +18 -0
  40. package/dist/schema/host-extractors.js.map +1 -0
  41. package/dist/schema/host-records.d.ts +237 -0
  42. package/dist/schema/host-records.js +42 -0
  43. package/dist/schema/host-records.js.map +1 -0
  44. package/dist/schema/notification-preferences.d.ts +195 -0
  45. package/dist/schema/notification-preferences.js +49 -0
  46. package/dist/schema/notification-preferences.js.map +1 -0
  47. package/dist/schema/scrape-cache.d.ts +136 -0
  48. package/dist/schema/scrape-cache.js +23 -0
  49. package/dist/schema/scrape-cache.js.map +1 -0
  50. package/dist/schema/sessions.d.ts +153 -0
  51. package/dist/schema/sessions.js +27 -0
  52. package/dist/schema/sessions.js.map +1 -0
  53. package/dist/schema/snapshots.d.ts +382 -0
  54. package/dist/schema/snapshots.js +60 -0
  55. package/dist/schema/snapshots.js.map +1 -0
  56. package/dist/schema/subscriptions.d.ts +604 -0
  57. package/dist/schema/subscriptions.js +79 -0
  58. package/dist/schema/subscriptions.js.map +1 -0
  59. package/dist/schema/usage-periods.d.ts +446 -0
  60. package/dist/schema/usage-periods.js +71 -0
  61. package/dist/schema/usage-periods.js.map +1 -0
  62. package/dist/tsconfig.tsbuildinfo +1 -0
  63. package/package.json +54 -0
  64. package/src/migrations/0001_initial.sql +398 -0
  65. package/src/migrations/0002_pg_cron.sql +109 -0
  66. package/src/migrations/0003_webhook_secret_cleanup.sql +55 -0
  67. package/src/migrations/0004_events_pg_notify.sql +39 -0
  68. package/src/migrations/0005_subscription_consecutive_failures.sql +19 -0
  69. package/src/migrations/0006_notification_preferences.sql +27 -0
  70. package/src/migrations/0007_crawl_jobs.sql +65 -0
  71. package/src/migrations/0008_sessions.sql +38 -0
  72. package/src/migrations/0009_scrape_cache.sql +34 -0
  73. package/src/migrations/0010_neon_defaults.sql +99 -0
  74. package/src/migrations/0011_credits_consumed_numeric.sql +12 -0
  75. package/src/migrations/0012_crawl_page_attestation.sql +10 -0
  76. package/src/migrations/0013_customer_deleted_at.sql +5 -0
@@ -0,0 +1,23 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ import { integer, pgTable, primaryKey, text, timestamp, uuid } from 'drizzle-orm/pg-core';
3
+ import { customers } from './customers.js';
4
+ /**
5
+ * scrape_cache — per-customer URL-level cache for /scrape maxAge (Phase A.5).
6
+ *
7
+ * Composite PK on (customer_id, url_canonical). One row per URL per
8
+ * customer — upserted on every fresh scrape so we always reflect the
9
+ * latest fetch. Cross-customer sharing happens deeper at the extractor
10
+ * cache layer (content_hash + schema_hash).
11
+ */
12
+ export const scrapeCache = pgTable('scrape_cache', {
13
+ customerId: uuid('customer_id')
14
+ .notNull()
15
+ .references(() => customers.id, { onDelete: 'cascade' }),
16
+ urlCanonical: text('url_canonical').notNull(),
17
+ extractionId: uuid('extraction_id'),
18
+ contentHash: text('content_hash').notNull(),
19
+ finalUrl: text('final_url').notNull(),
20
+ responseStatus: integer('response_status').notNull(),
21
+ fetchedAt: timestamp('fetched_at', { withTimezone: true }).notNull().defaultNow(),
22
+ }, (table) => [primaryKey({ columns: [table.customerId, table.urlCanonical] })]);
23
+ //# sourceMappingURL=scrape-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scrape-cache.js","sourceRoot":"","sources":["../../src/schema/scrape-cache.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAE1F,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,OAAO,CAChC,cAAc,EACd;IACE,UAAU,EAAE,IAAI,CAAC,aAAa,CAAC;SAC5B,OAAO,EAAE;SACT,UAAU,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IAC1D,YAAY,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC,OAAO,EAAE;IAC7C,YAAY,EAAE,IAAI,CAAC,eAAe,CAAC;IACnC,WAAW,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC,OAAO,EAAE;IAC3C,QAAQ,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,OAAO,EAAE;IACrC,cAAc,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC,OAAO,EAAE;IACpD,SAAS,EAAE,SAAS,CAAC,YAAY,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,UAAU,EAAE;CAClF,EACD,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,CAC7E,CAAC"}
@@ -0,0 +1,153 @@
1
+ /**
2
+ * sessions — named, per-customer cookie/header bundles for reuse
3
+ * across /scrape, /crawl, and /batch calls (Phase G).
4
+ *
5
+ * Cookies + headers are AES-256-GCM ciphertext, stored as a versioned
6
+ * JSON envelope by the helpers in @bluefields/control/sessions.ts.
7
+ * Unique on (customer_id, name) so save/use lookups are O(1).
8
+ */
9
+ export declare const sessions: import("drizzle-orm/pg-core").PgTableWithColumns<{
10
+ name: "sessions";
11
+ schema: undefined;
12
+ columns: {
13
+ id: import("drizzle-orm/pg-core").PgColumn<{
14
+ name: "id";
15
+ tableName: "sessions";
16
+ dataType: "string";
17
+ columnType: "PgUUID";
18
+ data: string;
19
+ driverParam: string;
20
+ notNull: true;
21
+ hasDefault: true;
22
+ isPrimaryKey: true;
23
+ isAutoincrement: false;
24
+ hasRuntimeDefault: false;
25
+ enumValues: undefined;
26
+ baseColumn: never;
27
+ identity: undefined;
28
+ generated: undefined;
29
+ }, {}, {}>;
30
+ customerId: import("drizzle-orm/pg-core").PgColumn<{
31
+ name: "customer_id";
32
+ tableName: "sessions";
33
+ dataType: "string";
34
+ columnType: "PgUUID";
35
+ data: string;
36
+ driverParam: string;
37
+ notNull: true;
38
+ hasDefault: false;
39
+ isPrimaryKey: false;
40
+ isAutoincrement: false;
41
+ hasRuntimeDefault: false;
42
+ enumValues: undefined;
43
+ baseColumn: never;
44
+ identity: undefined;
45
+ generated: undefined;
46
+ }, {}, {}>;
47
+ name: import("drizzle-orm/pg-core").PgColumn<{
48
+ name: "name";
49
+ tableName: "sessions";
50
+ dataType: "string";
51
+ columnType: "PgText";
52
+ data: string;
53
+ driverParam: string;
54
+ notNull: true;
55
+ hasDefault: false;
56
+ isPrimaryKey: false;
57
+ isAutoincrement: false;
58
+ hasRuntimeDefault: false;
59
+ enumValues: [string, ...string[]];
60
+ baseColumn: never;
61
+ identity: undefined;
62
+ generated: undefined;
63
+ }, {}, {}>;
64
+ cookiesCiphertext: import("drizzle-orm/pg-core").PgColumn<{
65
+ name: "cookies_ciphertext";
66
+ tableName: "sessions";
67
+ dataType: "string";
68
+ columnType: "PgText";
69
+ data: string;
70
+ driverParam: string;
71
+ notNull: true;
72
+ hasDefault: false;
73
+ isPrimaryKey: false;
74
+ isAutoincrement: false;
75
+ hasRuntimeDefault: false;
76
+ enumValues: [string, ...string[]];
77
+ baseColumn: never;
78
+ identity: undefined;
79
+ generated: undefined;
80
+ }, {}, {}>;
81
+ headersCiphertext: import("drizzle-orm/pg-core").PgColumn<{
82
+ name: "headers_ciphertext";
83
+ tableName: "sessions";
84
+ dataType: "string";
85
+ columnType: "PgText";
86
+ data: string;
87
+ driverParam: string;
88
+ notNull: false;
89
+ hasDefault: false;
90
+ isPrimaryKey: false;
91
+ isAutoincrement: false;
92
+ hasRuntimeDefault: false;
93
+ enumValues: [string, ...string[]];
94
+ baseColumn: never;
95
+ identity: undefined;
96
+ generated: undefined;
97
+ }, {}, {}>;
98
+ expiresAt: import("drizzle-orm/pg-core").PgColumn<{
99
+ name: "expires_at";
100
+ tableName: "sessions";
101
+ dataType: "date";
102
+ columnType: "PgTimestamp";
103
+ data: Date;
104
+ driverParam: string;
105
+ notNull: false;
106
+ hasDefault: false;
107
+ isPrimaryKey: false;
108
+ isAutoincrement: false;
109
+ hasRuntimeDefault: false;
110
+ enumValues: undefined;
111
+ baseColumn: never;
112
+ identity: undefined;
113
+ generated: undefined;
114
+ }, {}, {}>;
115
+ createdAt: import("drizzle-orm/pg-core").PgColumn<{
116
+ name: "created_at";
117
+ tableName: "sessions";
118
+ dataType: "date";
119
+ columnType: "PgTimestamp";
120
+ data: Date;
121
+ driverParam: string;
122
+ notNull: true;
123
+ hasDefault: true;
124
+ isPrimaryKey: false;
125
+ isAutoincrement: false;
126
+ hasRuntimeDefault: false;
127
+ enumValues: undefined;
128
+ baseColumn: never;
129
+ identity: undefined;
130
+ generated: undefined;
131
+ }, {}, {}>;
132
+ updatedAt: import("drizzle-orm/pg-core").PgColumn<{
133
+ name: "updated_at";
134
+ tableName: "sessions";
135
+ dataType: "date";
136
+ columnType: "PgTimestamp";
137
+ data: Date;
138
+ driverParam: string;
139
+ notNull: true;
140
+ hasDefault: true;
141
+ isPrimaryKey: false;
142
+ isAutoincrement: false;
143
+ hasRuntimeDefault: false;
144
+ enumValues: undefined;
145
+ baseColumn: never;
146
+ identity: undefined;
147
+ generated: undefined;
148
+ }, {}, {}>;
149
+ };
150
+ dialect: "pg";
151
+ }>;
152
+ export type Session = typeof sessions.$inferSelect;
153
+ export type NewSession = typeof sessions.$inferInsert;
@@ -0,0 +1,27 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ import { index, pgTable, text, timestamp, unique, uuid } from 'drizzle-orm/pg-core';
3
+ import { customers } from './customers.js';
4
+ /**
5
+ * sessions — named, per-customer cookie/header bundles for reuse
6
+ * across /scrape, /crawl, and /batch calls (Phase G).
7
+ *
8
+ * Cookies + headers are AES-256-GCM ciphertext, stored as a versioned
9
+ * JSON envelope by the helpers in @bluefields/control/sessions.ts.
10
+ * Unique on (customer_id, name) so save/use lookups are O(1).
11
+ */
12
+ export const sessions = pgTable('sessions', {
13
+ id: uuid('id').primaryKey().defaultRandom(),
14
+ customerId: uuid('customer_id')
15
+ .notNull()
16
+ .references(() => customers.id, { onDelete: 'cascade' }),
17
+ name: text('name').notNull(),
18
+ cookiesCiphertext: text('cookies_ciphertext').notNull(),
19
+ headersCiphertext: text('headers_ciphertext'),
20
+ expiresAt: timestamp('expires_at', { withTimezone: true }),
21
+ createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
22
+ updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
23
+ }, (table) => [
24
+ unique('sessions_customer_name_key').on(table.customerId, table.name),
25
+ index('idx_sessions_customer_name').on(table.customerId, table.name),
26
+ ]);
27
+ //# sourceMappingURL=sessions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessions.js","sourceRoot":"","sources":["../../src/schema/sessions.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAEpF,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,OAAO,CAC7B,UAAU,EACV;IACE,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,CAAC,aAAa,EAAE;IAC3C,UAAU,EAAE,IAAI,CAAC,aAAa,CAAC;SAC5B,OAAO,EAAE;SACT,UAAU,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IAC1D,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE;IAC5B,iBAAiB,EAAE,IAAI,CAAC,oBAAoB,CAAC,CAAC,OAAO,EAAE;IACvD,iBAAiB,EAAE,IAAI,CAAC,oBAAoB,CAAC;IAC7C,SAAS,EAAE,SAAS,CAAC,YAAY,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;IAC1D,SAAS,EAAE,SAAS,CAAC,YAAY,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,UAAU,EAAE;IACjF,SAAS,EAAE,SAAS,CAAC,YAAY,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,UAAU,EAAE;CAClF,EACD,CAAC,KAAK,EAAE,EAAE,CAAC;IACT,MAAM,CAAC,4BAA4B,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC;IACrE,KAAK,CAAC,4BAA4B,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC;CACrE,CACF,CAAC"}
@@ -0,0 +1,382 @@
1
+ /**
2
+ * Snapshots — the immutable provenance table.
3
+ *
4
+ * SPEC DEVIATION: The data-model spec writes `id UUID PRIMARY KEY` with
5
+ * `PARTITION BY RANGE (fetched_at)`. Postgres requires the partition column
6
+ * to appear in every unique/primary-key constraint on a partitioned table,
7
+ * so a sole-`id` PK is invalid SQL. We use composite primary key
8
+ * `(id, fetched_at)` to make the partition design actually work, matching
9
+ * the pattern already used in `cost_records` and `audit_log`. The chunk's
10
+ * acceptance criterion "migration runs cleanly" forces this fix.
11
+ *
12
+ * Consequence: FKs that reference `snapshots(id)` from non-partitioned
13
+ * tables (e.g. `extractions.snapshot_id`) cannot be expressed at the DB
14
+ * level — referential integrity is enforced by application code only.
15
+ */
16
+ export declare const snapshots: import("drizzle-orm/pg-core").PgTableWithColumns<{
17
+ name: "snapshots";
18
+ schema: undefined;
19
+ columns: {
20
+ id: import("drizzle-orm/pg-core").PgColumn<{
21
+ name: "id";
22
+ tableName: "snapshots";
23
+ dataType: "string";
24
+ columnType: "PgUUID";
25
+ data: string;
26
+ driverParam: string;
27
+ notNull: true;
28
+ hasDefault: true;
29
+ isPrimaryKey: false;
30
+ isAutoincrement: false;
31
+ hasRuntimeDefault: false;
32
+ enumValues: undefined;
33
+ baseColumn: never;
34
+ identity: undefined;
35
+ generated: undefined;
36
+ }, {}, {}>;
37
+ urlCanonical: import("drizzle-orm/pg-core").PgColumn<{
38
+ name: "url_canonical";
39
+ tableName: "snapshots";
40
+ dataType: "string";
41
+ columnType: "PgText";
42
+ data: string;
43
+ driverParam: string;
44
+ notNull: true;
45
+ hasDefault: false;
46
+ isPrimaryKey: false;
47
+ isAutoincrement: false;
48
+ hasRuntimeDefault: false;
49
+ enumValues: [string, ...string[]];
50
+ baseColumn: never;
51
+ identity: undefined;
52
+ generated: undefined;
53
+ }, {}, {}>;
54
+ urlSurt: import("drizzle-orm/pg-core").PgColumn<{
55
+ name: "url_surt";
56
+ tableName: "snapshots";
57
+ dataType: "string";
58
+ columnType: "PgText";
59
+ data: string;
60
+ driverParam: string;
61
+ notNull: true;
62
+ hasDefault: false;
63
+ isPrimaryKey: false;
64
+ isAutoincrement: false;
65
+ hasRuntimeDefault: false;
66
+ enumValues: [string, ...string[]];
67
+ baseColumn: never;
68
+ identity: undefined;
69
+ generated: undefined;
70
+ }, {}, {}>;
71
+ canonVersion: import("drizzle-orm/pg-core").PgColumn<{
72
+ name: "canon_version";
73
+ tableName: "snapshots";
74
+ dataType: "number";
75
+ columnType: "PgSmallInt";
76
+ data: number;
77
+ driverParam: string | number;
78
+ notNull: true;
79
+ hasDefault: false;
80
+ isPrimaryKey: false;
81
+ isAutoincrement: false;
82
+ hasRuntimeDefault: false;
83
+ enumValues: undefined;
84
+ baseColumn: never;
85
+ identity: undefined;
86
+ generated: undefined;
87
+ }, {}, {}>;
88
+ contentHash: import("drizzle-orm/pg-core").PgColumn<{
89
+ name: "content_hash";
90
+ tableName: "snapshots";
91
+ dataType: "string";
92
+ columnType: "PgText";
93
+ data: string;
94
+ driverParam: string;
95
+ notNull: true;
96
+ hasDefault: false;
97
+ isPrimaryKey: false;
98
+ isAutoincrement: false;
99
+ hasRuntimeDefault: false;
100
+ enumValues: [string, ...string[]];
101
+ baseColumn: never;
102
+ identity: undefined;
103
+ generated: undefined;
104
+ }, {}, {}>;
105
+ fetchedAt: import("drizzle-orm/pg-core").PgColumn<{
106
+ name: "fetched_at";
107
+ tableName: "snapshots";
108
+ dataType: "date";
109
+ columnType: "PgTimestamp";
110
+ data: Date;
111
+ driverParam: string;
112
+ notNull: true;
113
+ hasDefault: false;
114
+ isPrimaryKey: false;
115
+ isAutoincrement: false;
116
+ hasRuntimeDefault: false;
117
+ enumValues: undefined;
118
+ baseColumn: never;
119
+ identity: undefined;
120
+ generated: undefined;
121
+ }, {}, {}>;
122
+ fetcherId: import("drizzle-orm/pg-core").PgColumn<{
123
+ name: "fetcher_id";
124
+ tableName: "snapshots";
125
+ dataType: "string";
126
+ columnType: "PgText";
127
+ data: string;
128
+ driverParam: string;
129
+ notNull: true;
130
+ hasDefault: false;
131
+ isPrimaryKey: false;
132
+ isAutoincrement: false;
133
+ hasRuntimeDefault: false;
134
+ enumValues: [string, ...string[]];
135
+ baseColumn: never;
136
+ identity: undefined;
137
+ generated: undefined;
138
+ }, {}, {}>;
139
+ proxyEgressIp: import("drizzle-orm/pg-core").PgColumn<{
140
+ name: "proxy_egress_ip";
141
+ tableName: "snapshots";
142
+ dataType: "string";
143
+ columnType: "PgInet";
144
+ data: string;
145
+ driverParam: string;
146
+ notNull: true;
147
+ hasDefault: false;
148
+ isPrimaryKey: false;
149
+ isAutoincrement: false;
150
+ hasRuntimeDefault: false;
151
+ enumValues: undefined;
152
+ baseColumn: never;
153
+ identity: undefined;
154
+ generated: undefined;
155
+ }, {}, {}>;
156
+ userAgent: import("drizzle-orm/pg-core").PgColumn<{
157
+ name: "user_agent";
158
+ tableName: "snapshots";
159
+ dataType: "string";
160
+ columnType: "PgText";
161
+ data: string;
162
+ driverParam: string;
163
+ notNull: true;
164
+ hasDefault: false;
165
+ isPrimaryKey: false;
166
+ isAutoincrement: false;
167
+ hasRuntimeDefault: false;
168
+ enumValues: [string, ...string[]];
169
+ baseColumn: never;
170
+ identity: undefined;
171
+ generated: undefined;
172
+ }, {}, {}>;
173
+ responseStatus: import("drizzle-orm/pg-core").PgColumn<{
174
+ name: "response_status";
175
+ tableName: "snapshots";
176
+ dataType: "number";
177
+ columnType: "PgInteger";
178
+ data: number;
179
+ driverParam: string | number;
180
+ notNull: true;
181
+ hasDefault: false;
182
+ isPrimaryKey: false;
183
+ isAutoincrement: false;
184
+ hasRuntimeDefault: false;
185
+ enumValues: undefined;
186
+ baseColumn: never;
187
+ identity: undefined;
188
+ generated: undefined;
189
+ }, {}, {}>;
190
+ responseHeaders: import("drizzle-orm/pg-core").PgColumn<{
191
+ name: "response_headers";
192
+ tableName: "snapshots";
193
+ dataType: "json";
194
+ columnType: "PgJsonb";
195
+ data: unknown;
196
+ driverParam: unknown;
197
+ notNull: true;
198
+ hasDefault: false;
199
+ isPrimaryKey: false;
200
+ isAutoincrement: false;
201
+ hasRuntimeDefault: false;
202
+ enumValues: undefined;
203
+ baseColumn: never;
204
+ identity: undefined;
205
+ generated: undefined;
206
+ }, {}, {}>;
207
+ rawHtmlR2Key: import("drizzle-orm/pg-core").PgColumn<{
208
+ name: "raw_html_r2_key";
209
+ tableName: "snapshots";
210
+ dataType: "string";
211
+ columnType: "PgText";
212
+ data: string;
213
+ driverParam: string;
214
+ notNull: true;
215
+ hasDefault: false;
216
+ isPrimaryKey: false;
217
+ isAutoincrement: false;
218
+ hasRuntimeDefault: false;
219
+ enumValues: [string, ...string[]];
220
+ baseColumn: never;
221
+ identity: undefined;
222
+ generated: undefined;
223
+ }, {}, {}>;
224
+ rawHtmlSizeBytes: import("drizzle-orm/pg-core").PgColumn<{
225
+ name: "raw_html_size_bytes";
226
+ tableName: "snapshots";
227
+ dataType: "number";
228
+ columnType: "PgBigInt53";
229
+ data: number;
230
+ driverParam: string | number;
231
+ notNull: true;
232
+ hasDefault: false;
233
+ isPrimaryKey: false;
234
+ isAutoincrement: false;
235
+ hasRuntimeDefault: false;
236
+ enumValues: undefined;
237
+ baseColumn: never;
238
+ identity: undefined;
239
+ generated: undefined;
240
+ }, {}, {}>;
241
+ fetchDurationMs: import("drizzle-orm/pg-core").PgColumn<{
242
+ name: "fetch_duration_ms";
243
+ tableName: "snapshots";
244
+ dataType: "number";
245
+ columnType: "PgInteger";
246
+ data: number;
247
+ driverParam: string | number;
248
+ notNull: true;
249
+ hasDefault: false;
250
+ isPrimaryKey: false;
251
+ isAutoincrement: false;
252
+ hasRuntimeDefault: false;
253
+ enumValues: undefined;
254
+ baseColumn: never;
255
+ identity: undefined;
256
+ generated: undefined;
257
+ }, {}, {}>;
258
+ attestationSignature: import("drizzle-orm/pg-core").PgColumn<{
259
+ name: "attestation_signature";
260
+ tableName: "snapshots";
261
+ dataType: "string";
262
+ columnType: "PgText";
263
+ data: string;
264
+ driverParam: string;
265
+ notNull: true;
266
+ hasDefault: false;
267
+ isPrimaryKey: false;
268
+ isAutoincrement: false;
269
+ hasRuntimeDefault: false;
270
+ enumValues: [string, ...string[]];
271
+ baseColumn: never;
272
+ identity: undefined;
273
+ generated: undefined;
274
+ }, {}, {}>;
275
+ attestationKeyId: import("drizzle-orm/pg-core").PgColumn<{
276
+ name: "attestation_key_id";
277
+ tableName: "snapshots";
278
+ dataType: "string";
279
+ columnType: "PgText";
280
+ data: string;
281
+ driverParam: string;
282
+ notNull: true;
283
+ hasDefault: false;
284
+ isPrimaryKey: false;
285
+ isAutoincrement: false;
286
+ hasRuntimeDefault: false;
287
+ enumValues: [string, ...string[]];
288
+ baseColumn: never;
289
+ identity: undefined;
290
+ generated: undefined;
291
+ }, {}, {}>;
292
+ attestationAlgorithm: import("drizzle-orm/pg-core").PgColumn<{
293
+ name: "attestation_algorithm";
294
+ tableName: "snapshots";
295
+ dataType: "string";
296
+ columnType: "PgText";
297
+ data: string;
298
+ driverParam: string;
299
+ notNull: true;
300
+ hasDefault: true;
301
+ isPrimaryKey: false;
302
+ isAutoincrement: false;
303
+ hasRuntimeDefault: false;
304
+ enumValues: [string, ...string[]];
305
+ baseColumn: never;
306
+ identity: undefined;
307
+ generated: undefined;
308
+ }, {}, {}>;
309
+ manifestVersion: import("drizzle-orm/pg-core").PgColumn<{
310
+ name: "manifest_version";
311
+ tableName: "snapshots";
312
+ dataType: "number";
313
+ columnType: "PgSmallInt";
314
+ data: number;
315
+ driverParam: string | number;
316
+ notNull: true;
317
+ hasDefault: true;
318
+ isPrimaryKey: false;
319
+ isAutoincrement: false;
320
+ hasRuntimeDefault: false;
321
+ enumValues: undefined;
322
+ baseColumn: never;
323
+ identity: undefined;
324
+ generated: undefined;
325
+ }, {}, {}>;
326
+ encryptionKeyId: import("drizzle-orm/pg-core").PgColumn<{
327
+ name: "encryption_key_id";
328
+ tableName: "snapshots";
329
+ dataType: "string";
330
+ columnType: "PgText";
331
+ data: string;
332
+ driverParam: string;
333
+ notNull: false;
334
+ hasDefault: false;
335
+ isPrimaryKey: false;
336
+ isAutoincrement: false;
337
+ hasRuntimeDefault: false;
338
+ enumValues: [string, ...string[]];
339
+ baseColumn: never;
340
+ identity: undefined;
341
+ generated: undefined;
342
+ }, {}, {}>;
343
+ costMicroUsd: import("drizzle-orm/pg-core").PgColumn<{
344
+ name: "cost_micro_usd";
345
+ tableName: "snapshots";
346
+ dataType: "number";
347
+ columnType: "PgInteger";
348
+ data: number;
349
+ driverParam: string | number;
350
+ notNull: true;
351
+ hasDefault: false;
352
+ isPrimaryKey: false;
353
+ isAutoincrement: false;
354
+ hasRuntimeDefault: false;
355
+ enumValues: undefined;
356
+ baseColumn: never;
357
+ identity: undefined;
358
+ generated: undefined;
359
+ }, {}, {}>;
360
+ createdAt: import("drizzle-orm/pg-core").PgColumn<{
361
+ name: "created_at";
362
+ tableName: "snapshots";
363
+ dataType: "date";
364
+ columnType: "PgTimestamp";
365
+ data: Date;
366
+ driverParam: string;
367
+ notNull: true;
368
+ hasDefault: true;
369
+ isPrimaryKey: false;
370
+ isAutoincrement: false;
371
+ hasRuntimeDefault: false;
372
+ enumValues: undefined;
373
+ baseColumn: never;
374
+ identity: undefined;
375
+ generated: undefined;
376
+ }, {}, {}>;
377
+ };
378
+ dialect: "pg";
379
+ }>;
380
+ export type Snapshot = typeof snapshots.$inferSelect;
381
+ export type NewSnapshot = typeof snapshots.$inferInsert;
382
+ export declare const SNAPSHOTS_PARTITION_COLUMN = "fetched_at";
@@ -0,0 +1,60 @@
1
+ import { bigint, index, inet, integer, jsonb, pgTable, primaryKey, smallint, text, timestamp, uuid, } from 'drizzle-orm/pg-core';
2
+ /**
3
+ * Snapshots — the immutable provenance table.
4
+ *
5
+ * SPEC DEVIATION: The data-model spec writes `id UUID PRIMARY KEY` with
6
+ * `PARTITION BY RANGE (fetched_at)`. Postgres requires the partition column
7
+ * to appear in every unique/primary-key constraint on a partitioned table,
8
+ * so a sole-`id` PK is invalid SQL. We use composite primary key
9
+ * `(id, fetched_at)` to make the partition design actually work, matching
10
+ * the pattern already used in `cost_records` and `audit_log`. The chunk's
11
+ * acceptance criterion "migration runs cleanly" forces this fix.
12
+ *
13
+ * Consequence: FKs that reference `snapshots(id)` from non-partitioned
14
+ * tables (e.g. `extractions.snapshot_id`) cannot be expressed at the DB
15
+ * level — referential integrity is enforced by application code only.
16
+ */
17
+ export const snapshots = pgTable('snapshots', {
18
+ id: uuid('id').notNull().defaultRandom(),
19
+ urlCanonical: text('url_canonical').notNull(),
20
+ // SURT form for host-level clustering and ops (Review #2)
21
+ urlSurt: text('url_surt').notNull(),
22
+ // which rules produced url_canonical (Review #2)
23
+ canonVersion: smallint('canon_version').notNull(),
24
+ // SHA-256 hex of the raw response body
25
+ contentHash: text('content_hash').notNull(),
26
+ fetchedAt: timestamp('fetched_at', { withTimezone: true }).notNull(),
27
+ // which worker
28
+ fetcherId: text('fetcher_id').notNull(),
29
+ // the actual IP the fetch came from
30
+ proxyEgressIp: inet('proxy_egress_ip').notNull(),
31
+ userAgent: text('user_agent').notNull(),
32
+ responseStatus: integer('response_status').notNull(),
33
+ responseHeaders: jsonb('response_headers').notNull(),
34
+ // content-addressed R2 key: 'content/<first2-of-sha>/<sha256>' (Review #2)
35
+ rawHtmlR2Key: text('raw_html_r2_key').notNull(),
36
+ rawHtmlSizeBytes: bigint('raw_html_size_bytes', { mode: 'number' }).notNull(),
37
+ fetchDurationMs: integer('fetch_duration_ms').notNull(),
38
+ attestationSignature: text('attestation_signature').notNull(),
39
+ attestationKeyId: text('attestation_key_id').notNull(),
40
+ // IANA JOSE algorithm name (RFC 8037); supports future hybrid PQC (Review #3)
41
+ attestationAlgorithm: text('attestation_algorithm').notNull().default('EdDSA'),
42
+ // which manifest schema produced the signed bytes (Review #3)
43
+ manifestVersion: smallint('manifest_version').notNull().default(1),
44
+ // NULL = plaintext blob in R2; non-NULL = encrypted at rest (v2; Review #2)
45
+ encryptionKeyId: text('encryption_key_id'),
46
+ // track cost per fetch (Review #11)
47
+ costMicroUsd: integer('cost_micro_usd').notNull(),
48
+ createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
49
+ }, (table) => [
50
+ // Composite PK required for partitioned tables (see spec-deviation note above).
51
+ primaryKey({ columns: [table.id, table.fetchedAt] }),
52
+ index('idx_snap_url_time').on(table.urlCanonical, table.fetchedAt.desc()),
53
+ index('idx_snap_surt_time').on(table.urlSurt, table.fetchedAt.desc()),
54
+ index('idx_snap_hash').on(table.contentHash),
55
+ ]);
56
+ // SQL-level metadata: partitioning + partman bootstrap. Drizzle ORM has no
57
+ // native partitioning support; the partition clause and partman.create_parent
58
+ // call live in the raw migration file (0001_initial.sql).
59
+ export const SNAPSHOTS_PARTITION_COLUMN = 'fetched_at';
60
+ //# sourceMappingURL=snapshots.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshots.js","sourceRoot":"","sources":["../../src/schema/snapshots.ts"],"names":[],"mappings":"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"}