aegra-hono 0.11.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/LICENSE +201 -0
- package/README.md +115 -0
- package/dist/auth.d.ts +16 -0
- package/dist/auth.js +38 -0
- package/dist/auth.js.map +10 -0
- package/dist/cli.js +714 -0
- package/dist/cli.js.map +14 -0
- package/dist/graph.d.ts +28 -0
- package/dist/graph.js +26 -0
- package/dist/graph.js.map +10 -0
- package/dist/hono.d.ts +6 -0
- package/dist/hono.js +26 -0
- package/dist/hono.js.map +10 -0
- package/dist/index.js +10183 -0
- package/dist/index.js.map +41 -0
- package/dist/runtime.d.ts +35 -0
- package/dist/runtime.js +10171 -0
- package/dist/runtime.js.map +40 -0
- package/dist/types.d.ts +65 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +9 -0
- package/package.json +77 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,714 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __returnValue = (v) => v;
|
|
5
|
+
function __exportSetter(name, newValue) {
|
|
6
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
7
|
+
}
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, {
|
|
11
|
+
get: all[name],
|
|
12
|
+
enumerable: true,
|
|
13
|
+
configurable: true,
|
|
14
|
+
set: __exportSetter.bind(all, name)
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
18
|
+
|
|
19
|
+
// src/config/settings.ts
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
var booleanFromEnv, positiveIntFromEnv = (fallback) => z.coerce.number().int().positive().default(fallback), environmentSchema, raw, settings;
|
|
22
|
+
var init_settings = __esm(() => {
|
|
23
|
+
booleanFromEnv = z.stringbool().default(false);
|
|
24
|
+
environmentSchema = z.object({
|
|
25
|
+
PROJECT_NAME: z.string().default("Aegra"),
|
|
26
|
+
VERSION: z.string().default("0.11.0"),
|
|
27
|
+
ENV_MODE: z.string().default("PRODUCTION"),
|
|
28
|
+
LOG_VERBOSITY: z.string().default("verbose"),
|
|
29
|
+
LOG_EXCLUDE_PATHS: z.string().default(""),
|
|
30
|
+
HOST: z.string().default("0.0.0.0"),
|
|
31
|
+
PORT: z.coerce.number().int().min(1).max(65535).default(2026),
|
|
32
|
+
AEGRA_CONFIG: z.string().default("aegra.json"),
|
|
33
|
+
RUN_MIGRATIONS_ON_STARTUP: z.stringbool().default(true),
|
|
34
|
+
DATABASE_URL: z.string().optional(),
|
|
35
|
+
POSTGRES_USER: z.string().default("postgres"),
|
|
36
|
+
POSTGRES_PASSWORD: z.string().default("postgres"),
|
|
37
|
+
POSTGRES_HOST: z.string().default("localhost"),
|
|
38
|
+
POSTGRES_PORT: z.coerce.number().int().min(1).max(65535).default(5432),
|
|
39
|
+
POSTGRES_DB: z.string().default("aegra"),
|
|
40
|
+
REDIS_BROKER_ENABLED: booleanFromEnv,
|
|
41
|
+
REDIS_URL: z.string().default("redis://localhost:6379/0"),
|
|
42
|
+
WORKER_COUNT: positiveIntFromEnv(3),
|
|
43
|
+
N_JOBS_PER_WORKER: positiveIntFromEnv(10),
|
|
44
|
+
WORKER_QUEUE_KEY: z.string().default("aegra:jobs"),
|
|
45
|
+
BG_JOB_TIMEOUT_SECS: positiveIntFromEnv(3600),
|
|
46
|
+
BG_JOB_MAX_RETRIES: z.coerce.number().int().nonnegative().default(3),
|
|
47
|
+
WORKER_DRAIN_TIMEOUT: z.coerce.number().nonnegative().default(30),
|
|
48
|
+
STUCK_PENDING_THRESHOLD_SECONDS: positiveIntFromEnv(120),
|
|
49
|
+
LEASE_DURATION_SECONDS: positiveIntFromEnv(30),
|
|
50
|
+
HEARTBEAT_INTERVAL_SECONDS: positiveIntFromEnv(10),
|
|
51
|
+
REAPER_INTERVAL_SECONDS: positiveIntFromEnv(15),
|
|
52
|
+
POSTGRES_POLL_INTERVAL_SECONDS: positiveIntFromEnv(5),
|
|
53
|
+
KEEPALIVE_INTERVAL_SECS: positiveIntFromEnv(5),
|
|
54
|
+
CRON_ENABLED: z.stringbool().default(true),
|
|
55
|
+
CRON_POLL_INTERVAL_SECONDS: positiveIntFromEnv(60),
|
|
56
|
+
CRON_CLAIM_DURATION_SECONDS: positiveIntFromEnv(300),
|
|
57
|
+
CRON_MAX_PER_USER: z.coerce.number().int().nonnegative().default(100),
|
|
58
|
+
CRON_ALLOW_SECONDS_SCHEDULE: booleanFromEnv,
|
|
59
|
+
CRON_TICK_BATCH_SIZE: positiveIntFromEnv(100),
|
|
60
|
+
CRON_MAX_PAYLOAD_BYTES: positiveIntFromEnv(65536),
|
|
61
|
+
AEGRA_THREAD_TTL: z.string().optional(),
|
|
62
|
+
LANGGRAPH_THREAD_TTL: z.string().optional(),
|
|
63
|
+
MAX_SEARCH_LIMIT: positiveIntFromEnv(1000),
|
|
64
|
+
ENABLE_PROMETHEUS_METRICS: booleanFromEnv,
|
|
65
|
+
FF_V2_EVENT_STREAMING: z.stringbool().default(true),
|
|
66
|
+
OTEL_SERVICE_NAME: z.string().default("aegra-backend"),
|
|
67
|
+
OTEL_TARGETS: z.string().default(""),
|
|
68
|
+
OTEL_CONSOLE_EXPORT: booleanFromEnv,
|
|
69
|
+
OTEL_EXPORTER_OTLP_ENDPOINT: z.string().optional(),
|
|
70
|
+
OTEL_EXPORTER_OTLP_HEADERS: z.string().optional(),
|
|
71
|
+
LANGFUSE_BASE_URL: z.string().default("http://localhost:3000"),
|
|
72
|
+
LANGFUSE_PUBLIC_KEY: z.string().optional(),
|
|
73
|
+
LANGFUSE_SECRET_KEY: z.string().optional(),
|
|
74
|
+
PHOENIX_COLLECTOR_ENDPOINT: z.string().default("http://127.0.0.1:6006/v1/traces"),
|
|
75
|
+
PHOENIX_API_KEY: z.string().optional()
|
|
76
|
+
}).superRefine((value, issue) => {
|
|
77
|
+
if (value.LEASE_DURATION_SECONDS <= 2 * value.HEARTBEAT_INTERVAL_SECONDS) {
|
|
78
|
+
issue.addIssue({
|
|
79
|
+
code: "custom",
|
|
80
|
+
path: ["LEASE_DURATION_SECONDS"],
|
|
81
|
+
message: "LEASE_DURATION_SECONDS must exceed twice HEARTBEAT_INTERVAL_SECONDS"
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
raw = environmentSchema.parse(process.env);
|
|
86
|
+
settings = {
|
|
87
|
+
...raw,
|
|
88
|
+
DATABASE_URL: raw.DATABASE_URL ?? `postgresql://${encodeURIComponent(raw.POSTGRES_USER)}:${encodeURIComponent(raw.POSTGRES_PASSWORD)}@${raw.POSTGRES_HOST}:${raw.POSTGRES_PORT}/${raw.POSTGRES_DB}`
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// src/config/project.ts
|
|
93
|
+
import { readFile } from "fs/promises";
|
|
94
|
+
import { dirname, resolve } from "path";
|
|
95
|
+
import { z as z2 } from "zod";
|
|
96
|
+
async function loadConfig() {
|
|
97
|
+
if (cachedConfig)
|
|
98
|
+
return cachedConfig;
|
|
99
|
+
const configPath = resolve(settings.AEGRA_CONFIG);
|
|
100
|
+
const content = await readFile(configPath, "utf8");
|
|
101
|
+
cachedConfig = configSchema.parse(JSON.parse(content));
|
|
102
|
+
return cachedConfig;
|
|
103
|
+
}
|
|
104
|
+
function configDirectory() {
|
|
105
|
+
return dirname(resolve(settings.AEGRA_CONFIG));
|
|
106
|
+
}
|
|
107
|
+
var corsSchema, configSchema, cachedConfig;
|
|
108
|
+
var init_project = __esm(() => {
|
|
109
|
+
init_settings();
|
|
110
|
+
corsSchema = z2.object({
|
|
111
|
+
allow_origins: z2.array(z2.string()).default(["*"]),
|
|
112
|
+
allow_methods: z2.array(z2.string()).default(["*"]),
|
|
113
|
+
allow_headers: z2.array(z2.string()).default(["*"]),
|
|
114
|
+
allow_credentials: z2.boolean().optional(),
|
|
115
|
+
expose_headers: z2.array(z2.string()).default(["Content-Location", "Location"]),
|
|
116
|
+
max_age: z2.number().int().nonnegative().default(600)
|
|
117
|
+
});
|
|
118
|
+
configSchema = z2.object({
|
|
119
|
+
dependencies: z2.array(z2.string()).default([]),
|
|
120
|
+
graphs: z2.record(z2.string(), z2.string()).default({}),
|
|
121
|
+
auth: z2.object({ path: z2.string(), disable_studio_auth: z2.boolean().default(false) }).optional(),
|
|
122
|
+
http: z2.object({
|
|
123
|
+
app: z2.string().optional(),
|
|
124
|
+
enable_custom_route_auth: z2.boolean().default(false),
|
|
125
|
+
cors: corsSchema.optional()
|
|
126
|
+
}).optional(),
|
|
127
|
+
store: z2.object({
|
|
128
|
+
index: z2.object({ dims: z2.number().int().positive(), embed: z2.string(), fields: z2.array(z2.string()).nullable().optional() }).nullable().optional(),
|
|
129
|
+
scopes: z2.record(z2.string(), z2.array(z2.string())).default({})
|
|
130
|
+
}).optional(),
|
|
131
|
+
checkpointer: z2.object({
|
|
132
|
+
ttl: z2.object({
|
|
133
|
+
strategy: z2.enum(["delete", "keep_latest"]).default("delete"),
|
|
134
|
+
default_ttl: z2.number().positive().optional(),
|
|
135
|
+
sweep_interval_minutes: z2.number().positive().default(5),
|
|
136
|
+
sweep_limit: z2.number().int().positive().default(100)
|
|
137
|
+
}).nullable().optional()
|
|
138
|
+
}).optional()
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// src/core/schema.ts
|
|
143
|
+
var exports_schema = {};
|
|
144
|
+
__export(exports_schema, {
|
|
145
|
+
threads: () => threads,
|
|
146
|
+
threadTtl: () => threadTtl,
|
|
147
|
+
threadEvents: () => threadEvents,
|
|
148
|
+
storeItems: () => storeItems,
|
|
149
|
+
runs: () => runs,
|
|
150
|
+
migrations: () => migrations,
|
|
151
|
+
crons: () => crons,
|
|
152
|
+
assistants: () => assistants,
|
|
153
|
+
assistantVersions: () => assistantVersions
|
|
154
|
+
});
|
|
155
|
+
import { sql } from "drizzle-orm";
|
|
156
|
+
import { bigint, bigserial, boolean, doublePrecision, index, integer, jsonb, pgTable, primaryKey, text, timestamp, unique, uniqueIndex } from "drizzle-orm/pg-core";
|
|
157
|
+
var timestamps, migrations, assistants, assistantVersions, threads, runs, threadEvents, crons, storeItems, threadTtl;
|
|
158
|
+
var init_schema = __esm(() => {
|
|
159
|
+
timestamps = {
|
|
160
|
+
created_at: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
161
|
+
updated_at: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
162
|
+
};
|
|
163
|
+
migrations = pgTable("aegra_migrations", {
|
|
164
|
+
version: integer("version").primaryKey(),
|
|
165
|
+
applied_at: timestamp("applied_at", { withTimezone: true }).notNull().defaultNow()
|
|
166
|
+
});
|
|
167
|
+
assistants = pgTable("assistant", {
|
|
168
|
+
assistant_id: text("assistant_id").primaryKey().default(sql`gen_random_uuid()::text`),
|
|
169
|
+
name: text("name").notNull(),
|
|
170
|
+
description: text("description"),
|
|
171
|
+
graph_id: text("graph_id").notNull(),
|
|
172
|
+
config: jsonb("config").$type().notNull().default({}),
|
|
173
|
+
context: jsonb("context").$type().notNull().default({}),
|
|
174
|
+
user_id: text("user_id").notNull(),
|
|
175
|
+
version: integer("version").notNull().default(1),
|
|
176
|
+
metadata: jsonb("metadata").$type().notNull().default({}),
|
|
177
|
+
...timestamps
|
|
178
|
+
}, (table) => [
|
|
179
|
+
index("idx_assistant_user").on(table.user_id),
|
|
180
|
+
unique("assistant_user_id_unique").on(table.user_id, table.assistant_id),
|
|
181
|
+
uniqueIndex("idx_assistant_user_graph_config").on(table.user_id, table.graph_id, sql`md5(${table.config}::text)`),
|
|
182
|
+
index("idx_assistant_metadata_gin").using("gin", sql`${table.metadata} jsonb_path_ops`)
|
|
183
|
+
]);
|
|
184
|
+
assistantVersions = pgTable("assistant_versions", {
|
|
185
|
+
assistant_id: text("assistant_id").notNull().references(() => assistants.assistant_id, { onDelete: "cascade" }),
|
|
186
|
+
version: integer("version").notNull(),
|
|
187
|
+
graph_id: text("graph_id").notNull(),
|
|
188
|
+
config: jsonb("config").$type(),
|
|
189
|
+
context: jsonb("context").$type(),
|
|
190
|
+
metadata: jsonb("metadata").$type().notNull().default({}),
|
|
191
|
+
name: text("name"),
|
|
192
|
+
description: text("description"),
|
|
193
|
+
created_at: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
|
|
194
|
+
}, (table) => [primaryKey({ columns: [table.assistant_id, table.version] })]);
|
|
195
|
+
threads = pgTable("thread", {
|
|
196
|
+
thread_id: text("thread_id").primaryKey(),
|
|
197
|
+
status: text("status").notNull().default("idle"),
|
|
198
|
+
metadata_json: jsonb("metadata_json").$type().notNull().default({}),
|
|
199
|
+
user_id: text("user_id").notNull(),
|
|
200
|
+
...timestamps
|
|
201
|
+
}, (table) => [
|
|
202
|
+
index("idx_thread_user").on(table.user_id),
|
|
203
|
+
index("idx_thread_metadata_gin").using("gin", sql`${table.metadata_json} jsonb_path_ops`)
|
|
204
|
+
]);
|
|
205
|
+
runs = pgTable("runs", {
|
|
206
|
+
run_id: text("run_id").primaryKey().default(sql`gen_random_uuid()::text`),
|
|
207
|
+
thread_id: text("thread_id").notNull().references(() => threads.thread_id, { onDelete: "cascade" }),
|
|
208
|
+
assistant_id: text("assistant_id").references(() => assistants.assistant_id, { onDelete: "cascade" }),
|
|
209
|
+
status: text("status").notNull().default("pending"),
|
|
210
|
+
input: jsonb("input").$type(),
|
|
211
|
+
config: jsonb("config").$type(),
|
|
212
|
+
context: jsonb("context").$type(),
|
|
213
|
+
output: jsonb("output").$type(),
|
|
214
|
+
error_message: text("error_message"),
|
|
215
|
+
user_id: text("user_id").notNull(),
|
|
216
|
+
execution_params: jsonb("execution_params").$type(),
|
|
217
|
+
claimed_by: text("claimed_by"),
|
|
218
|
+
lease_expires_at: timestamp("lease_expires_at", { withTimezone: true }),
|
|
219
|
+
...timestamps
|
|
220
|
+
}, (table) => [
|
|
221
|
+
index("idx_runs_thread_id").on(table.thread_id),
|
|
222
|
+
index("idx_runs_status").on(table.status),
|
|
223
|
+
index("idx_runs_user").on(table.user_id),
|
|
224
|
+
index("idx_runs_assistant_id").on(table.assistant_id),
|
|
225
|
+
index("idx_runs_created_at").on(table.created_at),
|
|
226
|
+
index("idx_runs_lease_reaper").on(table.status, table.lease_expires_at).where(sql`${table.status} = 'running'`)
|
|
227
|
+
]);
|
|
228
|
+
threadEvents = pgTable("thread_events", {
|
|
229
|
+
seq: bigserial("seq", { mode: "number" }).primaryKey(),
|
|
230
|
+
thread_id: text("thread_id").notNull().references(() => threads.thread_id, { onDelete: "cascade" }),
|
|
231
|
+
run_id: text("run_id").notNull().references(() => runs.run_id, { onDelete: "cascade" }),
|
|
232
|
+
user_id: text("user_id").notNull(),
|
|
233
|
+
event_id: text("event_id").notNull().unique(),
|
|
234
|
+
method: text("method").notNull(),
|
|
235
|
+
namespace: text("namespace").array().notNull().default(sql`'{}'::text[]`),
|
|
236
|
+
event_data: jsonb("event_data").$type().notNull(),
|
|
237
|
+
event_timestamp: bigint("event_timestamp", { mode: "number" }).notNull(),
|
|
238
|
+
created_at: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
|
|
239
|
+
}, (table) => [index("idx_thread_events_replay").on(table.user_id, table.thread_id, table.seq)]);
|
|
240
|
+
crons = pgTable("crons", {
|
|
241
|
+
cron_id: text("cron_id").primaryKey().default(sql`gen_random_uuid()::text`),
|
|
242
|
+
assistant_id: text("assistant_id").notNull().references(() => assistants.assistant_id, { onDelete: "cascade" }),
|
|
243
|
+
thread_id: text("thread_id").references(() => threads.thread_id, { onDelete: "cascade" }),
|
|
244
|
+
user_id: text("user_id").notNull(),
|
|
245
|
+
schedule: text("schedule").notNull(),
|
|
246
|
+
payload: jsonb("payload").$type().notNull().default({}),
|
|
247
|
+
metadata: jsonb("metadata").$type().notNull().default({}),
|
|
248
|
+
on_run_completed: text("on_run_completed"),
|
|
249
|
+
enabled: boolean("enabled").notNull().default(true),
|
|
250
|
+
end_time: timestamp("end_time", { withTimezone: true }),
|
|
251
|
+
next_run_date: timestamp("next_run_date", { withTimezone: true }),
|
|
252
|
+
claimed_until: timestamp("claimed_until", { withTimezone: true }),
|
|
253
|
+
...timestamps
|
|
254
|
+
}, (table) => [
|
|
255
|
+
index("idx_cron_user").on(table.user_id),
|
|
256
|
+
index("idx_cron_assistant_id").on(table.assistant_id),
|
|
257
|
+
index("idx_cron_thread_id").on(table.thread_id),
|
|
258
|
+
index("idx_cron_next_run").on(table.enabled, table.next_run_date)
|
|
259
|
+
]);
|
|
260
|
+
storeItems = pgTable("store_items", {
|
|
261
|
+
user_id: text("user_id").notNull(),
|
|
262
|
+
namespace: text("namespace").array().notNull(),
|
|
263
|
+
key: text("key").notNull(),
|
|
264
|
+
value: jsonb("value").$type().notNull(),
|
|
265
|
+
...timestamps
|
|
266
|
+
}, (table) => [primaryKey({ columns: [table.namespace, table.key] })]);
|
|
267
|
+
threadTtl = pgTable("thread_ttl", {
|
|
268
|
+
thread_id: text("thread_id").primaryKey().references(() => threads.thread_id, { onDelete: "cascade" }),
|
|
269
|
+
strategy: text("strategy").notNull().default("delete"),
|
|
270
|
+
ttl_minutes: doublePrecision("ttl_minutes").notNull(),
|
|
271
|
+
created_at: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
272
|
+
expires_at: timestamp("expires_at", { withTimezone: true }).notNull()
|
|
273
|
+
}, (table) => [index("idx_thread_ttl_expires_at").on(table.expires_at)]);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// src/core/database.ts
|
|
277
|
+
var exports_database = {};
|
|
278
|
+
__export(exports_database, {
|
|
279
|
+
pingDatabase: () => pingDatabase,
|
|
280
|
+
migrate: () => migrate,
|
|
281
|
+
db: () => db,
|
|
282
|
+
closeDatabase: () => closeDatabase,
|
|
283
|
+
client: () => client
|
|
284
|
+
});
|
|
285
|
+
import postgres from "postgres";
|
|
286
|
+
import { drizzle } from "drizzle-orm/postgres-js";
|
|
287
|
+
async function migrate() {
|
|
288
|
+
for (const statement of migrations2)
|
|
289
|
+
await client.unsafe(statement);
|
|
290
|
+
if ((await loadConfig()).store?.index) {
|
|
291
|
+
await client.unsafe("CREATE EXTENSION IF NOT EXISTS vector");
|
|
292
|
+
await client.unsafe(`CREATE TABLE IF NOT EXISTS store_embeddings (
|
|
293
|
+
user_id text NOT NULL,
|
|
294
|
+
namespace text[] NOT NULL,
|
|
295
|
+
key text NOT NULL,
|
|
296
|
+
embedding vector NOT NULL,
|
|
297
|
+
PRIMARY KEY(namespace, key),
|
|
298
|
+
FOREIGN KEY(namespace, key) REFERENCES store_items(namespace, key) ON DELETE CASCADE
|
|
299
|
+
)`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
async function pingDatabase() {
|
|
303
|
+
try {
|
|
304
|
+
await client`SELECT 1`;
|
|
305
|
+
return true;
|
|
306
|
+
} catch {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
async function closeDatabase() {
|
|
311
|
+
await client.end({ timeout: 5 });
|
|
312
|
+
}
|
|
313
|
+
var client, db, migrations2;
|
|
314
|
+
var init_database = __esm(() => {
|
|
315
|
+
init_project();
|
|
316
|
+
init_settings();
|
|
317
|
+
init_schema();
|
|
318
|
+
client = postgres(settings.DATABASE_URL, {
|
|
319
|
+
max: 20,
|
|
320
|
+
idle_timeout: 30,
|
|
321
|
+
connect_timeout: 10,
|
|
322
|
+
transform: { undefined: null },
|
|
323
|
+
onnotice: (notice) => {
|
|
324
|
+
if (settings.LOG_VERBOSITY !== "verbose")
|
|
325
|
+
return;
|
|
326
|
+
console.log(JSON.stringify({ event: "postgres_notice", severity: notice.severity, code: notice.code, message: notice.message }));
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
db = drizzle({ client, schema: exports_schema });
|
|
330
|
+
migrations2 = [
|
|
331
|
+
`CREATE TABLE IF NOT EXISTS aegra_migrations (
|
|
332
|
+
version integer PRIMARY KEY,
|
|
333
|
+
applied_at timestamptz NOT NULL DEFAULT now()
|
|
334
|
+
)`,
|
|
335
|
+
`CREATE TABLE IF NOT EXISTS assistant (
|
|
336
|
+
assistant_id text PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
337
|
+
name text NOT NULL,
|
|
338
|
+
description text,
|
|
339
|
+
graph_id text NOT NULL,
|
|
340
|
+
config jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
341
|
+
context jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
342
|
+
user_id text NOT NULL,
|
|
343
|
+
version integer NOT NULL DEFAULT 1,
|
|
344
|
+
metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
345
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
346
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
347
|
+
UNIQUE(user_id, assistant_id)
|
|
348
|
+
)`,
|
|
349
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_assistant_user_graph_config
|
|
350
|
+
ON assistant(user_id, graph_id, md5(config::text))`,
|
|
351
|
+
`CREATE INDEX IF NOT EXISTS idx_assistant_user ON assistant(user_id)`,
|
|
352
|
+
`CREATE INDEX IF NOT EXISTS idx_assistant_metadata_gin ON assistant USING gin (metadata jsonb_path_ops)`,
|
|
353
|
+
`CREATE TABLE IF NOT EXISTS assistant_versions (
|
|
354
|
+
assistant_id text NOT NULL REFERENCES assistant(assistant_id) ON DELETE CASCADE,
|
|
355
|
+
version integer NOT NULL,
|
|
356
|
+
graph_id text NOT NULL,
|
|
357
|
+
config jsonb,
|
|
358
|
+
context jsonb,
|
|
359
|
+
metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
360
|
+
name text,
|
|
361
|
+
description text,
|
|
362
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
363
|
+
PRIMARY KEY(assistant_id, version)
|
|
364
|
+
)`,
|
|
365
|
+
`CREATE TABLE IF NOT EXISTS thread (
|
|
366
|
+
thread_id text PRIMARY KEY,
|
|
367
|
+
status text NOT NULL DEFAULT 'idle',
|
|
368
|
+
metadata_json jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
369
|
+
user_id text NOT NULL,
|
|
370
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
371
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
372
|
+
)`,
|
|
373
|
+
`CREATE INDEX IF NOT EXISTS idx_thread_user ON thread(user_id)`,
|
|
374
|
+
`CREATE INDEX IF NOT EXISTS idx_thread_metadata_gin ON thread USING gin (metadata_json jsonb_path_ops)`,
|
|
375
|
+
`CREATE TABLE IF NOT EXISTS runs (
|
|
376
|
+
run_id text PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
377
|
+
thread_id text NOT NULL REFERENCES thread(thread_id) ON DELETE CASCADE,
|
|
378
|
+
assistant_id text REFERENCES assistant(assistant_id) ON DELETE CASCADE,
|
|
379
|
+
status text NOT NULL DEFAULT 'pending',
|
|
380
|
+
input jsonb,
|
|
381
|
+
config jsonb,
|
|
382
|
+
context jsonb,
|
|
383
|
+
output jsonb,
|
|
384
|
+
error_message text,
|
|
385
|
+
user_id text NOT NULL,
|
|
386
|
+
execution_params jsonb,
|
|
387
|
+
claimed_by text,
|
|
388
|
+
lease_expires_at timestamptz,
|
|
389
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
390
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
391
|
+
)`,
|
|
392
|
+
`CREATE INDEX IF NOT EXISTS idx_runs_thread_id ON runs(thread_id)`,
|
|
393
|
+
`CREATE INDEX IF NOT EXISTS idx_runs_status ON runs(status)`,
|
|
394
|
+
`CREATE INDEX IF NOT EXISTS idx_runs_user ON runs(user_id)`,
|
|
395
|
+
`CREATE INDEX IF NOT EXISTS idx_runs_assistant_id ON runs(assistant_id)`,
|
|
396
|
+
`CREATE INDEX IF NOT EXISTS idx_runs_created_at ON runs(created_at)`,
|
|
397
|
+
`CREATE INDEX IF NOT EXISTS idx_runs_lease_reaper ON runs(status, lease_expires_at) WHERE status = 'running'`,
|
|
398
|
+
`CREATE TABLE IF NOT EXISTS thread_events (
|
|
399
|
+
seq bigserial PRIMARY KEY,
|
|
400
|
+
thread_id text NOT NULL REFERENCES thread(thread_id) ON DELETE CASCADE,
|
|
401
|
+
run_id text NOT NULL REFERENCES runs(run_id) ON DELETE CASCADE,
|
|
402
|
+
user_id text NOT NULL,
|
|
403
|
+
event_id text NOT NULL UNIQUE,
|
|
404
|
+
method text NOT NULL,
|
|
405
|
+
namespace text[] NOT NULL DEFAULT '{}',
|
|
406
|
+
event_data jsonb NOT NULL,
|
|
407
|
+
event_timestamp bigint NOT NULL,
|
|
408
|
+
created_at timestamptz NOT NULL DEFAULT now()
|
|
409
|
+
)`,
|
|
410
|
+
`CREATE INDEX IF NOT EXISTS idx_thread_events_replay ON thread_events(user_id, thread_id, seq)`,
|
|
411
|
+
`CREATE TABLE IF NOT EXISTS crons (
|
|
412
|
+
cron_id text PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
413
|
+
assistant_id text NOT NULL REFERENCES assistant(assistant_id) ON DELETE CASCADE,
|
|
414
|
+
thread_id text REFERENCES thread(thread_id) ON DELETE CASCADE,
|
|
415
|
+
user_id text NOT NULL,
|
|
416
|
+
schedule text NOT NULL,
|
|
417
|
+
payload jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
418
|
+
metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
419
|
+
on_run_completed text,
|
|
420
|
+
enabled boolean NOT NULL DEFAULT true,
|
|
421
|
+
end_time timestamptz,
|
|
422
|
+
next_run_date timestamptz,
|
|
423
|
+
claimed_until timestamptz,
|
|
424
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
425
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
426
|
+
)`,
|
|
427
|
+
`CREATE TABLE IF NOT EXISTS store_items (
|
|
428
|
+
user_id text NOT NULL,
|
|
429
|
+
namespace text[] NOT NULL,
|
|
430
|
+
key text NOT NULL,
|
|
431
|
+
value jsonb NOT NULL,
|
|
432
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
433
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
434
|
+
PRIMARY KEY(namespace, key)
|
|
435
|
+
)`,
|
|
436
|
+
`DO $$
|
|
437
|
+
DECLARE legacy_key boolean;
|
|
438
|
+
BEGIN
|
|
439
|
+
SELECT EXISTS (
|
|
440
|
+
SELECT 1 FROM pg_constraint c
|
|
441
|
+
JOIN pg_class t ON t.oid = c.conrelid
|
|
442
|
+
WHERE t.relname = 'store_items' AND c.contype = 'p'
|
|
443
|
+
AND pg_get_constraintdef(c.oid) = 'PRIMARY KEY (user_id, namespace, key)'
|
|
444
|
+
) INTO legacy_key;
|
|
445
|
+
IF legacy_key THEN
|
|
446
|
+
IF to_regclass('store_embeddings') IS NOT NULL THEN
|
|
447
|
+
ALTER TABLE store_embeddings DROP CONSTRAINT IF EXISTS store_embeddings_user_id_namespace_key_fkey;
|
|
448
|
+
ALTER TABLE store_embeddings DROP CONSTRAINT IF EXISTS store_embeddings_pkey;
|
|
449
|
+
END IF;
|
|
450
|
+
ALTER TABLE store_items DROP CONSTRAINT store_items_pkey;
|
|
451
|
+
ALTER TABLE store_items ADD CONSTRAINT store_items_pkey PRIMARY KEY (namespace, key);
|
|
452
|
+
IF to_regclass('store_embeddings') IS NOT NULL THEN
|
|
453
|
+
ALTER TABLE store_embeddings ADD CONSTRAINT store_embeddings_pkey PRIMARY KEY (namespace, key);
|
|
454
|
+
ALTER TABLE store_embeddings ADD CONSTRAINT store_embeddings_item_fkey
|
|
455
|
+
FOREIGN KEY (namespace, key) REFERENCES store_items(namespace, key) ON DELETE CASCADE;
|
|
456
|
+
END IF;
|
|
457
|
+
END IF;
|
|
458
|
+
END $$`,
|
|
459
|
+
`CREATE INDEX IF NOT EXISTS idx_cron_user ON crons(user_id)`,
|
|
460
|
+
`CREATE INDEX IF NOT EXISTS idx_cron_assistant_id ON crons(assistant_id)`,
|
|
461
|
+
`CREATE INDEX IF NOT EXISTS idx_cron_thread_id ON crons(thread_id)`,
|
|
462
|
+
`CREATE INDEX IF NOT EXISTS idx_cron_next_run ON crons(enabled, next_run_date)`,
|
|
463
|
+
`CREATE TABLE IF NOT EXISTS thread_ttl (
|
|
464
|
+
thread_id text PRIMARY KEY REFERENCES thread(thread_id) ON DELETE CASCADE,
|
|
465
|
+
strategy text NOT NULL DEFAULT 'delete',
|
|
466
|
+
ttl_minutes double precision NOT NULL,
|
|
467
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
468
|
+
expires_at timestamptz NOT NULL
|
|
469
|
+
)`,
|
|
470
|
+
`CREATE INDEX IF NOT EXISTS idx_thread_ttl_expires_at ON thread_ttl(expires_at)`
|
|
471
|
+
];
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
// src/cli.ts
|
|
475
|
+
import { mkdir } from "fs/promises";
|
|
476
|
+
import { basename, dirname as dirname2, resolve as resolve2 } from "path";
|
|
477
|
+
function usage(exitCode = 1) {
|
|
478
|
+
console.log(`Usage: aegra <command> [options]
|
|
479
|
+
|
|
480
|
+
Commands:
|
|
481
|
+
init [path] Create a TypeScript Aegra project
|
|
482
|
+
dev Start with Bun hot reload
|
|
483
|
+
serve Start the production server
|
|
484
|
+
up|down [args] Run Docker Compose
|
|
485
|
+
db upgrade Apply database migrations
|
|
486
|
+
version Print the version
|
|
487
|
+
|
|
488
|
+
Server options: --host, --port, --config|-c, --env-file|-e
|
|
489
|
+
Init options: --name|-n, --template|-t <basic|hono|auth|full>, --force`);
|
|
490
|
+
process.exit(exitCode);
|
|
491
|
+
}
|
|
492
|
+
function parseOptions(args) {
|
|
493
|
+
const positional = [];
|
|
494
|
+
const options = {};
|
|
495
|
+
for (let index2 = 0;index2 < args.length; index2++) {
|
|
496
|
+
const value = args[index2];
|
|
497
|
+
if (!value.startsWith("-")) {
|
|
498
|
+
positional.push(value);
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
const [rawName, inline] = value.split("=", 2);
|
|
502
|
+
const name = { "-c": "config", "-e": "env-file", "-n": "name", "-t": "template" }[rawName] ?? rawName.replace(/^--/, "");
|
|
503
|
+
if (["force", "help", "no-db-check"].includes(name)) {
|
|
504
|
+
options[name] = true;
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
const next = inline ?? args[++index2];
|
|
508
|
+
if (!next)
|
|
509
|
+
throw new Error(`Missing value for ${rawName}`);
|
|
510
|
+
options[name] = next;
|
|
511
|
+
}
|
|
512
|
+
return { positional, options };
|
|
513
|
+
}
|
|
514
|
+
async function loadEnv(path) {
|
|
515
|
+
const file = Bun.file(path);
|
|
516
|
+
if (!await file.exists())
|
|
517
|
+
return;
|
|
518
|
+
for (const line of (await file.text()).split(/\r?\n/)) {
|
|
519
|
+
const match = line.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/);
|
|
520
|
+
if (!match || process.env[match[1]] !== undefined)
|
|
521
|
+
continue;
|
|
522
|
+
process.env[match[1]] = match[2].replace(/^(['"])(.*)\1$/, "$2");
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
function run(command, env) {
|
|
526
|
+
const result = Bun.spawnSync(command, { stdin: "inherit", stdout: "inherit", stderr: "inherit", ...env ? { env } : {} });
|
|
527
|
+
if (result.exitCode !== 0)
|
|
528
|
+
process.exit(result.exitCode);
|
|
529
|
+
}
|
|
530
|
+
async function serverEnvironment(options) {
|
|
531
|
+
await loadEnv(String(options["env-file"] ?? resolve2(".env")));
|
|
532
|
+
if (options.host)
|
|
533
|
+
process.env.HOST = String(options.host);
|
|
534
|
+
if (options.port)
|
|
535
|
+
process.env.PORT = String(options.port);
|
|
536
|
+
if (options.config)
|
|
537
|
+
process.env.AEGRA_CONFIG = resolve2(String(options.config));
|
|
538
|
+
else if (!process.env.AEGRA_CONFIG && await Bun.file(resolve2("langgraph.json")).exists() && !await Bun.file(resolve2("aegra.json")).exists())
|
|
539
|
+
process.env.AEGRA_CONFIG = "langgraph.json";
|
|
540
|
+
return process.env;
|
|
541
|
+
}
|
|
542
|
+
function slugify(value) {
|
|
543
|
+
return value.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "aegra-agent";
|
|
544
|
+
}
|
|
545
|
+
async function writeProjectFile(root, path, content, force) {
|
|
546
|
+
const target = resolve2(root, path);
|
|
547
|
+
if (!force && await Bun.file(target).exists())
|
|
548
|
+
return "skipped";
|
|
549
|
+
await mkdir(dirname2(target), { recursive: true });
|
|
550
|
+
await Bun.write(target, content);
|
|
551
|
+
return "created";
|
|
552
|
+
}
|
|
553
|
+
async function initProject(args) {
|
|
554
|
+
const { positional, options } = parseOptions(args);
|
|
555
|
+
if (options.help)
|
|
556
|
+
usage(0);
|
|
557
|
+
const root = resolve2(positional[0] ?? ".");
|
|
558
|
+
const name = String(options.name ?? basename(root));
|
|
559
|
+
const slug = slugify(name);
|
|
560
|
+
const requestedTemplate = String(options.template ?? "basic");
|
|
561
|
+
const template = requestedTemplate === "1" ? "basic" : requestedTemplate;
|
|
562
|
+
if (!["basic", "hono", "auth", "full"].includes(template)) {
|
|
563
|
+
throw new Error("Template must be one of: basic, hono, auth, full");
|
|
564
|
+
}
|
|
565
|
+
const version = (await Bun.file(resolve2(import.meta.dir, "../package.json")).json()).version;
|
|
566
|
+
const escapedName = JSON.stringify(`Hello from ${name}`);
|
|
567
|
+
const graph = `import { AIMessage } from "@langchain/core/messages";
|
|
568
|
+
import { MessagesAnnotation, StateGraph, START, END } from "@langchain/langgraph";
|
|
569
|
+
import { defineGraph } from "aegra-hono/graph";
|
|
570
|
+
|
|
571
|
+
export const graph = defineGraph(new StateGraph(MessagesAnnotation)
|
|
572
|
+
.addNode("reply", async () => ({ messages: [new AIMessage(${escapedName})] }))
|
|
573
|
+
.addEdge(START, "reply").addEdge("reply", END).compile());
|
|
574
|
+
`;
|
|
575
|
+
const withHono = template === "hono" || template === "full";
|
|
576
|
+
const withAuth = template === "auth" || template === "full";
|
|
577
|
+
const config = {
|
|
578
|
+
name,
|
|
579
|
+
graphs: { [slug]: "./src/graph.ts:graph" },
|
|
580
|
+
...withAuth ? { auth: { path: "./src/auth.ts:auth" } } : {},
|
|
581
|
+
...withHono ? { http: { app: "./src/app.ts:app", ...withAuth ? { enable_custom_route_auth: true } : {} } } : {}
|
|
582
|
+
};
|
|
583
|
+
const compose = `services:
|
|
584
|
+
postgres:
|
|
585
|
+
image: pgvector/pgvector:pg18
|
|
586
|
+
environment:
|
|
587
|
+
POSTGRES_USER: postgres
|
|
588
|
+
POSTGRES_PASSWORD: postgres
|
|
589
|
+
POSTGRES_DB: aegra
|
|
590
|
+
ports:
|
|
591
|
+
- "5432:5432"
|
|
592
|
+
volumes:
|
|
593
|
+
- postgres-data:/var/lib/postgresql/data
|
|
594
|
+
healthcheck:
|
|
595
|
+
test: ["CMD-SHELL", "pg_isready -U postgres -d aegra"]
|
|
596
|
+
interval: 5s
|
|
597
|
+
timeout: 5s
|
|
598
|
+
retries: 10
|
|
599
|
+
${template === "full" ? ` redis:
|
|
600
|
+
image: redis:7-alpine
|
|
601
|
+
ports:
|
|
602
|
+
- "6379:6379"
|
|
603
|
+
volumes:
|
|
604
|
+
- redis-data:/data
|
|
605
|
+
healthcheck:
|
|
606
|
+
test: ["CMD", "redis-cli", "ping"]
|
|
607
|
+
interval: 5s
|
|
608
|
+
timeout: 5s
|
|
609
|
+
retries: 10
|
|
610
|
+
` : ""}volumes:
|
|
611
|
+
postgres-data:
|
|
612
|
+
${template === "full" ? ` redis-data:
|
|
613
|
+
` : ""}`;
|
|
614
|
+
const files = {
|
|
615
|
+
"aegra.json": `${JSON.stringify(config, null, 2)}
|
|
616
|
+
`,
|
|
617
|
+
"package.json": `${JSON.stringify({ name: slug, private: true, type: "module", scripts: { dev: "aegra dev", start: "aegra serve", check: "tsc --noEmit", db: "aegra db upgrade", services: "docker compose up -d" }, dependencies: { aegra: version, "@langchain/core": "1.2.10", "@langchain/langgraph": "1.4.14", ...withHono ? { hono: "4.13.7" } : {} }, devDependencies: { "@types/bun": "1.3.10", typescript: "5.9.3" }, engines: { bun: ">=1.3.10" } }, null, 2)}
|
|
618
|
+
`,
|
|
619
|
+
"tsconfig.json": `${JSON.stringify({ compilerOptions: { target: "ES2022", module: "Preserve", moduleResolution: "bundler", strict: true, noEmit: true, types: ["bun"], skipLibCheck: true }, include: ["src"] }, null, 2)}
|
|
620
|
+
`,
|
|
621
|
+
"src/graph.ts": graph,
|
|
622
|
+
".env.example": `DATABASE_URL=postgresql://postgres:postgres@localhost:5432/aegra
|
|
623
|
+
REDIS_BROKER_ENABLED=${template === "full" ? "true" : "false"}
|
|
624
|
+
REDIS_URL=redis://localhost:6379/0
|
|
625
|
+
${withAuth ? `AEGRA_AUTH_TOKEN=replace-me
|
|
626
|
+
` : ""}`,
|
|
627
|
+
".gitignore": `node_modules
|
|
628
|
+
.env
|
|
629
|
+
`,
|
|
630
|
+
"docker-compose.yml": compose,
|
|
631
|
+
"README.md": `# ${name}
|
|
632
|
+
|
|
633
|
+
\`\`\`bash
|
|
634
|
+
bun install
|
|
635
|
+
cp .env.example .env
|
|
636
|
+
docker compose up -d
|
|
637
|
+
bun run db
|
|
638
|
+
bun run dev
|
|
639
|
+
\`\`\`
|
|
640
|
+
`
|
|
641
|
+
};
|
|
642
|
+
if (withHono)
|
|
643
|
+
files["src/app.ts"] = `import { Hono } from "hono";
|
|
644
|
+
import { defineApp } from "aegra-hono/hono";
|
|
645
|
+
|
|
646
|
+
export const app = defineApp(new Hono().get("/hello", (context) => context.json({ message: ${escapedName} })));
|
|
647
|
+
`;
|
|
648
|
+
if (withAuth)
|
|
649
|
+
files["src/auth.ts"] = `import { Auth } from "aegra-hono/auth";
|
|
650
|
+
|
|
651
|
+
export const auth = new Auth();
|
|
652
|
+
auth.authenticate((request) => {
|
|
653
|
+
const token = process.env.AEGRA_AUTH_TOKEN;
|
|
654
|
+
if (!token || request.headers.get("authorization") !== \`Bearer \${token}\`) throw new Error("Invalid bearer token");
|
|
655
|
+
return { identity: request.headers.get("x-user-id")?.trim() || "authenticated-user", isAuthenticated: true, permissions: [] };
|
|
656
|
+
});
|
|
657
|
+
`;
|
|
658
|
+
let created = 0;
|
|
659
|
+
let skipped = 0;
|
|
660
|
+
for (const [path, content] of Object.entries(files)) {
|
|
661
|
+
if (await writeProjectFile(root, path, content, Boolean(options.force)) === "created")
|
|
662
|
+
created++;
|
|
663
|
+
else
|
|
664
|
+
skipped++;
|
|
665
|
+
}
|
|
666
|
+
console.log(`Created ${created} files in ${root}${skipped ? ` (${skipped} skipped; use --force to overwrite)` : ""}`);
|
|
667
|
+
}
|
|
668
|
+
async function main(argv = process.argv.slice(2)) {
|
|
669
|
+
const [command, ...args] = argv;
|
|
670
|
+
if (!command || command === "help" || command === "--help")
|
|
671
|
+
usage(command ? 0 : 1);
|
|
672
|
+
if (command === "init")
|
|
673
|
+
return initProject(args);
|
|
674
|
+
if (command === "version" || command === "--version") {
|
|
675
|
+
const pkg = await Bun.file(resolve2(import.meta.dir, "../package.json")).json();
|
|
676
|
+
console.log(pkg.version);
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
if (command === "dev" || command === "serve") {
|
|
680
|
+
const { options } = parseOptions(args);
|
|
681
|
+
if (options.help)
|
|
682
|
+
usage(0);
|
|
683
|
+
const env = await serverEnvironment(options);
|
|
684
|
+
const builtEntrypoint = resolve2(import.meta.dir, "index.js");
|
|
685
|
+
const entrypoint = await Bun.file(builtEntrypoint).exists() ? builtEntrypoint : resolve2(import.meta.dir, "index.ts");
|
|
686
|
+
if (command === "dev")
|
|
687
|
+
run(["bun", "--hot", entrypoint], env);
|
|
688
|
+
else
|
|
689
|
+
await import(entrypoint);
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
if (command === "up" || command === "down")
|
|
693
|
+
return run(["docker", "compose", command, ...args]);
|
|
694
|
+
if (command === "db" && args[0] === "upgrade") {
|
|
695
|
+
const { options } = parseOptions(args.slice(1));
|
|
696
|
+
await serverEnvironment(options);
|
|
697
|
+
const { migrate: migrate2, closeDatabase: closeDatabase2 } = await Promise.resolve().then(() => (init_database(), exports_database));
|
|
698
|
+
await migrate2();
|
|
699
|
+
await closeDatabase2();
|
|
700
|
+
console.log("Database is up to date");
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
usage();
|
|
704
|
+
}
|
|
705
|
+
if (import.meta.main)
|
|
706
|
+
main().catch((error) => {
|
|
707
|
+
console.error(error instanceof Error ? error.message : error);
|
|
708
|
+
process.exit(1);
|
|
709
|
+
});
|
|
710
|
+
export {
|
|
711
|
+
main
|
|
712
|
+
};
|
|
713
|
+
|
|
714
|
+
//# debugId=C015E558C48B8F4564756E2164756E21
|