@axiom-lattice/local-stores 2.0.9 → 3.0.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 +10 -10
- package/CHANGELOG.md +57 -0
- package/dist/index.d.mts +33 -4
- package/dist/index.d.ts +33 -4
- package/dist/index.js +515 -39
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +515 -39
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/LocalA2AApiKeyStore.test.ts +119 -0
- package/src/__tests__/LocalChannelInstallationStore.test.ts +38 -0
- package/src/__tests__/LocalProjectStore.test.ts +127 -0
- package/src/__tests__/LocalTaskStore.test.ts +284 -2
- package/src/__tests__/LocalTaskWorkItemStore.test.ts +215 -0
- package/src/database.ts +8 -6
- package/src/stores/LocalA2AApiKeyStore.ts +57 -14
- package/src/stores/LocalChannelInstallationStore.ts +1 -9
- package/src/stores/LocalProjectStore.ts +30 -4
- package/src/stores/LocalTaskStore.ts +208 -7
- package/src/stores/LocalTaskWorkItemStore.ts +47 -3
package/dist/index.js
CHANGED
|
@@ -96,8 +96,9 @@ var DatabaseWrapper = class {
|
|
|
96
96
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
97
97
|
run(sql, ...params) {
|
|
98
98
|
this.db.run(sql, params);
|
|
99
|
+
const changes = this.db.getRowsModified();
|
|
99
100
|
this.save();
|
|
100
|
-
return new RunResult(
|
|
101
|
+
return new RunResult(changes);
|
|
101
102
|
}
|
|
102
103
|
/**
|
|
103
104
|
* Prepare and execute a query returning all matching rows as objects.
|
|
@@ -182,19 +183,20 @@ var StatementWrapper = class {
|
|
|
182
183
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
183
184
|
run(...params) {
|
|
184
185
|
this.db.run(this.sql, params);
|
|
186
|
+
const changes = this.db.getRowsModified();
|
|
185
187
|
if (this.parent) {
|
|
186
188
|
this.parent.save();
|
|
187
189
|
}
|
|
188
|
-
return new RunResult(
|
|
190
|
+
return new RunResult(changes);
|
|
189
191
|
}
|
|
190
192
|
};
|
|
191
193
|
var RunResult = class {
|
|
192
|
-
constructor(
|
|
193
|
-
this.
|
|
194
|
+
constructor(modifiedRows) {
|
|
195
|
+
this.modifiedRows = modifiedRows;
|
|
194
196
|
}
|
|
195
197
|
/** Number of rows modified by the last INSERT/UPDATE/DELETE. */
|
|
196
198
|
get changes() {
|
|
197
|
-
return this.
|
|
199
|
+
return this.modifiedRows;
|
|
198
200
|
}
|
|
199
201
|
};
|
|
200
202
|
async function initDatabase(options = {}) {
|
|
@@ -662,6 +664,7 @@ CREATE TABLE IF NOT EXISTS lt_projects (
|
|
|
662
664
|
name TEXT NOT NULL,
|
|
663
665
|
description TEXT,
|
|
664
666
|
config TEXT,
|
|
667
|
+
kind TEXT DEFAULT 'business',
|
|
665
668
|
created_at TEXT NOT NULL,
|
|
666
669
|
updated_at TEXT NOT NULL,
|
|
667
670
|
PRIMARY KEY (tenant_id, id)
|
|
@@ -672,8 +675,15 @@ var LocalProjectStore = class {
|
|
|
672
675
|
constructor(db) {
|
|
673
676
|
this.db = db;
|
|
674
677
|
ensureTable(db, DDL4);
|
|
678
|
+
this.ensureColumn("lt_projects", "kind", "kind TEXT DEFAULT 'business'");
|
|
675
679
|
}
|
|
676
|
-
async getProjectsByWorkspace(tenantId, workspaceId) {
|
|
680
|
+
async getProjectsByWorkspace(tenantId, workspaceId, filter) {
|
|
681
|
+
if (filter?.kind !== void 0) {
|
|
682
|
+
const rows2 = this.db.prepare(
|
|
683
|
+
`SELECT * FROM lt_projects WHERE tenant_id = ? AND workspace_id = ? AND kind = ? ORDER BY created_at DESC`
|
|
684
|
+
).all(tenantId, workspaceId, filter.kind);
|
|
685
|
+
return rows2.map(mapRowToProject);
|
|
686
|
+
}
|
|
677
687
|
const rows = this.db.prepare(
|
|
678
688
|
`SELECT * FROM lt_projects WHERE tenant_id = ? AND workspace_id = ? ORDER BY created_at DESC`
|
|
679
689
|
).all(tenantId, workspaceId);
|
|
@@ -687,16 +697,18 @@ var LocalProjectStore = class {
|
|
|
687
697
|
}
|
|
688
698
|
async createProject(tenantId, workspaceId, id, data) {
|
|
689
699
|
const now = nowISO();
|
|
700
|
+
const kind = data.kind || "business";
|
|
690
701
|
this.db.prepare(
|
|
691
|
-
`INSERT INTO lt_projects (id, tenant_id, workspace_id, name, description, config, created_at, updated_at)
|
|
692
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
702
|
+
`INSERT INTO lt_projects (id, tenant_id, workspace_id, name, description, config, kind, created_at, updated_at)
|
|
703
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
693
704
|
ON CONFLICT(tenant_id, id) DO UPDATE SET
|
|
694
705
|
workspace_id = excluded.workspace_id,
|
|
695
706
|
name = excluded.name,
|
|
696
707
|
description = excluded.description,
|
|
697
708
|
config = excluded.config,
|
|
709
|
+
kind = excluded.kind,
|
|
698
710
|
updated_at = excluded.updated_at`
|
|
699
|
-
).run(id, tenantId, workspaceId, data.name, data.description || null, data.config ? JSON.stringify(data.config) : null, now, now);
|
|
711
|
+
).run(id, tenantId, workspaceId, data.name, data.description || null, data.config ? JSON.stringify(data.config) : null, kind, now, now);
|
|
700
712
|
return {
|
|
701
713
|
id,
|
|
702
714
|
tenantId,
|
|
@@ -704,6 +716,7 @@ var LocalProjectStore = class {
|
|
|
704
716
|
name: data.name,
|
|
705
717
|
description: data.description,
|
|
706
718
|
config: data.config,
|
|
719
|
+
kind,
|
|
707
720
|
createdAt: parseISO(now),
|
|
708
721
|
updatedAt: parseISO(now)
|
|
709
722
|
};
|
|
@@ -725,6 +738,10 @@ var LocalProjectStore = class {
|
|
|
725
738
|
setClauses.push("config = ?");
|
|
726
739
|
values.push(updates.config ? JSON.stringify(updates.config) : null);
|
|
727
740
|
}
|
|
741
|
+
if (updates.kind !== void 0) {
|
|
742
|
+
setClauses.push("kind = ?");
|
|
743
|
+
values.push(updates.kind);
|
|
744
|
+
}
|
|
728
745
|
if (setClauses.length === 0) return existing;
|
|
729
746
|
const now = nowISO();
|
|
730
747
|
setClauses.push("updated_at = ?");
|
|
@@ -741,6 +758,13 @@ var LocalProjectStore = class {
|
|
|
741
758
|
).run(tenantId, id);
|
|
742
759
|
return result.changes > 0;
|
|
743
760
|
}
|
|
761
|
+
/** Add a column if it does not exist (SQLite version compatible). */
|
|
762
|
+
ensureColumn(table, column, ddl) {
|
|
763
|
+
const cols = this.db.prepare(`PRAGMA table_info(${table})`).all();
|
|
764
|
+
if (!cols.some((c) => c.name === column)) {
|
|
765
|
+
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${ddl}`);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
744
768
|
};
|
|
745
769
|
function mapRowToProject(row) {
|
|
746
770
|
return {
|
|
@@ -750,6 +774,7 @@ function mapRowToProject(row) {
|
|
|
750
774
|
name: row.name,
|
|
751
775
|
description: row.description || void 0,
|
|
752
776
|
config: row.config ? JSON.parse(row.config) : void 0,
|
|
777
|
+
kind: row.kind || "business",
|
|
753
778
|
createdAt: parseISO(row.created_at),
|
|
754
779
|
updatedAt: parseISO(row.updated_at)
|
|
755
780
|
};
|
|
@@ -2843,15 +2868,7 @@ var LocalChannelInstallationStore = class {
|
|
|
2843
2868
|
this.db.prepare(
|
|
2844
2869
|
`INSERT INTO lt_channel_installations
|
|
2845
2870
|
(id, tenant_id, channel, name, config, enabled, fallback_agent_id, reject_when_no_binding, created_at, updated_at)
|
|
2846
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2847
|
-
ON CONFLICT(id) DO UPDATE SET
|
|
2848
|
-
channel = excluded.channel,
|
|
2849
|
-
name = excluded.name,
|
|
2850
|
-
config = excluded.config,
|
|
2851
|
-
enabled = excluded.enabled,
|
|
2852
|
-
fallback_agent_id = excluded.fallback_agent_id,
|
|
2853
|
-
reject_when_no_binding = excluded.reject_when_no_binding,
|
|
2854
|
-
updated_at = excluded.updated_at`
|
|
2871
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
2855
2872
|
).run(
|
|
2856
2873
|
installationId,
|
|
2857
2874
|
tenantId,
|
|
@@ -2943,7 +2960,7 @@ CREATE TABLE IF NOT EXISTS lt_a2a_api_keys (
|
|
|
2943
2960
|
key_value TEXT NOT NULL,
|
|
2944
2961
|
tenant_id TEXT NOT NULL,
|
|
2945
2962
|
project_id TEXT,
|
|
2946
|
-
|
|
2963
|
+
assistant_ids TEXT,
|
|
2947
2964
|
label TEXT,
|
|
2948
2965
|
enabled INTEGER NOT NULL DEFAULT 1,
|
|
2949
2966
|
created_at TEXT NOT NULL,
|
|
@@ -2953,10 +2970,41 @@ CREATE TABLE IF NOT EXISTS lt_a2a_api_keys (
|
|
|
2953
2970
|
function generateApiKey() {
|
|
2954
2971
|
return `a2a_${(0, import_crypto3.randomUUID)().replace(/-/g, "")}`;
|
|
2955
2972
|
}
|
|
2973
|
+
function parseAssistantIds(raw) {
|
|
2974
|
+
if (!raw) return void 0;
|
|
2975
|
+
try {
|
|
2976
|
+
const parsed = JSON.parse(raw);
|
|
2977
|
+
return Array.isArray(parsed) ? parsed : void 0;
|
|
2978
|
+
} catch {
|
|
2979
|
+
return void 0;
|
|
2980
|
+
}
|
|
2981
|
+
}
|
|
2956
2982
|
var LocalA2AApiKeyStore = class {
|
|
2957
2983
|
constructor(db) {
|
|
2958
2984
|
this.db = db;
|
|
2959
2985
|
ensureTable(db, DDL16);
|
|
2986
|
+
this.ensureColumn("lt_a2a_api_keys", "assistant_ids", "assistant_ids TEXT");
|
|
2987
|
+
this.repairManagementIds();
|
|
2988
|
+
}
|
|
2989
|
+
/** Add a column if it does not exist (SQLite version compatible). */
|
|
2990
|
+
ensureColumn(table, column, ddl) {
|
|
2991
|
+
const cols = this.db.prepare(`PRAGMA table_info(${table})`).all();
|
|
2992
|
+
if (!cols.some((c) => c.name === column)) {
|
|
2993
|
+
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${ddl}`);
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
repairManagementIds() {
|
|
2997
|
+
const rows = this.db.prepare(
|
|
2998
|
+
`SELECT rowid, id FROM lt_a2a_api_keys`
|
|
2999
|
+
).all();
|
|
3000
|
+
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
3001
|
+
for (const row of rows) {
|
|
3002
|
+
if (!row.id || !uuidPattern.test(row.id)) {
|
|
3003
|
+
this.db.prepare(
|
|
3004
|
+
`UPDATE lt_a2a_api_keys SET id = ? WHERE rowid = ?`
|
|
3005
|
+
).run((0, import_crypto3.randomUUID)(), row.rowid);
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
2960
3008
|
}
|
|
2961
3009
|
async findByKey(key) {
|
|
2962
3010
|
const rows = this.db.prepare(
|
|
@@ -2970,6 +3018,12 @@ var LocalA2AApiKeyStore = class {
|
|
|
2970
3018
|
}
|
|
2971
3019
|
return null;
|
|
2972
3020
|
}
|
|
3021
|
+
async findById(id) {
|
|
3022
|
+
const row = this.db.prepare(
|
|
3023
|
+
`SELECT * FROM lt_a2a_api_keys WHERE id = ?`
|
|
3024
|
+
).get(id);
|
|
3025
|
+
return row ? mapRowToRecord(row) : null;
|
|
3026
|
+
}
|
|
2973
3027
|
async list(params) {
|
|
2974
3028
|
const limit = params.limit || 100;
|
|
2975
3029
|
const offset = params.offset || 0;
|
|
@@ -2986,19 +3040,19 @@ var LocalA2AApiKeyStore = class {
|
|
|
2986
3040
|
return rows.map(mapRowToRecord);
|
|
2987
3041
|
}
|
|
2988
3042
|
async create(input) {
|
|
3043
|
+
const id = (0, import_crypto3.randomUUID)();
|
|
2989
3044
|
const key = generateApiKey();
|
|
2990
3045
|
const now = nowISO();
|
|
2991
3046
|
this.db.prepare(
|
|
2992
|
-
`INSERT INTO lt_a2a_api_keys (key_value, tenant_id, project_id,
|
|
2993
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
2994
|
-
).run((0, import_core3.encrypt)(key), input.tenantId, input.projectId
|
|
2995
|
-
const lastId = this.db.prepare(`SELECT last_insert_rowid() as id`).get();
|
|
3047
|
+
`INSERT INTO lt_a2a_api_keys (id, key_value, tenant_id, project_id, assistant_ids, label, created_at, updated_at)
|
|
3048
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
|
|
3049
|
+
).run(id, (0, import_core3.encrypt)(key), input.tenantId, input.projectId, input.assistantIds ? JSON.stringify(input.assistantIds) : null, input.label || null, now, now);
|
|
2996
3050
|
const record = {
|
|
2997
|
-
id
|
|
3051
|
+
id,
|
|
2998
3052
|
key,
|
|
2999
3053
|
tenantId: input.tenantId,
|
|
3000
3054
|
projectId: input.projectId,
|
|
3001
|
-
|
|
3055
|
+
assistantIds: input.assistantIds,
|
|
3002
3056
|
label: input.label,
|
|
3003
3057
|
enabled: true,
|
|
3004
3058
|
createdAt: parseISO(now),
|
|
@@ -3050,8 +3104,8 @@ var LocalA2AApiKeyStore = class {
|
|
|
3050
3104
|
map.set(key, {
|
|
3051
3105
|
key,
|
|
3052
3106
|
tenantId: row.tenant_id,
|
|
3053
|
-
projectId: row.project_id
|
|
3054
|
-
|
|
3107
|
+
projectId: row.project_id,
|
|
3108
|
+
assistantIds: parseAssistantIds(row.assistant_ids)
|
|
3055
3109
|
});
|
|
3056
3110
|
} catch {
|
|
3057
3111
|
}
|
|
@@ -3070,8 +3124,8 @@ function mapRowToRecord(row) {
|
|
|
3070
3124
|
id: row.id,
|
|
3071
3125
|
key,
|
|
3072
3126
|
tenantId: row.tenant_id,
|
|
3073
|
-
projectId: row.project_id
|
|
3074
|
-
|
|
3127
|
+
projectId: row.project_id,
|
|
3128
|
+
assistantIds: parseAssistantIds(row.assistant_ids),
|
|
3075
3129
|
label: row.label || void 0,
|
|
3076
3130
|
enabled: row.enabled === 1,
|
|
3077
3131
|
createdAt: parseISO(row.created_at),
|
|
@@ -3720,6 +3774,12 @@ function mapRowToTask(row) {
|
|
|
3720
3774
|
|
|
3721
3775
|
// src/stores/LocalTaskStore.ts
|
|
3722
3776
|
var import_crypto5 = require("crypto");
|
|
3777
|
+
var MAX_CANONICAL_TASK_TIME = "9999-12-31T23:59:59.999Z";
|
|
3778
|
+
var NEXT_UPDATED_AT_SQL = `updated_at = CASE
|
|
3779
|
+
WHEN strftime('%Y-%m-%dT%H:%M:%fZ', updated_at) = updated_at AND updated_at >= ?
|
|
3780
|
+
THEN strftime('%Y-%m-%dT%H:%M:%fZ', updated_at, '+0.001 seconds')
|
|
3781
|
+
ELSE ?
|
|
3782
|
+
END`;
|
|
3723
3783
|
var DDL20 = `
|
|
3724
3784
|
CREATE TABLE IF NOT EXISTS lt_tasks (
|
|
3725
3785
|
id TEXT NOT NULL,
|
|
@@ -3783,6 +3843,10 @@ function mapRowToTask2(row) {
|
|
|
3783
3843
|
updatedAt: parseISO(row.updated_at)
|
|
3784
3844
|
};
|
|
3785
3845
|
}
|
|
3846
|
+
function nextISO(previous) {
|
|
3847
|
+
const previousMs = new Date(previous).getTime();
|
|
3848
|
+
return new Date(Math.max(Date.now(), previousMs + 1)).toISOString();
|
|
3849
|
+
}
|
|
3786
3850
|
var LocalTaskStore = class {
|
|
3787
3851
|
constructor(db) {
|
|
3788
3852
|
this.db = db;
|
|
@@ -3803,7 +3867,7 @@ var LocalTaskStore = class {
|
|
|
3803
3867
|
}
|
|
3804
3868
|
}
|
|
3805
3869
|
async create(params) {
|
|
3806
|
-
const id = (0, import_crypto5.randomUUID)();
|
|
3870
|
+
const id = params.id ?? (0, import_crypto5.randomUUID)();
|
|
3807
3871
|
const now = nowISO();
|
|
3808
3872
|
this.db.prepare(
|
|
3809
3873
|
`INSERT INTO lt_tasks (id, tenant_id, owner_type, owner_id, title, description, status, priority, due_date, metadata, parent_id, source_id, context, require_review, dependencies, result, failure_reason, workspace_id, project_id, files, created_at, updated_at)
|
|
@@ -3904,8 +3968,8 @@ var LocalTaskStore = class {
|
|
|
3904
3968
|
const existing = await this.getById(tenantId, id);
|
|
3905
3969
|
if (!existing) return null;
|
|
3906
3970
|
const now = nowISO();
|
|
3907
|
-
const setClauses = [
|
|
3908
|
-
const values = [now];
|
|
3971
|
+
const setClauses = [NEXT_UPDATED_AT_SQL];
|
|
3972
|
+
const values = [now, now];
|
|
3909
3973
|
if (updates.title !== void 0) {
|
|
3910
3974
|
setClauses.push("title = ?");
|
|
3911
3975
|
values.push(updates.title);
|
|
@@ -3940,7 +4004,7 @@ var LocalTaskStore = class {
|
|
|
3940
4004
|
}
|
|
3941
4005
|
if (updates.context !== void 0) {
|
|
3942
4006
|
setClauses.push("context = ?");
|
|
3943
|
-
values.push(JSON.stringify(updates.context));
|
|
4007
|
+
values.push(updates.context === null ? null : JSON.stringify(updates.context));
|
|
3944
4008
|
}
|
|
3945
4009
|
if (updates.ownerType !== void 0) {
|
|
3946
4010
|
setClauses.push("owner_type = ?");
|
|
@@ -3979,10 +4043,369 @@ var LocalTaskStore = class {
|
|
|
3979
4043
|
values.push(JSON.stringify(updates.files));
|
|
3980
4044
|
}
|
|
3981
4045
|
if (setClauses.length === 1) return existing;
|
|
3982
|
-
values.push(tenantId, id);
|
|
3983
|
-
this.db.prepare(
|
|
3984
|
-
`UPDATE lt_tasks SET ${setClauses.join(", ")} WHERE tenant_id = ? AND id = ?`
|
|
4046
|
+
values.push(tenantId, id, MAX_CANONICAL_TASK_TIME);
|
|
4047
|
+
const result = this.db.prepare(
|
|
4048
|
+
`UPDATE lt_tasks SET ${setClauses.join(", ")} WHERE tenant_id = ? AND id = ? AND updated_at <> ?`
|
|
4049
|
+
).run(...values);
|
|
4050
|
+
if (result.changes === 0) return null;
|
|
4051
|
+
return this.getById(tenantId, id);
|
|
4052
|
+
}
|
|
4053
|
+
/**
|
|
4054
|
+
* Atomically update a task unless its current status is blocked.
|
|
4055
|
+
*
|
|
4056
|
+
* @param tenantId Tenant identifier.
|
|
4057
|
+
* @param id Task identifier.
|
|
4058
|
+
* @param updates Partial task data to update.
|
|
4059
|
+
* @param blockedStatuses Current statuses that prevent the update.
|
|
4060
|
+
* @returns The updated task, or `null` when missing or blocked.
|
|
4061
|
+
*/
|
|
4062
|
+
async updateIfStatusNotIn(tenantId, id, updates, blockedStatuses) {
|
|
4063
|
+
const now = nowISO();
|
|
4064
|
+
const setClauses = [NEXT_UPDATED_AT_SQL];
|
|
4065
|
+
const values = [now, now];
|
|
4066
|
+
if (updates.title !== void 0) {
|
|
4067
|
+
setClauses.push("title = ?");
|
|
4068
|
+
values.push(updates.title);
|
|
4069
|
+
}
|
|
4070
|
+
if (updates.description !== void 0) {
|
|
4071
|
+
setClauses.push("description = ?");
|
|
4072
|
+
values.push(updates.description);
|
|
4073
|
+
}
|
|
4074
|
+
if (updates.status !== void 0) {
|
|
4075
|
+
setClauses.push("status = ?");
|
|
4076
|
+
values.push(updates.status);
|
|
4077
|
+
}
|
|
4078
|
+
if (updates.priority !== void 0) {
|
|
4079
|
+
setClauses.push("priority = ?");
|
|
4080
|
+
values.push(updates.priority);
|
|
4081
|
+
}
|
|
4082
|
+
if (updates.dueDate !== void 0) {
|
|
4083
|
+
setClauses.push("due_date = ?");
|
|
4084
|
+
values.push(updates.dueDate);
|
|
4085
|
+
}
|
|
4086
|
+
if (updates.metadata !== void 0) {
|
|
4087
|
+
setClauses.push("metadata = ?");
|
|
4088
|
+
values.push(JSON.stringify(updates.metadata));
|
|
4089
|
+
}
|
|
4090
|
+
if (updates.parentId !== void 0) {
|
|
4091
|
+
setClauses.push("parent_id = ?");
|
|
4092
|
+
values.push(updates.parentId);
|
|
4093
|
+
}
|
|
4094
|
+
if (updates.sourceId !== void 0) {
|
|
4095
|
+
setClauses.push("source_id = ?");
|
|
4096
|
+
values.push(updates.sourceId);
|
|
4097
|
+
}
|
|
4098
|
+
if (updates.context !== void 0) {
|
|
4099
|
+
setClauses.push("context = ?");
|
|
4100
|
+
values.push(updates.context === null ? null : JSON.stringify(updates.context));
|
|
4101
|
+
}
|
|
4102
|
+
if (updates.ownerType !== void 0) {
|
|
4103
|
+
setClauses.push("owner_type = ?");
|
|
4104
|
+
values.push(updates.ownerType);
|
|
4105
|
+
}
|
|
4106
|
+
if (updates.ownerId !== void 0) {
|
|
4107
|
+
setClauses.push("owner_id = ?");
|
|
4108
|
+
values.push(updates.ownerId);
|
|
4109
|
+
}
|
|
4110
|
+
if (updates.requireReview !== void 0) {
|
|
4111
|
+
setClauses.push("require_review = ?");
|
|
4112
|
+
values.push(updates.requireReview ? 1 : 0);
|
|
4113
|
+
}
|
|
4114
|
+
if (updates.dependencies !== void 0) {
|
|
4115
|
+
setClauses.push("dependencies = ?");
|
|
4116
|
+
values.push(JSON.stringify(updates.dependencies));
|
|
4117
|
+
}
|
|
4118
|
+
if (updates.result !== void 0) {
|
|
4119
|
+
setClauses.push("result = ?");
|
|
4120
|
+
values.push(updates.result);
|
|
4121
|
+
}
|
|
4122
|
+
if (updates.failureReason !== void 0) {
|
|
4123
|
+
setClauses.push("failure_reason = ?");
|
|
4124
|
+
values.push(updates.failureReason);
|
|
4125
|
+
}
|
|
4126
|
+
if (updates.workspaceId !== void 0) {
|
|
4127
|
+
setClauses.push("workspace_id = ?");
|
|
4128
|
+
values.push(updates.workspaceId);
|
|
4129
|
+
}
|
|
4130
|
+
if (updates.projectId !== void 0) {
|
|
4131
|
+
setClauses.push("project_id = ?");
|
|
4132
|
+
values.push(updates.projectId);
|
|
4133
|
+
}
|
|
4134
|
+
if (updates.files !== void 0) {
|
|
4135
|
+
setClauses.push("files = ?");
|
|
4136
|
+
values.push(JSON.stringify(updates.files));
|
|
4137
|
+
}
|
|
4138
|
+
const statusClause = blockedStatuses.length > 0 ? ` AND status NOT IN (${blockedStatuses.map(() => "?").join(", ")})` : "";
|
|
4139
|
+
values.push(tenantId, id, MAX_CANONICAL_TASK_TIME, ...blockedStatuses);
|
|
4140
|
+
const result = this.db.prepare(
|
|
4141
|
+
`UPDATE lt_tasks SET ${setClauses.join(", ")} WHERE tenant_id = ? AND id = ? AND updated_at <> ?${statusClause}`
|
|
4142
|
+
).run(...values);
|
|
4143
|
+
if (result.changes === 0) return null;
|
|
4144
|
+
return this.getById(tenantId, id);
|
|
4145
|
+
}
|
|
4146
|
+
/** Atomically update a task only when its current status is expected. */
|
|
4147
|
+
async updateIfStatusIn(tenantId, id, updates, expectedStatuses) {
|
|
4148
|
+
if (expectedStatuses.length === 0) return null;
|
|
4149
|
+
const now = nowISO();
|
|
4150
|
+
const setClauses = [NEXT_UPDATED_AT_SQL];
|
|
4151
|
+
const values = [now, now];
|
|
4152
|
+
if (updates.title !== void 0) {
|
|
4153
|
+
setClauses.push("title = ?");
|
|
4154
|
+
values.push(updates.title);
|
|
4155
|
+
}
|
|
4156
|
+
if (updates.description !== void 0) {
|
|
4157
|
+
setClauses.push("description = ?");
|
|
4158
|
+
values.push(updates.description);
|
|
4159
|
+
}
|
|
4160
|
+
if (updates.status !== void 0) {
|
|
4161
|
+
setClauses.push("status = ?");
|
|
4162
|
+
values.push(updates.status);
|
|
4163
|
+
}
|
|
4164
|
+
if (updates.priority !== void 0) {
|
|
4165
|
+
setClauses.push("priority = ?");
|
|
4166
|
+
values.push(updates.priority);
|
|
4167
|
+
}
|
|
4168
|
+
if (updates.dueDate !== void 0) {
|
|
4169
|
+
setClauses.push("due_date = ?");
|
|
4170
|
+
values.push(updates.dueDate);
|
|
4171
|
+
}
|
|
4172
|
+
if (updates.metadata !== void 0) {
|
|
4173
|
+
setClauses.push("metadata = ?");
|
|
4174
|
+
values.push(JSON.stringify(updates.metadata));
|
|
4175
|
+
}
|
|
4176
|
+
if (updates.parentId !== void 0) {
|
|
4177
|
+
setClauses.push("parent_id = ?");
|
|
4178
|
+
values.push(updates.parentId);
|
|
4179
|
+
}
|
|
4180
|
+
if (updates.sourceId !== void 0) {
|
|
4181
|
+
setClauses.push("source_id = ?");
|
|
4182
|
+
values.push(updates.sourceId);
|
|
4183
|
+
}
|
|
4184
|
+
if (updates.context !== void 0) {
|
|
4185
|
+
setClauses.push("context = ?");
|
|
4186
|
+
values.push(updates.context === null ? null : JSON.stringify(updates.context));
|
|
4187
|
+
}
|
|
4188
|
+
if (updates.ownerType !== void 0) {
|
|
4189
|
+
setClauses.push("owner_type = ?");
|
|
4190
|
+
values.push(updates.ownerType);
|
|
4191
|
+
}
|
|
4192
|
+
if (updates.ownerId !== void 0) {
|
|
4193
|
+
setClauses.push("owner_id = ?");
|
|
4194
|
+
values.push(updates.ownerId);
|
|
4195
|
+
}
|
|
4196
|
+
if (updates.requireReview !== void 0) {
|
|
4197
|
+
setClauses.push("require_review = ?");
|
|
4198
|
+
values.push(updates.requireReview ? 1 : 0);
|
|
4199
|
+
}
|
|
4200
|
+
if (updates.dependencies !== void 0) {
|
|
4201
|
+
setClauses.push("dependencies = ?");
|
|
4202
|
+
values.push(JSON.stringify(updates.dependencies));
|
|
4203
|
+
}
|
|
4204
|
+
if (updates.result !== void 0) {
|
|
4205
|
+
setClauses.push("result = ?");
|
|
4206
|
+
values.push(updates.result);
|
|
4207
|
+
}
|
|
4208
|
+
if (updates.failureReason !== void 0) {
|
|
4209
|
+
setClauses.push("failure_reason = ?");
|
|
4210
|
+
values.push(updates.failureReason);
|
|
4211
|
+
}
|
|
4212
|
+
if (updates.workspaceId !== void 0) {
|
|
4213
|
+
setClauses.push("workspace_id = ?");
|
|
4214
|
+
values.push(updates.workspaceId);
|
|
4215
|
+
}
|
|
4216
|
+
if (updates.projectId !== void 0) {
|
|
4217
|
+
setClauses.push("project_id = ?");
|
|
4218
|
+
values.push(updates.projectId);
|
|
4219
|
+
}
|
|
4220
|
+
if (updates.files !== void 0) {
|
|
4221
|
+
setClauses.push("files = ?");
|
|
4222
|
+
values.push(JSON.stringify(updates.files));
|
|
4223
|
+
}
|
|
4224
|
+
values.push(tenantId, id, MAX_CANONICAL_TASK_TIME, ...expectedStatuses);
|
|
4225
|
+
const result = this.db.prepare(
|
|
4226
|
+
`UPDATE lt_tasks SET ${setClauses.join(", ")} WHERE tenant_id = ? AND id = ? AND updated_at <> ? AND status IN (${expectedStatuses.map(() => "?").join(", ")})`
|
|
4227
|
+
).run(...values);
|
|
4228
|
+
if (result.changes === 0) return null;
|
|
4229
|
+
return this.getById(tenantId, id);
|
|
4230
|
+
}
|
|
4231
|
+
/** Atomically update a task only when status and updatedAt match a read snapshot. */
|
|
4232
|
+
async updateIfStatusAndUpdatedAt(tenantId, id, updates, expectedStatuses, expectedUpdatedAt) {
|
|
4233
|
+
if (expectedStatuses.length === 0) return null;
|
|
4234
|
+
const expectedIso = new Date(expectedUpdatedAt).toISOString();
|
|
4235
|
+
if (expectedIso === MAX_CANONICAL_TASK_TIME) return null;
|
|
4236
|
+
const setClauses = ["updated_at = ?"];
|
|
4237
|
+
const values = [nextISO(expectedIso)];
|
|
4238
|
+
if (updates.title !== void 0) {
|
|
4239
|
+
setClauses.push("title = ?");
|
|
4240
|
+
values.push(updates.title);
|
|
4241
|
+
}
|
|
4242
|
+
if (updates.description !== void 0) {
|
|
4243
|
+
setClauses.push("description = ?");
|
|
4244
|
+
values.push(updates.description);
|
|
4245
|
+
}
|
|
4246
|
+
if (updates.status !== void 0) {
|
|
4247
|
+
setClauses.push("status = ?");
|
|
4248
|
+
values.push(updates.status);
|
|
4249
|
+
}
|
|
4250
|
+
if (updates.priority !== void 0) {
|
|
4251
|
+
setClauses.push("priority = ?");
|
|
4252
|
+
values.push(updates.priority);
|
|
4253
|
+
}
|
|
4254
|
+
if (updates.dueDate !== void 0) {
|
|
4255
|
+
setClauses.push("due_date = ?");
|
|
4256
|
+
values.push(updates.dueDate);
|
|
4257
|
+
}
|
|
4258
|
+
if (updates.metadata !== void 0) {
|
|
4259
|
+
setClauses.push("metadata = ?");
|
|
4260
|
+
values.push(JSON.stringify(updates.metadata));
|
|
4261
|
+
}
|
|
4262
|
+
if (updates.parentId !== void 0) {
|
|
4263
|
+
setClauses.push("parent_id = ?");
|
|
4264
|
+
values.push(updates.parentId);
|
|
4265
|
+
}
|
|
4266
|
+
if (updates.sourceId !== void 0) {
|
|
4267
|
+
setClauses.push("source_id = ?");
|
|
4268
|
+
values.push(updates.sourceId);
|
|
4269
|
+
}
|
|
4270
|
+
if (updates.context !== void 0) {
|
|
4271
|
+
setClauses.push("context = ?");
|
|
4272
|
+
values.push(updates.context === null ? null : JSON.stringify(updates.context));
|
|
4273
|
+
}
|
|
4274
|
+
if (updates.ownerType !== void 0) {
|
|
4275
|
+
setClauses.push("owner_type = ?");
|
|
4276
|
+
values.push(updates.ownerType);
|
|
4277
|
+
}
|
|
4278
|
+
if (updates.ownerId !== void 0) {
|
|
4279
|
+
setClauses.push("owner_id = ?");
|
|
4280
|
+
values.push(updates.ownerId);
|
|
4281
|
+
}
|
|
4282
|
+
if (updates.requireReview !== void 0) {
|
|
4283
|
+
setClauses.push("require_review = ?");
|
|
4284
|
+
values.push(updates.requireReview ? 1 : 0);
|
|
4285
|
+
}
|
|
4286
|
+
if (updates.dependencies !== void 0) {
|
|
4287
|
+
setClauses.push("dependencies = ?");
|
|
4288
|
+
values.push(JSON.stringify(updates.dependencies));
|
|
4289
|
+
}
|
|
4290
|
+
if (updates.result !== void 0) {
|
|
4291
|
+
setClauses.push("result = ?");
|
|
4292
|
+
values.push(updates.result);
|
|
4293
|
+
}
|
|
4294
|
+
if (updates.failureReason !== void 0) {
|
|
4295
|
+
setClauses.push("failure_reason = ?");
|
|
4296
|
+
values.push(updates.failureReason);
|
|
4297
|
+
}
|
|
4298
|
+
if (updates.workspaceId !== void 0) {
|
|
4299
|
+
setClauses.push("workspace_id = ?");
|
|
4300
|
+
values.push(updates.workspaceId);
|
|
4301
|
+
}
|
|
4302
|
+
if (updates.projectId !== void 0) {
|
|
4303
|
+
setClauses.push("project_id = ?");
|
|
4304
|
+
values.push(updates.projectId);
|
|
4305
|
+
}
|
|
4306
|
+
if (updates.files !== void 0) {
|
|
4307
|
+
setClauses.push("files = ?");
|
|
4308
|
+
values.push(JSON.stringify(updates.files));
|
|
4309
|
+
}
|
|
4310
|
+
values.push(tenantId, id, ...expectedStatuses, expectedIso);
|
|
4311
|
+
const result = this.db.prepare(
|
|
4312
|
+
`UPDATE lt_tasks SET ${setClauses.join(", ")} WHERE tenant_id = ? AND id = ? AND status IN (${expectedStatuses.map(() => "?").join(", ")}) AND updated_at = ?`
|
|
4313
|
+
).run(...values);
|
|
4314
|
+
if (result.changes === 0) return null;
|
|
4315
|
+
return this.getById(tenantId, id);
|
|
4316
|
+
}
|
|
4317
|
+
/** Atomically update a child only when both child and parent snapshots match. */
|
|
4318
|
+
async updateIfStatusUpdatedAtAndParentUpdatedAt(tenantId, id, updates, expectedStatuses, expectedUpdatedAt, parentId, expectedParentUpdatedAt) {
|
|
4319
|
+
if (expectedStatuses.length === 0) return null;
|
|
4320
|
+
const expectedIso = new Date(expectedUpdatedAt).toISOString();
|
|
4321
|
+
if (expectedIso === MAX_CANONICAL_TASK_TIME) return null;
|
|
4322
|
+
const parentExpectedIso = new Date(expectedParentUpdatedAt).toISOString();
|
|
4323
|
+
const setClauses = ["updated_at = ?"];
|
|
4324
|
+
const values = [nextISO(expectedIso)];
|
|
4325
|
+
if (updates.title !== void 0) {
|
|
4326
|
+
setClauses.push("title = ?");
|
|
4327
|
+
values.push(updates.title);
|
|
4328
|
+
}
|
|
4329
|
+
if (updates.description !== void 0) {
|
|
4330
|
+
setClauses.push("description = ?");
|
|
4331
|
+
values.push(updates.description);
|
|
4332
|
+
}
|
|
4333
|
+
if (updates.status !== void 0) {
|
|
4334
|
+
setClauses.push("status = ?");
|
|
4335
|
+
values.push(updates.status);
|
|
4336
|
+
}
|
|
4337
|
+
if (updates.priority !== void 0) {
|
|
4338
|
+
setClauses.push("priority = ?");
|
|
4339
|
+
values.push(updates.priority);
|
|
4340
|
+
}
|
|
4341
|
+
if (updates.dueDate !== void 0) {
|
|
4342
|
+
setClauses.push("due_date = ?");
|
|
4343
|
+
values.push(updates.dueDate);
|
|
4344
|
+
}
|
|
4345
|
+
if (updates.metadata !== void 0) {
|
|
4346
|
+
setClauses.push("metadata = ?");
|
|
4347
|
+
values.push(JSON.stringify(updates.metadata));
|
|
4348
|
+
}
|
|
4349
|
+
if (updates.parentId !== void 0) {
|
|
4350
|
+
setClauses.push("parent_id = ?");
|
|
4351
|
+
values.push(updates.parentId);
|
|
4352
|
+
}
|
|
4353
|
+
if (updates.sourceId !== void 0) {
|
|
4354
|
+
setClauses.push("source_id = ?");
|
|
4355
|
+
values.push(updates.sourceId);
|
|
4356
|
+
}
|
|
4357
|
+
if (updates.context !== void 0) {
|
|
4358
|
+
setClauses.push("context = ?");
|
|
4359
|
+
values.push(updates.context === null ? null : JSON.stringify(updates.context));
|
|
4360
|
+
}
|
|
4361
|
+
if (updates.ownerType !== void 0) {
|
|
4362
|
+
setClauses.push("owner_type = ?");
|
|
4363
|
+
values.push(updates.ownerType);
|
|
4364
|
+
}
|
|
4365
|
+
if (updates.ownerId !== void 0) {
|
|
4366
|
+
setClauses.push("owner_id = ?");
|
|
4367
|
+
values.push(updates.ownerId);
|
|
4368
|
+
}
|
|
4369
|
+
if (updates.requireReview !== void 0) {
|
|
4370
|
+
setClauses.push("require_review = ?");
|
|
4371
|
+
values.push(updates.requireReview ? 1 : 0);
|
|
4372
|
+
}
|
|
4373
|
+
if (updates.dependencies !== void 0) {
|
|
4374
|
+
setClauses.push("dependencies = ?");
|
|
4375
|
+
values.push(JSON.stringify(updates.dependencies));
|
|
4376
|
+
}
|
|
4377
|
+
if (updates.result !== void 0) {
|
|
4378
|
+
setClauses.push("result = ?");
|
|
4379
|
+
values.push(updates.result);
|
|
4380
|
+
}
|
|
4381
|
+
if (updates.failureReason !== void 0) {
|
|
4382
|
+
setClauses.push("failure_reason = ?");
|
|
4383
|
+
values.push(updates.failureReason);
|
|
4384
|
+
}
|
|
4385
|
+
if (updates.workspaceId !== void 0) {
|
|
4386
|
+
setClauses.push("workspace_id = ?");
|
|
4387
|
+
values.push(updates.workspaceId);
|
|
4388
|
+
}
|
|
4389
|
+
if (updates.projectId !== void 0) {
|
|
4390
|
+
setClauses.push("project_id = ?");
|
|
4391
|
+
values.push(updates.projectId);
|
|
4392
|
+
}
|
|
4393
|
+
if (updates.files !== void 0) {
|
|
4394
|
+
setClauses.push("files = ?");
|
|
4395
|
+
values.push(JSON.stringify(updates.files));
|
|
4396
|
+
}
|
|
4397
|
+
values.push(tenantId, id, ...expectedStatuses, expectedIso, parentId, parentExpectedIso);
|
|
4398
|
+
const result = this.db.prepare(
|
|
4399
|
+
`UPDATE lt_tasks AS child SET ${setClauses.join(", ")}
|
|
4400
|
+
WHERE tenant_id = ? AND id = ?
|
|
4401
|
+
AND status IN (${expectedStatuses.map(() => "?").join(", ")})
|
|
4402
|
+
AND updated_at = ?
|
|
4403
|
+
AND EXISTS (
|
|
4404
|
+
SELECT 1 FROM lt_tasks AS parent
|
|
4405
|
+
WHERE parent.tenant_id = child.tenant_id AND parent.id = ? AND parent.updated_at = ?
|
|
4406
|
+
)`
|
|
3985
4407
|
).run(...values);
|
|
4408
|
+
if (result.changes === 0) return null;
|
|
3986
4409
|
return this.getById(tenantId, id);
|
|
3987
4410
|
}
|
|
3988
4411
|
async delete(tenantId, id) {
|
|
@@ -4025,6 +4448,7 @@ function mapRowToWorkItem(row) {
|
|
|
4025
4448
|
attempt: row.attempt ?? void 0,
|
|
4026
4449
|
workspaceId: row.workspace_id ?? void 0,
|
|
4027
4450
|
projectId: row.project_id ?? void 0,
|
|
4451
|
+
eventKey: row.event_key ?? void 0,
|
|
4028
4452
|
createdAt: parseISO(row.created_at)
|
|
4029
4453
|
};
|
|
4030
4454
|
}
|
|
@@ -4032,8 +4456,18 @@ var LocalTaskWorkItemStore = class {
|
|
|
4032
4456
|
constructor(db) {
|
|
4033
4457
|
this.db = db;
|
|
4034
4458
|
ensureTable(db, DDL21);
|
|
4035
|
-
this.
|
|
4036
|
-
this.
|
|
4459
|
+
this.ensureColumn("lt_task_work_items", "workspace_id", "workspace_id TEXT");
|
|
4460
|
+
this.ensureColumn("lt_task_work_items", "project_id", "project_id TEXT");
|
|
4461
|
+
this.ensureColumn("lt_task_work_items", "event_key", "event_key TEXT");
|
|
4462
|
+
this.db.exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_task_work_items_event_key
|
|
4463
|
+
ON lt_task_work_items (tenant_id, task_id, event_key);`);
|
|
4464
|
+
}
|
|
4465
|
+
/** Add a column if it does not exist (SQLite version compatible). */
|
|
4466
|
+
ensureColumn(table, column, ddl) {
|
|
4467
|
+
const cols = this.db.prepare(`PRAGMA table_info(${table})`).all();
|
|
4468
|
+
if (!cols.some((c) => c.name === column)) {
|
|
4469
|
+
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${ddl}`);
|
|
4470
|
+
}
|
|
4037
4471
|
}
|
|
4038
4472
|
async create(params) {
|
|
4039
4473
|
const id = (0, import_uuid2.v4)();
|
|
@@ -4060,6 +4494,39 @@ var LocalTaskWorkItemStore = class {
|
|
|
4060
4494
|
).get(params.tenantId, id);
|
|
4061
4495
|
return mapRowToWorkItem(row);
|
|
4062
4496
|
}
|
|
4497
|
+
/** Find an event by its tenant- and task-scoped key without pagination. */
|
|
4498
|
+
async findByEventKey(tenantId, taskId, eventKey) {
|
|
4499
|
+
const row = this.db.prepare(
|
|
4500
|
+
`SELECT * FROM lt_task_work_items WHERE tenant_id = ? AND task_id = ? AND event_key = ?`
|
|
4501
|
+
).get(tenantId, taskId, eventKey);
|
|
4502
|
+
return row ? mapRowToWorkItem(row) : null;
|
|
4503
|
+
}
|
|
4504
|
+
/** Atomically return an existing event or create it once. */
|
|
4505
|
+
async createIfAbsentByEventKey(params) {
|
|
4506
|
+
const id = (0, import_uuid2.v4)();
|
|
4507
|
+
this.db.prepare(
|
|
4508
|
+
`INSERT INTO lt_task_work_items (id, tenant_id, task_id, action, actor, thread_id, summary, detail, attempt, workspace_id, project_id, event_key, created_at)
|
|
4509
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
4510
|
+
ON CONFLICT (tenant_id, task_id, event_key) DO NOTHING`
|
|
4511
|
+
).run(
|
|
4512
|
+
id,
|
|
4513
|
+
params.tenantId,
|
|
4514
|
+
params.taskId,
|
|
4515
|
+
params.action,
|
|
4516
|
+
params.actor,
|
|
4517
|
+
params.threadId || null,
|
|
4518
|
+
params.summary || null,
|
|
4519
|
+
params.detail ? JSON.stringify(params.detail) : null,
|
|
4520
|
+
params.attempt ?? null,
|
|
4521
|
+
params.workspaceId || null,
|
|
4522
|
+
params.projectId || null,
|
|
4523
|
+
params.eventKey,
|
|
4524
|
+
nowISO()
|
|
4525
|
+
);
|
|
4526
|
+
const item = await this.findByEventKey(params.tenantId, params.taskId, params.eventKey);
|
|
4527
|
+
if (!item) throw new Error("Task work item insert did not return a row");
|
|
4528
|
+
return item;
|
|
4529
|
+
}
|
|
4063
4530
|
async list(filter) {
|
|
4064
4531
|
const conditions = ["tenant_id = ?", "task_id = ?"];
|
|
4065
4532
|
const params = [filter.tenantId, filter.taskId];
|
|
@@ -4067,11 +4534,20 @@ var LocalTaskWorkItemStore = class {
|
|
|
4067
4534
|
conditions.push("action = ?");
|
|
4068
4535
|
params.push(filter.action);
|
|
4069
4536
|
}
|
|
4537
|
+
if (filter.workspaceId) {
|
|
4538
|
+
conditions.push("workspace_id = ?");
|
|
4539
|
+
params.push(filter.workspaceId);
|
|
4540
|
+
}
|
|
4541
|
+
if (filter.projectId) {
|
|
4542
|
+
conditions.push("project_id = ?");
|
|
4543
|
+
params.push(filter.projectId);
|
|
4544
|
+
}
|
|
4070
4545
|
const where = conditions.join(" AND ");
|
|
4071
4546
|
const limit = filter.limit || 100;
|
|
4072
4547
|
const offset = filter.offset || 0;
|
|
4548
|
+
const order = filter.order === "asc" ? "ASC" : "DESC";
|
|
4073
4549
|
const rows = this.db.prepare(
|
|
4074
|
-
`SELECT * FROM lt_task_work_items WHERE ${where} ORDER BY created_at
|
|
4550
|
+
`SELECT * FROM lt_task_work_items WHERE ${where} ORDER BY created_at ${order}, id ${order} LIMIT ? OFFSET ?`
|
|
4075
4551
|
).all(...params, limit, offset);
|
|
4076
4552
|
return rows.map(mapRowToWorkItem);
|
|
4077
4553
|
}
|