@andypai/agent-kanban 0.6.3 → 0.6.5
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/package.json +11 -3
- package/src/__tests__/index.test.ts +27 -1
- package/src/__tests__/jira-cache.test.ts +68 -0
- package/src/__tests__/jira-provider-read.test.ts +130 -0
- package/src/__tests__/jira-wiring.test.ts +41 -0
- package/src/__tests__/linear-provider-comment.test.ts +81 -0
- package/src/__tests__/linear-provider-sync.test.ts +107 -0
- package/src/__tests__/linear-state-resolution.test.ts +60 -0
- package/src/__tests__/local-provider-core.test.ts +245 -0
- package/src/__tests__/postgres-jira-provider.test.ts +55 -0
- package/src/__tests__/postgres-linear-provider.test.ts +90 -0
- package/src/__tests__/postgres-local-provider.test.ts +26 -0
- package/src/__tests__/provider-sync-core.test.ts +150 -0
- package/src/__tests__/provider-team-info.test.ts +37 -0
- package/src/__tests__/ui-board-slice.test.ts +14 -0
- package/src/__tests__/use-cases.test.ts +21 -42
- package/src/api.ts +78 -132
- package/src/column-roles.ts +3 -2
- package/src/index.ts +135 -92
- package/src/mcp/core.ts +7 -8
- package/src/provider-runtime.ts +12 -56
- package/src/providers/cache-task-mappers.ts +123 -0
- package/src/providers/factory.ts +103 -0
- package/src/providers/index.ts +4 -25
- package/src/providers/jira-cache.ts +41 -99
- package/src/providers/jira-core.ts +81 -81
- package/src/providers/linear-cache.ts +42 -69
- package/src/providers/linear-core.ts +44 -56
- package/src/providers/local-core.ts +226 -0
- package/src/providers/local.ts +6 -182
- package/src/providers/postgres-batch.ts +10 -0
- package/src/providers/postgres-jira-cache.ts +255 -213
- package/src/providers/postgres-jira.ts +2 -20
- package/src/providers/postgres-linear-cache.ts +279 -183
- package/src/providers/postgres-linear.ts +2 -20
- package/src/providers/postgres-local.ts +22 -47
- package/src/providers/sqlite-local-store.ts +121 -0
- package/src/providers/sync-core.ts +91 -0
- package/src/providers/team-info.ts +24 -0
- package/src/providers/warn-once.ts +15 -0
- package/src/use-cases.ts +7 -131
- package/src/webhook-events.ts +29 -1
- package/ui/dist/assets/index-DcZH7fI3.js +40 -0
- package/ui/dist/index.html +1 -1
- package/ui/dist/assets/index-Cigv8a9S.js +0 -40
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { ErrorCode, KanbanError } from '../errors'
|
|
2
|
+
import type {
|
|
3
|
+
ActivityEntry,
|
|
4
|
+
BoardBootstrap,
|
|
5
|
+
BoardConfig,
|
|
6
|
+
BoardMetrics,
|
|
7
|
+
BoardView,
|
|
8
|
+
Column,
|
|
9
|
+
ProviderCapabilities,
|
|
10
|
+
Task,
|
|
11
|
+
TaskComment,
|
|
12
|
+
} from '../types'
|
|
13
|
+
import type {
|
|
14
|
+
CreateTaskInput,
|
|
15
|
+
KanbanProvider,
|
|
16
|
+
ProviderContext,
|
|
17
|
+
ProviderSyncStatus,
|
|
18
|
+
TaskListFilters,
|
|
19
|
+
UpdateTaskInput,
|
|
20
|
+
} from './types'
|
|
21
|
+
|
|
22
|
+
type Awaitable<T> = T | Promise<T>
|
|
23
|
+
|
|
24
|
+
export type LocalTaskRecord = Task & {
|
|
25
|
+
column_name?: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface LocalStorePort {
|
|
29
|
+
readonly capabilities: ProviderCapabilities
|
|
30
|
+
initialize?(): Promise<void>
|
|
31
|
+
getBoard(): Awaitable<BoardView>
|
|
32
|
+
listColumns(): Awaitable<Column[]>
|
|
33
|
+
listTasks(filters?: TaskListFilters): Awaitable<LocalTaskRecord[]>
|
|
34
|
+
getTask(idOrRef: string): Awaitable<LocalTaskRecord>
|
|
35
|
+
getTaskVersion(idOrRef: string): Awaitable<string>
|
|
36
|
+
createTask(input: CreateTaskInput): Awaitable<LocalTaskRecord>
|
|
37
|
+
updateTask(
|
|
38
|
+
idOrRef: string,
|
|
39
|
+
input: Omit<UpdateTaskInput, 'expectedVersion'>,
|
|
40
|
+
): Awaitable<LocalTaskRecord>
|
|
41
|
+
moveTask(idOrRef: string, column: string): Awaitable<LocalTaskRecord>
|
|
42
|
+
deleteTask(idOrRef: string): Awaitable<LocalTaskRecord>
|
|
43
|
+
listComments(idOrRef: string): Awaitable<TaskComment[]>
|
|
44
|
+
getComment(idOrRef: string, commentId: string): Awaitable<TaskComment>
|
|
45
|
+
comment(idOrRef: string, body: string): Awaitable<TaskComment>
|
|
46
|
+
updateComment(idOrRef: string, commentId: string, body: string): Awaitable<TaskComment>
|
|
47
|
+
getActivity(limit?: number, taskId?: string): Awaitable<ActivityEntry[]>
|
|
48
|
+
getMetrics(): Awaitable<BoardMetrics>
|
|
49
|
+
getConfig(context?: { metrics?: BoardMetrics }): Awaitable<BoardConfig>
|
|
50
|
+
patchConfig(input: Partial<BoardConfig>): Awaitable<BoardConfig>
|
|
51
|
+
countComments?(taskId: string): Awaitable<number>
|
|
52
|
+
countCommentsByTask?(): Awaitable<Map<string, number>>
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function taskHasCommentCount(task: Task): boolean {
|
|
56
|
+
return typeof task.comment_count === 'number' && Number.isFinite(task.comment_count)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export class LocalProviderCore implements KanbanProvider {
|
|
60
|
+
readonly type = 'local' as const
|
|
61
|
+
|
|
62
|
+
constructor(private readonly store: LocalStorePort) {}
|
|
63
|
+
|
|
64
|
+
async initialize(): Promise<void> {
|
|
65
|
+
await this.store.initialize?.()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private enrichTask(task: LocalTaskRecord, commentCount?: number): LocalTaskRecord {
|
|
69
|
+
const revision = task.revision ?? 0
|
|
70
|
+
const assignees = task.assignees?.length ? task.assignees : task.assignee ? [task.assignee] : []
|
|
71
|
+
const labels = Array.isArray(task.labels) ? task.labels : []
|
|
72
|
+
return {
|
|
73
|
+
...task,
|
|
74
|
+
providerId: task.providerId ?? task.id,
|
|
75
|
+
externalRef: task.externalRef ?? task.id,
|
|
76
|
+
url: task.url ?? null,
|
|
77
|
+
assignees,
|
|
78
|
+
labels,
|
|
79
|
+
comment_count: commentCount ?? task.comment_count ?? 0,
|
|
80
|
+
version: task.version ?? String(revision),
|
|
81
|
+
source_updated_at: task.source_updated_at ?? null,
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private async commentCount(task: Task): Promise<number | undefined> {
|
|
86
|
+
if (taskHasCommentCount(task)) return task.comment_count
|
|
87
|
+
return this.store.countComments?.(task.id)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private async commentCountsFor(tasks: Task[]): Promise<Map<string, number> | undefined> {
|
|
91
|
+
if (tasks.every(taskHasCommentCount)) return undefined
|
|
92
|
+
return this.store.countCommentsByTask?.()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private async enrichTaskWithCount(task: LocalTaskRecord): Promise<LocalTaskRecord> {
|
|
96
|
+
return this.enrichTask(task, await this.commentCount(task))
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async getContext(): Promise<ProviderContext> {
|
|
100
|
+
await this.initialize()
|
|
101
|
+
return {
|
|
102
|
+
provider: this.type,
|
|
103
|
+
capabilities: this.store.capabilities,
|
|
104
|
+
team: null,
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async getBootstrap(): Promise<BoardBootstrap> {
|
|
109
|
+
await this.initialize()
|
|
110
|
+
const metrics = await this.getMetrics()
|
|
111
|
+
return {
|
|
112
|
+
provider: this.type,
|
|
113
|
+
capabilities: this.store.capabilities,
|
|
114
|
+
board: await this.getBoard(),
|
|
115
|
+
config: await this.store.getConfig({ metrics }),
|
|
116
|
+
metrics,
|
|
117
|
+
activity: await this.getActivity(50),
|
|
118
|
+
team: null,
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async getBoard(): Promise<BoardView> {
|
|
123
|
+
await this.initialize()
|
|
124
|
+
const board = await this.store.getBoard()
|
|
125
|
+
const tasks = board.columns.flatMap((column) => column.tasks)
|
|
126
|
+
const counts = await this.commentCountsFor(tasks)
|
|
127
|
+
return {
|
|
128
|
+
columns: board.columns.map((column) => ({
|
|
129
|
+
...column,
|
|
130
|
+
tasks: column.tasks.map((task) => this.enrichTask(task, counts?.get(task.id))),
|
|
131
|
+
})),
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async listColumns(): Promise<Column[]> {
|
|
136
|
+
await this.initialize()
|
|
137
|
+
return this.store.listColumns()
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async listTasks(filters: TaskListFilters = {}): Promise<Task[]> {
|
|
141
|
+
await this.initialize()
|
|
142
|
+
const tasks = await this.store.listTasks(filters)
|
|
143
|
+
const counts = await this.commentCountsFor(tasks)
|
|
144
|
+
return tasks.map((task) => this.enrichTask(task, counts?.get(task.id)))
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async getTask(idOrRef: string): Promise<Task> {
|
|
148
|
+
await this.initialize()
|
|
149
|
+
return this.enrichTaskWithCount(await this.store.getTask(idOrRef))
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async createTask(input: CreateTaskInput): Promise<Task> {
|
|
153
|
+
await this.initialize()
|
|
154
|
+
return this.enrichTaskWithCount(await this.store.createTask(input))
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async updateTask(idOrRef: string, input: UpdateTaskInput): Promise<Task> {
|
|
158
|
+
await this.initialize()
|
|
159
|
+
if (input.expectedVersion !== undefined) {
|
|
160
|
+
const currentVersion = await this.store.getTaskVersion(idOrRef)
|
|
161
|
+
if (currentVersion !== input.expectedVersion) {
|
|
162
|
+
throw new KanbanError(
|
|
163
|
+
ErrorCode.CONFLICT,
|
|
164
|
+
`Task ${idOrRef} was modified since you loaded it (expected version ${input.expectedVersion}, current ${currentVersion})`,
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const updates: Omit<UpdateTaskInput, 'expectedVersion'> = { ...input }
|
|
169
|
+
delete (updates as UpdateTaskInput).expectedVersion
|
|
170
|
+
return this.enrichTaskWithCount(await this.store.updateTask(idOrRef, updates))
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async moveTask(idOrRef: string, column: string): Promise<Task> {
|
|
174
|
+
await this.initialize()
|
|
175
|
+
return this.enrichTaskWithCount(await this.store.moveTask(idOrRef, column))
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async deleteTask(idOrRef: string): Promise<Task> {
|
|
179
|
+
await this.initialize()
|
|
180
|
+
return this.enrichTaskWithCount(await this.store.deleteTask(idOrRef))
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async listComments(idOrRef: string): Promise<TaskComment[]> {
|
|
184
|
+
await this.initialize()
|
|
185
|
+
return this.store.listComments(idOrRef)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async getComment(idOrRef: string, commentId: string): Promise<TaskComment> {
|
|
189
|
+
await this.initialize()
|
|
190
|
+
return this.store.getComment(idOrRef, commentId)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async comment(idOrRef: string, body: string): Promise<TaskComment> {
|
|
194
|
+
await this.initialize()
|
|
195
|
+
return this.store.comment(idOrRef, body)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async updateComment(idOrRef: string, commentId: string, body: string): Promise<TaskComment> {
|
|
199
|
+
await this.initialize()
|
|
200
|
+
return this.store.updateComment(idOrRef, commentId, body)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async getActivity(limit?: number, taskId?: string): Promise<ActivityEntry[]> {
|
|
204
|
+
await this.initialize()
|
|
205
|
+
return this.store.getActivity(limit, taskId)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async getMetrics(): Promise<BoardMetrics> {
|
|
209
|
+
await this.initialize()
|
|
210
|
+
return this.store.getMetrics()
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async getConfig(): Promise<BoardConfig> {
|
|
214
|
+
await this.initialize()
|
|
215
|
+
return this.store.getConfig()
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async patchConfig(input: Partial<BoardConfig>): Promise<BoardConfig> {
|
|
219
|
+
await this.initialize()
|
|
220
|
+
return this.store.patchConfig(input)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async getSyncStatus(): Promise<ProviderSyncStatus | null> {
|
|
224
|
+
return null
|
|
225
|
+
}
|
|
226
|
+
}
|
package/src/providers/local.ts
CHANGED
|
@@ -1,187 +1,11 @@
|
|
|
1
1
|
import type { Database } from 'bun:sqlite'
|
|
2
|
-
import { listActivity } from '../activity'
|
|
3
|
-
import { getConfigPath, loadConfig, saveConfig } from '../config'
|
|
4
|
-
import {
|
|
5
|
-
addComment,
|
|
6
|
-
countComments,
|
|
7
|
-
countCommentsByTask,
|
|
8
|
-
addTask,
|
|
9
|
-
deleteTask,
|
|
10
|
-
getBoardView,
|
|
11
|
-
getComment as getTaskComment,
|
|
12
|
-
getDbPath,
|
|
13
|
-
getTask,
|
|
14
|
-
listComments as listTaskComments,
|
|
15
|
-
listColumns,
|
|
16
|
-
listTasks,
|
|
17
|
-
moveTask,
|
|
18
|
-
updateComment as updateTaskComment,
|
|
19
|
-
updateTask,
|
|
20
|
-
} from '../db'
|
|
21
|
-
import { getBoardMetrics, getDiscoveredAssignees, getDiscoveredProjects } from '../metrics'
|
|
22
|
-
import type { BoardBootstrap, BoardConfig, Task, TaskComment } from '../types'
|
|
23
|
-
import { ErrorCode, KanbanError } from '../errors'
|
|
24
|
-
import { LOCAL_CAPABILITIES } from './capabilities'
|
|
25
|
-
import type {
|
|
26
|
-
CreateTaskInput,
|
|
27
|
-
KanbanProvider,
|
|
28
|
-
ProviderContext,
|
|
29
|
-
ProviderSyncStatus,
|
|
30
|
-
TaskListFilters,
|
|
31
|
-
UpdateTaskInput,
|
|
32
|
-
} from './types'
|
|
33
2
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
discoveredAssignees = getDiscoveredAssignees(db),
|
|
38
|
-
discoveredProjects = getDiscoveredProjects(db),
|
|
39
|
-
): BoardConfig {
|
|
40
|
-
return {
|
|
41
|
-
...loadConfig(dbPath),
|
|
42
|
-
provider: 'local',
|
|
43
|
-
discoveredAssignees,
|
|
44
|
-
discoveredProjects,
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export class LocalProvider implements KanbanProvider {
|
|
49
|
-
readonly type = 'local' as const
|
|
50
|
-
|
|
51
|
-
constructor(
|
|
52
|
-
private readonly db: Database,
|
|
53
|
-
private readonly dbPath = getDbPath(),
|
|
54
|
-
) {}
|
|
55
|
-
|
|
56
|
-
private enrichTask(task: Task, commentCount?: number): Task {
|
|
57
|
-
const revision = task.revision ?? 0
|
|
58
|
-
const assignees = task.assignee ? [task.assignee] : []
|
|
59
|
-
const labels = Array.isArray(task.labels) ? task.labels : []
|
|
60
|
-
return {
|
|
61
|
-
...task,
|
|
62
|
-
providerId: task.id,
|
|
63
|
-
externalRef: task.id,
|
|
64
|
-
url: null,
|
|
65
|
-
assignees,
|
|
66
|
-
labels,
|
|
67
|
-
comment_count: commentCount ?? countComments(this.db, task.id),
|
|
68
|
-
version: String(revision),
|
|
69
|
-
source_updated_at: null,
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
async getContext(): Promise<ProviderContext> {
|
|
74
|
-
return {
|
|
75
|
-
provider: this.type,
|
|
76
|
-
capabilities: LOCAL_CAPABILITIES,
|
|
77
|
-
team: null,
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async getBootstrap(): Promise<BoardBootstrap> {
|
|
82
|
-
const metrics = getBoardMetrics(this.db)
|
|
83
|
-
return {
|
|
84
|
-
provider: this.type,
|
|
85
|
-
capabilities: LOCAL_CAPABILITIES,
|
|
86
|
-
board: await this.getBoard(),
|
|
87
|
-
config: buildLocalConfig(this.db, this.dbPath, metrics.assignees, metrics.projects),
|
|
88
|
-
metrics,
|
|
89
|
-
activity: listActivity(this.db, { limit: 50 }),
|
|
90
|
-
team: null,
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
async getBoard() {
|
|
95
|
-
const board = getBoardView(this.db)
|
|
96
|
-
const counts = countCommentsByTask(this.db)
|
|
97
|
-
return {
|
|
98
|
-
columns: board.columns.map((column) => ({
|
|
99
|
-
...column,
|
|
100
|
-
tasks: column.tasks.map((task) => this.enrichTask(task, counts.get(task.id) ?? 0)),
|
|
101
|
-
})),
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
async listColumns() {
|
|
106
|
-
return listColumns(this.db)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
async listTasks(filters: TaskListFilters = {}) {
|
|
110
|
-
const counts = countCommentsByTask(this.db)
|
|
111
|
-
return listTasks(this.db, filters).map((task) =>
|
|
112
|
-
this.enrichTask(task, counts.get(task.id) ?? 0),
|
|
113
|
-
)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
async getTask(idOrRef: string) {
|
|
117
|
-
return this.enrichTask(getTask(this.db, idOrRef))
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
async createTask(input: CreateTaskInput) {
|
|
121
|
-
return this.enrichTask(addTask(this.db, input.title, input))
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
async updateTask(idOrRef: string, input: UpdateTaskInput) {
|
|
125
|
-
if (input.expectedVersion !== undefined) {
|
|
126
|
-
const current = getTask(this.db, idOrRef)
|
|
127
|
-
const currentVersion = String(current.revision ?? 0)
|
|
128
|
-
if (currentVersion !== input.expectedVersion) {
|
|
129
|
-
throw new KanbanError(
|
|
130
|
-
ErrorCode.CONFLICT,
|
|
131
|
-
`Task ${idOrRef} was modified since you loaded it (expected version ${input.expectedVersion}, current ${currentVersion})`,
|
|
132
|
-
)
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
const updates: Omit<UpdateTaskInput, 'expectedVersion'> = { ...input }
|
|
136
|
-
delete (updates as UpdateTaskInput).expectedVersion
|
|
137
|
-
return this.enrichTask(updateTask(this.db, idOrRef, updates))
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
async moveTask(idOrRef: string, column: string) {
|
|
141
|
-
return this.enrichTask(moveTask(this.db, idOrRef, column))
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
async deleteTask(idOrRef: string) {
|
|
145
|
-
return this.enrichTask(deleteTask(this.db, idOrRef))
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
async listComments(idOrRef: string): Promise<TaskComment[]> {
|
|
149
|
-
return listTaskComments(this.db, idOrRef)
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
async getComment(idOrRef: string, commentId: string): Promise<TaskComment> {
|
|
153
|
-
return getTaskComment(this.db, idOrRef, commentId)
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
async comment(idOrRef: string, body: string): Promise<TaskComment> {
|
|
157
|
-
return addComment(this.db, idOrRef, body)
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
async updateComment(idOrRef: string, commentId: string, body: string): Promise<TaskComment> {
|
|
161
|
-
return updateTaskComment(this.db, idOrRef, commentId, body)
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
async getActivity(limit?: number, taskId?: string) {
|
|
165
|
-
return listActivity(this.db, { limit, taskId })
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
async getMetrics() {
|
|
169
|
-
return getBoardMetrics(this.db)
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
async getConfig(): Promise<BoardConfig> {
|
|
173
|
-
return buildLocalConfig(this.db, this.dbPath)
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
async patchConfig(input: Partial<BoardConfig>) {
|
|
177
|
-
const config = loadConfig(this.dbPath)
|
|
178
|
-
if (input.members) config.members = input.members
|
|
179
|
-
if (input.projects) config.projects = input.projects
|
|
180
|
-
saveConfig(getConfigPath(this.dbPath), config)
|
|
181
|
-
return this.getConfig()
|
|
182
|
-
}
|
|
3
|
+
import { getDbPath } from '../db'
|
|
4
|
+
import { LocalProviderCore } from './local-core'
|
|
5
|
+
import { SqliteLocalStore } from './sqlite-local-store'
|
|
183
6
|
|
|
184
|
-
|
|
185
|
-
|
|
7
|
+
export class LocalProvider extends LocalProviderCore {
|
|
8
|
+
constructor(db: Database, dbPath = getDbPath()) {
|
|
9
|
+
super(new SqliteLocalStore(db, dbPath))
|
|
186
10
|
}
|
|
187
11
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Sql, TransactionSql } from 'postgres'
|
|
2
|
+
|
|
3
|
+
// postgres.js's TransactionSql does not extend Sql, so batch helpers accept either.
|
|
4
|
+
export type Exec = Sql | TransactionSql
|
|
5
|
+
|
|
6
|
+
// Bind a row array as a single jsonb parameter for jsonb_to_recordset batch
|
|
7
|
+
// statements. The cast works around sql.json's narrower JSONValue parameter type.
|
|
8
|
+
export function recordsetJson(sql: Exec, rows: unknown[]): ReturnType<Sql['json']> {
|
|
9
|
+
return sql.json(rows as never)
|
|
10
|
+
}
|