@c4t4/heyamigo 0.9.4 → 0.9.6
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/dist/db/schema.js +51 -0
- package/dist/memory/scheduler.js +24 -12
- package/dist/queue/cron-dispatch.js +32 -0
- package/dist/queue/cron-handlers.js +25 -0
- package/dist/queue/inbound.js +190 -0
- package/dist/queue/orchestrator.js +8 -3
- package/migrations/0003_phase4_inbound.sql +28 -0
- package/migrations/meta/0003_snapshot.json +658 -0
- package/migrations/meta/_journal.json +7 -0
- package/package.json +1 -1
package/dist/db/schema.js
CHANGED
|
@@ -128,3 +128,54 @@ export const crons = sqliteTable('crons', {
|
|
|
128
128
|
.on(t.name)
|
|
129
129
|
.where(sql `${t.recurrence} IS NOT NULL`),
|
|
130
130
|
}));
|
|
131
|
+
// ──────────────────────────────────────────────────────────────────
|
|
132
|
+
// Inbound queue (Phase 4)
|
|
133
|
+
// ──────────────────────────────────────────────────────────────────
|
|
134
|
+
// Messages received on any channel, waiting to be processed by a
|
|
135
|
+
// chat worker. The chat worker pool claims rows with per-address
|
|
136
|
+
// serialization (one in-flight job per chat preserves reply order),
|
|
137
|
+
// runs the AI provider, and enqueues outbound rows with the reply.
|
|
138
|
+
//
|
|
139
|
+
// `address` is the chat-level address (a group's address, or a DM's
|
|
140
|
+
// address). `actor_address` is the within-chat sender — for DMs it
|
|
141
|
+
// equals `address`; for groups it identifies which member sent the
|
|
142
|
+
// message. `person_id` and `actor_person_id` are the resolved
|
|
143
|
+
// person identities (nullable when resolution fails, e.g. an unknown
|
|
144
|
+
// member of a group).
|
|
145
|
+
//
|
|
146
|
+
// Idempotency: external_msg_id (channel-native message id, e.g.
|
|
147
|
+
// Baileys message key id). The same channel message arriving twice
|
|
148
|
+
// (network retransmit, replay on reconnect) maps to one row.
|
|
149
|
+
export const inbound = sqliteTable('inbound', {
|
|
150
|
+
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
151
|
+
address: text('address').notNull(), // chat-level address
|
|
152
|
+
actorAddress: text('actor_address'), // sender within chat; null = self/system
|
|
153
|
+
personId: text('person_id'), // resolved chat owner (DM partner / group)
|
|
154
|
+
actorPersonId: text('actor_person_id'), // resolved sender
|
|
155
|
+
externalMsgId: text('external_msg_id'), // channel-native msg id (idempotency)
|
|
156
|
+
text: text('text').notNull(), // body or media tag
|
|
157
|
+
mediaPath: text('media_path'), // path relative to storage/ when media
|
|
158
|
+
mediaMime: text('media_mime'),
|
|
159
|
+
mediaBytes: integer('media_bytes'),
|
|
160
|
+
pushName: text('push_name'), // sender's display name at send time
|
|
161
|
+
triggerReason: text('trigger_reason'), // 'alias'|'mention'|'reply'|'owner'|...
|
|
162
|
+
// 'pending'|'claimed'|'done'|'failed'|'dlq'
|
|
163
|
+
status: text('status').notNull(),
|
|
164
|
+
attempts: integer('attempts').notNull().default(0),
|
|
165
|
+
nextAttemptAt: integer('next_attempt_at'),
|
|
166
|
+
lastError: text('last_error'),
|
|
167
|
+
claimedBy: text('claimed_by'),
|
|
168
|
+
claimedAt: integer('claimed_at'),
|
|
169
|
+
receivedAt: integer('received_at').notNull(), // unix sec when WA delivered
|
|
170
|
+
createdAt: integer('created_at').notNull(), // unix sec when row inserted
|
|
171
|
+
updatedAt: integer('updated_at').notNull(),
|
|
172
|
+
}, t => ({
|
|
173
|
+
byStatusNext: index('inbound_by_status_next').on(t.status, t.nextAttemptAt),
|
|
174
|
+
byAddress: index('inbound_by_address').on(t.address),
|
|
175
|
+
byPerson: index('inbound_by_person').on(t.personId, t.receivedAt),
|
|
176
|
+
// Sparse unique on external_msg_id: enforced only when set. Same
|
|
177
|
+
// pattern as outbound's idempotency_key.
|
|
178
|
+
uniqExtId: uniqueIndex('inbound_external_msg_id_uq')
|
|
179
|
+
.on(t.externalMsgId)
|
|
180
|
+
.where(sql `${t.externalMsgId} IS NOT NULL`),
|
|
181
|
+
}));
|
package/dist/memory/scheduler.js
CHANGED
|
@@ -2,6 +2,8 @@ import fastq from 'fastq';
|
|
|
2
2
|
import { config } from '../config.js';
|
|
3
3
|
import { logger } from '../logger.js';
|
|
4
4
|
import { prunePrompts } from '../promptlog.js';
|
|
5
|
+
import { registerInternalCronHandler } from '../queue/cron-handlers.js';
|
|
6
|
+
import { deleteCron, enqueueCron } from '../queue/crons.js';
|
|
5
7
|
import { pruneMedia } from '../store/media.js';
|
|
6
8
|
import { runDigest } from './digest.js';
|
|
7
9
|
import { ensureScaffold, getLastDigestedAt, jsonlMtimeFor, loadDigestState, } from './store.js';
|
|
@@ -74,7 +76,6 @@ async function sweep() {
|
|
|
74
76
|
}
|
|
75
77
|
}
|
|
76
78
|
let sweepTimer = null;
|
|
77
|
-
let nudgeTimer = null;
|
|
78
79
|
const NUDGE_TICK_MS = 5 * 60 * 1000; // 5 minutes
|
|
79
80
|
export function startScheduler() {
|
|
80
81
|
if (sweepTimer)
|
|
@@ -95,13 +96,17 @@ export function startScheduler() {
|
|
|
95
96
|
sweepTimer = setInterval(() => {
|
|
96
97
|
void sweep().catch((err) => logger.error({ err }, 'sweep failed'));
|
|
97
98
|
}, config.memory.sweepIntervalMs);
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
99
|
+
// Proactive journal nudges (check-ins, silent-nudges). Migrated from
|
|
100
|
+
// setInterval to a cron row → orchestrator. Same cadence, same body;
|
|
101
|
+
// benefits are: survives restarts, visible in `crons` table, can be
|
|
102
|
+
// paused via control row without code change.
|
|
103
|
+
registerInternalCronHandler('journal-nudge-tick', runNudgeTickSafe);
|
|
104
|
+
enqueueCron({
|
|
105
|
+
name: 'journal-nudge-tick',
|
|
106
|
+
enqueueInto: 'internal',
|
|
107
|
+
payload: { handler: 'journal-nudge-tick' },
|
|
108
|
+
recurrence: `@every ${Math.floor(NUDGE_TICK_MS / 1000)}s`,
|
|
109
|
+
});
|
|
105
110
|
logger.info({
|
|
106
111
|
intervalMs: config.memory.sweepIntervalMs,
|
|
107
112
|
nudgeTickMs: NUDGE_TICK_MS,
|
|
@@ -121,11 +126,18 @@ export function stopScheduler() {
|
|
|
121
126
|
clearInterval(sweepTimer);
|
|
122
127
|
sweepTimer = null;
|
|
123
128
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
129
|
+
// Nudge cron is owned by the crons table; orchestrator stops on its
|
|
130
|
+
// own. Deleting the cron row here would re-arm itself on next boot,
|
|
131
|
+
// so leave it alone — disabling via the `enabled` column is the
|
|
132
|
+
// user-facing knob.
|
|
128
133
|
for (const t of pendingTimers.values())
|
|
129
134
|
clearTimeout(t);
|
|
130
135
|
pendingTimers.clear();
|
|
131
136
|
}
|
|
137
|
+
// Exported for callers (CLI, /nudge command) that want to surgically
|
|
138
|
+
// disable nudges without editing config. Use `setCronEnabled` from
|
|
139
|
+
// crons.ts for the on/off switch; this is a hard delete (regenerated
|
|
140
|
+
// on next startScheduler call).
|
|
141
|
+
export function deleteNudgeCron() {
|
|
142
|
+
return deleteCron('journal-nudge-tick');
|
|
143
|
+
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// in on later phases — when those queues exist, add their dispatch
|
|
6
6
|
// here and a cron can fire into them with no other changes.
|
|
7
7
|
import { logger } from '../logger.js';
|
|
8
|
+
import { getInternalCronHandler } from './cron-handlers.js';
|
|
8
9
|
import { enqueueOutbound } from './outbound.js';
|
|
9
10
|
export function dispatchCron(row) {
|
|
10
11
|
let payload;
|
|
@@ -19,6 +20,9 @@ export function dispatchCron(row) {
|
|
|
19
20
|
case 'outbound':
|
|
20
21
|
dispatchOutbound(row, payload);
|
|
21
22
|
return;
|
|
23
|
+
case 'internal':
|
|
24
|
+
dispatchInternal(row, payload);
|
|
25
|
+
return;
|
|
22
26
|
case 'inbound':
|
|
23
27
|
case 'async':
|
|
24
28
|
case 'memory_writes':
|
|
@@ -28,6 +32,34 @@ export function dispatchCron(row) {
|
|
|
28
32
|
logger.error({ name: row.name, target: row.enqueueInto }, 'cron has unknown target queue');
|
|
29
33
|
}
|
|
30
34
|
}
|
|
35
|
+
function dispatchInternal(row, payload) {
|
|
36
|
+
if (!payload || typeof payload !== 'object' || !('handler' in payload)) {
|
|
37
|
+
logger.error({ id: row.id, name: row.name }, "internal cron missing 'handler' in payload");
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const handlerName = payload.handler;
|
|
41
|
+
if (typeof handlerName !== 'string') {
|
|
42
|
+
logger.error({ id: row.id, name: row.name }, 'internal cron handler name not a string');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const handler = getInternalCronHandler(handlerName);
|
|
46
|
+
if (!handler) {
|
|
47
|
+
logger.error({ id: row.id, name: row.name, handler: handlerName }, 'internal cron handler not registered');
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
// Fire-and-forget. Handler errors are caught and logged but the
|
|
51
|
+
// cron row still gets marked fired so we don't stack up retries.
|
|
52
|
+
try {
|
|
53
|
+
const result = handler();
|
|
54
|
+
if (result && typeof result.catch === 'function') {
|
|
55
|
+
;
|
|
56
|
+
result.catch((err) => logger.error({ err, handler: handlerName }, 'internal cron handler rejected'));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
logger.error({ err, handler: handlerName }, 'internal cron handler threw');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
31
63
|
function dispatchOutbound(row, payload) {
|
|
32
64
|
if (!isOutboundPayload(payload)) {
|
|
33
65
|
logger.error({ id: row.id, payload }, 'cron outbound payload malformed');
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// In-process handler registry for the 'internal' cron target.
|
|
2
|
+
//
|
|
3
|
+
// Not every periodic task fits the queue→worker model. Things like
|
|
4
|
+
// "run the journal observer sweep" or "regenerate compressed memory"
|
|
5
|
+
// are pure in-process work — there's no queue to enqueue into, the
|
|
6
|
+
// orchestrator just needs to call a function.
|
|
7
|
+
//
|
|
8
|
+
// Mechanism: cron rows with enqueue_into='internal' carry a payload
|
|
9
|
+
// with `handler: <name>`. The dispatcher looks up the name in this
|
|
10
|
+
// registry and invokes the function. Registry is populated at boot,
|
|
11
|
+
// before the orchestrator starts polling.
|
|
12
|
+
import { logger } from '../logger.js';
|
|
13
|
+
const registry = new Map();
|
|
14
|
+
export function registerInternalCronHandler(name, handler) {
|
|
15
|
+
if (registry.has(name)) {
|
|
16
|
+
logger.warn({ name }, 'internal cron handler already registered; overwriting');
|
|
17
|
+
}
|
|
18
|
+
registry.set(name, handler);
|
|
19
|
+
}
|
|
20
|
+
export function getInternalCronHandler(name) {
|
|
21
|
+
return registry.get(name);
|
|
22
|
+
}
|
|
23
|
+
export function listInternalCronHandlers() {
|
|
24
|
+
return [...registry.keys()];
|
|
25
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// Inbound queue helpers. Gateway (gateway/incoming.ts) calls
|
|
2
|
+
// enqueueInbound; chat workers (queue/chat-worker.ts) drain via
|
|
3
|
+
// claimNextInbound. Per-address serialization preserves reply order
|
|
4
|
+
// within a chat while different chats run in parallel.
|
|
5
|
+
//
|
|
6
|
+
// Same primitives as outbound (claim/done/retry/dlq) with the added
|
|
7
|
+
// `address NOT IN (claimed)` filter in the claim query.
|
|
8
|
+
import { and, asc, eq, isNull, lte, notInArray, or, sql } from 'drizzle-orm';
|
|
9
|
+
import { getDb } from '../db/index.js';
|
|
10
|
+
import { inbound } from '../db/schema.js';
|
|
11
|
+
// Idempotent on external_msg_id when set. Same channel message
|
|
12
|
+
// arriving twice (Baileys replay, network retransmit) returns the
|
|
13
|
+
// existing row instead of duplicating.
|
|
14
|
+
export function enqueueInbound(input) {
|
|
15
|
+
const db = getDb();
|
|
16
|
+
const now = Math.floor(Date.now() / 1000);
|
|
17
|
+
if (input.externalMsgId) {
|
|
18
|
+
const found = db
|
|
19
|
+
.select()
|
|
20
|
+
.from(inbound)
|
|
21
|
+
.where(eq(inbound.externalMsgId, input.externalMsgId))
|
|
22
|
+
.get();
|
|
23
|
+
if (found)
|
|
24
|
+
return { inserted: false, row: found };
|
|
25
|
+
}
|
|
26
|
+
const row = db
|
|
27
|
+
.insert(inbound)
|
|
28
|
+
.values({
|
|
29
|
+
address: input.address,
|
|
30
|
+
actorAddress: input.actorAddress ?? null,
|
|
31
|
+
personId: input.personId ?? null,
|
|
32
|
+
actorPersonId: input.actorPersonId ?? null,
|
|
33
|
+
externalMsgId: input.externalMsgId ?? null,
|
|
34
|
+
text: input.text,
|
|
35
|
+
mediaPath: input.mediaPath ?? null,
|
|
36
|
+
mediaMime: input.mediaMime ?? null,
|
|
37
|
+
mediaBytes: input.mediaBytes ?? null,
|
|
38
|
+
pushName: input.pushName ?? null,
|
|
39
|
+
triggerReason: input.triggerReason ?? null,
|
|
40
|
+
status: 'pending',
|
|
41
|
+
attempts: 0,
|
|
42
|
+
nextAttemptAt: null,
|
|
43
|
+
lastError: null,
|
|
44
|
+
claimedBy: null,
|
|
45
|
+
claimedAt: null,
|
|
46
|
+
receivedAt: input.receivedAt ?? now,
|
|
47
|
+
createdAt: now,
|
|
48
|
+
updatedAt: now,
|
|
49
|
+
})
|
|
50
|
+
.returning()
|
|
51
|
+
.get();
|
|
52
|
+
return { inserted: true, row };
|
|
53
|
+
}
|
|
54
|
+
// Atomic claim with per-address serialization. Skips any pending row
|
|
55
|
+
// whose address already has another row in `claimed` state →
|
|
56
|
+
// preserves reply order per chat while letting different chats run
|
|
57
|
+
// in parallel.
|
|
58
|
+
export function claimNextInbound(workerId) {
|
|
59
|
+
const db = getDb();
|
|
60
|
+
const now = Math.floor(Date.now() / 1000);
|
|
61
|
+
return db.transaction((tx) => {
|
|
62
|
+
// Subquery: addresses currently claimed (= one in-flight per chat).
|
|
63
|
+
const busyAddrs = tx
|
|
64
|
+
.select({ address: inbound.address })
|
|
65
|
+
.from(inbound)
|
|
66
|
+
.where(eq(inbound.status, 'claimed'))
|
|
67
|
+
.all()
|
|
68
|
+
.map((r) => r.address);
|
|
69
|
+
const conds = [
|
|
70
|
+
eq(inbound.status, 'pending'),
|
|
71
|
+
or(isNull(inbound.nextAttemptAt), lte(inbound.nextAttemptAt, now)),
|
|
72
|
+
];
|
|
73
|
+
if (busyAddrs.length > 0) {
|
|
74
|
+
conds.push(notInArray(inbound.address, busyAddrs));
|
|
75
|
+
}
|
|
76
|
+
const target = tx
|
|
77
|
+
.select({ id: inbound.id })
|
|
78
|
+
.from(inbound)
|
|
79
|
+
.where(and(...conds))
|
|
80
|
+
.orderBy(asc(inbound.id))
|
|
81
|
+
.limit(1)
|
|
82
|
+
.get();
|
|
83
|
+
if (!target)
|
|
84
|
+
return null;
|
|
85
|
+
const claimed = tx
|
|
86
|
+
.update(inbound)
|
|
87
|
+
.set({
|
|
88
|
+
status: 'claimed',
|
|
89
|
+
claimedBy: workerId,
|
|
90
|
+
claimedAt: now,
|
|
91
|
+
updatedAt: now,
|
|
92
|
+
})
|
|
93
|
+
.where(and(eq(inbound.id, target.id), eq(inbound.status, 'pending')))
|
|
94
|
+
.returning()
|
|
95
|
+
.get();
|
|
96
|
+
return claimed ?? null;
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
export function markInboundDone(id, workerId) {
|
|
100
|
+
const db = getDb();
|
|
101
|
+
const now = Math.floor(Date.now() / 1000);
|
|
102
|
+
const result = db
|
|
103
|
+
.update(inbound)
|
|
104
|
+
.set({ status: 'done', updatedAt: now })
|
|
105
|
+
.where(and(eq(inbound.id, id), eq(inbound.status, 'claimed'), eq(inbound.claimedBy, workerId)))
|
|
106
|
+
.returning({ id: inbound.id })
|
|
107
|
+
.all();
|
|
108
|
+
return result.length > 0;
|
|
109
|
+
}
|
|
110
|
+
// Backoff: 5s, 30s, 2min, 5min, give up. Bigger gaps than outbound
|
|
111
|
+
// because chat replies are expensive (AI call); a faster retry loop
|
|
112
|
+
// would just burn tokens on transient errors.
|
|
113
|
+
const BACKOFF_SECONDS = [5, 30, 120, 300];
|
|
114
|
+
const MAX_ATTEMPTS = BACKOFF_SECONDS.length;
|
|
115
|
+
export function markInboundRetryOrDlq(id, workerId, errorMessage) {
|
|
116
|
+
const db = getDb();
|
|
117
|
+
return db.transaction((tx) => {
|
|
118
|
+
const row = tx.select().from(inbound).where(eq(inbound.id, id)).get();
|
|
119
|
+
if (!row || row.status !== 'claimed' || row.claimedBy !== workerId) {
|
|
120
|
+
return { retried: false, deadLettered: false };
|
|
121
|
+
}
|
|
122
|
+
const now = Math.floor(Date.now() / 1000);
|
|
123
|
+
const nextAttempts = row.attempts + 1;
|
|
124
|
+
if (nextAttempts > MAX_ATTEMPTS) {
|
|
125
|
+
tx.update(inbound)
|
|
126
|
+
.set({
|
|
127
|
+
status: 'dlq',
|
|
128
|
+
attempts: nextAttempts,
|
|
129
|
+
lastError: errorMessage,
|
|
130
|
+
claimedBy: null,
|
|
131
|
+
claimedAt: null,
|
|
132
|
+
updatedAt: now,
|
|
133
|
+
})
|
|
134
|
+
.where(eq(inbound.id, id))
|
|
135
|
+
.run();
|
|
136
|
+
return { retried: false, deadLettered: true };
|
|
137
|
+
}
|
|
138
|
+
const backoff = BACKOFF_SECONDS[Math.min(row.attempts, BACKOFF_SECONDS.length - 1)];
|
|
139
|
+
tx.update(inbound)
|
|
140
|
+
.set({
|
|
141
|
+
status: 'pending',
|
|
142
|
+
attempts: nextAttempts,
|
|
143
|
+
nextAttemptAt: now + backoff,
|
|
144
|
+
lastError: errorMessage,
|
|
145
|
+
claimedBy: null,
|
|
146
|
+
claimedAt: null,
|
|
147
|
+
updatedAt: now,
|
|
148
|
+
})
|
|
149
|
+
.where(eq(inbound.id, id))
|
|
150
|
+
.run();
|
|
151
|
+
return { retried: true, deadLettered: false };
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
export function markInboundFailed(id, workerId, errorMessage) {
|
|
155
|
+
const db = getDb();
|
|
156
|
+
const now = Math.floor(Date.now() / 1000);
|
|
157
|
+
const result = db
|
|
158
|
+
.update(inbound)
|
|
159
|
+
.set({
|
|
160
|
+
status: 'failed',
|
|
161
|
+
lastError: errorMessage,
|
|
162
|
+
claimedBy: null,
|
|
163
|
+
claimedAt: null,
|
|
164
|
+
updatedAt: now,
|
|
165
|
+
})
|
|
166
|
+
.where(and(eq(inbound.id, id), eq(inbound.status, 'claimed'), eq(inbound.claimedBy, workerId)))
|
|
167
|
+
.returning({ id: inbound.id })
|
|
168
|
+
.all();
|
|
169
|
+
return result.length > 0;
|
|
170
|
+
}
|
|
171
|
+
// Orchestrator helper. Chat workers run longer than sender workers
|
|
172
|
+
// (AI calls + memory writes), so the TTL is more generous. 300s
|
|
173
|
+
// matches the typical chat-track timeout (5min).
|
|
174
|
+
const CLAIM_TTL_SECONDS = 360;
|
|
175
|
+
export function reclaimStuckInbound() {
|
|
176
|
+
const db = getDb();
|
|
177
|
+
const cutoff = Math.floor(Date.now() / 1000) - CLAIM_TTL_SECONDS;
|
|
178
|
+
const result = db
|
|
179
|
+
.update(inbound)
|
|
180
|
+
.set({
|
|
181
|
+
status: 'pending',
|
|
182
|
+
claimedBy: null,
|
|
183
|
+
claimedAt: null,
|
|
184
|
+
updatedAt: sql `${inbound.updatedAt}`,
|
|
185
|
+
})
|
|
186
|
+
.where(and(eq(inbound.status, 'claimed'), lte(inbound.claimedAt, cutoff)))
|
|
187
|
+
.returning({ id: inbound.id })
|
|
188
|
+
.all();
|
|
189
|
+
return result.length;
|
|
190
|
+
}
|
|
@@ -16,6 +16,7 @@ import { and, eq, lt, ne } from 'drizzle-orm';
|
|
|
16
16
|
import { getDb } from '../db/index.js';
|
|
17
17
|
import { workers } from '../db/schema.js';
|
|
18
18
|
import { logger } from '../logger.js';
|
|
19
|
+
import { reclaimStuckInbound } from './inbound.js';
|
|
19
20
|
import { reclaimStuckOutbound } from './outbound.js';
|
|
20
21
|
import { clearControl, readControl, requestControl } from './control.js';
|
|
21
22
|
import { listDueCrons, markCronFired } from './crons.js';
|
|
@@ -98,9 +99,13 @@ async function tick(id) {
|
|
|
98
99
|
.run();
|
|
99
100
|
}
|
|
100
101
|
// Cross-queue housekeeping. More queues land in later phases.
|
|
101
|
-
const
|
|
102
|
-
if (
|
|
103
|
-
logger.info({ reclaimed }, 'reclaimed stuck outbound rows');
|
|
102
|
+
const reclaimedOutbound = reclaimStuckOutbound();
|
|
103
|
+
if (reclaimedOutbound > 0) {
|
|
104
|
+
logger.info({ reclaimed: reclaimedOutbound }, 'reclaimed stuck outbound rows');
|
|
105
|
+
}
|
|
106
|
+
const reclaimedInbound = reclaimStuckInbound();
|
|
107
|
+
if (reclaimedInbound > 0) {
|
|
108
|
+
logger.info({ reclaimed: reclaimedInbound }, 'reclaimed stuck inbound rows');
|
|
104
109
|
}
|
|
105
110
|
// Fire any due crons. Order: dispatch each in turn; if dispatch
|
|
106
111
|
// throws (it shouldn't — dispatch swallows), the cron is NOT
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
CREATE TABLE `inbound` (
|
|
2
|
+
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
|
3
|
+
`address` text NOT NULL,
|
|
4
|
+
`actor_address` text,
|
|
5
|
+
`person_id` text,
|
|
6
|
+
`actor_person_id` text,
|
|
7
|
+
`external_msg_id` text,
|
|
8
|
+
`text` text NOT NULL,
|
|
9
|
+
`media_path` text,
|
|
10
|
+
`media_mime` text,
|
|
11
|
+
`media_bytes` integer,
|
|
12
|
+
`push_name` text,
|
|
13
|
+
`trigger_reason` text,
|
|
14
|
+
`status` text NOT NULL,
|
|
15
|
+
`attempts` integer DEFAULT 0 NOT NULL,
|
|
16
|
+
`next_attempt_at` integer,
|
|
17
|
+
`last_error` text,
|
|
18
|
+
`claimed_by` text,
|
|
19
|
+
`claimed_at` integer,
|
|
20
|
+
`received_at` integer NOT NULL,
|
|
21
|
+
`created_at` integer NOT NULL,
|
|
22
|
+
`updated_at` integer NOT NULL
|
|
23
|
+
);
|
|
24
|
+
--> statement-breakpoint
|
|
25
|
+
CREATE INDEX `inbound_by_status_next` ON `inbound` (`status`,`next_attempt_at`);--> statement-breakpoint
|
|
26
|
+
CREATE INDEX `inbound_by_address` ON `inbound` (`address`);--> statement-breakpoint
|
|
27
|
+
CREATE INDEX `inbound_by_person` ON `inbound` (`person_id`,`received_at`);--> statement-breakpoint
|
|
28
|
+
CREATE UNIQUE INDEX `inbound_external_msg_id_uq` ON `inbound` (`external_msg_id`) WHERE "inbound"."external_msg_id" IS NOT NULL;
|
|
@@ -0,0 +1,658 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "6",
|
|
3
|
+
"dialect": "sqlite",
|
|
4
|
+
"id": "155b0aa7-93ec-4d7a-a853-e680b47d162c",
|
|
5
|
+
"prevId": "417b2708-624e-416f-af59-bee5d5e38556",
|
|
6
|
+
"tables": {
|
|
7
|
+
"control": {
|
|
8
|
+
"name": "control",
|
|
9
|
+
"columns": {
|
|
10
|
+
"key": {
|
|
11
|
+
"name": "key",
|
|
12
|
+
"type": "text",
|
|
13
|
+
"primaryKey": true,
|
|
14
|
+
"notNull": true,
|
|
15
|
+
"autoincrement": false
|
|
16
|
+
},
|
|
17
|
+
"value": {
|
|
18
|
+
"name": "value",
|
|
19
|
+
"type": "text",
|
|
20
|
+
"primaryKey": false,
|
|
21
|
+
"notNull": false,
|
|
22
|
+
"autoincrement": false
|
|
23
|
+
},
|
|
24
|
+
"requested_by": {
|
|
25
|
+
"name": "requested_by",
|
|
26
|
+
"type": "text",
|
|
27
|
+
"primaryKey": false,
|
|
28
|
+
"notNull": false,
|
|
29
|
+
"autoincrement": false
|
|
30
|
+
},
|
|
31
|
+
"requested_at": {
|
|
32
|
+
"name": "requested_at",
|
|
33
|
+
"type": "integer",
|
|
34
|
+
"primaryKey": false,
|
|
35
|
+
"notNull": true,
|
|
36
|
+
"autoincrement": false
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"indexes": {},
|
|
40
|
+
"foreignKeys": {},
|
|
41
|
+
"compositePrimaryKeys": {},
|
|
42
|
+
"uniqueConstraints": {},
|
|
43
|
+
"checkConstraints": {}
|
|
44
|
+
},
|
|
45
|
+
"crons": {
|
|
46
|
+
"name": "crons",
|
|
47
|
+
"columns": {
|
|
48
|
+
"id": {
|
|
49
|
+
"name": "id",
|
|
50
|
+
"type": "integer",
|
|
51
|
+
"primaryKey": true,
|
|
52
|
+
"notNull": true,
|
|
53
|
+
"autoincrement": true
|
|
54
|
+
},
|
|
55
|
+
"name": {
|
|
56
|
+
"name": "name",
|
|
57
|
+
"type": "text",
|
|
58
|
+
"primaryKey": false,
|
|
59
|
+
"notNull": true,
|
|
60
|
+
"autoincrement": false
|
|
61
|
+
},
|
|
62
|
+
"enqueue_into": {
|
|
63
|
+
"name": "enqueue_into",
|
|
64
|
+
"type": "text",
|
|
65
|
+
"primaryKey": false,
|
|
66
|
+
"notNull": true,
|
|
67
|
+
"autoincrement": false
|
|
68
|
+
},
|
|
69
|
+
"payload": {
|
|
70
|
+
"name": "payload",
|
|
71
|
+
"type": "text",
|
|
72
|
+
"primaryKey": false,
|
|
73
|
+
"notNull": true,
|
|
74
|
+
"autoincrement": false
|
|
75
|
+
},
|
|
76
|
+
"recurrence": {
|
|
77
|
+
"name": "recurrence",
|
|
78
|
+
"type": "text",
|
|
79
|
+
"primaryKey": false,
|
|
80
|
+
"notNull": false,
|
|
81
|
+
"autoincrement": false
|
|
82
|
+
},
|
|
83
|
+
"next_run_at": {
|
|
84
|
+
"name": "next_run_at",
|
|
85
|
+
"type": "integer",
|
|
86
|
+
"primaryKey": false,
|
|
87
|
+
"notNull": true,
|
|
88
|
+
"autoincrement": false
|
|
89
|
+
},
|
|
90
|
+
"last_run_at": {
|
|
91
|
+
"name": "last_run_at",
|
|
92
|
+
"type": "integer",
|
|
93
|
+
"primaryKey": false,
|
|
94
|
+
"notNull": false,
|
|
95
|
+
"autoincrement": false
|
|
96
|
+
},
|
|
97
|
+
"enabled": {
|
|
98
|
+
"name": "enabled",
|
|
99
|
+
"type": "integer",
|
|
100
|
+
"primaryKey": false,
|
|
101
|
+
"notNull": true,
|
|
102
|
+
"autoincrement": false,
|
|
103
|
+
"default": 1
|
|
104
|
+
},
|
|
105
|
+
"created_at": {
|
|
106
|
+
"name": "created_at",
|
|
107
|
+
"type": "integer",
|
|
108
|
+
"primaryKey": false,
|
|
109
|
+
"notNull": true,
|
|
110
|
+
"autoincrement": false
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
"indexes": {
|
|
114
|
+
"crons_by_due": {
|
|
115
|
+
"name": "crons_by_due",
|
|
116
|
+
"columns": [
|
|
117
|
+
"enabled",
|
|
118
|
+
"next_run_at"
|
|
119
|
+
],
|
|
120
|
+
"isUnique": false
|
|
121
|
+
},
|
|
122
|
+
"crons_name_uq": {
|
|
123
|
+
"name": "crons_name_uq",
|
|
124
|
+
"columns": [
|
|
125
|
+
"name"
|
|
126
|
+
],
|
|
127
|
+
"isUnique": true,
|
|
128
|
+
"where": "\"crons\".\"recurrence\" IS NOT NULL"
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
"foreignKeys": {},
|
|
132
|
+
"compositePrimaryKeys": {},
|
|
133
|
+
"uniqueConstraints": {},
|
|
134
|
+
"checkConstraints": {}
|
|
135
|
+
},
|
|
136
|
+
"identities": {
|
|
137
|
+
"name": "identities",
|
|
138
|
+
"columns": {
|
|
139
|
+
"person_id": {
|
|
140
|
+
"name": "person_id",
|
|
141
|
+
"type": "text",
|
|
142
|
+
"primaryKey": false,
|
|
143
|
+
"notNull": true,
|
|
144
|
+
"autoincrement": false
|
|
145
|
+
},
|
|
146
|
+
"address": {
|
|
147
|
+
"name": "address",
|
|
148
|
+
"type": "text",
|
|
149
|
+
"primaryKey": false,
|
|
150
|
+
"notNull": true,
|
|
151
|
+
"autoincrement": false
|
|
152
|
+
},
|
|
153
|
+
"added_at": {
|
|
154
|
+
"name": "added_at",
|
|
155
|
+
"type": "integer",
|
|
156
|
+
"primaryKey": false,
|
|
157
|
+
"notNull": true,
|
|
158
|
+
"autoincrement": false
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
"indexes": {
|
|
162
|
+
"identities_address_unique": {
|
|
163
|
+
"name": "identities_address_unique",
|
|
164
|
+
"columns": [
|
|
165
|
+
"address"
|
|
166
|
+
],
|
|
167
|
+
"isUnique": true
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
"foreignKeys": {
|
|
171
|
+
"identities_person_id_persons_id_fk": {
|
|
172
|
+
"name": "identities_person_id_persons_id_fk",
|
|
173
|
+
"tableFrom": "identities",
|
|
174
|
+
"tableTo": "persons",
|
|
175
|
+
"columnsFrom": [
|
|
176
|
+
"person_id"
|
|
177
|
+
],
|
|
178
|
+
"columnsTo": [
|
|
179
|
+
"id"
|
|
180
|
+
],
|
|
181
|
+
"onDelete": "no action",
|
|
182
|
+
"onUpdate": "no action"
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
"compositePrimaryKeys": {
|
|
186
|
+
"identities_person_id_address_pk": {
|
|
187
|
+
"columns": [
|
|
188
|
+
"person_id",
|
|
189
|
+
"address"
|
|
190
|
+
],
|
|
191
|
+
"name": "identities_person_id_address_pk"
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
"uniqueConstraints": {},
|
|
195
|
+
"checkConstraints": {}
|
|
196
|
+
},
|
|
197
|
+
"inbound": {
|
|
198
|
+
"name": "inbound",
|
|
199
|
+
"columns": {
|
|
200
|
+
"id": {
|
|
201
|
+
"name": "id",
|
|
202
|
+
"type": "integer",
|
|
203
|
+
"primaryKey": true,
|
|
204
|
+
"notNull": true,
|
|
205
|
+
"autoincrement": true
|
|
206
|
+
},
|
|
207
|
+
"address": {
|
|
208
|
+
"name": "address",
|
|
209
|
+
"type": "text",
|
|
210
|
+
"primaryKey": false,
|
|
211
|
+
"notNull": true,
|
|
212
|
+
"autoincrement": false
|
|
213
|
+
},
|
|
214
|
+
"actor_address": {
|
|
215
|
+
"name": "actor_address",
|
|
216
|
+
"type": "text",
|
|
217
|
+
"primaryKey": false,
|
|
218
|
+
"notNull": false,
|
|
219
|
+
"autoincrement": false
|
|
220
|
+
},
|
|
221
|
+
"person_id": {
|
|
222
|
+
"name": "person_id",
|
|
223
|
+
"type": "text",
|
|
224
|
+
"primaryKey": false,
|
|
225
|
+
"notNull": false,
|
|
226
|
+
"autoincrement": false
|
|
227
|
+
},
|
|
228
|
+
"actor_person_id": {
|
|
229
|
+
"name": "actor_person_id",
|
|
230
|
+
"type": "text",
|
|
231
|
+
"primaryKey": false,
|
|
232
|
+
"notNull": false,
|
|
233
|
+
"autoincrement": false
|
|
234
|
+
},
|
|
235
|
+
"external_msg_id": {
|
|
236
|
+
"name": "external_msg_id",
|
|
237
|
+
"type": "text",
|
|
238
|
+
"primaryKey": false,
|
|
239
|
+
"notNull": false,
|
|
240
|
+
"autoincrement": false
|
|
241
|
+
},
|
|
242
|
+
"text": {
|
|
243
|
+
"name": "text",
|
|
244
|
+
"type": "text",
|
|
245
|
+
"primaryKey": false,
|
|
246
|
+
"notNull": true,
|
|
247
|
+
"autoincrement": false
|
|
248
|
+
},
|
|
249
|
+
"media_path": {
|
|
250
|
+
"name": "media_path",
|
|
251
|
+
"type": "text",
|
|
252
|
+
"primaryKey": false,
|
|
253
|
+
"notNull": false,
|
|
254
|
+
"autoincrement": false
|
|
255
|
+
},
|
|
256
|
+
"media_mime": {
|
|
257
|
+
"name": "media_mime",
|
|
258
|
+
"type": "text",
|
|
259
|
+
"primaryKey": false,
|
|
260
|
+
"notNull": false,
|
|
261
|
+
"autoincrement": false
|
|
262
|
+
},
|
|
263
|
+
"media_bytes": {
|
|
264
|
+
"name": "media_bytes",
|
|
265
|
+
"type": "integer",
|
|
266
|
+
"primaryKey": false,
|
|
267
|
+
"notNull": false,
|
|
268
|
+
"autoincrement": false
|
|
269
|
+
},
|
|
270
|
+
"push_name": {
|
|
271
|
+
"name": "push_name",
|
|
272
|
+
"type": "text",
|
|
273
|
+
"primaryKey": false,
|
|
274
|
+
"notNull": false,
|
|
275
|
+
"autoincrement": false
|
|
276
|
+
},
|
|
277
|
+
"trigger_reason": {
|
|
278
|
+
"name": "trigger_reason",
|
|
279
|
+
"type": "text",
|
|
280
|
+
"primaryKey": false,
|
|
281
|
+
"notNull": false,
|
|
282
|
+
"autoincrement": false
|
|
283
|
+
},
|
|
284
|
+
"status": {
|
|
285
|
+
"name": "status",
|
|
286
|
+
"type": "text",
|
|
287
|
+
"primaryKey": false,
|
|
288
|
+
"notNull": true,
|
|
289
|
+
"autoincrement": false
|
|
290
|
+
},
|
|
291
|
+
"attempts": {
|
|
292
|
+
"name": "attempts",
|
|
293
|
+
"type": "integer",
|
|
294
|
+
"primaryKey": false,
|
|
295
|
+
"notNull": true,
|
|
296
|
+
"autoincrement": false,
|
|
297
|
+
"default": 0
|
|
298
|
+
},
|
|
299
|
+
"next_attempt_at": {
|
|
300
|
+
"name": "next_attempt_at",
|
|
301
|
+
"type": "integer",
|
|
302
|
+
"primaryKey": false,
|
|
303
|
+
"notNull": false,
|
|
304
|
+
"autoincrement": false
|
|
305
|
+
},
|
|
306
|
+
"last_error": {
|
|
307
|
+
"name": "last_error",
|
|
308
|
+
"type": "text",
|
|
309
|
+
"primaryKey": false,
|
|
310
|
+
"notNull": false,
|
|
311
|
+
"autoincrement": false
|
|
312
|
+
},
|
|
313
|
+
"claimed_by": {
|
|
314
|
+
"name": "claimed_by",
|
|
315
|
+
"type": "text",
|
|
316
|
+
"primaryKey": false,
|
|
317
|
+
"notNull": false,
|
|
318
|
+
"autoincrement": false
|
|
319
|
+
},
|
|
320
|
+
"claimed_at": {
|
|
321
|
+
"name": "claimed_at",
|
|
322
|
+
"type": "integer",
|
|
323
|
+
"primaryKey": false,
|
|
324
|
+
"notNull": false,
|
|
325
|
+
"autoincrement": false
|
|
326
|
+
},
|
|
327
|
+
"received_at": {
|
|
328
|
+
"name": "received_at",
|
|
329
|
+
"type": "integer",
|
|
330
|
+
"primaryKey": false,
|
|
331
|
+
"notNull": true,
|
|
332
|
+
"autoincrement": false
|
|
333
|
+
},
|
|
334
|
+
"created_at": {
|
|
335
|
+
"name": "created_at",
|
|
336
|
+
"type": "integer",
|
|
337
|
+
"primaryKey": false,
|
|
338
|
+
"notNull": true,
|
|
339
|
+
"autoincrement": false
|
|
340
|
+
},
|
|
341
|
+
"updated_at": {
|
|
342
|
+
"name": "updated_at",
|
|
343
|
+
"type": "integer",
|
|
344
|
+
"primaryKey": false,
|
|
345
|
+
"notNull": true,
|
|
346
|
+
"autoincrement": false
|
|
347
|
+
}
|
|
348
|
+
},
|
|
349
|
+
"indexes": {
|
|
350
|
+
"inbound_by_status_next": {
|
|
351
|
+
"name": "inbound_by_status_next",
|
|
352
|
+
"columns": [
|
|
353
|
+
"status",
|
|
354
|
+
"next_attempt_at"
|
|
355
|
+
],
|
|
356
|
+
"isUnique": false
|
|
357
|
+
},
|
|
358
|
+
"inbound_by_address": {
|
|
359
|
+
"name": "inbound_by_address",
|
|
360
|
+
"columns": [
|
|
361
|
+
"address"
|
|
362
|
+
],
|
|
363
|
+
"isUnique": false
|
|
364
|
+
},
|
|
365
|
+
"inbound_by_person": {
|
|
366
|
+
"name": "inbound_by_person",
|
|
367
|
+
"columns": [
|
|
368
|
+
"person_id",
|
|
369
|
+
"received_at"
|
|
370
|
+
],
|
|
371
|
+
"isUnique": false
|
|
372
|
+
},
|
|
373
|
+
"inbound_external_msg_id_uq": {
|
|
374
|
+
"name": "inbound_external_msg_id_uq",
|
|
375
|
+
"columns": [
|
|
376
|
+
"external_msg_id"
|
|
377
|
+
],
|
|
378
|
+
"isUnique": true,
|
|
379
|
+
"where": "\"inbound\".\"external_msg_id\" IS NOT NULL"
|
|
380
|
+
}
|
|
381
|
+
},
|
|
382
|
+
"foreignKeys": {},
|
|
383
|
+
"compositePrimaryKeys": {},
|
|
384
|
+
"uniqueConstraints": {},
|
|
385
|
+
"checkConstraints": {}
|
|
386
|
+
},
|
|
387
|
+
"outbound": {
|
|
388
|
+
"name": "outbound",
|
|
389
|
+
"columns": {
|
|
390
|
+
"id": {
|
|
391
|
+
"name": "id",
|
|
392
|
+
"type": "integer",
|
|
393
|
+
"primaryKey": true,
|
|
394
|
+
"notNull": true,
|
|
395
|
+
"autoincrement": true
|
|
396
|
+
},
|
|
397
|
+
"address": {
|
|
398
|
+
"name": "address",
|
|
399
|
+
"type": "text",
|
|
400
|
+
"primaryKey": false,
|
|
401
|
+
"notNull": true,
|
|
402
|
+
"autoincrement": false
|
|
403
|
+
},
|
|
404
|
+
"kind": {
|
|
405
|
+
"name": "kind",
|
|
406
|
+
"type": "text",
|
|
407
|
+
"primaryKey": false,
|
|
408
|
+
"notNull": true,
|
|
409
|
+
"autoincrement": false
|
|
410
|
+
},
|
|
411
|
+
"text": {
|
|
412
|
+
"name": "text",
|
|
413
|
+
"type": "text",
|
|
414
|
+
"primaryKey": false,
|
|
415
|
+
"notNull": false,
|
|
416
|
+
"autoincrement": false
|
|
417
|
+
},
|
|
418
|
+
"media_path": {
|
|
419
|
+
"name": "media_path",
|
|
420
|
+
"type": "text",
|
|
421
|
+
"primaryKey": false,
|
|
422
|
+
"notNull": false,
|
|
423
|
+
"autoincrement": false
|
|
424
|
+
},
|
|
425
|
+
"media_mime": {
|
|
426
|
+
"name": "media_mime",
|
|
427
|
+
"type": "text",
|
|
428
|
+
"primaryKey": false,
|
|
429
|
+
"notNull": false,
|
|
430
|
+
"autoincrement": false
|
|
431
|
+
},
|
|
432
|
+
"media_bytes": {
|
|
433
|
+
"name": "media_bytes",
|
|
434
|
+
"type": "integer",
|
|
435
|
+
"primaryKey": false,
|
|
436
|
+
"notNull": false,
|
|
437
|
+
"autoincrement": false
|
|
438
|
+
},
|
|
439
|
+
"quote_msg_id": {
|
|
440
|
+
"name": "quote_msg_id",
|
|
441
|
+
"type": "text",
|
|
442
|
+
"primaryKey": false,
|
|
443
|
+
"notNull": false,
|
|
444
|
+
"autoincrement": false
|
|
445
|
+
},
|
|
446
|
+
"idempotency_key": {
|
|
447
|
+
"name": "idempotency_key",
|
|
448
|
+
"type": "text",
|
|
449
|
+
"primaryKey": false,
|
|
450
|
+
"notNull": false,
|
|
451
|
+
"autoincrement": false
|
|
452
|
+
},
|
|
453
|
+
"status": {
|
|
454
|
+
"name": "status",
|
|
455
|
+
"type": "text",
|
|
456
|
+
"primaryKey": false,
|
|
457
|
+
"notNull": true,
|
|
458
|
+
"autoincrement": false
|
|
459
|
+
},
|
|
460
|
+
"attempts": {
|
|
461
|
+
"name": "attempts",
|
|
462
|
+
"type": "integer",
|
|
463
|
+
"primaryKey": false,
|
|
464
|
+
"notNull": true,
|
|
465
|
+
"autoincrement": false,
|
|
466
|
+
"default": 0
|
|
467
|
+
},
|
|
468
|
+
"next_attempt_at": {
|
|
469
|
+
"name": "next_attempt_at",
|
|
470
|
+
"type": "integer",
|
|
471
|
+
"primaryKey": false,
|
|
472
|
+
"notNull": false,
|
|
473
|
+
"autoincrement": false
|
|
474
|
+
},
|
|
475
|
+
"last_error": {
|
|
476
|
+
"name": "last_error",
|
|
477
|
+
"type": "text",
|
|
478
|
+
"primaryKey": false,
|
|
479
|
+
"notNull": false,
|
|
480
|
+
"autoincrement": false
|
|
481
|
+
},
|
|
482
|
+
"claimed_by": {
|
|
483
|
+
"name": "claimed_by",
|
|
484
|
+
"type": "text",
|
|
485
|
+
"primaryKey": false,
|
|
486
|
+
"notNull": false,
|
|
487
|
+
"autoincrement": false
|
|
488
|
+
},
|
|
489
|
+
"claimed_at": {
|
|
490
|
+
"name": "claimed_at",
|
|
491
|
+
"type": "integer",
|
|
492
|
+
"primaryKey": false,
|
|
493
|
+
"notNull": false,
|
|
494
|
+
"autoincrement": false
|
|
495
|
+
},
|
|
496
|
+
"created_at": {
|
|
497
|
+
"name": "created_at",
|
|
498
|
+
"type": "integer",
|
|
499
|
+
"primaryKey": false,
|
|
500
|
+
"notNull": true,
|
|
501
|
+
"autoincrement": false
|
|
502
|
+
},
|
|
503
|
+
"updated_at": {
|
|
504
|
+
"name": "updated_at",
|
|
505
|
+
"type": "integer",
|
|
506
|
+
"primaryKey": false,
|
|
507
|
+
"notNull": true,
|
|
508
|
+
"autoincrement": false
|
|
509
|
+
}
|
|
510
|
+
},
|
|
511
|
+
"indexes": {
|
|
512
|
+
"outbound_by_status_next": {
|
|
513
|
+
"name": "outbound_by_status_next",
|
|
514
|
+
"columns": [
|
|
515
|
+
"status",
|
|
516
|
+
"next_attempt_at"
|
|
517
|
+
],
|
|
518
|
+
"isUnique": false
|
|
519
|
+
},
|
|
520
|
+
"outbound_by_address": {
|
|
521
|
+
"name": "outbound_by_address",
|
|
522
|
+
"columns": [
|
|
523
|
+
"address"
|
|
524
|
+
],
|
|
525
|
+
"isUnique": false
|
|
526
|
+
},
|
|
527
|
+
"outbound_idempotency_key_uq": {
|
|
528
|
+
"name": "outbound_idempotency_key_uq",
|
|
529
|
+
"columns": [
|
|
530
|
+
"idempotency_key"
|
|
531
|
+
],
|
|
532
|
+
"isUnique": true,
|
|
533
|
+
"where": "\"outbound\".\"idempotency_key\" IS NOT NULL"
|
|
534
|
+
}
|
|
535
|
+
},
|
|
536
|
+
"foreignKeys": {},
|
|
537
|
+
"compositePrimaryKeys": {},
|
|
538
|
+
"uniqueConstraints": {},
|
|
539
|
+
"checkConstraints": {}
|
|
540
|
+
},
|
|
541
|
+
"persons": {
|
|
542
|
+
"name": "persons",
|
|
543
|
+
"columns": {
|
|
544
|
+
"id": {
|
|
545
|
+
"name": "id",
|
|
546
|
+
"type": "text",
|
|
547
|
+
"primaryKey": true,
|
|
548
|
+
"notNull": true,
|
|
549
|
+
"autoincrement": false
|
|
550
|
+
},
|
|
551
|
+
"display_name": {
|
|
552
|
+
"name": "display_name",
|
|
553
|
+
"type": "text",
|
|
554
|
+
"primaryKey": false,
|
|
555
|
+
"notNull": false,
|
|
556
|
+
"autoincrement": false
|
|
557
|
+
},
|
|
558
|
+
"timezone": {
|
|
559
|
+
"name": "timezone",
|
|
560
|
+
"type": "text",
|
|
561
|
+
"primaryKey": false,
|
|
562
|
+
"notNull": false,
|
|
563
|
+
"autoincrement": false
|
|
564
|
+
},
|
|
565
|
+
"created_at": {
|
|
566
|
+
"name": "created_at",
|
|
567
|
+
"type": "integer",
|
|
568
|
+
"primaryKey": false,
|
|
569
|
+
"notNull": true,
|
|
570
|
+
"autoincrement": false
|
|
571
|
+
}
|
|
572
|
+
},
|
|
573
|
+
"indexes": {},
|
|
574
|
+
"foreignKeys": {},
|
|
575
|
+
"compositePrimaryKeys": {},
|
|
576
|
+
"uniqueConstraints": {},
|
|
577
|
+
"checkConstraints": {}
|
|
578
|
+
},
|
|
579
|
+
"workers": {
|
|
580
|
+
"name": "workers",
|
|
581
|
+
"columns": {
|
|
582
|
+
"id": {
|
|
583
|
+
"name": "id",
|
|
584
|
+
"type": "text",
|
|
585
|
+
"primaryKey": true,
|
|
586
|
+
"notNull": true,
|
|
587
|
+
"autoincrement": false
|
|
588
|
+
},
|
|
589
|
+
"kind": {
|
|
590
|
+
"name": "kind",
|
|
591
|
+
"type": "text",
|
|
592
|
+
"primaryKey": false,
|
|
593
|
+
"notNull": true,
|
|
594
|
+
"autoincrement": false
|
|
595
|
+
},
|
|
596
|
+
"status": {
|
|
597
|
+
"name": "status",
|
|
598
|
+
"type": "text",
|
|
599
|
+
"primaryKey": false,
|
|
600
|
+
"notNull": true,
|
|
601
|
+
"autoincrement": false
|
|
602
|
+
},
|
|
603
|
+
"current_job": {
|
|
604
|
+
"name": "current_job",
|
|
605
|
+
"type": "text",
|
|
606
|
+
"primaryKey": false,
|
|
607
|
+
"notNull": false,
|
|
608
|
+
"autoincrement": false
|
|
609
|
+
},
|
|
610
|
+
"last_seen": {
|
|
611
|
+
"name": "last_seen",
|
|
612
|
+
"type": "integer",
|
|
613
|
+
"primaryKey": false,
|
|
614
|
+
"notNull": true,
|
|
615
|
+
"autoincrement": false
|
|
616
|
+
},
|
|
617
|
+
"started_at": {
|
|
618
|
+
"name": "started_at",
|
|
619
|
+
"type": "integer",
|
|
620
|
+
"primaryKey": false,
|
|
621
|
+
"notNull": true,
|
|
622
|
+
"autoincrement": false
|
|
623
|
+
}
|
|
624
|
+
},
|
|
625
|
+
"indexes": {
|
|
626
|
+
"workers_by_kind_status": {
|
|
627
|
+
"name": "workers_by_kind_status",
|
|
628
|
+
"columns": [
|
|
629
|
+
"kind",
|
|
630
|
+
"status"
|
|
631
|
+
],
|
|
632
|
+
"isUnique": false
|
|
633
|
+
},
|
|
634
|
+
"workers_by_last_seen": {
|
|
635
|
+
"name": "workers_by_last_seen",
|
|
636
|
+
"columns": [
|
|
637
|
+
"last_seen"
|
|
638
|
+
],
|
|
639
|
+
"isUnique": false
|
|
640
|
+
}
|
|
641
|
+
},
|
|
642
|
+
"foreignKeys": {},
|
|
643
|
+
"compositePrimaryKeys": {},
|
|
644
|
+
"uniqueConstraints": {},
|
|
645
|
+
"checkConstraints": {}
|
|
646
|
+
}
|
|
647
|
+
},
|
|
648
|
+
"views": {},
|
|
649
|
+
"enums": {},
|
|
650
|
+
"_meta": {
|
|
651
|
+
"schemas": {},
|
|
652
|
+
"tables": {},
|
|
653
|
+
"columns": {}
|
|
654
|
+
},
|
|
655
|
+
"internal": {
|
|
656
|
+
"indexes": {}
|
|
657
|
+
}
|
|
658
|
+
}
|