@shawnstack/quickforge 1.7.6 → 1.7.8
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/assets/AgentProfilesPage-BSM5w3bn.js +1 -0
- package/dist/assets/ChatPanelHost-cd80S4EX.js +48 -0
- package/dist/assets/CloudAccountSettingsPage-BJMrfgy9.js +1 -0
- package/dist/assets/{PluginsPage-BDKYfLna.js → PluginsPage-mJkTmVVV.js} +1 -1
- package/dist/assets/ScheduledTasksPage-DJj_NLm9.js +2 -0
- package/dist/assets/{SettingsWorkspacePage-DSy66Tuw.js → SettingsWorkspacePage-7lQ0Wl63.js} +391 -391
- package/dist/assets/ShareLinksSettingsPage-DjFq_Lnb.js +1 -0
- package/dist/assets/SharedConversationPage-BbuNpcpP.js +1 -0
- package/dist/assets/TerminalDock-CczcCJJE.js +2 -0
- package/dist/assets/WorkspaceInspector-0i6x-diB.js +36 -0
- package/dist/assets/{icons-2D18YbSI.js → icons-DM_qJ7VJ.js} +1 -1
- package/dist/assets/index-D-J_8Smf.css +3 -0
- package/dist/assets/index-DFASc15l.js +66 -0
- package/dist/assets/local-tools-Dn1Y9aPe.js +270 -0
- package/dist/assets/{model-display-label-C6-0eDxF.js → logger-8REuLPGK.js} +1 -1
- package/dist/assets/{mcp-servers-dialog-BEV4_TXC.js → mcp-servers-dialog-BG4b67ea.js} +2 -2
- package/dist/assets/{model-reference-o41fJ6jQ.js → model-display-label-CURwH0xM.js} +1 -1
- package/dist/assets/{monaco-DvXErxp2.js → monaco-bDr860xx.js} +1 -1
- package/dist/assets/{react-vendor-3UaA-C9W.js → react-vendor-BpWW_Nvp.js} +1 -1
- package/dist/assets/{skills-dialog-BTJ7mzk_.js → skills-dialog-DIzZvQqW.js} +3 -3
- package/dist/index.html +8 -8
- package/package.json +1 -1
- package/server/acp/server.mjs +17 -1
- package/server/agent-manager.mjs +251 -31
- package/server/ai-http-logger.mjs +162 -19
- package/server/ai-provider-options.mjs +4 -1
- package/server/auto-archive.mjs +42 -29
- package/server/auto-compaction.mjs +43 -1
- package/server/cloud/auto-approval.mjs +155 -0
- package/server/cloud/client.mjs +5 -0
- package/server/cloud/config.mjs +8 -16
- package/server/cloud/qf-agent-process.mjs +163 -3
- package/server/cloud/service-config.mjs +1 -1
- package/server/context-usage.mjs +11 -4
- package/server/index.mjs +69 -13
- package/server/lan-access-backup.mjs +256 -0
- package/server/lan-access-cutover.mjs +306 -0
- package/server/lan-access-json-file.mjs +75 -0
- package/server/lan-access-service.mjs +155 -0
- package/server/lan-access-store.mjs +110 -9
- package/server/maintenance/downgrade-lan-access-v1.mjs +101 -0
- package/server/maintenance/downgrade-session-state-v1.mjs +130 -0
- package/server/maintenance/downgrade-share-v1.mjs +101 -0
- package/server/maintenance/export-lan-access-v1.mjs +80 -0
- package/server/maintenance/export-scheduled-runs-v1.mjs +45 -0
- package/server/maintenance/export-session-state-v1.mjs +67 -0
- package/server/maintenance/export-share-v1.mjs +77 -0
- package/server/routes/agent.mjs +37 -4
- package/server/routes/backup.mjs +221 -39
- package/server/routes/cloud.mjs +28 -1
- package/server/routes/scheduled-tasks.mjs +347 -134
- package/server/routes/shares.mjs +4 -2
- package/server/routes/storage.mjs +138 -8
- package/server/routes/workspace.mjs +265 -4
- package/server/scheduled-runs-backup.mjs +235 -0
- package/server/scheduled-runs-cutover.mjs +497 -0
- package/server/scheduled-task-runs-service.mjs +279 -0
- package/server/session-index-service.mjs +495 -0
- package/server/session-state-backup.mjs +277 -0
- package/server/session-state-cutover.mjs +370 -0
- package/server/session-state-service.mjs +709 -0
- package/server/share-backup.mjs +255 -0
- package/server/share-cutover.mjs +296 -0
- package/server/share-json-file.mjs +60 -0
- package/server/share-service.mjs +163 -0
- package/server/share-store.mjs +196 -19
- package/server/skills.mjs +0 -1
- package/server/sqlite/database.mjs +233 -0
- package/server/sqlite/lan-access-repository.mjs +652 -0
- package/server/sqlite/migrations.mjs +577 -0
- package/server/sqlite/scheduled-task-runs-repository.mjs +464 -0
- package/server/sqlite/session-index-repository.mjs +297 -0
- package/server/sqlite/session-state-repository.mjs +786 -0
- package/server/sqlite/share-repository.mjs +854 -0
- package/server/storage.mjs +317 -1
- package/dist/assets/AgentProfilesPage-Dl5Peqov.js +0 -1
- package/dist/assets/ChatPanelHost-D3987TUJ.js +0 -307
- package/dist/assets/CloudAccountSettingsPage-BG5Nn3bq.js +0 -1
- package/dist/assets/ScheduledTasksPage--GAki3lB.js +0 -2
- package/dist/assets/ShareLinksSettingsPage-BQQMsvJo.js +0 -1
- package/dist/assets/SharedConversationPage-B9BUVCri.js +0 -1
- package/dist/assets/TerminalDock-CT1nYo7P.js +0 -2
- package/dist/assets/WorkspaceInspector-BnfDLf9a.js +0 -36
- package/dist/assets/index-1ei6nhs-.css +0 -3
- package/dist/assets/index-BivLuZvX.js +0 -65
- package/dist/assets/mermaid-renderer-jEbqFaTe.js +0 -2
- /package/dist/assets/{plugin-api-Uu2xAB4d.js → plugin-api-BrdQeM4B.js} +0 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { getSqliteStorage } from './database.mjs'
|
|
2
|
+
|
|
3
|
+
const SELECT_COLUMNS = `
|
|
4
|
+
scope, project_id, session_id, created_at, last_modified, message_count,
|
|
5
|
+
pinned_at, archived_at, is_pinned, is_archived, state_version,
|
|
6
|
+
metadata_json, metadata_digest, indexed_at
|
|
7
|
+
`
|
|
8
|
+
|
|
9
|
+
function assertStorage(storage) {
|
|
10
|
+
if (!storage || typeof storage.prepare !== 'function' || typeof storage.transaction !== 'function') {
|
|
11
|
+
throw new TypeError('Session index repository requires a SQLite storage handle')
|
|
12
|
+
}
|
|
13
|
+
return storage
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function assertNonEmptyString(value, field) {
|
|
17
|
+
if (typeof value !== 'string' || value.trim().length === 0) throw new TypeError(`${field} must be a non-empty string`)
|
|
18
|
+
return value
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function normalizeBucket(scope, projectId) {
|
|
22
|
+
if (scope === 'global') return { scope, projectId: '' }
|
|
23
|
+
if (scope !== 'project') throw new TypeError('scope must be global or project')
|
|
24
|
+
return { scope, projectId: assertNonEmptyString(projectId, 'projectId') }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function assertPlainObject(value, field) {
|
|
28
|
+
if (!value || typeof value !== 'object' || Array.isArray(value) || Object.getPrototypeOf(value) !== Object.prototype) {
|
|
29
|
+
throw new TypeError(`${field} must be a plain object`)
|
|
30
|
+
}
|
|
31
|
+
return value
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function nullableString(value, field) {
|
|
35
|
+
if (value === undefined || value === null) return null
|
|
36
|
+
if (typeof value !== 'string') throw new TypeError(`${field} must be a string or null`)
|
|
37
|
+
return value
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function nullableInteger(value, field) {
|
|
41
|
+
if (value === undefined || value === null) return null
|
|
42
|
+
if (!Number.isInteger(value) || value < 0) throw new TypeError(`${field} must be a non-negative integer or null`)
|
|
43
|
+
return value
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function normalizeRow(row) {
|
|
47
|
+
if (!row || typeof row !== 'object' || Array.isArray(row)) throw new TypeError('row must be an object')
|
|
48
|
+
const bucket = normalizeBucket(row.scope, row.projectId)
|
|
49
|
+
const metadata = assertPlainObject(row.metadata, 'metadata')
|
|
50
|
+
let metadataJson
|
|
51
|
+
try {
|
|
52
|
+
metadataJson = JSON.stringify(metadata)
|
|
53
|
+
} catch {
|
|
54
|
+
throw new TypeError('metadata must be JSON-serializable')
|
|
55
|
+
}
|
|
56
|
+
if (typeof row.metadataDigest !== 'string' || !/^[0-9a-f]{64}$/.test(row.metadataDigest)) {
|
|
57
|
+
throw new TypeError('metadataDigest must be a lowercase SHA-256 hex digest')
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
...bucket,
|
|
61
|
+
sessionId: assertNonEmptyString(row.sessionId, 'sessionId'),
|
|
62
|
+
createdAt: nullableString(row.createdAt, 'createdAt'),
|
|
63
|
+
lastModified: nullableString(row.lastModified, 'lastModified'),
|
|
64
|
+
messageCount: nullableInteger(row.messageCount, 'messageCount'),
|
|
65
|
+
pinnedAt: nullableString(row.pinnedAt, 'pinnedAt'),
|
|
66
|
+
archivedAt: nullableString(row.archivedAt, 'archivedAt'),
|
|
67
|
+
isPinned: row.pinnedAt ? 1 : 0,
|
|
68
|
+
isArchived: row.archivedAt ? 1 : 0,
|
|
69
|
+
stateVersion: nullableInteger(row.stateVersion, 'stateVersion'),
|
|
70
|
+
metadataJson,
|
|
71
|
+
metadataDigest: row.metadataDigest,
|
|
72
|
+
indexedAt: assertNonEmptyString(row.indexedAt, 'indexedAt'),
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function mapRow(row) {
|
|
77
|
+
if (!row) return null
|
|
78
|
+
return {
|
|
79
|
+
scope: row.scope,
|
|
80
|
+
projectId: row.scope === 'project' ? row.project_id : null,
|
|
81
|
+
sessionId: row.session_id,
|
|
82
|
+
createdAt: row.created_at,
|
|
83
|
+
lastModified: row.last_modified,
|
|
84
|
+
messageCount: row.message_count,
|
|
85
|
+
pinnedAt: row.pinned_at,
|
|
86
|
+
archivedAt: row.archived_at,
|
|
87
|
+
isPinned: row.is_pinned === 1,
|
|
88
|
+
isArchived: row.is_archived === 1,
|
|
89
|
+
stateVersion: row.state_version,
|
|
90
|
+
metadata: JSON.parse(row.metadata_json),
|
|
91
|
+
metadataDigest: row.metadata_digest,
|
|
92
|
+
indexedAt: row.indexed_at,
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function rowParameters(row) {
|
|
97
|
+
return [
|
|
98
|
+
row.scope, row.projectId, row.sessionId, row.createdAt, row.lastModified,
|
|
99
|
+
row.messageCount, row.pinnedAt, row.archivedAt, row.isPinned, row.isArchived,
|
|
100
|
+
row.stateVersion, row.metadataJson, row.metadataDigest, row.indexedAt,
|
|
101
|
+
]
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function normalizeQuery(options = {}) {
|
|
105
|
+
const scopeMode = options.scopeMode ?? 'all'
|
|
106
|
+
if (!['all', 'global', 'project', 'projects'].includes(scopeMode)) throw new TypeError('scopeMode is invalid')
|
|
107
|
+
const projectId = scopeMode === 'project' ? assertNonEmptyString(options.projectId, 'projectId') : null
|
|
108
|
+
const archive = options.archive ?? 'exclude'
|
|
109
|
+
if (!['exclude', 'only', 'include'].includes(archive)) throw new TypeError('archive is invalid')
|
|
110
|
+
const sort = options.sort ?? 'lastModified'
|
|
111
|
+
if (!['createdAt', 'lastModified', 'pinnedAt'].includes(sort)) throw new TypeError('sort is invalid')
|
|
112
|
+
const direction = options.direction ?? 'desc'
|
|
113
|
+
if (!['asc', 'desc'].includes(direction)) throw new TypeError('direction is invalid')
|
|
114
|
+
const limit = options.limit
|
|
115
|
+
const offset = options.offset ?? 0
|
|
116
|
+
if (!Number.isSafeInteger(limit) || limit <= 0) throw new TypeError('limit must be a positive safe integer')
|
|
117
|
+
if (!Number.isSafeInteger(offset) || offset < 0) throw new TypeError('offset must be a non-negative safe integer')
|
|
118
|
+
return { scopeMode, projectId, archive, pinnedOnly: options.pinnedOnly === true, sort, direction, limit, offset }
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const SORT_COLUMNS = Object.freeze({ createdAt: 'created_at', lastModified: 'last_modified', pinnedAt: 'pinned_at' })
|
|
122
|
+
|
|
123
|
+
function buildQuery(options, { page = true } = {}) {
|
|
124
|
+
const query = normalizeQuery(options)
|
|
125
|
+
const where = ['(message_count IS NULL OR message_count <> 0)']
|
|
126
|
+
const parameters = []
|
|
127
|
+
if (query.scopeMode === 'global') where.push("scope = 'global' AND project_id = ''")
|
|
128
|
+
else if (query.scopeMode === 'project') {
|
|
129
|
+
where.push("scope = 'project' AND project_id = ?")
|
|
130
|
+
parameters.push(query.projectId)
|
|
131
|
+
} else if (query.scopeMode === 'projects') where.push("scope = 'project'")
|
|
132
|
+
if (query.archive === 'exclude') where.push('is_archived = 0')
|
|
133
|
+
else if (query.archive === 'only') where.push('is_archived = 1')
|
|
134
|
+
if (query.pinnedOnly) where.push('is_pinned = 1')
|
|
135
|
+
|
|
136
|
+
const column = SORT_COLUMNS[query.sort]
|
|
137
|
+
const order = query.sort === 'pinnedAt'
|
|
138
|
+
? `${column} ${query.direction.toUpperCase()}`
|
|
139
|
+
: `is_pinned DESC, pinned_at DESC, ${column} ${query.direction.toUpperCase()}`
|
|
140
|
+
const whereSql = `WHERE ${where.join(' AND ')}`
|
|
141
|
+
return {
|
|
142
|
+
query,
|
|
143
|
+
parameters,
|
|
144
|
+
whereSql,
|
|
145
|
+
order,
|
|
146
|
+
rowsSql: `SELECT ${SELECT_COLUMNS} FROM session_index ${whereSql} ORDER BY ${order}${page ? ' LIMIT ? OFFSET ?' : ''}`,
|
|
147
|
+
countSql: `SELECT COUNT(*) AS count FROM session_index ${whereSql}`,
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function analyzeInto(database, options) {
|
|
152
|
+
const built = buildQuery(options, { page: false })
|
|
153
|
+
const aggregate = built.query.scopeMode === 'all' || built.query.scopeMode === 'projects'
|
|
154
|
+
const duplicateSessionIdCount = aggregate
|
|
155
|
+
? Number(database.prepare(`SELECT COUNT(*) AS count FROM (SELECT session_id FROM session_index ${built.whereSql} GROUP BY session_id HAVING COUNT(*) > 1)`).get(...built.parameters).count)
|
|
156
|
+
: 0
|
|
157
|
+
const column = SORT_COLUMNS[built.query.sort]
|
|
158
|
+
const tieColumns = built.query.sort === 'pinnedAt' ? column : `is_pinned, pinned_at, ${column}`
|
|
159
|
+
const fullSortKeyTieCount = Number(database.prepare(`SELECT COUNT(*) AS count FROM (SELECT ${tieColumns} FROM session_index ${built.whereSql} GROUP BY ${tieColumns} HAVING COUNT(*) > 1)`).get(...built.parameters).count)
|
|
160
|
+
return { duplicateSessionIdCount, fullSortKeyTieCount }
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function createSessionIndexRepository(storageHandle) {
|
|
164
|
+
const storage = assertStorage(storageHandle ?? getSqliteStorage())
|
|
165
|
+
const upsertSql = `
|
|
166
|
+
INSERT INTO session_index (${SELECT_COLUMNS}) VALUES (${new Array(14).fill('?').join(', ')})
|
|
167
|
+
ON CONFLICT(scope, project_id, session_id) DO UPDATE SET
|
|
168
|
+
created_at = excluded.created_at,
|
|
169
|
+
last_modified = excluded.last_modified,
|
|
170
|
+
message_count = excluded.message_count,
|
|
171
|
+
pinned_at = excluded.pinned_at,
|
|
172
|
+
archived_at = excluded.archived_at,
|
|
173
|
+
is_pinned = excluded.is_pinned,
|
|
174
|
+
is_archived = excluded.is_archived,
|
|
175
|
+
state_version = excluded.state_version,
|
|
176
|
+
metadata_json = excluded.metadata_json,
|
|
177
|
+
metadata_digest = excluded.metadata_digest,
|
|
178
|
+
indexed_at = excluded.indexed_at
|
|
179
|
+
`
|
|
180
|
+
|
|
181
|
+
function upsertInto(database, row) {
|
|
182
|
+
const normalized = normalizeRow(row)
|
|
183
|
+
database.prepare(upsertSql).run(...rowParameters(normalized))
|
|
184
|
+
return normalized
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function get(scope, projectId, sessionId) {
|
|
188
|
+
const bucket = normalizeBucket(scope, projectId)
|
|
189
|
+
assertNonEmptyString(sessionId, 'sessionId')
|
|
190
|
+
return mapRow(storage.prepare(`SELECT ${SELECT_COLUMNS} FROM session_index WHERE scope = ? AND project_id = ? AND session_id = ?`)
|
|
191
|
+
.get(bucket.scope, bucket.projectId, sessionId))
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function upsert(row) {
|
|
195
|
+
const normalized = upsertInto(storage, row)
|
|
196
|
+
return get(normalized.scope, normalized.projectId, normalized.sessionId)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function deleteRow(scope, projectId, sessionId) {
|
|
200
|
+
const bucket = normalizeBucket(scope, projectId)
|
|
201
|
+
assertNonEmptyString(sessionId, 'sessionId')
|
|
202
|
+
return storage.prepare('DELETE FROM session_index WHERE scope = ? AND project_id = ? AND session_id = ?')
|
|
203
|
+
.run(bucket.scope, bucket.projectId, sessionId).changes > 0
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function applyChanges({ upserts = [], deletes = [] } = {}) {
|
|
207
|
+
if (!Array.isArray(upserts) || !Array.isArray(deletes)) throw new TypeError('upserts and deletes must be arrays')
|
|
208
|
+
const normalizedUpserts = upserts.map(normalizeRow)
|
|
209
|
+
const normalizedDeletes = deletes.map((entry) => ({
|
|
210
|
+
...normalizeBucket(entry?.scope, entry?.projectId),
|
|
211
|
+
sessionId: assertNonEmptyString(entry?.sessionId, 'sessionId'),
|
|
212
|
+
}))
|
|
213
|
+
return storage.transaction((database) => {
|
|
214
|
+
const deleteStatement = database.prepare('DELETE FROM session_index WHERE scope = ? AND project_id = ? AND session_id = ?')
|
|
215
|
+
for (const entry of normalizedDeletes) deleteStatement.run(entry.scope, entry.projectId, entry.sessionId)
|
|
216
|
+
for (const row of normalizedUpserts) database.prepare(upsertSql).run(...rowParameters(row))
|
|
217
|
+
return { upserted: normalizedUpserts.length, deleted: normalizedDeletes.length }
|
|
218
|
+
})
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function replaceBucket(scope, projectId, rows) {
|
|
222
|
+
const bucket = normalizeBucket(scope, projectId)
|
|
223
|
+
if (!Array.isArray(rows)) throw new TypeError('rows must be an array')
|
|
224
|
+
const normalizedRows = rows.map((row) => normalizeRow({ ...row, ...bucket }))
|
|
225
|
+
return storage.transaction((database) => {
|
|
226
|
+
database.prepare('DELETE FROM session_index WHERE scope = ? AND project_id = ?').run(bucket.scope, bucket.projectId)
|
|
227
|
+
for (const row of normalizedRows) database.prepare(upsertSql).run(...rowParameters(row))
|
|
228
|
+
return normalizedRows.length
|
|
229
|
+
})
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function replaceAll(rows) {
|
|
233
|
+
if (!Array.isArray(rows)) throw new TypeError('rows must be an array')
|
|
234
|
+
const normalizedRows = rows.map(normalizeRow)
|
|
235
|
+
const keys = new Set()
|
|
236
|
+
for (const row of normalizedRows) {
|
|
237
|
+
const key = `${row.scope}\0${row.projectId}\0${row.sessionId}`
|
|
238
|
+
if (keys.has(key)) throw new TypeError('Duplicate session index composite key')
|
|
239
|
+
keys.add(key)
|
|
240
|
+
}
|
|
241
|
+
return storage.transaction((database) => {
|
|
242
|
+
database.exec('DELETE FROM session_index')
|
|
243
|
+
for (const row of normalizedRows) database.prepare(upsertSql).run(...rowParameters(row))
|
|
244
|
+
return normalizedRows.length
|
|
245
|
+
})
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function count() {
|
|
249
|
+
return Number(storage.prepare('SELECT COUNT(*) AS count FROM session_index').get().count)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function listVerification() {
|
|
253
|
+
return storage.prepare(`SELECT scope, project_id, session_id, metadata_digest FROM session_index ORDER BY scope, project_id, session_id`)
|
|
254
|
+
.all()
|
|
255
|
+
.map((row) => ({
|
|
256
|
+
scope: row.scope,
|
|
257
|
+
projectId: row.scope === 'project' ? row.project_id : null,
|
|
258
|
+
sessionId: row.session_id,
|
|
259
|
+
metadataDigest: row.metadata_digest,
|
|
260
|
+
}))
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function listIntegrity() {
|
|
264
|
+
return storage.prepare(`SELECT ${SELECT_COLUMNS} FROM session_index ORDER BY scope, project_id, session_id`).all().map(mapRow)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function listAll() {
|
|
268
|
+
return storage.prepare(`SELECT ${SELECT_COLUMNS} FROM session_index ORDER BY scope, project_id, session_id`).all().map(mapRow)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function listPage(options) {
|
|
272
|
+
const built = buildQuery(options)
|
|
273
|
+
return storage.transaction((database) => {
|
|
274
|
+
const total = Number(database.prepare(built.countSql).get(...built.parameters).count)
|
|
275
|
+
const rows = database.prepare(built.rowsSql).all(...built.parameters, built.query.limit, built.query.offset).map(mapRow)
|
|
276
|
+
return { values: rows.map((row) => row.metadata), rows, total }
|
|
277
|
+
}, { mode: 'deferred' })
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function readMetadataEntries() {
|
|
281
|
+
return storage.prepare('SELECT metadata_json FROM session_index').all().map((row) => JSON.parse(row.metadata_json))
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function analyzeQuery(options) {
|
|
285
|
+
return analyzeInto(storage, options)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function explainQueryPlan(options) {
|
|
289
|
+
const built = buildQuery(options)
|
|
290
|
+
return storage.prepare(`EXPLAIN QUERY PLAN ${built.rowsSql}`).all(...built.parameters, built.query.limit, built.query.offset)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return Object.freeze({
|
|
294
|
+
get, upsert, delete: deleteRow, applyChanges, replaceBucket, replaceAll,
|
|
295
|
+
count, listVerification, listIntegrity, listAll, listPage, readMetadataEntries, analyzeQuery, explainQueryPlan,
|
|
296
|
+
})
|
|
297
|
+
}
|