@sixb/sqlite 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/checkpoints.d.ts +4 -0
- package/dist/agents/checkpoints.d.ts.map +1 -1
- package/dist/ai-usage-storage.d.ts +2 -1
- package/dist/ai-usage-storage.d.ts.map +1 -1
- package/dist/index.js +180 -22
- package/dist/index.js.map +7 -7
- package/dist/object-storage.d.ts +4 -3
- package/dist/object-storage.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/agents/checkpoints.ts +30 -0
- package/src/ai-usage-storage.ts +19 -0
- package/src/object-query-compiler.ts +50 -0
- package/src/object-storage.ts +138 -34
package/dist/object-storage.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CountObjectsInput, CountObjectsResult, ExistsObjectsInput, ExistsObjectsResult, FacetObjectsInput, FacetObjectsResult, LinkDirection, ObjectLinkRow, ObjectQueryCapabilities, ObjectRow, ObjectStorage, QueryObjectsInput, QueryObjectsResult } from "@sixb/core/storage";
|
|
1
|
+
import type { CountObjectsInput, CountObjectsResult, ExistsObjectsInput, ExistsObjectsResult, FacetObjectsInput, FacetObjectsResult, LinkBatchKey, LinkDirection, ObjectBatchKey, ObjectLinkRow, ObjectQueryCapabilities, ObjectRow, ObjectStorage, QueryObjectLinksInput, QueryObjectLinksResult, QueryObjectsInput, QueryObjectsResult } from "@sixb/core/storage";
|
|
2
2
|
import { type SqliteStoreConnection } from "./transactions";
|
|
3
3
|
export interface SqliteObjectStorageOptions {
|
|
4
4
|
/** Path to SQLite database file. Defaults to ':memory:' for in-memory database. */
|
|
@@ -39,7 +39,7 @@ export declare class SqliteObjectStorage implements ObjectStorage {
|
|
|
39
39
|
objectTypeId: string;
|
|
40
40
|
primaryId: string;
|
|
41
41
|
}[];
|
|
42
|
-
}): Promise<Map<
|
|
42
|
+
}): Promise<Map<ObjectBatchKey, ObjectRow>>;
|
|
43
43
|
listLinksBatch(params: {
|
|
44
44
|
projectId: string;
|
|
45
45
|
items: readonly {
|
|
@@ -47,7 +47,8 @@ export declare class SqliteObjectStorage implements ObjectStorage {
|
|
|
47
47
|
objectId: string;
|
|
48
48
|
linkId: string;
|
|
49
49
|
}[];
|
|
50
|
-
}): Promise<Map<
|
|
50
|
+
}): Promise<Map<LinkBatchKey, ObjectLinkRow[]>>;
|
|
51
|
+
queryLinks(params: QueryObjectLinksInput): Promise<QueryObjectLinksResult>;
|
|
51
52
|
listIncidentLinksBatch(params: {
|
|
52
53
|
projectId: string;
|
|
53
54
|
items: readonly {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"object-storage.d.ts","sourceRoot":"","sources":["../src/object-storage.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EAGnB,iBAAiB,EACjB,kBAAkB,EAClB,aAAa,
|
|
1
|
+
{"version":3,"file":"object-storage.d.ts","sourceRoot":"","sources":["../src/object-storage.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sixb/sqlite",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "SQLite storage provider for Sixb",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sixb",
|
|
@@ -36,10 +36,10 @@
|
|
|
36
36
|
"prepack": "bun run build"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
|
-
"@sixb/core": "0.1.
|
|
39
|
+
"@sixb/core": "0.1.4"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@sixb/core": "0.1.
|
|
42
|
+
"@sixb/core": "0.1.4",
|
|
43
43
|
"@types/bun": "latest",
|
|
44
44
|
"typescript": "^5.7.0"
|
|
45
45
|
},
|
|
@@ -23,6 +23,8 @@ import {
|
|
|
23
23
|
rowToThreadRecord,
|
|
24
24
|
} from "./rows"
|
|
25
25
|
|
|
26
|
+
const SQLITE_CHECKPOINT_RUN_ID_BATCH_SIZE = 500
|
|
27
|
+
|
|
26
28
|
export class SqliteAgentContextCheckpointStore implements AgentContextCheckpointStore {
|
|
27
29
|
constructor(private readonly db: Database) {}
|
|
28
30
|
|
|
@@ -146,6 +148,34 @@ export class SqliteAgentContextCheckpointStore implements AgentContextCheckpoint
|
|
|
146
148
|
return this.findLatest(input.projectId, input.threadId)
|
|
147
149
|
}
|
|
148
150
|
|
|
151
|
+
async getByRunIds(input: {
|
|
152
|
+
readonly projectId: string
|
|
153
|
+
readonly runIds: readonly string[]
|
|
154
|
+
}): Promise<readonly AgentContextCheckpointRecord[]> {
|
|
155
|
+
const runIds = [...new Set(input.runIds)]
|
|
156
|
+
if (runIds.length === 0) return []
|
|
157
|
+
|
|
158
|
+
const rows: AgentContextCheckpointRow[] = []
|
|
159
|
+
for (let offset = 0; offset < runIds.length; offset += SQLITE_CHECKPOINT_RUN_ID_BATCH_SIZE) {
|
|
160
|
+
const batch = runIds.slice(offset, offset + SQLITE_CHECKPOINT_RUN_ID_BATCH_SIZE)
|
|
161
|
+
const placeholders = batch.map(() => "?").join(", ")
|
|
162
|
+
rows.push(
|
|
163
|
+
...(this.db
|
|
164
|
+
.query(
|
|
165
|
+
`SELECT * FROM agent_context_checkpoints WHERE project_id = ? AND created_by_run_id IN (${placeholders})`
|
|
166
|
+
)
|
|
167
|
+
.all(input.projectId, ...batch) as AgentContextCheckpointRow[])
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
const byRunId = new Map(
|
|
171
|
+
rows.map((row) => [row.created_by_run_id, rowToContextCheckpointRecord(row)] as const)
|
|
172
|
+
)
|
|
173
|
+
return runIds.flatMap((runId) => {
|
|
174
|
+
const checkpoint = byRunId.get(runId)
|
|
175
|
+
return checkpoint ? [checkpoint] : []
|
|
176
|
+
})
|
|
177
|
+
}
|
|
178
|
+
|
|
149
179
|
private findLatest(projectId: string, threadId: string): AgentContextCheckpointRecord | null {
|
|
150
180
|
const row = this.db
|
|
151
181
|
.query(
|
package/src/ai-usage-storage.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
AiUsageStorageError,
|
|
8
8
|
aggregateAiModelCallUsage,
|
|
9
9
|
assertAiUsageExecutionId,
|
|
10
|
+
type GetLatestAiModelCallForExecutionInput,
|
|
10
11
|
normalizeAiModelCallRecord,
|
|
11
12
|
type RecordAiModelCallInput,
|
|
12
13
|
type RecordAiModelCallResult,
|
|
@@ -84,6 +85,24 @@ export class SqliteAiUsageStorage implements AiUsageStorage {
|
|
|
84
85
|
return summary!
|
|
85
86
|
}
|
|
86
87
|
|
|
88
|
+
async getLatestForExecution(
|
|
89
|
+
input: GetLatestAiModelCallForExecutionInput
|
|
90
|
+
): Promise<AiModelCallUsageRecord | null> {
|
|
91
|
+
assertNonBlankProjectId(input.projectId)
|
|
92
|
+
assertAiUsageExecutionId(input.executionId)
|
|
93
|
+
const row = this.connection.db
|
|
94
|
+
.query(
|
|
95
|
+
`
|
|
96
|
+
SELECT * FROM ai_model_call_usage
|
|
97
|
+
WHERE project_id = ? AND execution_id = ?
|
|
98
|
+
ORDER BY occurred_at DESC, id DESC
|
|
99
|
+
LIMIT 1
|
|
100
|
+
`
|
|
101
|
+
)
|
|
102
|
+
.get(input.projectId, input.executionId) as AiUsageRow | null
|
|
103
|
+
return row ? this.rowToRecord(row) : null
|
|
104
|
+
}
|
|
105
|
+
|
|
87
106
|
async summarizeExecutions(
|
|
88
107
|
input: SummarizeAiUsageExecutionsInput
|
|
89
108
|
): Promise<readonly AiUsageExecutionSummary[]> {
|
|
@@ -98,6 +98,8 @@ function compileObjectQueryInternal(
|
|
|
98
98
|
switch (query.kind) {
|
|
99
99
|
case "start":
|
|
100
100
|
return compileStart(projectId, query)
|
|
101
|
+
case "refs":
|
|
102
|
+
return compileRefs(projectId, query)
|
|
101
103
|
case "filter":
|
|
102
104
|
return compileFilter(projectId, query.input, query.predicate)
|
|
103
105
|
case "sort":
|
|
@@ -162,6 +164,54 @@ function compileStart(
|
|
|
162
164
|
}
|
|
163
165
|
}
|
|
164
166
|
|
|
167
|
+
function compileRefs(
|
|
168
|
+
projectId: string,
|
|
169
|
+
query: Extract<ObjectQuery, { kind: "refs" }>
|
|
170
|
+
): CompiledObjectQuery {
|
|
171
|
+
if (query.refs.length === 0) {
|
|
172
|
+
throw new Error("[Sixb] SQLite object storage requires at least one ref")
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const order = compileOrder(identityOrderFields())
|
|
176
|
+
const selectedOrder = compileOrder(identityOrderFields(), "selected")
|
|
177
|
+
const refsJson = JSON.stringify(query.refs)
|
|
178
|
+
const requested = `
|
|
179
|
+
SELECT DISTINCT
|
|
180
|
+
json_extract(ref.value, '$.objectTypeId') AS object_type_id,
|
|
181
|
+
json_extract(ref.value, '$.primaryId') AS primary_id
|
|
182
|
+
FROM json_each(?) AS ref
|
|
183
|
+
`
|
|
184
|
+
const sql = `
|
|
185
|
+
WITH requested AS (${requested})
|
|
186
|
+
SELECT selected.*, selected.properties AS _cursor_properties
|
|
187
|
+
FROM requested
|
|
188
|
+
JOIN objects AS selected
|
|
189
|
+
ON selected.project_id = ?
|
|
190
|
+
AND selected.object_type_id = requested.object_type_id
|
|
191
|
+
AND selected.primary_id = requested.primary_id
|
|
192
|
+
ORDER BY ${selectedOrder.sql}
|
|
193
|
+
`
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
sql,
|
|
197
|
+
args: [refsJson, projectId, ...selectedOrder.args],
|
|
198
|
+
totalSql: `
|
|
199
|
+
WITH requested AS (${requested})
|
|
200
|
+
SELECT COUNT(*) AS total
|
|
201
|
+
FROM requested
|
|
202
|
+
JOIN objects AS selected
|
|
203
|
+
ON selected.project_id = ?
|
|
204
|
+
AND selected.object_type_id = requested.object_type_id
|
|
205
|
+
AND selected.primary_id = requested.primary_id
|
|
206
|
+
`,
|
|
207
|
+
totalArgs: [refsJson, projectId],
|
|
208
|
+
order,
|
|
209
|
+
hasMore: () => false,
|
|
210
|
+
trimRows: identityRows,
|
|
211
|
+
nextPageToken: () => undefined,
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
165
215
|
function compileFilter(
|
|
166
216
|
projectId: string,
|
|
167
217
|
inputQuery: ObjectQuery,
|
package/src/object-storage.ts
CHANGED
|
@@ -9,16 +9,21 @@ import type {
|
|
|
9
9
|
ExpandedObjectRow,
|
|
10
10
|
FacetObjectsInput,
|
|
11
11
|
FacetObjectsResult,
|
|
12
|
+
LinkBatchKey,
|
|
12
13
|
LinkDirection,
|
|
14
|
+
ObjectBatchKey,
|
|
13
15
|
ObjectFacetRequest,
|
|
14
16
|
ObjectLinkRow,
|
|
15
17
|
ObjectQueryCapabilities,
|
|
16
18
|
ObjectRow,
|
|
17
19
|
ObjectRowLinks,
|
|
18
20
|
ObjectStorage,
|
|
21
|
+
QueryObjectLinksInput,
|
|
22
|
+
QueryObjectLinksResult,
|
|
19
23
|
QueryObjectsInput,
|
|
20
24
|
QueryObjectsResult,
|
|
21
25
|
} from "@sixb/core/storage"
|
|
26
|
+
import { linkBatchKey, objectBatchKey } from "@sixb/core/storage"
|
|
22
27
|
import { installFreshSqliteSchema } from "./migrations"
|
|
23
28
|
import { type CompiledObjectQuery, compileObjectQuery } from "./object-query-compiler"
|
|
24
29
|
import {
|
|
@@ -41,6 +46,7 @@ const SQLITE_OBJECT_QUERY_CAPABILITIES: ObjectQueryCapabilities = {
|
|
|
41
46
|
facetObjects: true,
|
|
42
47
|
nodes: {
|
|
43
48
|
start: true,
|
|
49
|
+
refs: true,
|
|
44
50
|
filter: true,
|
|
45
51
|
text: true,
|
|
46
52
|
sort: true,
|
|
@@ -92,7 +98,7 @@ const SQLITE_OBJECT_QUERY_CAPABILITIES: ObjectQueryCapabilities = {
|
|
|
92
98
|
stablePageTokens: true,
|
|
93
99
|
},
|
|
94
100
|
notes: [
|
|
95
|
-
"SQLite object query pushdown supports start/filter/text/sort/limit/page/traverse/set/project/expand over JSON properties and object links.",
|
|
101
|
+
"SQLite object query pushdown supports start/refs/filter/text/sort/limit/page/traverse/set/project/expand over JSON properties and object links.",
|
|
96
102
|
"expand hydrates linked objects in-database (top-N per parent via row_number() + json_group_array); core resolves each expansion's cardinality before pushdown, and a mixed/unresolved one stays on the fallback.",
|
|
97
103
|
"Ordered decimal predicates and sorting use the bounded core fallback because SQLite has no native exact decimal type; canonical decimal equality remains pushdown-safe.",
|
|
98
104
|
"Relevance sorting, vector search, and unresolved start.includeSubtypes remain planner fallback or rejection cases.",
|
|
@@ -219,22 +225,24 @@ export class SqliteObjectStorage implements ObjectStorage {
|
|
|
219
225
|
async getByPrimaryIdBatch(params: {
|
|
220
226
|
projectId: string
|
|
221
227
|
items: readonly { objectTypeId: string; primaryId: string }[]
|
|
222
|
-
}): Promise<Map<
|
|
223
|
-
const result = new Map<
|
|
228
|
+
}): Promise<Map<ObjectBatchKey, ObjectRow>> {
|
|
229
|
+
const result = new Map<ObjectBatchKey, ObjectRow>()
|
|
224
230
|
if (params.items.length === 0) return result
|
|
225
231
|
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
232
|
+
const rows = this.db
|
|
233
|
+
.query(
|
|
234
|
+
`
|
|
235
|
+
SELECT object.*
|
|
236
|
+
FROM json_each(?) AS requested
|
|
237
|
+
JOIN objects AS object
|
|
238
|
+
ON object.project_id = ?
|
|
239
|
+
AND object.object_type_id = json_extract(requested.value, '$.objectTypeId')
|
|
240
|
+
AND object.primary_id = json_extract(requested.value, '$.primaryId')
|
|
241
|
+
`
|
|
242
|
+
)
|
|
243
|
+
.all(JSON.stringify(params.items), params.projectId) as DatabaseRow[]
|
|
244
|
+
for (const row of rows) {
|
|
245
|
+
result.set(objectBatchKey(row.object_type_id, row.primary_id), this.rowToObject(row))
|
|
238
246
|
}
|
|
239
247
|
return result
|
|
240
248
|
}
|
|
@@ -242,8 +250,8 @@ export class SqliteObjectStorage implements ObjectStorage {
|
|
|
242
250
|
async listLinksBatch(params: {
|
|
243
251
|
projectId: string
|
|
244
252
|
items: readonly { objectTypeId: string; objectId: string; linkId: string }[]
|
|
245
|
-
}): Promise<Map<
|
|
246
|
-
const result = new Map<
|
|
253
|
+
}): Promise<Map<LinkBatchKey, ObjectLinkRow[]>> {
|
|
254
|
+
const result = new Map<LinkBatchKey, ObjectLinkRow[]>()
|
|
247
255
|
if (params.items.length === 0) return result
|
|
248
256
|
|
|
249
257
|
const stmt = this.db.query(
|
|
@@ -258,7 +266,7 @@ export class SqliteObjectStorage implements ObjectStorage {
|
|
|
258
266
|
) as LinkDatabaseRow[]
|
|
259
267
|
if (rows.length > 0) {
|
|
260
268
|
result.set(
|
|
261
|
-
|
|
269
|
+
linkBatchKey(item.objectTypeId, item.objectId, item.linkId),
|
|
262
270
|
rows.map((r) => this.rowToLink(r))
|
|
263
271
|
)
|
|
264
272
|
}
|
|
@@ -266,26 +274,116 @@ export class SqliteObjectStorage implements ObjectStorage {
|
|
|
266
274
|
return result
|
|
267
275
|
}
|
|
268
276
|
|
|
277
|
+
async queryLinks(params: QueryObjectLinksInput): Promise<QueryObjectLinksResult> {
|
|
278
|
+
assertLinkQueryLimit(params.limit)
|
|
279
|
+
if (params.objectRefs.length === 0 || params.endpointObjectTypeIds?.length === 0) {
|
|
280
|
+
return { links: [], hasMore: false }
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const sourceJoin = `
|
|
284
|
+
SELECT link.*
|
|
285
|
+
FROM links AS link
|
|
286
|
+
JOIN requested
|
|
287
|
+
ON requested.object_type_id = link.source_type_id
|
|
288
|
+
AND requested.object_id = link.source_id
|
|
289
|
+
WHERE link.project_id = ?
|
|
290
|
+
`
|
|
291
|
+
const targetJoin = `
|
|
292
|
+
SELECT link.*
|
|
293
|
+
FROM links AS link
|
|
294
|
+
JOIN requested
|
|
295
|
+
ON requested.object_type_id = link.target_type_id
|
|
296
|
+
AND requested.object_id = link.target_id
|
|
297
|
+
WHERE link.project_id = ?
|
|
298
|
+
`
|
|
299
|
+
const incidentSql =
|
|
300
|
+
params.direction === "outgoing"
|
|
301
|
+
? sourceJoin
|
|
302
|
+
: params.direction === "incoming"
|
|
303
|
+
? targetJoin
|
|
304
|
+
: `${sourceJoin} UNION ${targetJoin}`
|
|
305
|
+
const args: (string | number)[] = [JSON.stringify(params.objectRefs), params.projectId]
|
|
306
|
+
if (params.direction === "both") args.push(params.projectId)
|
|
307
|
+
|
|
308
|
+
const predicates: string[] = []
|
|
309
|
+
if (params.linkId !== undefined) {
|
|
310
|
+
predicates.push("link_id = ?")
|
|
311
|
+
args.push(params.linkId)
|
|
312
|
+
}
|
|
313
|
+
if (params.endpointObjectTypeIds !== undefined) {
|
|
314
|
+
const allowedTypes = JSON.stringify([...new Set(params.endpointObjectTypeIds)])
|
|
315
|
+
predicates.push(
|
|
316
|
+
"source_type_id IN (SELECT value FROM json_each(?))",
|
|
317
|
+
"target_type_id IN (SELECT value FROM json_each(?))"
|
|
318
|
+
)
|
|
319
|
+
args.push(allowedTypes, allowedTypes)
|
|
320
|
+
}
|
|
321
|
+
if (params.after) {
|
|
322
|
+
predicates.push(
|
|
323
|
+
"(source_type_id, source_id, link_id, target_type_id, target_id) > (?, ?, ?, ?, ?)"
|
|
324
|
+
)
|
|
325
|
+
args.push(...params.after)
|
|
326
|
+
}
|
|
327
|
+
args.push(params.limit + 1)
|
|
328
|
+
|
|
329
|
+
const whereSql = predicates.length > 0 ? `WHERE ${predicates.join(" AND ")}` : ""
|
|
330
|
+
const rows = this.db
|
|
331
|
+
.query(
|
|
332
|
+
`
|
|
333
|
+
WITH requested AS (
|
|
334
|
+
SELECT DISTINCT
|
|
335
|
+
json_extract(value, '$.objectTypeId') AS object_type_id,
|
|
336
|
+
json_extract(value, '$.primaryId') AS object_id
|
|
337
|
+
FROM json_each(?)
|
|
338
|
+
), incident AS (${incidentSql})
|
|
339
|
+
SELECT *
|
|
340
|
+
FROM incident
|
|
341
|
+
${whereSql}
|
|
342
|
+
ORDER BY source_type_id, source_id, link_id, target_type_id, target_id
|
|
343
|
+
LIMIT ?
|
|
344
|
+
`
|
|
345
|
+
)
|
|
346
|
+
.all(...args) as LinkDatabaseRow[]
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
links: rows.slice(0, params.limit).map((row) => this.rowToLink(row)),
|
|
350
|
+
hasMore: rows.length > params.limit,
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
269
354
|
async listIncidentLinksBatch(params: {
|
|
270
355
|
projectId: string
|
|
271
356
|
items: readonly { objectTypeId: string; objectId: string }[]
|
|
272
357
|
}): Promise<readonly ObjectLinkRow[]> {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
358
|
+
if (params.items.length === 0) return []
|
|
359
|
+
|
|
360
|
+
const rows = this.db
|
|
361
|
+
.query(
|
|
362
|
+
`
|
|
363
|
+
WITH requested AS (
|
|
364
|
+
SELECT
|
|
365
|
+
json_extract(value, '$.objectTypeId') AS object_type_id,
|
|
366
|
+
json_extract(value, '$.objectId') AS object_id
|
|
367
|
+
FROM json_each(?)
|
|
368
|
+
)
|
|
369
|
+
SELECT link.*
|
|
370
|
+
FROM links AS link
|
|
371
|
+
JOIN requested
|
|
372
|
+
ON requested.object_type_id = link.source_type_id
|
|
373
|
+
AND requested.object_id = link.source_id
|
|
374
|
+
WHERE link.project_id = ?
|
|
375
|
+
UNION
|
|
376
|
+
SELECT link.*
|
|
377
|
+
FROM links AS link
|
|
378
|
+
JOIN requested
|
|
379
|
+
ON requested.object_type_id = link.target_type_id
|
|
380
|
+
AND requested.object_id = link.target_id
|
|
381
|
+
WHERE link.project_id = ?
|
|
382
|
+
`
|
|
383
|
+
)
|
|
384
|
+
.all(JSON.stringify(params.items), params.projectId, params.projectId) as LinkDatabaseRow[]
|
|
385
|
+
|
|
386
|
+
return rows.map((row) => this.rowToLink(row))
|
|
289
387
|
}
|
|
290
388
|
|
|
291
389
|
async listByPrimaryIdPage(params: {
|
|
@@ -457,6 +555,12 @@ function assertReconciliationPageLimit(limit: number): void {
|
|
|
457
555
|
}
|
|
458
556
|
}
|
|
459
557
|
|
|
558
|
+
function assertLinkQueryLimit(limit: number): void {
|
|
559
|
+
if (!Number.isSafeInteger(limit) || limit <= 0) {
|
|
560
|
+
throw new Error("Object link query limit must be a positive safe integer.")
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
460
564
|
function readTotal(db: Database, compiled: CompiledObjectQuery): number {
|
|
461
565
|
const row = db.query(compiled.totalSql).get(...compiled.totalArgs) as { total: number }
|
|
462
566
|
return row.total
|