@shipfox/api-runners 10.0.0 → 10.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +4 -4
- package/CHANGELOG.md +28 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +8 -13
- package/dist/config.js.map +1 -1
- package/dist/core/admin-runner-instances.d.ts +34 -0
- package/dist/core/admin-runner-instances.d.ts.map +1 -0
- package/dist/core/admin-runner-instances.js +58 -0
- package/dist/core/admin-runner-instances.js.map +1 -0
- package/dist/core/errors.d.ts +6 -0
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +12 -0
- package/dist/core/errors.js.map +1 -1
- package/dist/core/index.d.ts +3 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +3 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/provisioner-tokens.d.ts +33 -0
- package/dist/core/provisioner-tokens.d.ts.map +1 -1
- package/dist/core/provisioner-tokens.js +107 -2
- package/dist/core/provisioner-tokens.js.map +1 -1
- package/dist/db/admin-runner-instances.d.ts +34 -0
- package/dist/db/admin-runner-instances.d.ts.map +1 -0
- package/dist/db/admin-runner-instances.js +62 -0
- package/dist/db/admin-runner-instances.js.map +1 -0
- package/dist/db/db.d.ts +290 -0
- package/dist/db/db.d.ts.map +1 -1
- package/dist/db/db.js +2 -0
- package/dist/db/db.js.map +1 -1
- package/dist/db/index.d.ts +3 -1
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +2 -1
- package/dist/db/index.js.map +1 -1
- package/dist/db/job-executions.d.ts.map +1 -1
- package/dist/db/job-executions.js +11 -0
- package/dist/db/job-executions.js.map +1 -1
- package/dist/db/provisioner-tokens.d.ts +38 -0
- package/dist/db/provisioner-tokens.d.ts.map +1 -1
- package/dist/db/provisioner-tokens.js +157 -3
- package/dist/db/provisioner-tokens.js.map +1 -1
- package/dist/db/reservations.d.ts.map +1 -1
- package/dist/db/reservations.js +60 -8
- package/dist/db/reservations.js.map +1 -1
- package/dist/db/schema/admin-command-results.d.ts +134 -0
- package/dist/db/schema/admin-command-results.d.ts.map +1 -0
- package/dist/db/schema/admin-command-results.js +18 -0
- package/dist/db/schema/admin-command-results.js.map +1 -0
- package/dist/db/schema/runner-instances.d.ts +17 -0
- package/dist/db/schema/runner-instances.d.ts.map +1 -1
- package/dist/db/schema/runner-instances.js +3 -0
- package/dist/db/schema/runner-instances.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/presentation/dto/admin-provisioner-token.d.ts +16 -0
- package/dist/presentation/dto/admin-provisioner-token.d.ts.map +1 -0
- package/dist/presentation/dto/admin-provisioner-token.js +22 -0
- package/dist/presentation/dto/admin-provisioner-token.js.map +1 -0
- package/dist/presentation/dto/admin-runner-instances.d.ts +23 -0
- package/dist/presentation/dto/admin-runner-instances.d.ts.map +1 -0
- package/dist/presentation/dto/admin-runner-instances.js +19 -0
- package/dist/presentation/dto/admin-runner-instances.js.map +1 -0
- package/dist/presentation/dto/index.d.ts +2 -0
- package/dist/presentation/dto/index.d.ts.map +1 -1
- package/dist/presentation/dto/index.js +2 -0
- package/dist/presentation/dto/index.js.map +1 -1
- package/dist/presentation/routes/admin-provisioner-tokens.d.ts +4 -0
- package/dist/presentation/routes/admin-provisioner-tokens.d.ts.map +1 -0
- package/dist/presentation/routes/admin-provisioner-tokens.js +167 -0
- package/dist/presentation/routes/admin-provisioner-tokens.js.map +1 -0
- package/dist/presentation/routes/admin-runner-instances.d.ts +3 -0
- package/dist/presentation/routes/admin-runner-instances.d.ts.map +1 -0
- package/dist/presentation/routes/admin-runner-instances.js +63 -0
- package/dist/presentation/routes/admin-runner-instances.js.map +1 -0
- package/dist/presentation/routes/index.d.ts.map +1 -1
- package/dist/presentation/routes/index.js +11 -1
- package/dist/presentation/routes/index.js.map +1 -1
- package/dist/presentation/routes/runner-enrollment.d.ts +1 -0
- package/dist/presentation/routes/runner-enrollment.d.ts.map +1 -1
- package/dist/presentation/routes/runner-enrollment.js +11 -4
- package/dist/presentation/routes/runner-enrollment.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/drizzle/0002_special_shadowcat.sql +1 -0
- package/drizzle/0003_flaky_sally_floyd.sql +11 -0
- package/drizzle/meta/0002_snapshot.json +2099 -0
- package/drizzle/meta/0003_snapshot.json +2178 -0
- package/drizzle/meta/_journal.json +14 -0
- package/package.json +10 -9
- package/src/config.test.ts +11 -0
- package/src/config.ts +19 -8
- package/src/core/admin-runner-instances.ts +125 -0
- package/src/core/errors.ts +14 -0
- package/src/core/index.ts +12 -0
- package/src/core/provisioner-tokens.ts +157 -1
- package/src/db/admin-runner-instances.ts +121 -0
- package/src/db/db.ts +2 -0
- package/src/db/index.ts +10 -0
- package/src/db/job-executions.test.ts +17 -0
- package/src/db/job-executions.ts +22 -0
- package/src/db/provisioner-tokens.ts +263 -9
- package/src/db/reservations.test.ts +253 -0
- package/src/db/reservations.ts +170 -15
- package/src/db/schema/admin-command-results.ts +29 -0
- package/src/db/schema/runner-instances.ts +1 -0
- package/src/index.test.ts +1 -1
- package/src/index.ts +6 -1
- package/src/presentation/dto/admin-provisioner-token.ts +23 -0
- package/src/presentation/dto/admin-runner-instances.ts +19 -0
- package/src/presentation/dto/index.ts +2 -0
- package/src/presentation/routes/admin-provisioner-tokens.test.ts +227 -0
- package/src/presentation/routes/admin-provisioner-tokens.ts +177 -0
- package/src/presentation/routes/admin-runner-instances.test.ts +671 -0
- package/src/presentation/routes/admin-runner-instances.ts +67 -0
- package/src/presentation/routes/index.ts +8 -0
- package/src/presentation/routes/runner-enrollment.test.ts +60 -2
- package/src/presentation/routes/runner-enrollment.ts +24 -4
- package/test/globalSetup.ts +1 -0
- package/tsconfig.build.tsbuildinfo +1 -1
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { timestampIdCursorWhere } from '@shipfox/node-drizzle';
|
|
2
|
+
import { writeOutboxEvent } from '@shipfox/node-outbox';
|
|
3
|
+
import { and, desc, eq, gt, inArray, isNotNull, isNull, lte, or, sql } from 'drizzle-orm';
|
|
4
|
+
import { ProvisionerAdminIdempotencyKeyReuseError } from '#core/errors.js';
|
|
2
5
|
import { db } from './db.js';
|
|
6
|
+
import { runnersAdminCommandResults } from './schema/admin-command-results.js';
|
|
7
|
+
import { runnersOutbox } from './schema/outbox.js';
|
|
3
8
|
import { provisionerTokens, toProvisionerToken } from './schema/provisioner-tokens.js';
|
|
4
9
|
import { runnerActivationTokens } from './schema/runner-activation-tokens.js';
|
|
5
10
|
import { runnerBootstrapTokens, runnerControlSessions } from './schema/runner-control-sessions.js';
|
|
@@ -27,8 +32,8 @@ async function cascadeProvisionerRevocation(tx, provisionerId) {
|
|
|
27
32
|
revokedAt: sql`now()`
|
|
28
33
|
}).where(and(eq(runnerSessions.provisionerId, provisionerId), eq(runnerSessions.claimsUsed, 0), isNull(runnerSessions.revokedAt)));
|
|
29
34
|
}
|
|
30
|
-
|
|
31
|
-
const rows = await
|
|
35
|
+
async function insertProvisionerToken(tx, params) {
|
|
36
|
+
const rows = await tx.insert(provisionerTokens).values({
|
|
32
37
|
scope: params.scope,
|
|
33
38
|
workspaceId: params.workspaceId ?? null,
|
|
34
39
|
hashedToken: params.hashedToken,
|
|
@@ -39,8 +44,81 @@ export async function createProvisionerToken(params) {
|
|
|
39
44
|
}).returning();
|
|
40
45
|
const row = rows[0];
|
|
41
46
|
if (!row) throw new Error('Insert returned no rows');
|
|
47
|
+
return row;
|
|
48
|
+
}
|
|
49
|
+
export async function createProvisionerToken(params) {
|
|
50
|
+
const row = await db().transaction((tx)=>insertProvisionerToken(tx, params));
|
|
42
51
|
return toProvisionerToken(row);
|
|
43
52
|
}
|
|
53
|
+
async function lockAdminCommand(tx, params) {
|
|
54
|
+
await tx.execute(sql`select pg_advisory_xact_lock(hashtext(${`runners_admin_command:${params.actorId}:${params.idempotencyKeyFingerprint}`}))`);
|
|
55
|
+
}
|
|
56
|
+
async function loadAdminCommandResult(tx, params) {
|
|
57
|
+
const rows = await tx.select().from(runnersAdminCommandResults).where(and(eq(runnersAdminCommandResults.actorId, params.actorId), eq(runnersAdminCommandResults.idempotencyKeyFingerprint, params.idempotencyKeyFingerprint))).limit(1);
|
|
58
|
+
const result = rows[0];
|
|
59
|
+
if (!result) return undefined;
|
|
60
|
+
if (result.command !== params.command || result.requestFingerprint !== params.requestFingerprint) {
|
|
61
|
+
throw new ProvisionerAdminIdempotencyKeyReuseError();
|
|
62
|
+
}
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
async function storeAdminCommandResult(tx, params, result) {
|
|
66
|
+
await tx.insert(runnersAdminCommandResults).values({
|
|
67
|
+
actorId: params.actorId,
|
|
68
|
+
idempotencyKeyFingerprint: params.idempotencyKeyFingerprint,
|
|
69
|
+
command: params.command,
|
|
70
|
+
requestFingerprint: params.requestFingerprint,
|
|
71
|
+
result
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
async function writeAdministrationAction(tx, event) {
|
|
75
|
+
await writeOutboxEvent(tx, runnersOutbox, {
|
|
76
|
+
type: 'administration.action.performed',
|
|
77
|
+
payload: event
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
export async function createInstallationProvisionerTokenWithAudit(params) {
|
|
81
|
+
const command = 'runners.provisioner_token.create';
|
|
82
|
+
return await db().transaction(async (tx)=>{
|
|
83
|
+
await lockAdminCommand(tx, params);
|
|
84
|
+
const existing = await loadAdminCommandResult(tx, {
|
|
85
|
+
actorId: params.actorId,
|
|
86
|
+
idempotencyKeyFingerprint: params.idempotencyKeyFingerprint,
|
|
87
|
+
requestFingerprint: params.requestFingerprint,
|
|
88
|
+
command
|
|
89
|
+
});
|
|
90
|
+
if (existing) {
|
|
91
|
+
const rows = await tx.select().from(provisionerTokens).where(eq(provisionerTokens.id, existing.result.provisionerTokenId)).limit(1);
|
|
92
|
+
const row = rows[0];
|
|
93
|
+
if (!row) throw new Error('Idempotent provisioner token result is missing');
|
|
94
|
+
return {
|
|
95
|
+
token: toProvisionerToken(row),
|
|
96
|
+
correlationId: existing.result.correlationId,
|
|
97
|
+
replayed: true
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const expiresAt = params.ttlSeconds === undefined ? undefined : new Date(Date.now() + params.ttlSeconds * 1000);
|
|
101
|
+
const row = await insertProvisionerToken(tx, {
|
|
102
|
+
scope: 'installation',
|
|
103
|
+
hashedToken: params.hashedToken,
|
|
104
|
+
prefix: params.prefix,
|
|
105
|
+
createdByUserId: params.actorId,
|
|
106
|
+
name: params.name,
|
|
107
|
+
expiresAt
|
|
108
|
+
});
|
|
109
|
+
const event = params.event(row.id);
|
|
110
|
+
await writeAdministrationAction(tx, event);
|
|
111
|
+
await storeAdminCommandResult(tx, params, {
|
|
112
|
+
provisionerTokenId: row.id,
|
|
113
|
+
correlationId: event.correlationId
|
|
114
|
+
});
|
|
115
|
+
return {
|
|
116
|
+
token: toProvisionerToken(row),
|
|
117
|
+
correlationId: event.correlationId,
|
|
118
|
+
replayed: false
|
|
119
|
+
};
|
|
120
|
+
});
|
|
121
|
+
}
|
|
44
122
|
export async function revokeInstallationProvisionerToken(params) {
|
|
45
123
|
const rows = await db().transaction(async (tx)=>{
|
|
46
124
|
const rows = await tx.update(provisionerTokens).set({
|
|
@@ -59,6 +137,82 @@ export async function revokeInstallationProvisionerToken(params) {
|
|
|
59
137
|
const existingRow = existingRows[0];
|
|
60
138
|
return existingRow ? toProvisionerToken(existingRow) : undefined;
|
|
61
139
|
}
|
|
140
|
+
export async function revokeInstallationProvisionerTokenWithAudit(params) {
|
|
141
|
+
const command = 'runners.provisioner_token.revoke';
|
|
142
|
+
return await db().transaction(async (tx)=>{
|
|
143
|
+
await lockAdminCommand(tx, params);
|
|
144
|
+
const existing = await loadAdminCommandResult(tx, {
|
|
145
|
+
actorId: params.actorId,
|
|
146
|
+
idempotencyKeyFingerprint: params.idempotencyKeyFingerprint,
|
|
147
|
+
requestFingerprint: params.requestFingerprint,
|
|
148
|
+
command
|
|
149
|
+
});
|
|
150
|
+
if (existing) {
|
|
151
|
+
const rows = await tx.select().from(provisionerTokens).where(eq(provisionerTokens.id, existing.result.provisionerTokenId)).limit(1);
|
|
152
|
+
const row = rows[0];
|
|
153
|
+
if (!row) throw new Error('Idempotent provisioner token result is missing');
|
|
154
|
+
return {
|
|
155
|
+
token: toProvisionerToken(row),
|
|
156
|
+
correlationId: existing.result.correlationId,
|
|
157
|
+
replayed: true
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
const updatedRows = await tx.update(provisionerTokens).set({
|
|
161
|
+
revokedAt: new Date(),
|
|
162
|
+
revokedByUserId: params.actorId,
|
|
163
|
+
updatedAt: new Date()
|
|
164
|
+
}).where(and(eq(provisionerTokens.id, params.tokenId), eq(provisionerTokens.scope, 'installation'), isNull(provisionerTokens.revokedAt))).returning();
|
|
165
|
+
const row = updatedRows[0];
|
|
166
|
+
if (row) await cascadeProvisionerRevocation(tx, params.tokenId);
|
|
167
|
+
const existingRows = row ? [
|
|
168
|
+
row
|
|
169
|
+
] : await tx.select().from(provisionerTokens).where(and(eq(provisionerTokens.id, params.tokenId), eq(provisionerTokens.scope, 'installation'))).limit(1);
|
|
170
|
+
const tokenRow = existingRows[0];
|
|
171
|
+
if (!tokenRow) return undefined;
|
|
172
|
+
const changed = row !== undefined;
|
|
173
|
+
const event = changed ? params.event(tokenRow.id) : undefined;
|
|
174
|
+
if (event) await writeAdministrationAction(tx, event);
|
|
175
|
+
await storeAdminCommandResult(tx, params, {
|
|
176
|
+
provisionerTokenId: tokenRow.id,
|
|
177
|
+
correlationId: event?.correlationId ?? params.correlationId
|
|
178
|
+
});
|
|
179
|
+
return {
|
|
180
|
+
token: toProvisionerToken(tokenRow),
|
|
181
|
+
correlationId: event?.correlationId ?? params.correlationId,
|
|
182
|
+
replayed: false
|
|
183
|
+
};
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
export async function listInstallationProvisionerTokens(params) {
|
|
187
|
+
const conditions = [
|
|
188
|
+
eq(provisionerTokens.scope, 'installation')
|
|
189
|
+
];
|
|
190
|
+
const cursor = timestampIdCursorWhere({
|
|
191
|
+
timestampColumn: provisionerTokens.createdAt,
|
|
192
|
+
idColumn: provisionerTokens.id,
|
|
193
|
+
cursor: params.cursor
|
|
194
|
+
});
|
|
195
|
+
if (cursor) conditions.push(cursor);
|
|
196
|
+
if (params.status === 'active') {
|
|
197
|
+
conditions.push(isNull(provisionerTokens.revokedAt));
|
|
198
|
+
const activeExpiry = or(isNull(provisionerTokens.expiresAt), gt(provisionerTokens.expiresAt, sql`now()`));
|
|
199
|
+
if (activeExpiry) conditions.push(activeExpiry);
|
|
200
|
+
}
|
|
201
|
+
if (params.status === 'expired') {
|
|
202
|
+
conditions.push(isNull(provisionerTokens.revokedAt), isNotNull(provisionerTokens.expiresAt), lte(provisionerTokens.expiresAt, sql`now()`));
|
|
203
|
+
}
|
|
204
|
+
if (params.status === 'revoked') conditions.push(isNotNull(provisionerTokens.revokedAt));
|
|
205
|
+
const rows = await db().select().from(provisionerTokens).where(and(...conditions)).orderBy(desc(provisionerTokens.createdAt), desc(provisionerTokens.id)).limit(params.limit + 1);
|
|
206
|
+
const pageRows = rows.length > params.limit ? rows.slice(0, params.limit) : rows;
|
|
207
|
+
const last = pageRows.at(-1);
|
|
208
|
+
return {
|
|
209
|
+
tokens: pageRows.map(toProvisionerToken),
|
|
210
|
+
nextCursor: rows.length > params.limit && last ? {
|
|
211
|
+
createdAt: last.createdAt,
|
|
212
|
+
id: last.id
|
|
213
|
+
} : null
|
|
214
|
+
};
|
|
215
|
+
}
|
|
62
216
|
export async function listUsableProvisionerTokensByWorkspaceId(workspaceId) {
|
|
63
217
|
const now = new Date();
|
|
64
218
|
const rows = await db().select().from(provisionerTokens).where(and(eq(provisionerTokens.workspaceId, workspaceId), eq(provisionerTokens.scope, 'workspace'), isNull(provisionerTokens.revokedAt), or(isNull(provisionerTokens.expiresAt), gt(provisionerTokens.expiresAt, now)))).orderBy(desc(provisionerTokens.createdAt), desc(provisionerTokens.id));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/db/provisioner-tokens.ts"],"sourcesContent":["import {and, desc, eq, gt, inArray, isNull, or, sql} from 'drizzle-orm';\nimport type {\n ActiveProvisionerToken,\n ProvisionerScope,\n ProvisionerToken,\n} from '#core/entities/provisioner-token.js';\nimport {db} from './db.js';\nimport {provisionerTokens, toProvisionerToken} from './schema/provisioner-tokens.js';\nimport {runnerActivationTokens} from './schema/runner-activation-tokens.js';\nimport {runnerBootstrapTokens, runnerControlSessions} from './schema/runner-control-sessions.js';\nimport {providerRunners} from './schema/runner-instances.js';\nimport {runnerSessions} from './schema/runner-sessions.js';\n\nexport interface CreateProvisionerTokenParams {\n scope: ProvisionerScope;\n workspaceId?: string | undefined;\n hashedToken: string;\n prefix: string;\n createdByUserId: string;\n name?: string | undefined;\n expiresAt?: Date | undefined;\n}\n\nasync function cascadeProvisionerRevocation(\n tx: Parameters<Parameters<ReturnType<typeof db>['transaction']>[0]>[0],\n provisionerId: string,\n) {\n await tx\n .update(runnerBootstrapTokens)\n .set({revokedAt: sql`now()`})\n .where(eq(runnerBootstrapTokens.provisionerId, provisionerId));\n await tx\n .update(runnerControlSessions)\n .set({closedAt: sql`now()`, closeReason: 'provisioner-revoked'})\n .where(\n and(\n eq(runnerControlSessions.provisionerId, provisionerId),\n isNull(runnerControlSessions.closedAt),\n ),\n );\n await tx\n .update(runnerActivationTokens)\n .set({revokedAt: sql`now()`})\n .where(\n and(\n isNull(runnerActivationTokens.consumedAt),\n isNull(runnerActivationTokens.revokedAt),\n inArray(\n runnerActivationTokens.runnerInstanceId,\n tx\n .select({id: providerRunners.id})\n .from(providerRunners)\n .where(eq(providerRunners.provisionerId, provisionerId)),\n ),\n ),\n );\n await tx\n .update(providerRunners)\n .set({state: 'terminated', terminatedAt: sql`now()`, updatedAt: sql`now()`})\n .where(\n and(\n eq(providerRunners.provisionerId, provisionerId),\n isNull(providerRunners.runnerSessionId),\n ),\n );\n await tx\n .update(runnerSessions)\n .set({revokedAt: sql`now()`})\n .where(\n and(\n eq(runnerSessions.provisionerId, provisionerId),\n eq(runnerSessions.claimsUsed, 0),\n isNull(runnerSessions.revokedAt),\n ),\n );\n}\n\nexport async function createProvisionerToken(\n params: CreateProvisionerTokenParams,\n): Promise<ProvisionerToken> {\n const rows = await db()\n .insert(provisionerTokens)\n .values({\n scope: params.scope,\n workspaceId: params.workspaceId ?? null,\n hashedToken: params.hashedToken,\n prefix: params.prefix,\n createdByUserId: params.createdByUserId,\n name: params.name ?? null,\n expiresAt: params.expiresAt ?? null,\n })\n .returning();\n\n const row = rows[0];\n if (!row) throw new Error('Insert returned no rows');\n return toProvisionerToken(row);\n}\n\nexport async function revokeInstallationProvisionerToken(params: {\n tokenId: string;\n revokedByUserId: string;\n}): Promise<ProvisionerToken | undefined> {\n const rows = await db().transaction(async (tx) => {\n const rows = await tx\n .update(provisionerTokens)\n .set({revokedAt: new Date(), revokedByUserId: params.revokedByUserId, updatedAt: new Date()})\n .where(\n and(\n eq(provisionerTokens.id, params.tokenId),\n eq(provisionerTokens.scope, 'installation'),\n isNull(provisionerTokens.revokedAt),\n ),\n )\n .returning();\n if (rows[0]) {\n await cascadeProvisionerRevocation(tx, params.tokenId);\n }\n return rows;\n });\n\n const row = rows[0];\n if (row) return toProvisionerToken(row);\n\n const existingRows = await db()\n .select()\n .from(provisionerTokens)\n .where(\n and(eq(provisionerTokens.id, params.tokenId), eq(provisionerTokens.scope, 'installation')),\n )\n .limit(1);\n const existingRow = existingRows[0];\n return existingRow ? toProvisionerToken(existingRow) : undefined;\n}\n\nexport async function listUsableProvisionerTokensByWorkspaceId(\n workspaceId: string,\n): Promise<ProvisionerToken[]> {\n const now = new Date();\n const rows = await db()\n .select()\n .from(provisionerTokens)\n .where(\n and(\n eq(provisionerTokens.workspaceId, workspaceId),\n eq(provisionerTokens.scope, 'workspace'),\n isNull(provisionerTokens.revokedAt),\n or(isNull(provisionerTokens.expiresAt), gt(provisionerTokens.expiresAt, now)),\n ),\n )\n .orderBy(desc(provisionerTokens.createdAt), desc(provisionerTokens.id));\n\n return rows.map(toProvisionerToken);\n}\n\nexport async function resolveProvisionerTokenByHash(\n hashedToken: string,\n): Promise<ProvisionerToken | undefined> {\n const rows = await db()\n .select()\n .from(provisionerTokens)\n .where(eq(provisionerTokens.hashedToken, hashedToken))\n .limit(1);\n\n const row = rows[0];\n if (!row) return undefined;\n return toProvisionerToken(row);\n}\n\nexport async function revokeProvisionerToken(params: {\n tokenId: string;\n workspaceId: string;\n revokedByUserId: string;\n}): Promise<ProvisionerToken | undefined> {\n const rows = await db().transaction(async (tx) => {\n const rows = await tx\n .update(provisionerTokens)\n .set({revokedAt: new Date(), revokedByUserId: params.revokedByUserId, updatedAt: new Date()})\n .where(\n and(\n eq(provisionerTokens.id, params.tokenId),\n eq(provisionerTokens.workspaceId, params.workspaceId),\n eq(provisionerTokens.scope, 'workspace'),\n isNull(provisionerTokens.revokedAt),\n ),\n )\n .returning();\n if (rows[0]) await cascadeProvisionerRevocation(tx, params.tokenId);\n return rows;\n });\n\n const row = rows[0];\n if (row) return toProvisionerToken(row);\n\n const existingRows = await db()\n .select()\n .from(provisionerTokens)\n .where(\n and(\n eq(provisionerTokens.id, params.tokenId),\n eq(provisionerTokens.workspaceId, params.workspaceId),\n eq(provisionerTokens.scope, 'workspace'),\n ),\n )\n .limit(1);\n\n const existingRow = existingRows[0];\n if (!existingRow) return undefined;\n return toProvisionerToken(existingRow);\n}\n\nexport async function touchProvisionerLastSeen(params: {\n tokenId: string;\n throttleSeconds: number;\n}): Promise<void> {\n await db()\n .update(provisionerTokens)\n .set({lastSeenAt: sql`now()`, updatedAt: sql`now()`})\n .where(\n and(\n eq(provisionerTokens.id, params.tokenId),\n or(\n isNull(provisionerTokens.lastSeenAt),\n sql`${provisionerTokens.lastSeenAt} < now() - (${params.throttleSeconds} || ' seconds')::interval`,\n ),\n ),\n );\n}\n\nexport async function listActiveProvisionerTokens(params: {\n workspaceId: string;\n windowSeconds: number;\n}): Promise<ActiveProvisionerToken[]> {\n const rows = await db()\n .select()\n .from(provisionerTokens)\n .where(\n and(\n eq(provisionerTokens.workspaceId, params.workspaceId),\n eq(provisionerTokens.scope, 'workspace'),\n isNull(provisionerTokens.revokedAt),\n or(isNull(provisionerTokens.expiresAt), gt(provisionerTokens.expiresAt, sql`now()`)),\n sql`${provisionerTokens.lastSeenAt} > now() - (${params.windowSeconds} || ' seconds')::interval`,\n ),\n )\n .orderBy(desc(provisionerTokens.lastSeenAt), desc(provisionerTokens.id));\n\n return rows.map(toProvisionerToken).filter((token): token is ActiveProvisionerToken => {\n return token.lastSeenAt !== null;\n });\n}\n"],"names":["and","desc","eq","gt","inArray","isNull","or","sql","db","provisionerTokens","toProvisionerToken","runnerActivationTokens","runnerBootstrapTokens","runnerControlSessions","providerRunners","runnerSessions","cascadeProvisionerRevocation","tx","provisionerId","update","set","revokedAt","where","closedAt","closeReason","consumedAt","runnerInstanceId","select","id","from","state","terminatedAt","updatedAt","runnerSessionId","claimsUsed","createProvisionerToken","params","rows","insert","values","scope","workspaceId","hashedToken","prefix","createdByUserId","name","expiresAt","returning","row","Error","revokeInstallationProvisionerToken","transaction","Date","revokedByUserId","tokenId","existingRows","limit","existingRow","undefined","listUsableProvisionerTokensByWorkspaceId","now","orderBy","createdAt","map","resolveProvisionerTokenByHash","revokeProvisionerToken","touchProvisionerLastSeen","lastSeenAt","throttleSeconds","listActiveProvisionerTokens","windowSeconds","filter","token"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/db/provisioner-tokens.ts"],"sourcesContent":["import type {\n AdministrationActionEvent,\n AdministrationActionEventMap,\n} from '@shipfox/api-common-dto';\nimport {type TimestampIdCursor, timestampIdCursorWhere} from '@shipfox/node-drizzle';\nimport {writeOutboxEvent} from '@shipfox/node-outbox';\nimport {and, desc, eq, gt, inArray, isNotNull, isNull, lte, or, type SQL, sql} from 'drizzle-orm';\nimport type {\n ActiveProvisionerToken,\n ProvisionerScope,\n ProvisionerToken,\n} from '#core/entities/provisioner-token.js';\nimport {ProvisionerAdminIdempotencyKeyReuseError} from '#core/errors.js';\nimport {db} from './db.js';\nimport {runnersAdminCommandResults} from './schema/admin-command-results.js';\nimport {runnersOutbox} from './schema/outbox.js';\nimport {provisionerTokens, toProvisionerToken} from './schema/provisioner-tokens.js';\nimport {runnerActivationTokens} from './schema/runner-activation-tokens.js';\nimport {runnerBootstrapTokens, runnerControlSessions} from './schema/runner-control-sessions.js';\nimport {providerRunners} from './schema/runner-instances.js';\nimport {runnerSessions} from './schema/runner-sessions.js';\n\ntype Tx = Parameters<Parameters<ReturnType<typeof db>['transaction']>[0]>[0];\n\nexport interface CreateProvisionerTokenParams {\n scope: ProvisionerScope;\n workspaceId?: string | undefined;\n hashedToken: string;\n prefix: string;\n createdByUserId: string;\n name?: string | undefined;\n expiresAt?: Date | undefined;\n}\n\nasync function cascadeProvisionerRevocation(tx: Tx, provisionerId: string) {\n await tx\n .update(runnerBootstrapTokens)\n .set({revokedAt: sql`now()`})\n .where(eq(runnerBootstrapTokens.provisionerId, provisionerId));\n await tx\n .update(runnerControlSessions)\n .set({closedAt: sql`now()`, closeReason: 'provisioner-revoked'})\n .where(\n and(\n eq(runnerControlSessions.provisionerId, provisionerId),\n isNull(runnerControlSessions.closedAt),\n ),\n );\n await tx\n .update(runnerActivationTokens)\n .set({revokedAt: sql`now()`})\n .where(\n and(\n isNull(runnerActivationTokens.consumedAt),\n isNull(runnerActivationTokens.revokedAt),\n inArray(\n runnerActivationTokens.runnerInstanceId,\n tx\n .select({id: providerRunners.id})\n .from(providerRunners)\n .where(eq(providerRunners.provisionerId, provisionerId)),\n ),\n ),\n );\n await tx\n .update(providerRunners)\n .set({state: 'terminated', terminatedAt: sql`now()`, updatedAt: sql`now()`})\n .where(\n and(\n eq(providerRunners.provisionerId, provisionerId),\n isNull(providerRunners.runnerSessionId),\n ),\n );\n await tx\n .update(runnerSessions)\n .set({revokedAt: sql`now()`})\n .where(\n and(\n eq(runnerSessions.provisionerId, provisionerId),\n eq(runnerSessions.claimsUsed, 0),\n isNull(runnerSessions.revokedAt),\n ),\n );\n}\n\nasync function insertProvisionerToken(tx: Tx, params: CreateProvisionerTokenParams) {\n const rows = await tx\n .insert(provisionerTokens)\n .values({\n scope: params.scope,\n workspaceId: params.workspaceId ?? null,\n hashedToken: params.hashedToken,\n prefix: params.prefix,\n createdByUserId: params.createdByUserId,\n name: params.name ?? null,\n expiresAt: params.expiresAt ?? null,\n })\n .returning();\n\n const row = rows[0];\n if (!row) throw new Error('Insert returned no rows');\n return row;\n}\n\nexport async function createProvisionerToken(\n params: CreateProvisionerTokenParams,\n): Promise<ProvisionerToken> {\n const row = await db().transaction((tx) => insertProvisionerToken(tx, params));\n return toProvisionerToken(row);\n}\n\ninterface AuditedProvisionerTokenCommandParams {\n actorId: string;\n command: string;\n correlationId: string;\n idempotencyKeyFingerprint: string;\n requestFingerprint: string;\n event: (tokenId: string) => AdministrationActionEvent;\n}\n\nasync function lockAdminCommand(\n tx: Tx,\n params: Pick<AuditedProvisionerTokenCommandParams, 'actorId' | 'idempotencyKeyFingerprint'>,\n): Promise<void> {\n await tx.execute(\n sql`select pg_advisory_xact_lock(hashtext(${`runners_admin_command:${params.actorId}:${params.idempotencyKeyFingerprint}`}))`,\n );\n}\n\nasync function loadAdminCommandResult(\n tx: Tx,\n params: Pick<\n AuditedProvisionerTokenCommandParams,\n 'actorId' | 'idempotencyKeyFingerprint' | 'requestFingerprint'\n > & {command: string},\n) {\n const rows = await tx\n .select()\n .from(runnersAdminCommandResults)\n .where(\n and(\n eq(runnersAdminCommandResults.actorId, params.actorId),\n eq(runnersAdminCommandResults.idempotencyKeyFingerprint, params.idempotencyKeyFingerprint),\n ),\n )\n .limit(1);\n const result = rows[0];\n if (!result) return undefined;\n if (\n result.command !== params.command ||\n result.requestFingerprint !== params.requestFingerprint\n ) {\n throw new ProvisionerAdminIdempotencyKeyReuseError();\n }\n return result;\n}\n\nasync function storeAdminCommandResult(\n tx: Tx,\n params: AuditedProvisionerTokenCommandParams,\n result: {provisionerTokenId: string; correlationId: string},\n): Promise<void> {\n await tx.insert(runnersAdminCommandResults).values({\n actorId: params.actorId,\n idempotencyKeyFingerprint: params.idempotencyKeyFingerprint,\n command: params.command,\n requestFingerprint: params.requestFingerprint,\n result,\n });\n}\n\nasync function writeAdministrationAction(tx: Tx, event: AdministrationActionEvent): Promise<void> {\n await writeOutboxEvent<AdministrationActionEventMap>(tx, runnersOutbox, {\n type: 'administration.action.performed',\n payload: event,\n });\n}\n\nexport async function createInstallationProvisionerTokenWithAudit(\n params: AuditedProvisionerTokenCommandParams & {\n name?: string | undefined;\n ttlSeconds?: number | undefined;\n hashedToken: string;\n prefix: string;\n },\n): Promise<{token: ProvisionerToken; correlationId: string; replayed: boolean}> {\n const command = 'runners.provisioner_token.create';\n return await db().transaction(async (tx) => {\n await lockAdminCommand(tx, params);\n const existing = await loadAdminCommandResult(tx, {\n actorId: params.actorId,\n idempotencyKeyFingerprint: params.idempotencyKeyFingerprint,\n requestFingerprint: params.requestFingerprint,\n command,\n });\n if (existing) {\n const rows = await tx\n .select()\n .from(provisionerTokens)\n .where(eq(provisionerTokens.id, existing.result.provisionerTokenId))\n .limit(1);\n const row = rows[0];\n if (!row) throw new Error('Idempotent provisioner token result is missing');\n return {\n token: toProvisionerToken(row),\n correlationId: existing.result.correlationId,\n replayed: true,\n };\n }\n\n const expiresAt =\n params.ttlSeconds === undefined ? undefined : new Date(Date.now() + params.ttlSeconds * 1000);\n const row = await insertProvisionerToken(tx, {\n scope: 'installation',\n hashedToken: params.hashedToken,\n prefix: params.prefix,\n createdByUserId: params.actorId,\n name: params.name,\n expiresAt,\n });\n const event = params.event(row.id);\n await writeAdministrationAction(tx, event);\n await storeAdminCommandResult(tx, params, {\n provisionerTokenId: row.id,\n correlationId: event.correlationId,\n });\n return {token: toProvisionerToken(row), correlationId: event.correlationId, replayed: false};\n });\n}\n\nexport async function revokeInstallationProvisionerToken(params: {\n tokenId: string;\n revokedByUserId: string;\n}): Promise<ProvisionerToken | undefined> {\n const rows = await db().transaction(async (tx) => {\n const rows = await tx\n .update(provisionerTokens)\n .set({revokedAt: new Date(), revokedByUserId: params.revokedByUserId, updatedAt: new Date()})\n .where(\n and(\n eq(provisionerTokens.id, params.tokenId),\n eq(provisionerTokens.scope, 'installation'),\n isNull(provisionerTokens.revokedAt),\n ),\n )\n .returning();\n if (rows[0]) {\n await cascadeProvisionerRevocation(tx, params.tokenId);\n }\n return rows;\n });\n\n const row = rows[0];\n if (row) return toProvisionerToken(row);\n\n const existingRows = await db()\n .select()\n .from(provisionerTokens)\n .where(\n and(eq(provisionerTokens.id, params.tokenId), eq(provisionerTokens.scope, 'installation')),\n )\n .limit(1);\n const existingRow = existingRows[0];\n return existingRow ? toProvisionerToken(existingRow) : undefined;\n}\n\nexport async function revokeInstallationProvisionerTokenWithAudit(\n params: AuditedProvisionerTokenCommandParams & {tokenId: string},\n): Promise<{token: ProvisionerToken; correlationId: string; replayed: boolean} | undefined> {\n const command = 'runners.provisioner_token.revoke';\n return await db().transaction(async (tx) => {\n await lockAdminCommand(tx, params);\n const existing = await loadAdminCommandResult(tx, {\n actorId: params.actorId,\n idempotencyKeyFingerprint: params.idempotencyKeyFingerprint,\n requestFingerprint: params.requestFingerprint,\n command,\n });\n if (existing) {\n const rows = await tx\n .select()\n .from(provisionerTokens)\n .where(eq(provisionerTokens.id, existing.result.provisionerTokenId))\n .limit(1);\n const row = rows[0];\n if (!row) throw new Error('Idempotent provisioner token result is missing');\n return {\n token: toProvisionerToken(row),\n correlationId: existing.result.correlationId,\n replayed: true,\n };\n }\n\n const updatedRows = await tx\n .update(provisionerTokens)\n .set({revokedAt: new Date(), revokedByUserId: params.actorId, updatedAt: new Date()})\n .where(\n and(\n eq(provisionerTokens.id, params.tokenId),\n eq(provisionerTokens.scope, 'installation'),\n isNull(provisionerTokens.revokedAt),\n ),\n )\n .returning();\n const row = updatedRows[0];\n if (row) await cascadeProvisionerRevocation(tx, params.tokenId);\n\n const existingRows = row\n ? [row]\n : await tx\n .select()\n .from(provisionerTokens)\n .where(\n and(\n eq(provisionerTokens.id, params.tokenId),\n eq(provisionerTokens.scope, 'installation'),\n ),\n )\n .limit(1);\n const tokenRow = existingRows[0];\n if (!tokenRow) return undefined;\n\n const changed = row !== undefined;\n const event = changed ? params.event(tokenRow.id) : undefined;\n if (event) await writeAdministrationAction(tx, event);\n await storeAdminCommandResult(tx, params, {\n provisionerTokenId: tokenRow.id,\n correlationId: event?.correlationId ?? params.correlationId,\n });\n return {\n token: toProvisionerToken(tokenRow),\n correlationId: event?.correlationId ?? params.correlationId,\n replayed: false,\n };\n });\n}\n\nexport type InstallationProvisionerTokenStatus = 'active' | 'expired' | 'revoked';\n\nexport interface ListInstallationProvisionerTokensParams {\n limit: number;\n cursor?: TimestampIdCursor | undefined;\n status?: InstallationProvisionerTokenStatus | undefined;\n}\n\nexport async function listInstallationProvisionerTokens(\n params: ListInstallationProvisionerTokensParams,\n): Promise<{tokens: ProvisionerToken[]; nextCursor: TimestampIdCursor | null}> {\n const conditions: SQL[] = [eq(provisionerTokens.scope, 'installation')];\n const cursor = timestampIdCursorWhere({\n timestampColumn: provisionerTokens.createdAt,\n idColumn: provisionerTokens.id,\n cursor: params.cursor,\n });\n if (cursor) conditions.push(cursor);\n\n if (params.status === 'active') {\n conditions.push(isNull(provisionerTokens.revokedAt));\n const activeExpiry = or(\n isNull(provisionerTokens.expiresAt),\n gt(provisionerTokens.expiresAt, sql`now()`),\n );\n if (activeExpiry) conditions.push(activeExpiry);\n }\n if (params.status === 'expired') {\n conditions.push(\n isNull(provisionerTokens.revokedAt),\n isNotNull(provisionerTokens.expiresAt),\n lte(provisionerTokens.expiresAt, sql`now()`),\n );\n }\n if (params.status === 'revoked') conditions.push(isNotNull(provisionerTokens.revokedAt));\n\n const rows = await db()\n .select()\n .from(provisionerTokens)\n .where(and(...conditions))\n .orderBy(desc(provisionerTokens.createdAt), desc(provisionerTokens.id))\n .limit(params.limit + 1);\n const pageRows = rows.length > params.limit ? rows.slice(0, params.limit) : rows;\n const last = pageRows.at(-1);\n return {\n tokens: pageRows.map(toProvisionerToken),\n nextCursor:\n rows.length > params.limit && last ? {createdAt: last.createdAt, id: last.id} : null,\n };\n}\n\nexport async function listUsableProvisionerTokensByWorkspaceId(\n workspaceId: string,\n): Promise<ProvisionerToken[]> {\n const now = new Date();\n const rows = await db()\n .select()\n .from(provisionerTokens)\n .where(\n and(\n eq(provisionerTokens.workspaceId, workspaceId),\n eq(provisionerTokens.scope, 'workspace'),\n isNull(provisionerTokens.revokedAt),\n or(isNull(provisionerTokens.expiresAt), gt(provisionerTokens.expiresAt, now)),\n ),\n )\n .orderBy(desc(provisionerTokens.createdAt), desc(provisionerTokens.id));\n\n return rows.map(toProvisionerToken);\n}\n\nexport async function resolveProvisionerTokenByHash(\n hashedToken: string,\n): Promise<ProvisionerToken | undefined> {\n const rows = await db()\n .select()\n .from(provisionerTokens)\n .where(eq(provisionerTokens.hashedToken, hashedToken))\n .limit(1);\n\n const row = rows[0];\n if (!row) return undefined;\n return toProvisionerToken(row);\n}\n\nexport async function revokeProvisionerToken(params: {\n tokenId: string;\n workspaceId: string;\n revokedByUserId: string;\n}): Promise<ProvisionerToken | undefined> {\n const rows = await db().transaction(async (tx) => {\n const rows = await tx\n .update(provisionerTokens)\n .set({revokedAt: new Date(), revokedByUserId: params.revokedByUserId, updatedAt: new Date()})\n .where(\n and(\n eq(provisionerTokens.id, params.tokenId),\n eq(provisionerTokens.workspaceId, params.workspaceId),\n eq(provisionerTokens.scope, 'workspace'),\n isNull(provisionerTokens.revokedAt),\n ),\n )\n .returning();\n if (rows[0]) await cascadeProvisionerRevocation(tx, params.tokenId);\n return rows;\n });\n\n const row = rows[0];\n if (row) return toProvisionerToken(row);\n\n const existingRows = await db()\n .select()\n .from(provisionerTokens)\n .where(\n and(\n eq(provisionerTokens.id, params.tokenId),\n eq(provisionerTokens.workspaceId, params.workspaceId),\n eq(provisionerTokens.scope, 'workspace'),\n ),\n )\n .limit(1);\n\n const existingRow = existingRows[0];\n if (!existingRow) return undefined;\n return toProvisionerToken(existingRow);\n}\n\nexport async function touchProvisionerLastSeen(params: {\n tokenId: string;\n throttleSeconds: number;\n}): Promise<void> {\n await db()\n .update(provisionerTokens)\n .set({lastSeenAt: sql`now()`, updatedAt: sql`now()`})\n .where(\n and(\n eq(provisionerTokens.id, params.tokenId),\n or(\n isNull(provisionerTokens.lastSeenAt),\n sql`${provisionerTokens.lastSeenAt} < now() - (${params.throttleSeconds} || ' seconds')::interval`,\n ),\n ),\n );\n}\n\nexport async function listActiveProvisionerTokens(params: {\n workspaceId: string;\n windowSeconds: number;\n}): Promise<ActiveProvisionerToken[]> {\n const rows = await db()\n .select()\n .from(provisionerTokens)\n .where(\n and(\n eq(provisionerTokens.workspaceId, params.workspaceId),\n eq(provisionerTokens.scope, 'workspace'),\n isNull(provisionerTokens.revokedAt),\n or(isNull(provisionerTokens.expiresAt), gt(provisionerTokens.expiresAt, sql`now()`)),\n sql`${provisionerTokens.lastSeenAt} > now() - (${params.windowSeconds} || ' seconds')::interval`,\n ),\n )\n .orderBy(desc(provisionerTokens.lastSeenAt), desc(provisionerTokens.id));\n\n return rows.map(toProvisionerToken).filter((token): token is ActiveProvisionerToken => {\n return token.lastSeenAt !== null;\n });\n}\n"],"names":["timestampIdCursorWhere","writeOutboxEvent","and","desc","eq","gt","inArray","isNotNull","isNull","lte","or","sql","ProvisionerAdminIdempotencyKeyReuseError","db","runnersAdminCommandResults","runnersOutbox","provisionerTokens","toProvisionerToken","runnerActivationTokens","runnerBootstrapTokens","runnerControlSessions","providerRunners","runnerSessions","cascadeProvisionerRevocation","tx","provisionerId","update","set","revokedAt","where","closedAt","closeReason","consumedAt","runnerInstanceId","select","id","from","state","terminatedAt","updatedAt","runnerSessionId","claimsUsed","insertProvisionerToken","params","rows","insert","values","scope","workspaceId","hashedToken","prefix","createdByUserId","name","expiresAt","returning","row","Error","createProvisionerToken","transaction","lockAdminCommand","execute","actorId","idempotencyKeyFingerprint","loadAdminCommandResult","limit","result","undefined","command","requestFingerprint","storeAdminCommandResult","writeAdministrationAction","event","type","payload","createInstallationProvisionerTokenWithAudit","existing","provisionerTokenId","token","correlationId","replayed","ttlSeconds","Date","now","revokeInstallationProvisionerToken","revokedByUserId","tokenId","existingRows","existingRow","revokeInstallationProvisionerTokenWithAudit","updatedRows","tokenRow","changed","listInstallationProvisionerTokens","conditions","cursor","timestampColumn","createdAt","idColumn","push","status","activeExpiry","orderBy","pageRows","length","slice","last","at","tokens","map","nextCursor","listUsableProvisionerTokensByWorkspaceId","resolveProvisionerTokenByHash","revokeProvisionerToken","touchProvisionerLastSeen","lastSeenAt","throttleSeconds","listActiveProvisionerTokens","windowSeconds","filter"],"mappings":"AAIA,SAAgCA,sBAAsB,QAAO,wBAAwB;AACrF,SAAQC,gBAAgB,QAAO,uBAAuB;AACtD,SAAQC,GAAG,EAAEC,IAAI,EAAEC,EAAE,EAAEC,EAAE,EAAEC,OAAO,EAAEC,SAAS,EAAEC,MAAM,EAAEC,GAAG,EAAEC,EAAE,EAAYC,GAAG,QAAO,cAAc;AAMlG,SAAQC,wCAAwC,QAAO,kBAAkB;AACzE,SAAQC,EAAE,QAAO,UAAU;AAC3B,SAAQC,0BAA0B,QAAO,oCAAoC;AAC7E,SAAQC,aAAa,QAAO,qBAAqB;AACjD,SAAQC,iBAAiB,EAAEC,kBAAkB,QAAO,iCAAiC;AACrF,SAAQC,sBAAsB,QAAO,uCAAuC;AAC5E,SAAQC,qBAAqB,EAAEC,qBAAqB,QAAO,sCAAsC;AACjG,SAAQC,eAAe,QAAO,+BAA+B;AAC7D,SAAQC,cAAc,QAAO,8BAA8B;AAc3D,eAAeC,6BAA6BC,EAAM,EAAEC,aAAqB;IACvE,MAAMD,GACHE,MAAM,CAACP,uBACPQ,GAAG,CAAC;QAACC,WAAWjB,GAAG,CAAC,KAAK,CAAC;IAAA,GAC1BkB,KAAK,CAACzB,GAAGe,sBAAsBM,aAAa,EAAEA;IACjD,MAAMD,GACHE,MAAM,CAACN,uBACPO,GAAG,CAAC;QAACG,UAAUnB,GAAG,CAAC,KAAK,CAAC;QAAEoB,aAAa;IAAqB,GAC7DF,KAAK,CACJ3B,IACEE,GAAGgB,sBAAsBK,aAAa,EAAEA,gBACxCjB,OAAOY,sBAAsBU,QAAQ;IAG3C,MAAMN,GACHE,MAAM,CAACR,wBACPS,GAAG,CAAC;QAACC,WAAWjB,GAAG,CAAC,KAAK,CAAC;IAAA,GAC1BkB,KAAK,CACJ3B,IACEM,OAAOU,uBAAuBc,UAAU,GACxCxB,OAAOU,uBAAuBU,SAAS,GACvCtB,QACEY,uBAAuBe,gBAAgB,EACvCT,GACGU,MAAM,CAAC;QAACC,IAAId,gBAAgBc,EAAE;IAAA,GAC9BC,IAAI,CAACf,iBACLQ,KAAK,CAACzB,GAAGiB,gBAAgBI,aAAa,EAAEA;IAInD,MAAMD,GACHE,MAAM,CAACL,iBACPM,GAAG,CAAC;QAACU,OAAO;QAAcC,cAAc3B,GAAG,CAAC,KAAK,CAAC;QAAE4B,WAAW5B,GAAG,CAAC,KAAK,CAAC;IAAA,GACzEkB,KAAK,CACJ3B,IACEE,GAAGiB,gBAAgBI,aAAa,EAAEA,gBAClCjB,OAAOa,gBAAgBmB,eAAe;IAG5C,MAAMhB,GACHE,MAAM,CAACJ,gBACPK,GAAG,CAAC;QAACC,WAAWjB,GAAG,CAAC,KAAK,CAAC;IAAA,GAC1BkB,KAAK,CACJ3B,IACEE,GAAGkB,eAAeG,aAAa,EAAEA,gBACjCrB,GAAGkB,eAAemB,UAAU,EAAE,IAC9BjC,OAAOc,eAAeM,SAAS;AAGvC;AAEA,eAAec,uBAAuBlB,EAAM,EAAEmB,MAAoC;IAChF,MAAMC,OAAO,MAAMpB,GAChBqB,MAAM,CAAC7B,mBACP8B,MAAM,CAAC;QACNC,OAAOJ,OAAOI,KAAK;QACnBC,aAAaL,OAAOK,WAAW,IAAI;QACnCC,aAAaN,OAAOM,WAAW;QAC/BC,QAAQP,OAAOO,MAAM;QACrBC,iBAAiBR,OAAOQ,eAAe;QACvCC,MAAMT,OAAOS,IAAI,IAAI;QACrBC,WAAWV,OAAOU,SAAS,IAAI;IACjC,GACCC,SAAS;IAEZ,MAAMC,MAAMX,IAAI,CAAC,EAAE;IACnB,IAAI,CAACW,KAAK,MAAM,IAAIC,MAAM;IAC1B,OAAOD;AACT;AAEA,OAAO,eAAeE,uBACpBd,MAAoC;IAEpC,MAAMY,MAAM,MAAM1C,KAAK6C,WAAW,CAAC,CAAClC,KAAOkB,uBAAuBlB,IAAImB;IACtE,OAAO1B,mBAAmBsC;AAC5B;AAWA,eAAeI,iBACbnC,EAAM,EACNmB,MAA2F;IAE3F,MAAMnB,GAAGoC,OAAO,CACdjD,GAAG,CAAC,sCAAsC,EAAE,CAAC,sBAAsB,EAAEgC,OAAOkB,OAAO,CAAC,CAAC,EAAElB,OAAOmB,yBAAyB,EAAE,CAAC,EAAE,CAAC;AAEjI;AAEA,eAAeC,uBACbvC,EAAM,EACNmB,MAGqB;IAErB,MAAMC,OAAO,MAAMpB,GAChBU,MAAM,GACNE,IAAI,CAACtB,4BACLe,KAAK,CACJ3B,IACEE,GAAGU,2BAA2B+C,OAAO,EAAElB,OAAOkB,OAAO,GACrDzD,GAAGU,2BAA2BgD,yBAAyB,EAAEnB,OAAOmB,yBAAyB,IAG5FE,KAAK,CAAC;IACT,MAAMC,SAASrB,IAAI,CAAC,EAAE;IACtB,IAAI,CAACqB,QAAQ,OAAOC;IACpB,IACED,OAAOE,OAAO,KAAKxB,OAAOwB,OAAO,IACjCF,OAAOG,kBAAkB,KAAKzB,OAAOyB,kBAAkB,EACvD;QACA,MAAM,IAAIxD;IACZ;IACA,OAAOqD;AACT;AAEA,eAAeI,wBACb7C,EAAM,EACNmB,MAA4C,EAC5CsB,MAA2D;IAE3D,MAAMzC,GAAGqB,MAAM,CAAC/B,4BAA4BgC,MAAM,CAAC;QACjDe,SAASlB,OAAOkB,OAAO;QACvBC,2BAA2BnB,OAAOmB,yBAAyB;QAC3DK,SAASxB,OAAOwB,OAAO;QACvBC,oBAAoBzB,OAAOyB,kBAAkB;QAC7CH;IACF;AACF;AAEA,eAAeK,0BAA0B9C,EAAM,EAAE+C,KAAgC;IAC/E,MAAMtE,iBAA+CuB,IAAIT,eAAe;QACtEyD,MAAM;QACNC,SAASF;IACX;AACF;AAEA,OAAO,eAAeG,4CACpB/B,MAKC;IAED,MAAMwB,UAAU;IAChB,OAAO,MAAMtD,KAAK6C,WAAW,CAAC,OAAOlC;QACnC,MAAMmC,iBAAiBnC,IAAImB;QAC3B,MAAMgC,WAAW,MAAMZ,uBAAuBvC,IAAI;YAChDqC,SAASlB,OAAOkB,OAAO;YACvBC,2BAA2BnB,OAAOmB,yBAAyB;YAC3DM,oBAAoBzB,OAAOyB,kBAAkB;YAC7CD;QACF;QACA,IAAIQ,UAAU;YACZ,MAAM/B,OAAO,MAAMpB,GAChBU,MAAM,GACNE,IAAI,CAACpB,mBACLa,KAAK,CAACzB,GAAGY,kBAAkBmB,EAAE,EAAEwC,SAASV,MAAM,CAACW,kBAAkB,GACjEZ,KAAK,CAAC;YACT,MAAMT,MAAMX,IAAI,CAAC,EAAE;YACnB,IAAI,CAACW,KAAK,MAAM,IAAIC,MAAM;YAC1B,OAAO;gBACLqB,OAAO5D,mBAAmBsC;gBAC1BuB,eAAeH,SAASV,MAAM,CAACa,aAAa;gBAC5CC,UAAU;YACZ;QACF;QAEA,MAAM1B,YACJV,OAAOqC,UAAU,KAAKd,YAAYA,YAAY,IAAIe,KAAKA,KAAKC,GAAG,KAAKvC,OAAOqC,UAAU,GAAG;QAC1F,MAAMzB,MAAM,MAAMb,uBAAuBlB,IAAI;YAC3CuB,OAAO;YACPE,aAAaN,OAAOM,WAAW;YAC/BC,QAAQP,OAAOO,MAAM;YACrBC,iBAAiBR,OAAOkB,OAAO;YAC/BT,MAAMT,OAAOS,IAAI;YACjBC;QACF;QACA,MAAMkB,QAAQ5B,OAAO4B,KAAK,CAAChB,IAAIpB,EAAE;QACjC,MAAMmC,0BAA0B9C,IAAI+C;QACpC,MAAMF,wBAAwB7C,IAAImB,QAAQ;YACxCiC,oBAAoBrB,IAAIpB,EAAE;YAC1B2C,eAAeP,MAAMO,aAAa;QACpC;QACA,OAAO;YAACD,OAAO5D,mBAAmBsC;YAAMuB,eAAeP,MAAMO,aAAa;YAAEC,UAAU;QAAK;IAC7F;AACF;AAEA,OAAO,eAAeI,mCAAmCxC,MAGxD;IACC,MAAMC,OAAO,MAAM/B,KAAK6C,WAAW,CAAC,OAAOlC;QACzC,MAAMoB,OAAO,MAAMpB,GAChBE,MAAM,CAACV,mBACPW,GAAG,CAAC;YAACC,WAAW,IAAIqD;YAAQG,iBAAiBzC,OAAOyC,eAAe;YAAE7C,WAAW,IAAI0C;QAAM,GAC1FpD,KAAK,CACJ3B,IACEE,GAAGY,kBAAkBmB,EAAE,EAAEQ,OAAO0C,OAAO,GACvCjF,GAAGY,kBAAkB+B,KAAK,EAAE,iBAC5BvC,OAAOQ,kBAAkBY,SAAS,IAGrC0B,SAAS;QACZ,IAAIV,IAAI,CAAC,EAAE,EAAE;YACX,MAAMrB,6BAA6BC,IAAImB,OAAO0C,OAAO;QACvD;QACA,OAAOzC;IACT;IAEA,MAAMW,MAAMX,IAAI,CAAC,EAAE;IACnB,IAAIW,KAAK,OAAOtC,mBAAmBsC;IAEnC,MAAM+B,eAAe,MAAMzE,KACxBqB,MAAM,GACNE,IAAI,CAACpB,mBACLa,KAAK,CACJ3B,IAAIE,GAAGY,kBAAkBmB,EAAE,EAAEQ,OAAO0C,OAAO,GAAGjF,GAAGY,kBAAkB+B,KAAK,EAAE,kBAE3EiB,KAAK,CAAC;IACT,MAAMuB,cAAcD,YAAY,CAAC,EAAE;IACnC,OAAOC,cAActE,mBAAmBsE,eAAerB;AACzD;AAEA,OAAO,eAAesB,4CACpB7C,MAAgE;IAEhE,MAAMwB,UAAU;IAChB,OAAO,MAAMtD,KAAK6C,WAAW,CAAC,OAAOlC;QACnC,MAAMmC,iBAAiBnC,IAAImB;QAC3B,MAAMgC,WAAW,MAAMZ,uBAAuBvC,IAAI;YAChDqC,SAASlB,OAAOkB,OAAO;YACvBC,2BAA2BnB,OAAOmB,yBAAyB;YAC3DM,oBAAoBzB,OAAOyB,kBAAkB;YAC7CD;QACF;QACA,IAAIQ,UAAU;YACZ,MAAM/B,OAAO,MAAMpB,GAChBU,MAAM,GACNE,IAAI,CAACpB,mBACLa,KAAK,CAACzB,GAAGY,kBAAkBmB,EAAE,EAAEwC,SAASV,MAAM,CAACW,kBAAkB,GACjEZ,KAAK,CAAC;YACT,MAAMT,MAAMX,IAAI,CAAC,EAAE;YACnB,IAAI,CAACW,KAAK,MAAM,IAAIC,MAAM;YAC1B,OAAO;gBACLqB,OAAO5D,mBAAmBsC;gBAC1BuB,eAAeH,SAASV,MAAM,CAACa,aAAa;gBAC5CC,UAAU;YACZ;QACF;QAEA,MAAMU,cAAc,MAAMjE,GACvBE,MAAM,CAACV,mBACPW,GAAG,CAAC;YAACC,WAAW,IAAIqD;YAAQG,iBAAiBzC,OAAOkB,OAAO;YAAEtB,WAAW,IAAI0C;QAAM,GAClFpD,KAAK,CACJ3B,IACEE,GAAGY,kBAAkBmB,EAAE,EAAEQ,OAAO0C,OAAO,GACvCjF,GAAGY,kBAAkB+B,KAAK,EAAE,iBAC5BvC,OAAOQ,kBAAkBY,SAAS,IAGrC0B,SAAS;QACZ,MAAMC,MAAMkC,WAAW,CAAC,EAAE;QAC1B,IAAIlC,KAAK,MAAMhC,6BAA6BC,IAAImB,OAAO0C,OAAO;QAE9D,MAAMC,eAAe/B,MACjB;YAACA;SAAI,GACL,MAAM/B,GACHU,MAAM,GACNE,IAAI,CAACpB,mBACLa,KAAK,CACJ3B,IACEE,GAAGY,kBAAkBmB,EAAE,EAAEQ,OAAO0C,OAAO,GACvCjF,GAAGY,kBAAkB+B,KAAK,EAAE,kBAG/BiB,KAAK,CAAC;QACb,MAAM0B,WAAWJ,YAAY,CAAC,EAAE;QAChC,IAAI,CAACI,UAAU,OAAOxB;QAEtB,MAAMyB,UAAUpC,QAAQW;QACxB,MAAMK,QAAQoB,UAAUhD,OAAO4B,KAAK,CAACmB,SAASvD,EAAE,IAAI+B;QACpD,IAAIK,OAAO,MAAMD,0BAA0B9C,IAAI+C;QAC/C,MAAMF,wBAAwB7C,IAAImB,QAAQ;YACxCiC,oBAAoBc,SAASvD,EAAE;YAC/B2C,eAAeP,OAAOO,iBAAiBnC,OAAOmC,aAAa;QAC7D;QACA,OAAO;YACLD,OAAO5D,mBAAmByE;YAC1BZ,eAAeP,OAAOO,iBAAiBnC,OAAOmC,aAAa;YAC3DC,UAAU;QACZ;IACF;AACF;AAUA,OAAO,eAAea,kCACpBjD,MAA+C;IAE/C,MAAMkD,aAAoB;QAACzF,GAAGY,kBAAkB+B,KAAK,EAAE;KAAgB;IACvE,MAAM+C,SAAS9F,uBAAuB;QACpC+F,iBAAiB/E,kBAAkBgF,SAAS;QAC5CC,UAAUjF,kBAAkBmB,EAAE;QAC9B2D,QAAQnD,OAAOmD,MAAM;IACvB;IACA,IAAIA,QAAQD,WAAWK,IAAI,CAACJ;IAE5B,IAAInD,OAAOwD,MAAM,KAAK,UAAU;QAC9BN,WAAWK,IAAI,CAAC1F,OAAOQ,kBAAkBY,SAAS;QAClD,MAAMwE,eAAe1F,GACnBF,OAAOQ,kBAAkBqC,SAAS,GAClChD,GAAGW,kBAAkBqC,SAAS,EAAE1C,GAAG,CAAC,KAAK,CAAC;QAE5C,IAAIyF,cAAcP,WAAWK,IAAI,CAACE;IACpC;IACA,IAAIzD,OAAOwD,MAAM,KAAK,WAAW;QAC/BN,WAAWK,IAAI,CACb1F,OAAOQ,kBAAkBY,SAAS,GAClCrB,UAAUS,kBAAkBqC,SAAS,GACrC5C,IAAIO,kBAAkBqC,SAAS,EAAE1C,GAAG,CAAC,KAAK,CAAC;IAE/C;IACA,IAAIgC,OAAOwD,MAAM,KAAK,WAAWN,WAAWK,IAAI,CAAC3F,UAAUS,kBAAkBY,SAAS;IAEtF,MAAMgB,OAAO,MAAM/B,KAChBqB,MAAM,GACNE,IAAI,CAACpB,mBACLa,KAAK,CAAC3B,OAAO2F,aACbQ,OAAO,CAAClG,KAAKa,kBAAkBgF,SAAS,GAAG7F,KAAKa,kBAAkBmB,EAAE,GACpE6B,KAAK,CAACrB,OAAOqB,KAAK,GAAG;IACxB,MAAMsC,WAAW1D,KAAK2D,MAAM,GAAG5D,OAAOqB,KAAK,GAAGpB,KAAK4D,KAAK,CAAC,GAAG7D,OAAOqB,KAAK,IAAIpB;IAC5E,MAAM6D,OAAOH,SAASI,EAAE,CAAC,CAAC;IAC1B,OAAO;QACLC,QAAQL,SAASM,GAAG,CAAC3F;QACrB4F,YACEjE,KAAK2D,MAAM,GAAG5D,OAAOqB,KAAK,IAAIyC,OAAO;YAACT,WAAWS,KAAKT,SAAS;YAAE7D,IAAIsE,KAAKtE,EAAE;QAAA,IAAI;IACpF;AACF;AAEA,OAAO,eAAe2E,yCACpB9D,WAAmB;IAEnB,MAAMkC,MAAM,IAAID;IAChB,MAAMrC,OAAO,MAAM/B,KAChBqB,MAAM,GACNE,IAAI,CAACpB,mBACLa,KAAK,CACJ3B,IACEE,GAAGY,kBAAkBgC,WAAW,EAAEA,cAClC5C,GAAGY,kBAAkB+B,KAAK,EAAE,cAC5BvC,OAAOQ,kBAAkBY,SAAS,GAClClB,GAAGF,OAAOQ,kBAAkBqC,SAAS,GAAGhD,GAAGW,kBAAkBqC,SAAS,EAAE6B,QAG3EmB,OAAO,CAAClG,KAAKa,kBAAkBgF,SAAS,GAAG7F,KAAKa,kBAAkBmB,EAAE;IAEvE,OAAOS,KAAKgE,GAAG,CAAC3F;AAClB;AAEA,OAAO,eAAe8F,8BACpB9D,WAAmB;IAEnB,MAAML,OAAO,MAAM/B,KAChBqB,MAAM,GACNE,IAAI,CAACpB,mBACLa,KAAK,CAACzB,GAAGY,kBAAkBiC,WAAW,EAAEA,cACxCe,KAAK,CAAC;IAET,MAAMT,MAAMX,IAAI,CAAC,EAAE;IACnB,IAAI,CAACW,KAAK,OAAOW;IACjB,OAAOjD,mBAAmBsC;AAC5B;AAEA,OAAO,eAAeyD,uBAAuBrE,MAI5C;IACC,MAAMC,OAAO,MAAM/B,KAAK6C,WAAW,CAAC,OAAOlC;QACzC,MAAMoB,OAAO,MAAMpB,GAChBE,MAAM,CAACV,mBACPW,GAAG,CAAC;YAACC,WAAW,IAAIqD;YAAQG,iBAAiBzC,OAAOyC,eAAe;YAAE7C,WAAW,IAAI0C;QAAM,GAC1FpD,KAAK,CACJ3B,IACEE,GAAGY,kBAAkBmB,EAAE,EAAEQ,OAAO0C,OAAO,GACvCjF,GAAGY,kBAAkBgC,WAAW,EAAEL,OAAOK,WAAW,GACpD5C,GAAGY,kBAAkB+B,KAAK,EAAE,cAC5BvC,OAAOQ,kBAAkBY,SAAS,IAGrC0B,SAAS;QACZ,IAAIV,IAAI,CAAC,EAAE,EAAE,MAAMrB,6BAA6BC,IAAImB,OAAO0C,OAAO;QAClE,OAAOzC;IACT;IAEA,MAAMW,MAAMX,IAAI,CAAC,EAAE;IACnB,IAAIW,KAAK,OAAOtC,mBAAmBsC;IAEnC,MAAM+B,eAAe,MAAMzE,KACxBqB,MAAM,GACNE,IAAI,CAACpB,mBACLa,KAAK,CACJ3B,IACEE,GAAGY,kBAAkBmB,EAAE,EAAEQ,OAAO0C,OAAO,GACvCjF,GAAGY,kBAAkBgC,WAAW,EAAEL,OAAOK,WAAW,GACpD5C,GAAGY,kBAAkB+B,KAAK,EAAE,eAG/BiB,KAAK,CAAC;IAET,MAAMuB,cAAcD,YAAY,CAAC,EAAE;IACnC,IAAI,CAACC,aAAa,OAAOrB;IACzB,OAAOjD,mBAAmBsE;AAC5B;AAEA,OAAO,eAAe0B,yBAAyBtE,MAG9C;IACC,MAAM9B,KACHa,MAAM,CAACV,mBACPW,GAAG,CAAC;QAACuF,YAAYvG,GAAG,CAAC,KAAK,CAAC;QAAE4B,WAAW5B,GAAG,CAAC,KAAK,CAAC;IAAA,GAClDkB,KAAK,CACJ3B,IACEE,GAAGY,kBAAkBmB,EAAE,EAAEQ,OAAO0C,OAAO,GACvC3E,GACEF,OAAOQ,kBAAkBkG,UAAU,GACnCvG,GAAG,CAAC,EAAEK,kBAAkBkG,UAAU,CAAC,YAAY,EAAEvE,OAAOwE,eAAe,CAAC,yBAAyB,CAAC;AAI5G;AAEA,OAAO,eAAeC,4BAA4BzE,MAGjD;IACC,MAAMC,OAAO,MAAM/B,KAChBqB,MAAM,GACNE,IAAI,CAACpB,mBACLa,KAAK,CACJ3B,IACEE,GAAGY,kBAAkBgC,WAAW,EAAEL,OAAOK,WAAW,GACpD5C,GAAGY,kBAAkB+B,KAAK,EAAE,cAC5BvC,OAAOQ,kBAAkBY,SAAS,GAClClB,GAAGF,OAAOQ,kBAAkBqC,SAAS,GAAGhD,GAAGW,kBAAkBqC,SAAS,EAAE1C,GAAG,CAAC,KAAK,CAAC,IAClFA,GAAG,CAAC,EAAEK,kBAAkBkG,UAAU,CAAC,YAAY,EAAEvE,OAAO0E,aAAa,CAAC,yBAAyB,CAAC,GAGnGhB,OAAO,CAAClG,KAAKa,kBAAkBkG,UAAU,GAAG/G,KAAKa,kBAAkBmB,EAAE;IAExE,OAAOS,KAAKgE,GAAG,CAAC3F,oBAAoBqG,MAAM,CAAC,CAACzC;QAC1C,OAAOA,MAAMqC,UAAU,KAAK;IAC9B;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reservations.d.ts","sourceRoot":"","sources":["../../src/db/reservations.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"reservations.d.ts","sourceRoot":"","sources":["../../src/db/reservations.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,SAAS,CAAC;AAUhC,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,0BAA0B;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,mBAAmB,EAAE,CAAC;IACjC,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,MAAM,WAAW,sCAAsC;IACrD,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,mBAAmB,EAAE,CAAC;IACjC,uBAAuB,EAAE,MAAM,CAAC;IAChC,oBAAoB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC1C,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,cAAc,CAAC,EAAE,CAAC,YAAY,EAAE,gBAAgB,EAAE,KAAK,IAAI,CAAC;CAC7D;AAcD,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,0BAA0B,GACjC,OAAO,CAAC;IAAC,KAAK,EAAE,UAAU,EAAE,CAAC;IAAC,YAAY,EAAE,gBAAgB,EAAE,CAAA;CAAC,CAAC,CAIlE;AAED,wBAAsB,sBAAsB,CAC1C,EAAE,EAAE,EAAE,EACN,MAAM,EAAE,0BAA0B,GACjC,OAAO,CAAC;IAAC,KAAK,EAAE,UAAU,EAAE,CAAC;IAAC,YAAY,EAAE,gBAAgB,EAAE,CAAA;CAAC,CAAC,CAGlE;AAED,wBAAsB,gCAAgC,CACpD,MAAM,EAAE,sCAAsC,GAC7C,OAAO,CAAC;IAAC,KAAK,EAAE,UAAU,EAAE,CAAC;IAAC,YAAY,EAAE,gBAAgB,EAAE,CAAA;CAAC,CAAC,CA2ClE;AAsOD,wBAAsB,4BAA4B,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAMtE;AA0BD,wBAAsB,yBAAyB,CAAC,MAAM,CAAC,EAAE;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAc1F;AAED,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAM5E;AAyED,wBAAsB,uBAAuB,CAC3C,EAAE,EAAE,EAAE,EACN,MAAM,EAAE;IACN,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,KAAK,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAC,CAAC,CAAC;CACzD,GACA,OAAO,CAAC,MAAM,CAAC,CA0CjB"}
|
package/dist/db/reservations.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { canonicalizeLabels } from '@shipfox/runner-labels';
|
|
2
|
-
import { and, asc, eq, gt, inArray, lt, sql } from 'drizzle-orm';
|
|
2
|
+
import { and, arrayContains, asc, eq, exists, gt, inArray, isNotNull, isNull, lt, sql } from 'drizzle-orm';
|
|
3
3
|
import { db } from './db.js';
|
|
4
4
|
import { pendingJobExecutions } from './schema/pending-job-executions.js';
|
|
5
5
|
import { provisionerCapabilitySnapshots } from './schema/provisioner-capability-snapshots.js';
|
|
6
6
|
import { provisionerTokens } from './schema/provisioner-tokens.js';
|
|
7
7
|
import { reservations } from './schema/reservations.js';
|
|
8
|
+
import { runnerActivationTokens } from './schema/runner-activation-tokens.js';
|
|
9
|
+
import { runnerControlSessions } from './schema/runner-control-sessions.js';
|
|
10
|
+
import { providerRunners } from './schema/runner-instances.js';
|
|
8
11
|
export async function pollDemandAndReserve(params) {
|
|
9
12
|
return await db().transaction(async (tx)=>{
|
|
10
13
|
return await pollDemandAndReserveTx(tx, params);
|
|
@@ -126,6 +129,13 @@ async function pollDemandAndReserveLockedTx(tx, params) {
|
|
|
126
129
|
if (!inserted) throw new Error('Insert returned no rows');
|
|
127
130
|
remainingMaxReservations -= grant;
|
|
128
131
|
drawSlots(satisfyingTemplates, grant);
|
|
132
|
+
await bindIdleRunnerInstancesTx(tx, {
|
|
133
|
+
provisionerId: params.provisionerId,
|
|
134
|
+
reservationId: inserted.id,
|
|
135
|
+
requiredLabels: demand.requiredLabels,
|
|
136
|
+
count: grant,
|
|
137
|
+
workspaceId: params.workspaceId
|
|
138
|
+
});
|
|
129
139
|
reservedAfterGrant += grant;
|
|
130
140
|
grants.push({
|
|
131
141
|
reservationId: inserted.id,
|
|
@@ -148,6 +158,20 @@ async function pollDemandAndReserveLockedTx(tx, params) {
|
|
|
148
158
|
reservations: grants
|
|
149
159
|
};
|
|
150
160
|
}
|
|
161
|
+
async function bindIdleRunnerInstancesTx(tx, params) {
|
|
162
|
+
const idleRunners = await tx.select({
|
|
163
|
+
id: providerRunners.id
|
|
164
|
+
}).from(providerRunners).where(and(eq(providerRunners.provisionerId, params.provisionerId), isNull(providerRunners.workspaceId), isNull(providerRunners.reservationId), isNull(providerRunners.intendedReservationId), isNull(providerRunners.runnerSessionId), isNotNull(providerRunners.providerRunnerId), eq(providerRunners.state, 'running'), arrayContains(providerRunners.labels, params.requiredLabels), exists(tx.select({
|
|
165
|
+
id: runnerControlSessions.id
|
|
166
|
+
}).from(runnerControlSessions).where(and(eq(runnerControlSessions.runnerInstanceId, providerRunners.id), eq(runnerControlSessions.provisionerId, params.provisionerId), isNull(runnerControlSessions.closedAt), gt(runnerControlSessions.expiresAt, sql`now()`)))))).orderBy(asc(providerRunners.createdAt), asc(providerRunners.id)).limit(params.count).for('update');
|
|
167
|
+
if (idleRunners.length === 0) return;
|
|
168
|
+
await tx.update(providerRunners).set({
|
|
169
|
+
workspaceId: params.workspaceId,
|
|
170
|
+
reservationId: params.reservationId,
|
|
171
|
+
assignedAt: sql`now()`,
|
|
172
|
+
updatedAt: sql`now()`
|
|
173
|
+
}).where(and(inArray(providerRunners.id, idleRunners.map((runner)=>runner.id)), isNull(providerRunners.workspaceId), isNull(providerRunners.reservationId), isNull(providerRunners.runnerSessionId)));
|
|
174
|
+
}
|
|
151
175
|
async function listInstallationDemandWorkspaceIds(eligibleWorkspaceIds) {
|
|
152
176
|
if (eligibleWorkspaceIds.size === 0) return [];
|
|
153
177
|
const rows = await db().select({
|
|
@@ -171,17 +195,45 @@ async function listActiveWorkspaceCapabilityLabelsTx(tx, params) {
|
|
|
171
195
|
return rows.map((row)=>row.labels);
|
|
172
196
|
}
|
|
173
197
|
export async function deleteExpiredReservations(params) {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
198
|
+
return await db().transaction(async (tx)=>{
|
|
199
|
+
const expiredRows = await tx.select({
|
|
200
|
+
id: reservations.id
|
|
201
|
+
}).from(reservations).where(lt(reservations.expiresAt, sql`now()`)).orderBy(asc(reservations.expiresAt)).limit(params?.limit ?? 1000);
|
|
202
|
+
return await deleteReservationsWithCleanupTx(tx, expiredRows.map((reservation)=>reservation.id), {
|
|
203
|
+
expiredOnly: true
|
|
204
|
+
});
|
|
179
205
|
});
|
|
180
|
-
return deleted.length;
|
|
181
206
|
}
|
|
182
207
|
export async function deleteReservationsByIds(ids) {
|
|
183
208
|
if (ids.length === 0) return 0;
|
|
184
|
-
|
|
209
|
+
return await db().transaction(async (tx)=>{
|
|
210
|
+
return await deleteReservationsWithCleanupTx(tx, ids);
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
async function deleteReservationsWithCleanupTx(tx, ids, params = {}) {
|
|
214
|
+
if (ids.length === 0) return 0;
|
|
215
|
+
const assignedRunners = await tx.select({
|
|
216
|
+
id: providerRunners.id,
|
|
217
|
+
reservationId: providerRunners.reservationId
|
|
218
|
+
}).from(providerRunners).where(and(inArray(providerRunners.reservationId, ids), isNull(providerRunners.runnerSessionId), isNull(providerRunners.reservationReleasedAt))).for('update');
|
|
219
|
+
const reservationRows = await tx.select({
|
|
220
|
+
id: reservations.id
|
|
221
|
+
}).from(reservations).where(and(inArray(reservations.id, ids), params.expiredOnly ? lt(reservations.expiresAt, sql`now()`) : undefined)).for('update');
|
|
222
|
+
const reservationIds = reservationRows.map((reservation)=>reservation.id);
|
|
223
|
+
if (reservationIds.length === 0) return 0;
|
|
224
|
+
const assignedRunnerIds = assignedRunners.filter((runner)=>runner.reservationId && reservationIds.includes(runner.reservationId)).map((runner)=>runner.id);
|
|
225
|
+
if (assignedRunnerIds.length > 0) {
|
|
226
|
+
await tx.update(runnerActivationTokens).set({
|
|
227
|
+
revokedAt: sql`now()`
|
|
228
|
+
}).where(and(inArray(runnerActivationTokens.runnerInstanceId, assignedRunnerIds), isNull(runnerActivationTokens.consumedAt), isNull(runnerActivationTokens.revokedAt)));
|
|
229
|
+
await tx.update(providerRunners).set({
|
|
230
|
+
workspaceId: null,
|
|
231
|
+
reservationId: null,
|
|
232
|
+
assignedAt: null,
|
|
233
|
+
updatedAt: sql`now()`
|
|
234
|
+
}).where(and(inArray(providerRunners.id, assignedRunnerIds), isNull(providerRunners.runnerSessionId), isNull(providerRunners.reservationReleasedAt)));
|
|
235
|
+
}
|
|
236
|
+
const deleted = await tx.delete(reservations).where(inArray(reservations.id, reservationIds)).returning({
|
|
185
237
|
id: reservations.id
|
|
186
238
|
});
|
|
187
239
|
return deleted.length;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/db/reservations.ts"],"sourcesContent":["import {canonicalizeLabels} from '@shipfox/runner-labels';\nimport {and, asc, eq, gt, inArray, lt, sql} from 'drizzle-orm';\nimport type {Tx} from './db.js';\nimport {db} from './db.js';\nimport {pendingJobExecutions} from './schema/pending-job-executions.js';\nimport {provisionerCapabilitySnapshots} from './schema/provisioner-capability-snapshots.js';\nimport {provisionerTokens} from './schema/provisioner-tokens.js';\nimport {reservations} from './schema/reservations.js';\n\nexport interface ReservationTemplate {\n templateKey: string;\n labels: string[];\n availableSlots: number;\n starting: number;\n running: number;\n}\n\nexport interface DemandStat {\n workspaceId?: string;\n labels: string[];\n queued: number;\n reserved: number;\n oldestQueuedAt: Date;\n}\n\nexport interface ReservationGrant {\n reservationId: string;\n workspaceId?: string;\n labels: string[];\n count: number;\n expiresAt: Date;\n}\n\nexport interface PollDemandAndReserveParams {\n workspaceId: string;\n provisionerId: string;\n maxReservations: number;\n ttlSeconds: number;\n templates: ReservationTemplate[];\n capabilityWindowSeconds?: number;\n}\n\nexport interface InstallationPollDemandAndReserveParams {\n provisionerId: string;\n maxReservations: number;\n ttlSeconds: number;\n templates: ReservationTemplate[];\n capabilityWindowSeconds: number;\n eligibleWorkspaceIds: ReadonlySet<string>;\n signal?: AbortSignal;\n onReservations?: (reservations: ReservationGrant[]) => void;\n}\n\ninterface NormalizedTemplate {\n templateKey: string;\n labels: string[];\n remainingSlots: number;\n}\n\ninterface DemandRow {\n requiredLabels: string[];\n queued: number;\n oldestQueuedAt: Date;\n}\n\nexport async function pollDemandAndReserve(\n params: PollDemandAndReserveParams,\n): Promise<{stats: DemandStat[]; reservations: ReservationGrant[]}> {\n return await db().transaction(async (tx) => {\n return await pollDemandAndReserveTx(tx, params);\n });\n}\n\nexport async function pollDemandAndReserveTx(\n tx: Tx,\n params: PollDemandAndReserveParams,\n): Promise<{stats: DemandStat[]; reservations: ReservationGrant[]}> {\n await tx.execute(sql`select pg_advisory_xact_lock(hashtext(${params.workspaceId}))`);\n return await pollDemandAndReserveLockedTx(tx, params);\n}\n\nexport async function pollInstallationDemandAndReserve(\n params: InstallationPollDemandAndReserveParams,\n): Promise<{stats: DemandStat[]; reservations: ReservationGrant[]}> {\n const candidateWorkspaceIds = await listInstallationDemandWorkspaceIds(\n params.eligibleWorkspaceIds,\n );\n const results: Array<{stats: DemandStat[]; reservations: ReservationGrant[]}> = [];\n let remainingMaxReservations = params.maxReservations;\n const remainingTemplates = params.templates.map((template) => ({\n ...template,\n labels: [...canonicalizeLabels(template.labels)],\n remainingSlots: template.availableSlots,\n }));\n for (const workspaceId of candidateWorkspaceIds) {\n if (params.signal?.aborted || remainingMaxReservations === 0) break;\n const result = await db().transaction(async (tx) => {\n const lockResult = await tx.execute<{locked: boolean}>(\n sql`select pg_try_advisory_xact_lock(hashtext(${workspaceId})) as locked`,\n );\n const locked = lockResult.rows[0];\n if (!locked?.locked) return {stats: [], reservations: []};\n return await pollDemandAndReserveLockedTx(tx, {\n workspaceId,\n provisionerId: params.provisionerId,\n maxReservations: remainingMaxReservations,\n ttlSeconds: params.ttlSeconds,\n templates: remainingTemplates.map((template) => ({\n ...template,\n availableSlots: template.remainingSlots,\n })),\n capabilityWindowSeconds: params.capabilityWindowSeconds,\n });\n });\n results.push(result);\n consumeInstallationTemplateSlots(remainingTemplates, result.reservations);\n params.onReservations?.(result.reservations);\n remainingMaxReservations -= result.reservations.reduce(\n (total, reservation) => total + reservation.count,\n 0,\n );\n }\n return {\n stats: results.flatMap((result) => result.stats),\n reservations: results.flatMap((result) => result.reservations),\n };\n}\n\nfunction consumeInstallationTemplateSlots(\n templates: NormalizedTemplate[],\n reservations: ReservationGrant[],\n): void {\n for (const reservation of reservations) {\n const satisfyingTemplates = templates\n .filter((template) => isSubset(reservation.labels, template.labels))\n .sort(\n (a, b) => a.labels.length - b.labels.length || a.templateKey.localeCompare(b.templateKey),\n );\n drawSlots(satisfyingTemplates, reservation.count);\n }\n}\n\nasync function pollDemandAndReserveLockedTx(\n tx: Tx,\n params: PollDemandAndReserveParams,\n): Promise<{stats: DemandStat[]; reservations: ReservationGrant[]}> {\n let demandRows = (\n await tx\n .select({\n requiredLabels: pendingJobExecutions.requiredLabels,\n queued: sql<number>`count(*)::int`,\n oldestQueuedAt: sql<Date | string>`min(${pendingJobExecutions.createdAt})`,\n })\n .from(pendingJobExecutions)\n .where(eq(pendingJobExecutions.workspaceId, params.workspaceId))\n .groupBy(pendingJobExecutions.requiredLabels)\n ).map((row) => ({\n ...row,\n oldestQueuedAt: new Date(row.oldestQueuedAt),\n }));\n if (params.capabilityWindowSeconds !== undefined) {\n const capabilityLabels = await listActiveWorkspaceCapabilityLabelsTx(tx, {\n workspaceId: params.workspaceId,\n windowSeconds: params.capabilityWindowSeconds,\n });\n demandRows = demandRows.filter(\n (demand) => !capabilityLabels.some((labels) => isSubset(demand.requiredLabels, labels)),\n );\n }\n\n const activeReservationRows = await tx\n .select({\n requiredLabels: reservations.requiredLabels,\n reserved: sql<number>`coalesce(sum(${reservations.count}), 0)::int`,\n })\n .from(reservations)\n .where(\n and(eq(reservations.workspaceId, params.workspaceId), gt(reservations.expiresAt, sql`now()`)),\n )\n .groupBy(reservations.requiredLabels);\n\n const reservedByLabels = new Map(\n activeReservationRows.map((row) => [labelKey(row.requiredLabels), row.reserved]),\n );\n const activeProvisionerReservationRows = await tx\n .select({\n requiredLabels: reservations.requiredLabels,\n reserved: sql<number>`coalesce(sum(${reservations.count}), 0)::int`,\n })\n .from(reservations)\n .where(\n and(\n eq(reservations.workspaceId, params.workspaceId),\n eq(reservations.provisionerId, params.provisionerId),\n gt(reservations.expiresAt, sql`now()`),\n ),\n )\n .groupBy(reservations.requiredLabels);\n\n const templates = params.templates.map((template) => ({\n templateKey: template.templateKey,\n labels: [...canonicalizeLabels(template.labels)],\n remainingSlots: template.availableSlots,\n }));\n deductProvisionerReservations(templates, activeProvisionerReservationRows);\n const stats: DemandStat[] = [];\n const grants: ReservationGrant[] = [];\n let remainingMaxReservations = params.maxReservations;\n const workspaceIdField =\n params.capabilityWindowSeconds === undefined ? {} : {workspaceId: params.workspaceId};\n\n for (const demand of sortDemandRows(demandRows)) {\n const satisfyingTemplates = templates\n .filter((template) => isSubset(demand.requiredLabels, template.labels))\n .sort(\n (a, b) => a.labels.length - b.labels.length || a.templateKey.localeCompare(b.templateKey),\n );\n\n if (satisfyingTemplates.length === 0) continue;\n\n const reserved = reservedByLabels.get(labelKey(demand.requiredLabels)) ?? 0;\n const unreserved = Math.max(0, demand.queued - reserved);\n const capacity = satisfyingTemplates.reduce(\n (total, template) => total + template.remainingSlots,\n 0,\n );\n const grant = Math.min(unreserved, capacity, remainingMaxReservations);\n let reservedAfterGrant = reserved;\n\n if (grant > 0 && params.maxReservations > 0) {\n const [inserted] = await tx\n .insert(reservations)\n .values({\n workspaceId: params.workspaceId,\n provisionerId: params.provisionerId,\n requiredLabels: demand.requiredLabels,\n count: grant,\n expiresAt: sql`now() + (${params.ttlSeconds} || ' seconds')::interval`,\n })\n .returning({id: reservations.id, expiresAt: reservations.expiresAt});\n\n if (!inserted) throw new Error('Insert returned no rows');\n\n remainingMaxReservations -= grant;\n drawSlots(satisfyingTemplates, grant);\n reservedAfterGrant += grant;\n grants.push({\n reservationId: inserted.id,\n ...workspaceIdField,\n labels: demand.requiredLabels,\n count: grant,\n expiresAt: inserted.expiresAt,\n });\n }\n\n stats.push({\n ...workspaceIdField,\n labels: demand.requiredLabels,\n queued: demand.queued,\n reserved: reservedAfterGrant,\n oldestQueuedAt: demand.oldestQueuedAt,\n });\n }\n\n return {stats, reservations: grants};\n}\n\nasync function listInstallationDemandWorkspaceIds(eligibleWorkspaceIds: ReadonlySet<string>) {\n if (eligibleWorkspaceIds.size === 0) return [];\n const rows = await db()\n .select({\n workspaceId: pendingJobExecutions.workspaceId,\n oldestQueuedAt: sql<Date>`min(${pendingJobExecutions.createdAt})`,\n })\n .from(pendingJobExecutions)\n .where(inArray(pendingJobExecutions.workspaceId, [...eligibleWorkspaceIds]))\n .groupBy(pendingJobExecutions.workspaceId)\n .orderBy(\n asc(sql`min(${pendingJobExecutions.createdAt})`),\n asc(pendingJobExecutions.workspaceId),\n );\n return rows.map((row) => row.workspaceId);\n}\n\nexport async function listQueuedDemandWorkspaceIds(): Promise<string[]> {\n const rows = await db()\n .select({workspaceId: pendingJobExecutions.workspaceId})\n .from(pendingJobExecutions)\n .groupBy(pendingJobExecutions.workspaceId);\n return rows.map((row) => row.workspaceId);\n}\n\nasync function listActiveWorkspaceCapabilityLabelsTx(\n tx: Tx,\n params: {workspaceId: string; windowSeconds: number},\n): Promise<string[][]> {\n const rows = await tx\n .select({labels: provisionerCapabilitySnapshots.labels})\n .from(provisionerCapabilitySnapshots)\n .innerJoin(\n provisionerTokens,\n eq(provisionerTokens.id, provisionerCapabilitySnapshots.provisionerId),\n )\n .where(\n and(\n eq(provisionerCapabilitySnapshots.workspaceId, params.workspaceId),\n eq(provisionerTokens.workspaceId, params.workspaceId),\n eq(provisionerTokens.scope, 'workspace'),\n sql`${provisionerCapabilitySnapshots.advertisedAt} > now() - (${params.windowSeconds} || ' seconds')::interval`,\n sql`${provisionerTokens.revokedAt} is null`,\n sql`(${provisionerTokens.expiresAt} is null or ${provisionerTokens.expiresAt} > now())`,\n ),\n );\n return rows.map((row) => row.labels);\n}\n\nexport async function deleteExpiredReservations(params?: {limit?: number}): Promise<number> {\n const expiredIds = db()\n .select({id: reservations.id})\n .from(reservations)\n .where(lt(reservations.expiresAt, sql`now()`))\n .orderBy(asc(reservations.expiresAt))\n .limit(params?.limit ?? 1000);\n\n const deleted = await db()\n .delete(reservations)\n .where(inArray(reservations.id, expiredIds))\n .returning({id: reservations.id});\n\n return deleted.length;\n}\n\nexport async function deleteReservationsByIds(ids: string[]): Promise<number> {\n if (ids.length === 0) return 0;\n\n const deleted = await db()\n .delete(reservations)\n .where(inArray(reservations.id, ids))\n .returning({id: reservations.id});\n\n return deleted.length;\n}\n\nexport async function releaseReservationUnits(\n tx: Tx,\n params: {\n workspaceId: string;\n provisionerId: string;\n releases: Array<{reservationId: string; count: number}>;\n },\n): Promise<number> {\n let released = 0;\n\n for (const release of params.releases) {\n if (release.count <= 0) continue;\n\n const decremented = await tx\n .update(reservations)\n .set({count: sql`${reservations.count} - ${release.count}`})\n .where(\n and(\n eq(reservations.id, release.reservationId),\n eq(reservations.workspaceId, params.workspaceId),\n eq(reservations.provisionerId, params.provisionerId),\n gt(reservations.count, release.count),\n gt(reservations.expiresAt, sql`now()`),\n ),\n )\n .returning({id: reservations.id});\n\n if (decremented.length > 0) {\n released += release.count;\n continue;\n }\n\n const deleted = await tx\n .delete(reservations)\n .where(\n and(\n eq(reservations.id, release.reservationId),\n eq(reservations.workspaceId, params.workspaceId),\n eq(reservations.provisionerId, params.provisionerId),\n sql`${reservations.count} <= ${release.count}`,\n gt(reservations.expiresAt, sql`now()`),\n ),\n )\n .returning({count: reservations.count});\n\n released += deleted.reduce((total, row) => total + row.count, 0);\n }\n\n return released;\n}\n\nfunction deductProvisionerReservations(\n templates: NormalizedTemplate[],\n activeReservations: {requiredLabels: string[]; reserved: number}[],\n): void {\n for (const reservation of sortReservationRows(activeReservations)) {\n const satisfyingTemplates = templates\n .filter((template) => isSubset(reservation.requiredLabels, template.labels))\n .sort(\n (a, b) => a.labels.length - b.labels.length || a.templateKey.localeCompare(b.templateKey),\n );\n drawSlots(satisfyingTemplates, reservation.reserved);\n }\n}\n\nfunction sortDemandRows(rows: DemandRow[]): DemandRow[] {\n return [...rows].sort((a, b) => {\n const specificity = b.requiredLabels.length - a.requiredLabels.length;\n if (specificity !== 0) return specificity;\n return a.oldestQueuedAt.getTime() - b.oldestQueuedAt.getTime();\n });\n}\n\nfunction sortReservationRows<T extends {requiredLabels: string[]}>(rows: T[]): T[] {\n return [...rows].sort((a, b) => {\n const specificity = b.requiredLabels.length - a.requiredLabels.length;\n if (specificity !== 0) return specificity;\n return labelKey(a.requiredLabels).localeCompare(labelKey(b.requiredLabels));\n });\n}\n\nfunction isSubset(requiredLabels: string[], availableLabels: string[]): boolean {\n return requiredLabels.every((label) => availableLabels.includes(label));\n}\n\nfunction drawSlots(templates: NormalizedTemplate[], count: number): void {\n let remaining = count;\n for (const template of templates) {\n if (remaining === 0) return;\n const used = Math.min(template.remainingSlots, remaining);\n template.remainingSlots -= used;\n remaining -= used;\n }\n}\n\nfunction labelKey(labels: string[]): string {\n return JSON.stringify(labels);\n}\n"],"names":["canonicalizeLabels","and","asc","eq","gt","inArray","lt","sql","db","pendingJobExecutions","provisionerCapabilitySnapshots","provisionerTokens","reservations","pollDemandAndReserve","params","transaction","tx","pollDemandAndReserveTx","execute","workspaceId","pollDemandAndReserveLockedTx","pollInstallationDemandAndReserve","candidateWorkspaceIds","listInstallationDemandWorkspaceIds","eligibleWorkspaceIds","results","remainingMaxReservations","maxReservations","remainingTemplates","templates","map","template","labels","remainingSlots","availableSlots","signal","aborted","result","lockResult","locked","rows","stats","provisionerId","ttlSeconds","capabilityWindowSeconds","push","consumeInstallationTemplateSlots","onReservations","reduce","total","reservation","count","flatMap","satisfyingTemplates","filter","isSubset","sort","a","b","length","templateKey","localeCompare","drawSlots","demandRows","select","requiredLabels","queued","oldestQueuedAt","createdAt","from","where","groupBy","row","Date","undefined","capabilityLabels","listActiveWorkspaceCapabilityLabelsTx","windowSeconds","demand","some","activeReservationRows","reserved","expiresAt","reservedByLabels","Map","labelKey","activeProvisionerReservationRows","deductProvisionerReservations","grants","workspaceIdField","sortDemandRows","get","unreserved","Math","max","capacity","grant","min","reservedAfterGrant","inserted","insert","values","returning","id","Error","reservationId","size","orderBy","listQueuedDemandWorkspaceIds","innerJoin","scope","advertisedAt","revokedAt","deleteExpiredReservations","expiredIds","limit","deleted","delete","deleteReservationsByIds","ids","releaseReservationUnits","released","release","releases","decremented","update","set","activeReservations","sortReservationRows","specificity","getTime","availableLabels","every","label","includes","remaining","used","JSON","stringify"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/db/reservations.ts"],"sourcesContent":["import {canonicalizeLabels} from '@shipfox/runner-labels';\nimport {\n and,\n arrayContains,\n asc,\n eq,\n exists,\n gt,\n inArray,\n isNotNull,\n isNull,\n lt,\n sql,\n} from 'drizzle-orm';\nimport type {Tx} from './db.js';\nimport {db} from './db.js';\nimport {pendingJobExecutions} from './schema/pending-job-executions.js';\nimport {provisionerCapabilitySnapshots} from './schema/provisioner-capability-snapshots.js';\nimport {provisionerTokens} from './schema/provisioner-tokens.js';\nimport {reservations} from './schema/reservations.js';\nimport {runnerActivationTokens} from './schema/runner-activation-tokens.js';\nimport {runnerControlSessions} from './schema/runner-control-sessions.js';\nimport {providerRunners} from './schema/runner-instances.js';\n\nexport interface ReservationTemplate {\n templateKey: string;\n labels: string[];\n availableSlots: number;\n starting: number;\n running: number;\n}\n\nexport interface DemandStat {\n workspaceId?: string;\n labels: string[];\n queued: number;\n reserved: number;\n oldestQueuedAt: Date;\n}\n\nexport interface ReservationGrant {\n reservationId: string;\n workspaceId?: string;\n labels: string[];\n count: number;\n expiresAt: Date;\n}\n\nexport interface PollDemandAndReserveParams {\n workspaceId: string;\n provisionerId: string;\n maxReservations: number;\n ttlSeconds: number;\n templates: ReservationTemplate[];\n capabilityWindowSeconds?: number;\n}\n\nexport interface InstallationPollDemandAndReserveParams {\n provisionerId: string;\n maxReservations: number;\n ttlSeconds: number;\n templates: ReservationTemplate[];\n capabilityWindowSeconds: number;\n eligibleWorkspaceIds: ReadonlySet<string>;\n signal?: AbortSignal;\n onReservations?: (reservations: ReservationGrant[]) => void;\n}\n\ninterface NormalizedTemplate {\n templateKey: string;\n labels: string[];\n remainingSlots: number;\n}\n\ninterface DemandRow {\n requiredLabels: string[];\n queued: number;\n oldestQueuedAt: Date;\n}\n\nexport async function pollDemandAndReserve(\n params: PollDemandAndReserveParams,\n): Promise<{stats: DemandStat[]; reservations: ReservationGrant[]}> {\n return await db().transaction(async (tx) => {\n return await pollDemandAndReserveTx(tx, params);\n });\n}\n\nexport async function pollDemandAndReserveTx(\n tx: Tx,\n params: PollDemandAndReserveParams,\n): Promise<{stats: DemandStat[]; reservations: ReservationGrant[]}> {\n await tx.execute(sql`select pg_advisory_xact_lock(hashtext(${params.workspaceId}))`);\n return await pollDemandAndReserveLockedTx(tx, params);\n}\n\nexport async function pollInstallationDemandAndReserve(\n params: InstallationPollDemandAndReserveParams,\n): Promise<{stats: DemandStat[]; reservations: ReservationGrant[]}> {\n const candidateWorkspaceIds = await listInstallationDemandWorkspaceIds(\n params.eligibleWorkspaceIds,\n );\n const results: Array<{stats: DemandStat[]; reservations: ReservationGrant[]}> = [];\n let remainingMaxReservations = params.maxReservations;\n const remainingTemplates = params.templates.map((template) => ({\n ...template,\n labels: [...canonicalizeLabels(template.labels)],\n remainingSlots: template.availableSlots,\n }));\n for (const workspaceId of candidateWorkspaceIds) {\n if (params.signal?.aborted || remainingMaxReservations === 0) break;\n const result = await db().transaction(async (tx) => {\n const lockResult = await tx.execute<{locked: boolean}>(\n sql`select pg_try_advisory_xact_lock(hashtext(${workspaceId})) as locked`,\n );\n const locked = lockResult.rows[0];\n if (!locked?.locked) return {stats: [], reservations: []};\n return await pollDemandAndReserveLockedTx(tx, {\n workspaceId,\n provisionerId: params.provisionerId,\n maxReservations: remainingMaxReservations,\n ttlSeconds: params.ttlSeconds,\n templates: remainingTemplates.map((template) => ({\n ...template,\n availableSlots: template.remainingSlots,\n })),\n capabilityWindowSeconds: params.capabilityWindowSeconds,\n });\n });\n results.push(result);\n consumeInstallationTemplateSlots(remainingTemplates, result.reservations);\n params.onReservations?.(result.reservations);\n remainingMaxReservations -= result.reservations.reduce(\n (total, reservation) => total + reservation.count,\n 0,\n );\n }\n return {\n stats: results.flatMap((result) => result.stats),\n reservations: results.flatMap((result) => result.reservations),\n };\n}\n\nfunction consumeInstallationTemplateSlots(\n templates: NormalizedTemplate[],\n reservations: ReservationGrant[],\n): void {\n for (const reservation of reservations) {\n const satisfyingTemplates = templates\n .filter((template) => isSubset(reservation.labels, template.labels))\n .sort(\n (a, b) => a.labels.length - b.labels.length || a.templateKey.localeCompare(b.templateKey),\n );\n drawSlots(satisfyingTemplates, reservation.count);\n }\n}\n\nasync function pollDemandAndReserveLockedTx(\n tx: Tx,\n params: PollDemandAndReserveParams,\n): Promise<{stats: DemandStat[]; reservations: ReservationGrant[]}> {\n let demandRows = (\n await tx\n .select({\n requiredLabels: pendingJobExecutions.requiredLabels,\n queued: sql<number>`count(*)::int`,\n oldestQueuedAt: sql<Date | string>`min(${pendingJobExecutions.createdAt})`,\n })\n .from(pendingJobExecutions)\n .where(eq(pendingJobExecutions.workspaceId, params.workspaceId))\n .groupBy(pendingJobExecutions.requiredLabels)\n ).map((row) => ({\n ...row,\n oldestQueuedAt: new Date(row.oldestQueuedAt),\n }));\n if (params.capabilityWindowSeconds !== undefined) {\n const capabilityLabels = await listActiveWorkspaceCapabilityLabelsTx(tx, {\n workspaceId: params.workspaceId,\n windowSeconds: params.capabilityWindowSeconds,\n });\n demandRows = demandRows.filter(\n (demand) => !capabilityLabels.some((labels) => isSubset(demand.requiredLabels, labels)),\n );\n }\n\n const activeReservationRows = await tx\n .select({\n requiredLabels: reservations.requiredLabels,\n reserved: sql<number>`coalesce(sum(${reservations.count}), 0)::int`,\n })\n .from(reservations)\n .where(\n and(eq(reservations.workspaceId, params.workspaceId), gt(reservations.expiresAt, sql`now()`)),\n )\n .groupBy(reservations.requiredLabels);\n\n const reservedByLabels = new Map(\n activeReservationRows.map((row) => [labelKey(row.requiredLabels), row.reserved]),\n );\n const activeProvisionerReservationRows = await tx\n .select({\n requiredLabels: reservations.requiredLabels,\n reserved: sql<number>`coalesce(sum(${reservations.count}), 0)::int`,\n })\n .from(reservations)\n .where(\n and(\n eq(reservations.workspaceId, params.workspaceId),\n eq(reservations.provisionerId, params.provisionerId),\n gt(reservations.expiresAt, sql`now()`),\n ),\n )\n .groupBy(reservations.requiredLabels);\n\n const templates = params.templates.map((template) => ({\n templateKey: template.templateKey,\n labels: [...canonicalizeLabels(template.labels)],\n remainingSlots: template.availableSlots,\n }));\n deductProvisionerReservations(templates, activeProvisionerReservationRows);\n const stats: DemandStat[] = [];\n const grants: ReservationGrant[] = [];\n let remainingMaxReservations = params.maxReservations;\n const workspaceIdField =\n params.capabilityWindowSeconds === undefined ? {} : {workspaceId: params.workspaceId};\n\n for (const demand of sortDemandRows(demandRows)) {\n const satisfyingTemplates = templates\n .filter((template) => isSubset(demand.requiredLabels, template.labels))\n .sort(\n (a, b) => a.labels.length - b.labels.length || a.templateKey.localeCompare(b.templateKey),\n );\n\n if (satisfyingTemplates.length === 0) continue;\n\n const reserved = reservedByLabels.get(labelKey(demand.requiredLabels)) ?? 0;\n const unreserved = Math.max(0, demand.queued - reserved);\n const capacity = satisfyingTemplates.reduce(\n (total, template) => total + template.remainingSlots,\n 0,\n );\n const grant = Math.min(unreserved, capacity, remainingMaxReservations);\n let reservedAfterGrant = reserved;\n\n if (grant > 0 && params.maxReservations > 0) {\n const [inserted] = await tx\n .insert(reservations)\n .values({\n workspaceId: params.workspaceId,\n provisionerId: params.provisionerId,\n requiredLabels: demand.requiredLabels,\n count: grant,\n expiresAt: sql`now() + (${params.ttlSeconds} || ' seconds')::interval`,\n })\n .returning({id: reservations.id, expiresAt: reservations.expiresAt});\n\n if (!inserted) throw new Error('Insert returned no rows');\n\n remainingMaxReservations -= grant;\n drawSlots(satisfyingTemplates, grant);\n await bindIdleRunnerInstancesTx(tx, {\n provisionerId: params.provisionerId,\n reservationId: inserted.id,\n requiredLabels: demand.requiredLabels,\n count: grant,\n workspaceId: params.workspaceId,\n });\n reservedAfterGrant += grant;\n grants.push({\n reservationId: inserted.id,\n ...workspaceIdField,\n labels: demand.requiredLabels,\n count: grant,\n expiresAt: inserted.expiresAt,\n });\n }\n\n stats.push({\n ...workspaceIdField,\n labels: demand.requiredLabels,\n queued: demand.queued,\n reserved: reservedAfterGrant,\n oldestQueuedAt: demand.oldestQueuedAt,\n });\n }\n\n return {stats, reservations: grants};\n}\n\nasync function bindIdleRunnerInstancesTx(\n tx: Tx,\n params: {\n provisionerId: string;\n reservationId: string;\n workspaceId: string;\n requiredLabels: string[];\n count: number;\n },\n): Promise<void> {\n const idleRunners = await tx\n .select({id: providerRunners.id})\n .from(providerRunners)\n .where(\n and(\n eq(providerRunners.provisionerId, params.provisionerId),\n isNull(providerRunners.workspaceId),\n isNull(providerRunners.reservationId),\n isNull(providerRunners.intendedReservationId),\n isNull(providerRunners.runnerSessionId),\n isNotNull(providerRunners.providerRunnerId),\n eq(providerRunners.state, 'running'),\n arrayContains(providerRunners.labels, params.requiredLabels),\n exists(\n tx\n .select({id: runnerControlSessions.id})\n .from(runnerControlSessions)\n .where(\n and(\n eq(runnerControlSessions.runnerInstanceId, providerRunners.id),\n eq(runnerControlSessions.provisionerId, params.provisionerId),\n isNull(runnerControlSessions.closedAt),\n gt(runnerControlSessions.expiresAt, sql`now()`),\n ),\n ),\n ),\n ),\n )\n .orderBy(asc(providerRunners.createdAt), asc(providerRunners.id))\n .limit(params.count)\n .for('update');\n\n if (idleRunners.length === 0) return;\n\n await tx\n .update(providerRunners)\n .set({\n workspaceId: params.workspaceId,\n reservationId: params.reservationId,\n assignedAt: sql`now()`,\n updatedAt: sql`now()`,\n })\n .where(\n and(\n inArray(\n providerRunners.id,\n idleRunners.map((runner) => runner.id),\n ),\n isNull(providerRunners.workspaceId),\n isNull(providerRunners.reservationId),\n isNull(providerRunners.runnerSessionId),\n ),\n );\n}\n\nasync function listInstallationDemandWorkspaceIds(eligibleWorkspaceIds: ReadonlySet<string>) {\n if (eligibleWorkspaceIds.size === 0) return [];\n const rows = await db()\n .select({\n workspaceId: pendingJobExecutions.workspaceId,\n oldestQueuedAt: sql<Date>`min(${pendingJobExecutions.createdAt})`,\n })\n .from(pendingJobExecutions)\n .where(inArray(pendingJobExecutions.workspaceId, [...eligibleWorkspaceIds]))\n .groupBy(pendingJobExecutions.workspaceId)\n .orderBy(\n asc(sql`min(${pendingJobExecutions.createdAt})`),\n asc(pendingJobExecutions.workspaceId),\n );\n return rows.map((row) => row.workspaceId);\n}\n\nexport async function listQueuedDemandWorkspaceIds(): Promise<string[]> {\n const rows = await db()\n .select({workspaceId: pendingJobExecutions.workspaceId})\n .from(pendingJobExecutions)\n .groupBy(pendingJobExecutions.workspaceId);\n return rows.map((row) => row.workspaceId);\n}\n\nasync function listActiveWorkspaceCapabilityLabelsTx(\n tx: Tx,\n params: {workspaceId: string; windowSeconds: number},\n): Promise<string[][]> {\n const rows = await tx\n .select({labels: provisionerCapabilitySnapshots.labels})\n .from(provisionerCapabilitySnapshots)\n .innerJoin(\n provisionerTokens,\n eq(provisionerTokens.id, provisionerCapabilitySnapshots.provisionerId),\n )\n .where(\n and(\n eq(provisionerCapabilitySnapshots.workspaceId, params.workspaceId),\n eq(provisionerTokens.workspaceId, params.workspaceId),\n eq(provisionerTokens.scope, 'workspace'),\n sql`${provisionerCapabilitySnapshots.advertisedAt} > now() - (${params.windowSeconds} || ' seconds')::interval`,\n sql`${provisionerTokens.revokedAt} is null`,\n sql`(${provisionerTokens.expiresAt} is null or ${provisionerTokens.expiresAt} > now())`,\n ),\n );\n return rows.map((row) => row.labels);\n}\n\nexport async function deleteExpiredReservations(params?: {limit?: number}): Promise<number> {\n return await db().transaction(async (tx) => {\n const expiredRows = await tx\n .select({id: reservations.id})\n .from(reservations)\n .where(lt(reservations.expiresAt, sql`now()`))\n .orderBy(asc(reservations.expiresAt))\n .limit(params?.limit ?? 1000);\n return await deleteReservationsWithCleanupTx(\n tx,\n expiredRows.map((reservation) => reservation.id),\n {expiredOnly: true},\n );\n });\n}\n\nexport async function deleteReservationsByIds(ids: string[]): Promise<number> {\n if (ids.length === 0) return 0;\n\n return await db().transaction(async (tx) => {\n return await deleteReservationsWithCleanupTx(tx, ids);\n });\n}\n\nasync function deleteReservationsWithCleanupTx(\n tx: Tx,\n ids: string[],\n params: {expiredOnly?: boolean} = {},\n): Promise<number> {\n if (ids.length === 0) return 0;\n\n const assignedRunners = await tx\n .select({id: providerRunners.id, reservationId: providerRunners.reservationId})\n .from(providerRunners)\n .where(\n and(\n inArray(providerRunners.reservationId, ids),\n isNull(providerRunners.runnerSessionId),\n isNull(providerRunners.reservationReleasedAt),\n ),\n )\n .for('update');\n const reservationRows = await tx\n .select({id: reservations.id})\n .from(reservations)\n .where(\n and(\n inArray(reservations.id, ids),\n params.expiredOnly ? lt(reservations.expiresAt, sql`now()`) : undefined,\n ),\n )\n .for('update');\n const reservationIds = reservationRows.map((reservation) => reservation.id);\n\n if (reservationIds.length === 0) return 0;\n\n const assignedRunnerIds = assignedRunners\n .filter((runner) => runner.reservationId && reservationIds.includes(runner.reservationId))\n .map((runner) => runner.id);\n if (assignedRunnerIds.length > 0) {\n await tx\n .update(runnerActivationTokens)\n .set({revokedAt: sql`now()`})\n .where(\n and(\n inArray(runnerActivationTokens.runnerInstanceId, assignedRunnerIds),\n isNull(runnerActivationTokens.consumedAt),\n isNull(runnerActivationTokens.revokedAt),\n ),\n );\n await tx\n .update(providerRunners)\n .set({\n workspaceId: null,\n reservationId: null,\n assignedAt: null,\n updatedAt: sql`now()`,\n })\n .where(\n and(\n inArray(providerRunners.id, assignedRunnerIds),\n isNull(providerRunners.runnerSessionId),\n isNull(providerRunners.reservationReleasedAt),\n ),\n );\n }\n\n const deleted = await tx\n .delete(reservations)\n .where(inArray(reservations.id, reservationIds))\n .returning({id: reservations.id});\n\n return deleted.length;\n}\n\nexport async function releaseReservationUnits(\n tx: Tx,\n params: {\n workspaceId: string;\n provisionerId: string;\n releases: Array<{reservationId: string; count: number}>;\n },\n): Promise<number> {\n let released = 0;\n\n for (const release of params.releases) {\n if (release.count <= 0) continue;\n\n const decremented = await tx\n .update(reservations)\n .set({count: sql`${reservations.count} - ${release.count}`})\n .where(\n and(\n eq(reservations.id, release.reservationId),\n eq(reservations.workspaceId, params.workspaceId),\n eq(reservations.provisionerId, params.provisionerId),\n gt(reservations.count, release.count),\n gt(reservations.expiresAt, sql`now()`),\n ),\n )\n .returning({id: reservations.id});\n\n if (decremented.length > 0) {\n released += release.count;\n continue;\n }\n\n const deleted = await tx\n .delete(reservations)\n .where(\n and(\n eq(reservations.id, release.reservationId),\n eq(reservations.workspaceId, params.workspaceId),\n eq(reservations.provisionerId, params.provisionerId),\n sql`${reservations.count} <= ${release.count}`,\n gt(reservations.expiresAt, sql`now()`),\n ),\n )\n .returning({count: reservations.count});\n\n released += deleted.reduce((total, row) => total + row.count, 0);\n }\n\n return released;\n}\n\nfunction deductProvisionerReservations(\n templates: NormalizedTemplate[],\n activeReservations: {requiredLabels: string[]; reserved: number}[],\n): void {\n for (const reservation of sortReservationRows(activeReservations)) {\n const satisfyingTemplates = templates\n .filter((template) => isSubset(reservation.requiredLabels, template.labels))\n .sort(\n (a, b) => a.labels.length - b.labels.length || a.templateKey.localeCompare(b.templateKey),\n );\n drawSlots(satisfyingTemplates, reservation.reserved);\n }\n}\n\nfunction sortDemandRows(rows: DemandRow[]): DemandRow[] {\n return [...rows].sort((a, b) => {\n const specificity = b.requiredLabels.length - a.requiredLabels.length;\n if (specificity !== 0) return specificity;\n return a.oldestQueuedAt.getTime() - b.oldestQueuedAt.getTime();\n });\n}\n\nfunction sortReservationRows<T extends {requiredLabels: string[]}>(rows: T[]): T[] {\n return [...rows].sort((a, b) => {\n const specificity = b.requiredLabels.length - a.requiredLabels.length;\n if (specificity !== 0) return specificity;\n return labelKey(a.requiredLabels).localeCompare(labelKey(b.requiredLabels));\n });\n}\n\nfunction isSubset(requiredLabels: string[], availableLabels: string[]): boolean {\n return requiredLabels.every((label) => availableLabels.includes(label));\n}\n\nfunction drawSlots(templates: NormalizedTemplate[], count: number): void {\n let remaining = count;\n for (const template of templates) {\n if (remaining === 0) return;\n const used = Math.min(template.remainingSlots, remaining);\n template.remainingSlots -= used;\n remaining -= used;\n }\n}\n\nfunction labelKey(labels: string[]): string {\n return JSON.stringify(labels);\n}\n"],"names":["canonicalizeLabels","and","arrayContains","asc","eq","exists","gt","inArray","isNotNull","isNull","lt","sql","db","pendingJobExecutions","provisionerCapabilitySnapshots","provisionerTokens","reservations","runnerActivationTokens","runnerControlSessions","providerRunners","pollDemandAndReserve","params","transaction","tx","pollDemandAndReserveTx","execute","workspaceId","pollDemandAndReserveLockedTx","pollInstallationDemandAndReserve","candidateWorkspaceIds","listInstallationDemandWorkspaceIds","eligibleWorkspaceIds","results","remainingMaxReservations","maxReservations","remainingTemplates","templates","map","template","labels","remainingSlots","availableSlots","signal","aborted","result","lockResult","locked","rows","stats","provisionerId","ttlSeconds","capabilityWindowSeconds","push","consumeInstallationTemplateSlots","onReservations","reduce","total","reservation","count","flatMap","satisfyingTemplates","filter","isSubset","sort","a","b","length","templateKey","localeCompare","drawSlots","demandRows","select","requiredLabels","queued","oldestQueuedAt","createdAt","from","where","groupBy","row","Date","undefined","capabilityLabels","listActiveWorkspaceCapabilityLabelsTx","windowSeconds","demand","some","activeReservationRows","reserved","expiresAt","reservedByLabels","Map","labelKey","activeProvisionerReservationRows","deductProvisionerReservations","grants","workspaceIdField","sortDemandRows","get","unreserved","Math","max","capacity","grant","min","reservedAfterGrant","inserted","insert","values","returning","id","Error","bindIdleRunnerInstancesTx","reservationId","idleRunners","intendedReservationId","runnerSessionId","providerRunnerId","state","runnerInstanceId","closedAt","orderBy","limit","for","update","set","assignedAt","updatedAt","runner","size","listQueuedDemandWorkspaceIds","innerJoin","scope","advertisedAt","revokedAt","deleteExpiredReservations","expiredRows","deleteReservationsWithCleanupTx","expiredOnly","deleteReservationsByIds","ids","assignedRunners","reservationReleasedAt","reservationRows","reservationIds","assignedRunnerIds","includes","consumedAt","deleted","delete","releaseReservationUnits","released","release","releases","decremented","activeReservations","sortReservationRows","specificity","getTime","availableLabels","every","label","remaining","used","JSON","stringify"],"mappings":"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"}
|