@southwind-ai/database 0.1.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.
- package/index.ts +5 -0
- package/package.json +27 -0
- package/src/bundle-validation.ts +218 -0
- package/src/dialects.ts +139 -0
- package/src/runner.ts +193 -0
- package/src/schema/bulk-data.ts +87 -0
- package/src/schema/common.ts +33 -0
- package/src/schema/durable-jobs.ts +266 -0
- package/src/schema/file-storage.ts +94 -0
- package/src/schema/governance.ts +204 -0
- package/src/schema/governed-exports.ts +90 -0
- package/src/schema/identity.ts +169 -0
- package/src/schema/index.ts +33 -0
- package/src/schema/license-legacy.ts +93 -0
- package/src/schema/license-v1.ts +246 -0
- package/src/schema/migration-history.ts +30 -0
- package/src/schema/notifications.ts +60 -0
- package/src/schema/platform-settings.ts +69 -0
- package/src/schema/scheduler.ts +89 -0
- package/src/schema/work-orders.ts +23 -0
- package/src/schema/workflow.ts +86 -0
- package/src/sql-store.ts +101 -0
- package/src/store.ts +32 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { sql } from "drizzle-orm";
|
|
2
|
+
import {
|
|
3
|
+
boolean,
|
|
4
|
+
check,
|
|
5
|
+
index,
|
|
6
|
+
integer,
|
|
7
|
+
jsonb,
|
|
8
|
+
pgTable,
|
|
9
|
+
text,
|
|
10
|
+
timestamp,
|
|
11
|
+
uniqueIndex,
|
|
12
|
+
varchar,
|
|
13
|
+
} from "drizzle-orm/pg-core";
|
|
14
|
+
|
|
15
|
+
export const durableJobs = pgTable(
|
|
16
|
+
"durable_jobs",
|
|
17
|
+
{
|
|
18
|
+
id: varchar("id", { length: 64 }).primaryKey(),
|
|
19
|
+
handlerKey: varchar("handler_key", { length: 160 }).notNull(),
|
|
20
|
+
queue: varchar("queue", { length: 80 }).notNull().default("default"),
|
|
21
|
+
status: varchar("status", { length: 32 }).notNull().default("queued"),
|
|
22
|
+
idempotencyKey: varchar("idempotency_key", { length: 160 }).notNull(),
|
|
23
|
+
payload: jsonb("payload").$type<Record<string, unknown>>().notNull(),
|
|
24
|
+
priority: integer("priority").notNull().default(0),
|
|
25
|
+
requestedBy: varchar("requested_by", { length: 64 }).notNull(),
|
|
26
|
+
maxAttempts: integer("max_attempts").notNull().default(3),
|
|
27
|
+
timeoutSeconds: integer("timeout_seconds").notNull().default(900),
|
|
28
|
+
availableAt: timestamp("available_at", { withTimezone: true }).notNull(),
|
|
29
|
+
cancelRequestedAt: timestamp("cancel_requested_at", { withTimezone: true }),
|
|
30
|
+
startedAt: timestamp("started_at", { withTimezone: true }),
|
|
31
|
+
finishedAt: timestamp("finished_at", { withTimezone: true }),
|
|
32
|
+
createdAt: timestamp("created_at", { withTimezone: true })
|
|
33
|
+
.notNull()
|
|
34
|
+
.defaultNow(),
|
|
35
|
+
updatedAt: timestamp("updated_at", { withTimezone: true })
|
|
36
|
+
.notNull()
|
|
37
|
+
.defaultNow(),
|
|
38
|
+
},
|
|
39
|
+
(table) => [
|
|
40
|
+
uniqueIndex("uk_durable_jobs_idempotency").on(
|
|
41
|
+
table.requestedBy,
|
|
42
|
+
table.handlerKey,
|
|
43
|
+
table.idempotencyKey,
|
|
44
|
+
),
|
|
45
|
+
index("idx_durable_jobs_claim").on(
|
|
46
|
+
table.queue,
|
|
47
|
+
table.status,
|
|
48
|
+
table.availableAt,
|
|
49
|
+
table.priority,
|
|
50
|
+
),
|
|
51
|
+
index("idx_durable_jobs_actor_created").on(
|
|
52
|
+
table.requestedBy,
|
|
53
|
+
table.createdAt,
|
|
54
|
+
),
|
|
55
|
+
check("ck_durable_jobs_attempts", sql`${table.maxAttempts} > 0`),
|
|
56
|
+
check("ck_durable_jobs_timeout", sql`${table.timeoutSeconds} > 0`),
|
|
57
|
+
],
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
export const durableJobStages = pgTable(
|
|
61
|
+
"durable_job_stages",
|
|
62
|
+
{
|
|
63
|
+
id: varchar("id", { length: 64 }).primaryKey(),
|
|
64
|
+
jobId: varchar("job_id", { length: 64 })
|
|
65
|
+
.notNull()
|
|
66
|
+
.references(() => durableJobs.id, { onDelete: "restrict" }),
|
|
67
|
+
key: varchar("key", { length: 160 }).notNull(),
|
|
68
|
+
ordinal: integer("ordinal").notNull(),
|
|
69
|
+
status: varchar("status", { length: 32 }).notNull().default("pending"),
|
|
70
|
+
currentAttempt: integer("current_attempt").notNull().default(0),
|
|
71
|
+
startedAt: timestamp("started_at", { withTimezone: true }),
|
|
72
|
+
finishedAt: timestamp("finished_at", { withTimezone: true }),
|
|
73
|
+
createdAt: timestamp("created_at", { withTimezone: true })
|
|
74
|
+
.notNull()
|
|
75
|
+
.defaultNow(),
|
|
76
|
+
updatedAt: timestamp("updated_at", { withTimezone: true })
|
|
77
|
+
.notNull()
|
|
78
|
+
.defaultNow(),
|
|
79
|
+
},
|
|
80
|
+
(table) => [
|
|
81
|
+
uniqueIndex("uk_durable_job_stages_key").on(table.jobId, table.key),
|
|
82
|
+
uniqueIndex("uk_durable_job_stages_ordinal").on(table.jobId, table.ordinal),
|
|
83
|
+
check("ck_durable_job_stages_ordinal", sql`${table.ordinal} >= 0`),
|
|
84
|
+
check("ck_durable_job_stages_attempt", sql`${table.currentAttempt} >= 0`),
|
|
85
|
+
],
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
export const durableJobAttempts = pgTable(
|
|
89
|
+
"durable_job_attempts",
|
|
90
|
+
{
|
|
91
|
+
id: varchar("id", { length: 64 }).primaryKey(),
|
|
92
|
+
jobId: varchar("job_id", { length: 64 })
|
|
93
|
+
.notNull()
|
|
94
|
+
.references(() => durableJobs.id, { onDelete: "restrict" }),
|
|
95
|
+
stageId: varchar("stage_id", { length: 64 })
|
|
96
|
+
.notNull()
|
|
97
|
+
.references(() => durableJobStages.id, { onDelete: "restrict" }),
|
|
98
|
+
attemptNumber: integer("attempt_number").notNull(),
|
|
99
|
+
status: varchar("status", { length: 32 }).notNull().default("running"),
|
|
100
|
+
handlerKey: varchar("handler_key", { length: 160 }).notNull(),
|
|
101
|
+
workerId: varchar("worker_id", { length: 160 }).notNull(),
|
|
102
|
+
startedAt: timestamp("started_at", { withTimezone: true }).notNull(),
|
|
103
|
+
timeoutAt: timestamp("timeout_at", { withTimezone: true }).notNull(),
|
|
104
|
+
finishedAt: timestamp("finished_at", { withTimezone: true }),
|
|
105
|
+
createdAt: timestamp("created_at", { withTimezone: true })
|
|
106
|
+
.notNull()
|
|
107
|
+
.defaultNow(),
|
|
108
|
+
},
|
|
109
|
+
(table) => [
|
|
110
|
+
uniqueIndex("uk_durable_job_attempts_number").on(
|
|
111
|
+
table.stageId,
|
|
112
|
+
table.attemptNumber,
|
|
113
|
+
),
|
|
114
|
+
index("idx_durable_job_attempts_job").on(table.jobId, table.startedAt),
|
|
115
|
+
check("ck_durable_job_attempts_number", sql`${table.attemptNumber} > 0`),
|
|
116
|
+
],
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
export const durableJobLeases = pgTable(
|
|
120
|
+
"durable_job_leases",
|
|
121
|
+
{
|
|
122
|
+
id: varchar("id", { length: 64 }).primaryKey(),
|
|
123
|
+
jobId: varchar("job_id", { length: 64 })
|
|
124
|
+
.notNull()
|
|
125
|
+
.references(() => durableJobs.id, { onDelete: "restrict" }),
|
|
126
|
+
stageId: varchar("stage_id", { length: 64 })
|
|
127
|
+
.notNull()
|
|
128
|
+
.references(() => durableJobStages.id, { onDelete: "restrict" }),
|
|
129
|
+
attemptId: varchar("attempt_id", { length: 64 })
|
|
130
|
+
.notNull()
|
|
131
|
+
.references(() => durableJobAttempts.id, { onDelete: "restrict" }),
|
|
132
|
+
workerId: varchar("worker_id", { length: 160 }).notNull(),
|
|
133
|
+
tokenHash: varchar("token_hash", { length: 64 }).notNull(),
|
|
134
|
+
fencingToken: integer("fencing_token").notNull(),
|
|
135
|
+
status: varchar("status", { length: 24 }).notNull().default("active"),
|
|
136
|
+
acquiredAt: timestamp("acquired_at", { withTimezone: true }).notNull(),
|
|
137
|
+
heartbeatAt: timestamp("heartbeat_at", { withTimezone: true }).notNull(),
|
|
138
|
+
expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
|
|
139
|
+
releasedAt: timestamp("released_at", { withTimezone: true }),
|
|
140
|
+
},
|
|
141
|
+
(table) => [
|
|
142
|
+
uniqueIndex("uk_durable_job_leases_active_stage")
|
|
143
|
+
.on(table.stageId)
|
|
144
|
+
.where(sql`${table.status} = 'active'`),
|
|
145
|
+
index("idx_durable_job_leases_expiry").on(table.status, table.expiresAt),
|
|
146
|
+
check("ck_durable_job_leases_fencing", sql`${table.fencingToken} > 0`),
|
|
147
|
+
],
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
export const durableJobProgress = pgTable(
|
|
151
|
+
"durable_job_progress",
|
|
152
|
+
{
|
|
153
|
+
id: varchar("id", { length: 64 }).primaryKey(),
|
|
154
|
+
jobId: varchar("job_id", { length: 64 })
|
|
155
|
+
.notNull()
|
|
156
|
+
.references(() => durableJobs.id, { onDelete: "restrict" }),
|
|
157
|
+
stageId: varchar("stage_id", { length: 64 })
|
|
158
|
+
.notNull()
|
|
159
|
+
.references(() => durableJobStages.id, { onDelete: "restrict" }),
|
|
160
|
+
attemptId: varchar("attempt_id", { length: 64 })
|
|
161
|
+
.notNull()
|
|
162
|
+
.references(() => durableJobAttempts.id, { onDelete: "restrict" }),
|
|
163
|
+
sequence: integer("sequence").notNull(),
|
|
164
|
+
completedUnits: integer("completed_units").notNull(),
|
|
165
|
+
totalUnits: integer("total_units"),
|
|
166
|
+
message: varchar("message", { length: 500 }),
|
|
167
|
+
metadata: jsonb("metadata").$type<Record<string, unknown>>(),
|
|
168
|
+
recordedAt: timestamp("recorded_at", { withTimezone: true })
|
|
169
|
+
.notNull()
|
|
170
|
+
.defaultNow(),
|
|
171
|
+
},
|
|
172
|
+
(table) => [
|
|
173
|
+
uniqueIndex("uk_durable_job_progress_sequence").on(
|
|
174
|
+
table.attemptId,
|
|
175
|
+
table.sequence,
|
|
176
|
+
),
|
|
177
|
+
index("idx_durable_job_progress_job").on(table.jobId, table.recordedAt),
|
|
178
|
+
check("ck_durable_job_progress_sequence", sql`${table.sequence} > 0`),
|
|
179
|
+
check(
|
|
180
|
+
"ck_durable_job_progress_completed",
|
|
181
|
+
sql`${table.completedUnits} >= 0`,
|
|
182
|
+
),
|
|
183
|
+
],
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
export const durableJobArtifacts = pgTable(
|
|
187
|
+
"durable_job_artifacts",
|
|
188
|
+
{
|
|
189
|
+
id: varchar("id", { length: 64 }).primaryKey(),
|
|
190
|
+
jobId: varchar("job_id", { length: 64 })
|
|
191
|
+
.notNull()
|
|
192
|
+
.references(() => durableJobs.id, { onDelete: "restrict" }),
|
|
193
|
+
stageId: varchar("stage_id", { length: 64 }).references(
|
|
194
|
+
() => durableJobStages.id,
|
|
195
|
+
{ onDelete: "restrict" },
|
|
196
|
+
),
|
|
197
|
+
kind: varchar("kind", { length: 80 }).notNull(),
|
|
198
|
+
artifactRef: varchar("artifact_ref", { length: 37 }).notNull(),
|
|
199
|
+
filename: varchar("filename", { length: 240 }).notNull(),
|
|
200
|
+
contentType: varchar("content_type", { length: 120 }).notNull(),
|
|
201
|
+
byteSize: integer("byte_size").notNull(),
|
|
202
|
+
sha256: varchar("sha256", { length: 64 }).notNull(),
|
|
203
|
+
ownerId: varchar("owner_id", { length: 64 }).notNull(),
|
|
204
|
+
expiresAt: timestamp("expires_at", { withTimezone: true }),
|
|
205
|
+
createdAt: timestamp("created_at", { withTimezone: true })
|
|
206
|
+
.notNull()
|
|
207
|
+
.defaultNow(),
|
|
208
|
+
},
|
|
209
|
+
(table) => [
|
|
210
|
+
uniqueIndex("uk_durable_job_artifacts_kind").on(table.jobId, table.kind),
|
|
211
|
+
index("idx_durable_job_artifacts_expiry").on(table.expiresAt),
|
|
212
|
+
check("ck_durable_job_artifacts_size", sql`${table.byteSize} >= 0`),
|
|
213
|
+
],
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
export const durableJobFailures = pgTable(
|
|
217
|
+
"durable_job_failures",
|
|
218
|
+
{
|
|
219
|
+
id: varchar("id", { length: 64 }).primaryKey(),
|
|
220
|
+
jobId: varchar("job_id", { length: 64 })
|
|
221
|
+
.notNull()
|
|
222
|
+
.references(() => durableJobs.id, { onDelete: "restrict" }),
|
|
223
|
+
stageId: varchar("stage_id", { length: 64 }).references(
|
|
224
|
+
() => durableJobStages.id,
|
|
225
|
+
{ onDelete: "restrict" },
|
|
226
|
+
),
|
|
227
|
+
attemptId: varchar("attempt_id", { length: 64 }).references(
|
|
228
|
+
() => durableJobAttempts.id,
|
|
229
|
+
{ onDelete: "restrict" },
|
|
230
|
+
),
|
|
231
|
+
code: varchar("code", { length: 120 }).notNull(),
|
|
232
|
+
message: text("message").notNull(),
|
|
233
|
+
retryable: boolean("retryable").notNull(),
|
|
234
|
+
details: jsonb("details").$type<Record<string, unknown>>(),
|
|
235
|
+
occurredAt: timestamp("occurred_at", { withTimezone: true })
|
|
236
|
+
.notNull()
|
|
237
|
+
.defaultNow(),
|
|
238
|
+
},
|
|
239
|
+
(table) => [
|
|
240
|
+
index("idx_durable_job_failures_job").on(table.jobId, table.occurredAt),
|
|
241
|
+
],
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
export const durableJobRecoveries = pgTable(
|
|
245
|
+
"durable_job_recoveries",
|
|
246
|
+
{
|
|
247
|
+
id: varchar("id", { length: 64 }).primaryKey(),
|
|
248
|
+
jobId: varchar("job_id", { length: 64 })
|
|
249
|
+
.notNull()
|
|
250
|
+
.references(() => durableJobs.id, { onDelete: "restrict" }),
|
|
251
|
+
fromStatus: varchar("from_status", { length: 32 }).notNull(),
|
|
252
|
+
requestedBy: varchar("requested_by", { length: 64 }).notNull(),
|
|
253
|
+
reason: varchar("reason", { length: 500 }).notNull(),
|
|
254
|
+
idempotencyKey: varchar("idempotency_key", { length: 160 }).notNull(),
|
|
255
|
+
createdAt: timestamp("created_at", { withTimezone: true })
|
|
256
|
+
.notNull()
|
|
257
|
+
.defaultNow(),
|
|
258
|
+
},
|
|
259
|
+
(table) => [
|
|
260
|
+
uniqueIndex("uk_durable_job_recoveries_idempotency").on(
|
|
261
|
+
table.jobId,
|
|
262
|
+
table.idempotencyKey,
|
|
263
|
+
),
|
|
264
|
+
index("idx_durable_job_recoveries_job").on(table.jobId, table.createdAt),
|
|
265
|
+
],
|
|
266
|
+
);
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import {
|
|
2
|
+
bigint,
|
|
3
|
+
index,
|
|
4
|
+
integer,
|
|
5
|
+
pgTable,
|
|
6
|
+
timestamp,
|
|
7
|
+
uniqueIndex,
|
|
8
|
+
varchar,
|
|
9
|
+
} from "drizzle-orm/pg-core";
|
|
10
|
+
|
|
11
|
+
export const fileUploadSessions = pgTable(
|
|
12
|
+
"file_upload_sessions",
|
|
13
|
+
{
|
|
14
|
+
id: varchar("id", { length: 40 }).primaryKey(),
|
|
15
|
+
ownerId: varchar("owner_id", { length: 64 }).notNull(),
|
|
16
|
+
purpose: varchar("purpose", { length: 80 }).notNull(),
|
|
17
|
+
filename: varchar("filename", { length: 240 }).notNull(),
|
|
18
|
+
extension: varchar("extension", { length: 24 }).notNull(),
|
|
19
|
+
mimeType: varchar("mime_type", { length: 120 }).notNull(),
|
|
20
|
+
expectedByteSize: bigint("expected_byte_size", {
|
|
21
|
+
mode: "number",
|
|
22
|
+
}).notNull(),
|
|
23
|
+
expectedSha256: varchar("expected_sha256", { length: 64 }),
|
|
24
|
+
receivedByteSize: bigint("received_byte_size", { mode: "number" })
|
|
25
|
+
.notNull()
|
|
26
|
+
.default(0),
|
|
27
|
+
nextPartNumber: integer("next_part_number").notNull().default(0),
|
|
28
|
+
status: varchar("status", { length: 24 }).notNull(),
|
|
29
|
+
expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
|
|
30
|
+
fileExpiresAt: timestamp("file_expires_at", { withTimezone: true }),
|
|
31
|
+
fileId: varchar("file_id", { length: 40 }),
|
|
32
|
+
createdAt: timestamp("created_at", { withTimezone: true })
|
|
33
|
+
.notNull()
|
|
34
|
+
.defaultNow(),
|
|
35
|
+
updatedAt: timestamp("updated_at", { withTimezone: true })
|
|
36
|
+
.notNull()
|
|
37
|
+
.defaultNow(),
|
|
38
|
+
},
|
|
39
|
+
(table) => [
|
|
40
|
+
index("idx_file_upload_sessions_owner_created").on(
|
|
41
|
+
table.ownerId,
|
|
42
|
+
table.createdAt,
|
|
43
|
+
),
|
|
44
|
+
index("idx_file_upload_sessions_expiry").on(table.status, table.expiresAt),
|
|
45
|
+
],
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
export const fileUploadParts = pgTable(
|
|
49
|
+
"file_upload_parts",
|
|
50
|
+
{
|
|
51
|
+
sessionId: varchar("session_id", { length: 40 })
|
|
52
|
+
.notNull()
|
|
53
|
+
.references(() => fileUploadSessions.id, { onDelete: "cascade" }),
|
|
54
|
+
partNumber: integer("part_number").notNull(),
|
|
55
|
+
byteOffset: bigint("byte_offset", { mode: "number" }).notNull(),
|
|
56
|
+
artifactRef: varchar("artifact_ref", { length: 37 }).notNull(),
|
|
57
|
+
byteSize: bigint("byte_size", { mode: "number" }).notNull(),
|
|
58
|
+
sha256: varchar("sha256", { length: 64 }).notNull(),
|
|
59
|
+
createdAt: timestamp("created_at", { withTimezone: true })
|
|
60
|
+
.notNull()
|
|
61
|
+
.defaultNow(),
|
|
62
|
+
},
|
|
63
|
+
(table) => [
|
|
64
|
+
uniqueIndex("uk_file_upload_parts_session_part").on(
|
|
65
|
+
table.sessionId,
|
|
66
|
+
table.partNumber,
|
|
67
|
+
),
|
|
68
|
+
uniqueIndex("uk_file_upload_parts_artifact_ref").on(table.artifactRef),
|
|
69
|
+
],
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
export const fileMetadata = pgTable(
|
|
73
|
+
"file_metadata",
|
|
74
|
+
{
|
|
75
|
+
id: varchar("id", { length: 40 }).primaryKey(),
|
|
76
|
+
ownerId: varchar("owner_id", { length: 64 }).notNull(),
|
|
77
|
+
purpose: varchar("purpose", { length: 80 }).notNull(),
|
|
78
|
+
filename: varchar("filename", { length: 240 }).notNull(),
|
|
79
|
+
extension: varchar("extension", { length: 24 }).notNull(),
|
|
80
|
+
mimeType: varchar("mime_type", { length: 120 }).notNull(),
|
|
81
|
+
artifactRef: varchar("artifact_ref", { length: 37 }).notNull(),
|
|
82
|
+
byteSize: bigint("byte_size", { mode: "number" }).notNull(),
|
|
83
|
+
sha256: varchar("sha256", { length: 64 }).notNull(),
|
|
84
|
+
expiresAt: timestamp("expires_at", { withTimezone: true }),
|
|
85
|
+
createdAt: timestamp("created_at", { withTimezone: true })
|
|
86
|
+
.notNull()
|
|
87
|
+
.defaultNow(),
|
|
88
|
+
},
|
|
89
|
+
(table) => [
|
|
90
|
+
uniqueIndex("uk_file_metadata_artifact_ref").on(table.artifactRef),
|
|
91
|
+
index("idx_file_metadata_owner_created").on(table.ownerId, table.createdAt),
|
|
92
|
+
index("idx_file_metadata_expiry").on(table.expiresAt),
|
|
93
|
+
],
|
|
94
|
+
);
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
// @windy-module system.license begin
|
|
2
|
+
import { sql } from "drizzle-orm";
|
|
3
|
+
import { timestamp as licenseTimestamp } from "drizzle-orm/pg-core";
|
|
4
|
+
import { idColumn as licenseIdColumn } from "./common.js";
|
|
5
|
+
// @windy-module system.license end
|
|
6
|
+
// @windy-module system.audit begin
|
|
7
|
+
import { timestamp as auditTimestamp } from "drizzle-orm/pg-core";
|
|
8
|
+
import { idColumn as auditIdColumn } from "./common.js";
|
|
9
|
+
// @windy-module system.audit end
|
|
10
|
+
import {
|
|
11
|
+
boolean,
|
|
12
|
+
index,
|
|
13
|
+
jsonb,
|
|
14
|
+
pgTable,
|
|
15
|
+
text,
|
|
16
|
+
// @windy-module system.license begin
|
|
17
|
+
uniqueIndex,
|
|
18
|
+
// @windy-module system.license end
|
|
19
|
+
varchar,
|
|
20
|
+
} from "drizzle-orm/pg-core";
|
|
21
|
+
import { auditColumns, statusColumn } from "./common.js";
|
|
22
|
+
|
|
23
|
+
export const featureFlags = pgTable(
|
|
24
|
+
"feature_flags",
|
|
25
|
+
{
|
|
26
|
+
key: varchar("key", { length: 160 }).primaryKey(),
|
|
27
|
+
module: varchar("module", { length: 96 }).notNull(),
|
|
28
|
+
label: varchar("label", { length: 120 }).notNull(),
|
|
29
|
+
description: text("description"),
|
|
30
|
+
enabled: boolean("enabled").notNull().default(false),
|
|
31
|
+
visible: varchar("visible", { length: 32 }).notNull().default("visible"),
|
|
32
|
+
stage: varchar("stage", { length: 32 }).notNull().default("stable"),
|
|
33
|
+
// 旧布尔列仅为迁移兼容保留;正式 Guard 只读取 licenseKey 对应的 Entitlement。
|
|
34
|
+
licenseRequired: boolean("license_required").notNull().default(false),
|
|
35
|
+
licenseKey: varchar("license_key", { length: 160 }),
|
|
36
|
+
allowedLicenseVersions: jsonb("allowed_license_versions").$type<string[]>(),
|
|
37
|
+
dependencies: jsonb("dependencies").$type<string[]>(),
|
|
38
|
+
status: statusColumn().notNull().default("active"),
|
|
39
|
+
...auditColumns,
|
|
40
|
+
},
|
|
41
|
+
(table) => [
|
|
42
|
+
index("idx_feature_flags_module").on(table.module),
|
|
43
|
+
index("idx_feature_flags_license").on(table.licenseKey),
|
|
44
|
+
],
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// @windy-module system.license begin
|
|
48
|
+
export const licenseOperators = pgTable(
|
|
49
|
+
"license_operators",
|
|
50
|
+
{
|
|
51
|
+
id: licenseIdColumn().primaryKey(),
|
|
52
|
+
username: varchar("username", { length: 80 }).notNull(),
|
|
53
|
+
displayName: varchar("display_name", { length: 120 }).notNull(),
|
|
54
|
+
tokenHash: varchar("token_hash", { length: 128 }),
|
|
55
|
+
passwordHash: text("password_hash"),
|
|
56
|
+
role: varchar("role", { length: 40 }).notNull().default("readonly"),
|
|
57
|
+
permissions: jsonb("permissions").$type<string[]>().notNull(),
|
|
58
|
+
status: statusColumn().notNull(),
|
|
59
|
+
lastUsedAt: licenseTimestamp("last_used_at", { withTimezone: true }),
|
|
60
|
+
createdAt: licenseTimestamp("created_at", { withTimezone: true })
|
|
61
|
+
.notNull()
|
|
62
|
+
.defaultNow(),
|
|
63
|
+
updatedAt: licenseTimestamp("updated_at", { withTimezone: true }),
|
|
64
|
+
updatedBy: varchar("updated_by", { length: 64 }),
|
|
65
|
+
disabledAt: licenseTimestamp("disabled_at", { withTimezone: true }),
|
|
66
|
+
disabledBy: varchar("disabled_by", { length: 64 }),
|
|
67
|
+
},
|
|
68
|
+
(table) => [
|
|
69
|
+
uniqueIndex("uk_license_operators_username").on(table.username),
|
|
70
|
+
index("idx_license_operators_status").on(table.status),
|
|
71
|
+
],
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
export const licenseOperatorSessions = pgTable(
|
|
75
|
+
"license_operator_sessions",
|
|
76
|
+
{
|
|
77
|
+
id: licenseIdColumn().primaryKey(),
|
|
78
|
+
operatorId: varchar("operator_id", { length: 64 })
|
|
79
|
+
.notNull()
|
|
80
|
+
.references(() => licenseOperators.id, { onDelete: "restrict" }),
|
|
81
|
+
tokenHash: varchar("token_hash", { length: 128 }).notNull(),
|
|
82
|
+
status: statusColumn().notNull().default("active"),
|
|
83
|
+
issuedAt: licenseTimestamp("issued_at", { withTimezone: true })
|
|
84
|
+
.notNull()
|
|
85
|
+
.defaultNow(),
|
|
86
|
+
expiresAt: licenseTimestamp("expires_at", { withTimezone: true }).notNull(),
|
|
87
|
+
revokedAt: licenseTimestamp("revoked_at", { withTimezone: true }),
|
|
88
|
+
lastSeenAt: licenseTimestamp("last_seen_at", { withTimezone: true }),
|
|
89
|
+
ip: varchar("ip", { length: 80 }),
|
|
90
|
+
userAgent: text("user_agent"),
|
|
91
|
+
},
|
|
92
|
+
(table) => [
|
|
93
|
+
uniqueIndex("uk_license_operator_sessions_token_hash").on(table.tokenHash),
|
|
94
|
+
index("idx_license_operator_sessions_operator").on(table.operatorId),
|
|
95
|
+
index("idx_license_operator_sessions_status").on(table.status),
|
|
96
|
+
index("idx_license_operator_sessions_expires").on(table.expiresAt),
|
|
97
|
+
],
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
export const licenseKeys = pgTable(
|
|
101
|
+
"license_keys",
|
|
102
|
+
{
|
|
103
|
+
id: licenseIdColumn().primaryKey(),
|
|
104
|
+
keyId: varchar("key_id", { length: 160 }).notNull(),
|
|
105
|
+
publicKeyPem: text("public_key_pem").notNull(),
|
|
106
|
+
algorithm: varchar("algorithm", { length: 40 })
|
|
107
|
+
.notNull()
|
|
108
|
+
.default("ED25519"),
|
|
109
|
+
status: statusColumn().notNull(),
|
|
110
|
+
rotatedFromKeyId: varchar("rotated_from_key_id", { length: 160 }),
|
|
111
|
+
createdAt: licenseTimestamp("created_at", { withTimezone: true }).notNull(),
|
|
112
|
+
createdBy: varchar("created_by", { length: 64 }).notNull(),
|
|
113
|
+
retiredAt: licenseTimestamp("retired_at", { withTimezone: true }),
|
|
114
|
+
retiredBy: varchar("retired_by", { length: 64 }),
|
|
115
|
+
retainUntil: licenseTimestamp("retain_until", { withTimezone: true }),
|
|
116
|
+
},
|
|
117
|
+
(table) => [
|
|
118
|
+
uniqueIndex("uk_license_keys_key_id").on(table.keyId),
|
|
119
|
+
uniqueIndex("uk_license_keys_single_active")
|
|
120
|
+
.on(table.status)
|
|
121
|
+
.where(sql`${table.status} = 'active'`),
|
|
122
|
+
index("idx_license_keys_status").on(table.status),
|
|
123
|
+
],
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
export const licenseKeyCompatibilities = pgTable(
|
|
127
|
+
"license_key_compatibilities",
|
|
128
|
+
{
|
|
129
|
+
id: licenseIdColumn().primaryKey(),
|
|
130
|
+
productCode: varchar("product_code", { length: 120 }).notNull(),
|
|
131
|
+
softwareVersion: varchar("software_version", { length: 80 }).notNull(),
|
|
132
|
+
publicKeyId: varchar("public_key_id", { length: 160 })
|
|
133
|
+
.notNull()
|
|
134
|
+
.references(() => licenseKeys.keyId, { onDelete: "restrict" }),
|
|
135
|
+
declaredAt: licenseTimestamp("declared_at", { withTimezone: true })
|
|
136
|
+
.notNull()
|
|
137
|
+
.defaultNow(),
|
|
138
|
+
declaredBy: varchar("declared_by", { length: 64 }).notNull(),
|
|
139
|
+
},
|
|
140
|
+
(table) => [
|
|
141
|
+
uniqueIndex("uk_license_key_compatibility").on(
|
|
142
|
+
table.productCode,
|
|
143
|
+
table.softwareVersion,
|
|
144
|
+
table.publicKeyId,
|
|
145
|
+
),
|
|
146
|
+
index("idx_license_key_compatibility_version").on(
|
|
147
|
+
table.productCode,
|
|
148
|
+
table.softwareVersion,
|
|
149
|
+
),
|
|
150
|
+
index("idx_license_key_compatibility_key").on(table.publicKeyId),
|
|
151
|
+
],
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
export const licenseGovernanceEvents = pgTable(
|
|
155
|
+
"license_governance_events",
|
|
156
|
+
{
|
|
157
|
+
id: licenseIdColumn().primaryKey(),
|
|
158
|
+
type: varchar("type", { length: 100 }).notNull(),
|
|
159
|
+
resourceType: varchar("resource_type", { length: 80 }).notNull(),
|
|
160
|
+
resourceId: varchar("resource_id", { length: 160 }).notNull(),
|
|
161
|
+
actorId: varchar("actor_id", { length: 64 }).notNull(),
|
|
162
|
+
occurredAt: licenseTimestamp("occurred_at", { withTimezone: true })
|
|
163
|
+
.notNull()
|
|
164
|
+
.defaultNow(),
|
|
165
|
+
payload: jsonb("payload").$type<Record<string, unknown>>().notNull(),
|
|
166
|
+
},
|
|
167
|
+
(table) => [
|
|
168
|
+
index("idx_license_governance_resource_time").on(
|
|
169
|
+
table.resourceType,
|
|
170
|
+
table.resourceId,
|
|
171
|
+
table.occurredAt,
|
|
172
|
+
),
|
|
173
|
+
index("idx_license_governance_type_time").on(table.type, table.occurredAt),
|
|
174
|
+
index("idx_license_governance_actor_time").on(
|
|
175
|
+
table.actorId,
|
|
176
|
+
table.occurredAt,
|
|
177
|
+
),
|
|
178
|
+
],
|
|
179
|
+
);
|
|
180
|
+
// @windy-module system.license end
|
|
181
|
+
|
|
182
|
+
// @windy-module system.audit begin
|
|
183
|
+
export const auditLogs = pgTable(
|
|
184
|
+
"audit_logs",
|
|
185
|
+
{
|
|
186
|
+
id: auditIdColumn().primaryKey(),
|
|
187
|
+
actorId: varchar("actor_id", { length: 64 }),
|
|
188
|
+
action: varchar("action", { length: 120 }).notNull(),
|
|
189
|
+
resource: varchar("resource", { length: 160 }).notNull(),
|
|
190
|
+
resourceId: varchar("resource_id", { length: 160 }),
|
|
191
|
+
ip: varchar("ip", { length: 64 }),
|
|
192
|
+
userAgent: text("user_agent"),
|
|
193
|
+
metadata: jsonb("metadata").$type<Record<string, unknown>>(),
|
|
194
|
+
createdAt: auditTimestamp("created_at", { withTimezone: true })
|
|
195
|
+
.notNull()
|
|
196
|
+
.defaultNow(),
|
|
197
|
+
},
|
|
198
|
+
(table) => [
|
|
199
|
+
index("idx_audit_logs_actor").on(table.actorId),
|
|
200
|
+
index("idx_audit_logs_resource").on(table.resource, table.resourceId),
|
|
201
|
+
index("idx_audit_logs_created").on(table.createdAt),
|
|
202
|
+
],
|
|
203
|
+
);
|
|
204
|
+
// @windy-module system.audit end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { sql } from "drizzle-orm";
|
|
2
|
+
import type { LifecycleStatus } from "@southwind-ai/shared";
|
|
3
|
+
import {
|
|
4
|
+
boolean,
|
|
5
|
+
check,
|
|
6
|
+
index,
|
|
7
|
+
jsonb,
|
|
8
|
+
pgTable,
|
|
9
|
+
timestamp,
|
|
10
|
+
uniqueIndex,
|
|
11
|
+
varchar,
|
|
12
|
+
} from "drizzle-orm/pg-core";
|
|
13
|
+
import type { GovernedExportScopeSnapshot } from "./common.js";
|
|
14
|
+
import { durableJobs } from "./durable-jobs.js";
|
|
15
|
+
|
|
16
|
+
export interface GovernedExportQuerySnapshot {
|
|
17
|
+
q?: string;
|
|
18
|
+
status?: LifecycleStatus;
|
|
19
|
+
includeDeleted: boolean;
|
|
20
|
+
sort?: Array<{ field: string; order: "asc" | "desc" }>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const governedExportRequests = pgTable(
|
|
24
|
+
"governed_export_requests",
|
|
25
|
+
{
|
|
26
|
+
id: varchar("id", { length: 64 }).primaryKey(),
|
|
27
|
+
resourceType: varchar("resource_type", { length: 160 }).notNull(),
|
|
28
|
+
policyKey: varchar("policy_key", { length: 160 }).notNull(),
|
|
29
|
+
policyVersion: varchar("policy_version", { length: 40 }).notNull(),
|
|
30
|
+
fieldKeys: jsonb("field_keys").$type<string[]>().notNull(),
|
|
31
|
+
requesterId: varchar("requester_id", { length: 64 }).notNull(),
|
|
32
|
+
requesterName: varchar("requester_name", { length: 160 }).notNull(),
|
|
33
|
+
idempotencyKey: varchar("idempotency_key", { length: 160 }).notNull(),
|
|
34
|
+
requestFingerprint: varchar("request_fingerprint", {
|
|
35
|
+
length: 64,
|
|
36
|
+
}).notNull(),
|
|
37
|
+
reason: varchar("reason", { length: 500 }).notNull(),
|
|
38
|
+
scopeSnapshot: jsonb("scope_snapshot")
|
|
39
|
+
.$type<GovernedExportScopeSnapshot>()
|
|
40
|
+
.notNull(),
|
|
41
|
+
scopeHash: varchar("scope_hash", { length: 64 }).notNull(),
|
|
42
|
+
querySnapshot: jsonb("query_snapshot")
|
|
43
|
+
.$type<GovernedExportQuerySnapshot>()
|
|
44
|
+
.notNull(),
|
|
45
|
+
status: varchar("status", { length: 32 }).notNull(),
|
|
46
|
+
approvalRequired: boolean("approval_required").notNull(),
|
|
47
|
+
approvalId: varchar("approval_id", { length: 64 }),
|
|
48
|
+
durableJobId: varchar("durable_job_id", { length: 64 }).references(
|
|
49
|
+
() => durableJobs.id,
|
|
50
|
+
{ onDelete: "restrict" },
|
|
51
|
+
),
|
|
52
|
+
expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
|
|
53
|
+
createdAt: timestamp("created_at", { withTimezone: true })
|
|
54
|
+
.notNull()
|
|
55
|
+
.defaultNow(),
|
|
56
|
+
updatedAt: timestamp("updated_at", { withTimezone: true })
|
|
57
|
+
.notNull()
|
|
58
|
+
.defaultNow(),
|
|
59
|
+
},
|
|
60
|
+
(table) => [
|
|
61
|
+
uniqueIndex("uk_governed_exports_idempotency").on(
|
|
62
|
+
table.requesterId,
|
|
63
|
+
table.resourceType,
|
|
64
|
+
table.idempotencyKey,
|
|
65
|
+
),
|
|
66
|
+
uniqueIndex("uk_governed_exports_job")
|
|
67
|
+
.on(table.durableJobId)
|
|
68
|
+
.where(sql`${table.durableJobId} is not null`),
|
|
69
|
+
index("idx_governed_exports_owner_created").on(
|
|
70
|
+
table.requesterId,
|
|
71
|
+
table.createdAt,
|
|
72
|
+
),
|
|
73
|
+
index("idx_governed_exports_expiry").on(table.status, table.expiresAt),
|
|
74
|
+
check(
|
|
75
|
+
"ck_governed_exports_status",
|
|
76
|
+
sql`${table.status} in ('pending-approval', 'queued', 'running', 'succeeded', 'failed', 'cancelled', 'rejected')`,
|
|
77
|
+
),
|
|
78
|
+
check(
|
|
79
|
+
"ck_governed_exports_scope_hash",
|
|
80
|
+
sql`${table.scopeHash} ~ '^[0-9a-f]{64}$'`,
|
|
81
|
+
),
|
|
82
|
+
check(
|
|
83
|
+
"ck_governed_exports_approval_job_state",
|
|
84
|
+
sql`(${table.approvalRequired} or ${table.approvalId} is null)
|
|
85
|
+
and (${table.status} <> 'pending-approval' or (${table.approvalRequired} and ${table.durableJobId} is null))
|
|
86
|
+
and (${table.status} not in ('running', 'succeeded') or ${table.durableJobId} is not null)
|
|
87
|
+
and (${table.durableJobId} is null or not ${table.approvalRequired} or ${table.approvalId} is not null)`,
|
|
88
|
+
),
|
|
89
|
+
],
|
|
90
|
+
);
|