@remit/drizzle-service 0.0.68 → 0.0.69
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
CHANGED
package/src/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ export { CreateFailedConflictError, NotFoundError } from "./error.js";
|
|
|
2
2
|
export { CalendarCollectionRepo } from "./repos/calendar-collection.js";
|
|
3
3
|
export { CalendarEventIndexRepo } from "./repos/calendar-event-index.js";
|
|
4
4
|
export { CalendarObjectRepo } from "./repos/calendar-object.js";
|
|
5
|
+
export { CalendarSuggestionRepo } from "./repos/calendar-suggestion.js";
|
|
5
6
|
export { DrizzleCalendarUnitOfWork } from "./repos/calendar-unit-of-work.js";
|
|
6
7
|
export {
|
|
7
8
|
type CascadeDeleteLogger,
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { calendarSuggestionRepositoryConformance } from "@remit/data-ports/conformance";
|
|
2
|
+
import { NotFoundError } from "../error.js";
|
|
3
|
+
import { randomId } from "../id.js";
|
|
4
|
+
import { calendarSuggestionTable } from "../schema.js";
|
|
5
|
+
import { createSqliteTestDb } from "../test-db-sqlite.js";
|
|
6
|
+
import { CalendarSuggestionRepo } from "./calendar-suggestion.js";
|
|
7
|
+
|
|
8
|
+
let close: (() => Promise<void>) | undefined;
|
|
9
|
+
|
|
10
|
+
calendarSuggestionRepositoryConformance({
|
|
11
|
+
async createRepository() {
|
|
12
|
+
const { db, close: closeDb } = await createSqliteTestDb({
|
|
13
|
+
calendarSuggestions: calendarSuggestionTable,
|
|
14
|
+
});
|
|
15
|
+
close = closeDb;
|
|
16
|
+
return new CalendarSuggestionRepo(db as never);
|
|
17
|
+
},
|
|
18
|
+
teardown: async () => {
|
|
19
|
+
await close?.();
|
|
20
|
+
},
|
|
21
|
+
makeId: () => randomId(),
|
|
22
|
+
isNotFoundError: (error) => error instanceof NotFoundError,
|
|
23
|
+
});
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CalendarSuggestionItem,
|
|
3
|
+
ICalendarSuggestionRepository,
|
|
4
|
+
PutCalendarSuggestionInput,
|
|
5
|
+
ResultList,
|
|
6
|
+
SettleCalendarSuggestionInput,
|
|
7
|
+
} from "@remit/data-ports";
|
|
8
|
+
import { deriveCalendarSuggestionId } from "@remit/data-ports/id";
|
|
9
|
+
import { CalendarSuggestionState } from "@remit/domain-enums";
|
|
10
|
+
import { and, asc, desc, eq, lt, or } from "drizzle-orm";
|
|
11
|
+
import type { Db } from "../db.js";
|
|
12
|
+
import { NotFoundError } from "../error.js";
|
|
13
|
+
import { decodeToken, resultList } from "../pagination.js";
|
|
14
|
+
import { calendarSuggestionTable } from "../schema.js";
|
|
15
|
+
|
|
16
|
+
type DB = Db<Record<string, unknown>>;
|
|
17
|
+
|
|
18
|
+
function rowToCalendarSuggestion(
|
|
19
|
+
row: typeof calendarSuggestionTable.$inferSelect,
|
|
20
|
+
): CalendarSuggestionItem {
|
|
21
|
+
return {
|
|
22
|
+
suggestionId: row.suggestionId,
|
|
23
|
+
accountConfigId: row.accountConfigId,
|
|
24
|
+
messageId: row.messageId,
|
|
25
|
+
bodyPartId: row.bodyPartId,
|
|
26
|
+
icalUid: row.icalUid,
|
|
27
|
+
sequence: row.sequence,
|
|
28
|
+
method: row.method,
|
|
29
|
+
source: row.source,
|
|
30
|
+
state: row.state,
|
|
31
|
+
summary: row.summary,
|
|
32
|
+
dtStart: row.dtStart,
|
|
33
|
+
dtEnd: row.dtEnd,
|
|
34
|
+
allDay: row.allDay,
|
|
35
|
+
location: row.location,
|
|
36
|
+
organizer: row.organizer,
|
|
37
|
+
zoneCertainty: row.zoneCertainty,
|
|
38
|
+
icalData: row.icalData,
|
|
39
|
+
acceptedCalendarObjectId: row.acceptedCalendarObjectId,
|
|
40
|
+
createdAt: row.createdAt,
|
|
41
|
+
updatedAt: row.updatedAt,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export class CalendarSuggestionRepo implements ICalendarSuggestionRepository {
|
|
46
|
+
constructor(private db: DB) {}
|
|
47
|
+
|
|
48
|
+
async put(
|
|
49
|
+
input: PutCalendarSuggestionInput,
|
|
50
|
+
): Promise<CalendarSuggestionItem> {
|
|
51
|
+
const now = Date.now();
|
|
52
|
+
const suggestionId = deriveCalendarSuggestionId(
|
|
53
|
+
input.messageId,
|
|
54
|
+
input.bodyPartId,
|
|
55
|
+
input.icalUid,
|
|
56
|
+
);
|
|
57
|
+
const [row] = await this.db
|
|
58
|
+
.insert(calendarSuggestionTable)
|
|
59
|
+
.values({
|
|
60
|
+
...input,
|
|
61
|
+
suggestionId,
|
|
62
|
+
state: CalendarSuggestionState.Pending,
|
|
63
|
+
acceptedCalendarObjectId: "",
|
|
64
|
+
createdAt: now,
|
|
65
|
+
updatedAt: now,
|
|
66
|
+
})
|
|
67
|
+
.onConflictDoUpdate({
|
|
68
|
+
target: [
|
|
69
|
+
calendarSuggestionTable.accountConfigId,
|
|
70
|
+
calendarSuggestionTable.suggestionId,
|
|
71
|
+
],
|
|
72
|
+
// `state` and `acceptedCalendarObjectId` are deliberately absent from
|
|
73
|
+
// the update set: they carry what a person decided, and a producer
|
|
74
|
+
// re-reading the message must not walk that back to Pending.
|
|
75
|
+
set: { ...input, updatedAt: now },
|
|
76
|
+
})
|
|
77
|
+
.returning();
|
|
78
|
+
return rowToCalendarSuggestion(row);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async get(
|
|
82
|
+
accountConfigId: string,
|
|
83
|
+
suggestionId: string,
|
|
84
|
+
): Promise<CalendarSuggestionItem> {
|
|
85
|
+
const [row] = await this.db
|
|
86
|
+
.select()
|
|
87
|
+
.from(calendarSuggestionTable)
|
|
88
|
+
.where(
|
|
89
|
+
and(
|
|
90
|
+
eq(calendarSuggestionTable.accountConfigId, accountConfigId),
|
|
91
|
+
eq(calendarSuggestionTable.suggestionId, suggestionId),
|
|
92
|
+
),
|
|
93
|
+
);
|
|
94
|
+
if (!row) {
|
|
95
|
+
throw new NotFoundError(`Calendar suggestion not found: ${suggestionId}`);
|
|
96
|
+
}
|
|
97
|
+
return rowToCalendarSuggestion(row);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async listByMessage(
|
|
101
|
+
accountConfigId: string,
|
|
102
|
+
messageId: string,
|
|
103
|
+
): Promise<CalendarSuggestionItem[]> {
|
|
104
|
+
const rows = await this.db
|
|
105
|
+
.select()
|
|
106
|
+
.from(calendarSuggestionTable)
|
|
107
|
+
.where(
|
|
108
|
+
and(
|
|
109
|
+
eq(calendarSuggestionTable.accountConfigId, accountConfigId),
|
|
110
|
+
eq(calendarSuggestionTable.messageId, messageId),
|
|
111
|
+
),
|
|
112
|
+
)
|
|
113
|
+
.orderBy(
|
|
114
|
+
asc(calendarSuggestionTable.createdAt),
|
|
115
|
+
asc(calendarSuggestionTable.suggestionId),
|
|
116
|
+
);
|
|
117
|
+
return rows.map(rowToCalendarSuggestion);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Newest first, paged on a keyset over `(createdAt, suggestionId)` — the
|
|
122
|
+
* exact trailing members of the `byState` index's sort key, read backwards.
|
|
123
|
+
* The order is the index's own, never a sort applied on top of it.
|
|
124
|
+
*/
|
|
125
|
+
async listByState(
|
|
126
|
+
accountConfigId: string,
|
|
127
|
+
state: CalendarSuggestionItem["state"],
|
|
128
|
+
options?: { limit?: number; continuationToken?: string },
|
|
129
|
+
): Promise<ResultList<CalendarSuggestionItem>> {
|
|
130
|
+
const limit = options?.limit ?? 100;
|
|
131
|
+
const cursor = options?.continuationToken
|
|
132
|
+
? decodeToken(options.continuationToken)
|
|
133
|
+
: undefined;
|
|
134
|
+
const after = cursor
|
|
135
|
+
? {
|
|
136
|
+
createdAt: cursor.createdAt as number,
|
|
137
|
+
suggestionId: cursor.suggestionId as string,
|
|
138
|
+
}
|
|
139
|
+
: undefined;
|
|
140
|
+
|
|
141
|
+
const rows = await this.db
|
|
142
|
+
.select()
|
|
143
|
+
.from(calendarSuggestionTable)
|
|
144
|
+
.where(
|
|
145
|
+
and(
|
|
146
|
+
eq(calendarSuggestionTable.accountConfigId, accountConfigId),
|
|
147
|
+
eq(calendarSuggestionTable.state, state),
|
|
148
|
+
after
|
|
149
|
+
? or(
|
|
150
|
+
lt(calendarSuggestionTable.createdAt, after.createdAt),
|
|
151
|
+
and(
|
|
152
|
+
eq(calendarSuggestionTable.createdAt, after.createdAt),
|
|
153
|
+
lt(calendarSuggestionTable.suggestionId, after.suggestionId),
|
|
154
|
+
),
|
|
155
|
+
)
|
|
156
|
+
: undefined,
|
|
157
|
+
),
|
|
158
|
+
)
|
|
159
|
+
.orderBy(
|
|
160
|
+
desc(calendarSuggestionTable.createdAt),
|
|
161
|
+
desc(calendarSuggestionTable.suggestionId),
|
|
162
|
+
)
|
|
163
|
+
.limit(limit);
|
|
164
|
+
|
|
165
|
+
const items = rows.map(rowToCalendarSuggestion);
|
|
166
|
+
const lastItem = items[items.length - 1];
|
|
167
|
+
return resultList(
|
|
168
|
+
items,
|
|
169
|
+
limit,
|
|
170
|
+
lastItem
|
|
171
|
+
? {
|
|
172
|
+
createdAt: lastItem.createdAt,
|
|
173
|
+
suggestionId: lastItem.suggestionId,
|
|
174
|
+
}
|
|
175
|
+
: undefined,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async settle(
|
|
180
|
+
accountConfigId: string,
|
|
181
|
+
suggestionId: string,
|
|
182
|
+
input: SettleCalendarSuggestionInput,
|
|
183
|
+
): Promise<CalendarSuggestionItem> {
|
|
184
|
+
const [row] = await this.db
|
|
185
|
+
.update(calendarSuggestionTable)
|
|
186
|
+
.set({ ...input, updatedAt: Date.now() })
|
|
187
|
+
.where(
|
|
188
|
+
and(
|
|
189
|
+
eq(calendarSuggestionTable.accountConfigId, accountConfigId),
|
|
190
|
+
eq(calendarSuggestionTable.suggestionId, suggestionId),
|
|
191
|
+
),
|
|
192
|
+
)
|
|
193
|
+
.returning();
|
|
194
|
+
if (!row) {
|
|
195
|
+
throw new NotFoundError(`Calendar suggestion not found: ${suggestionId}`);
|
|
196
|
+
}
|
|
197
|
+
return rowToCalendarSuggestion(row);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async supersedeIfPending(
|
|
201
|
+
accountConfigId: string,
|
|
202
|
+
suggestionId: string,
|
|
203
|
+
): Promise<CalendarSuggestionItem | null> {
|
|
204
|
+
const [row] = await this.db
|
|
205
|
+
.update(calendarSuggestionTable)
|
|
206
|
+
.set({
|
|
207
|
+
state: CalendarSuggestionState.Superseded,
|
|
208
|
+
acceptedCalendarObjectId: "",
|
|
209
|
+
updatedAt: Date.now(),
|
|
210
|
+
})
|
|
211
|
+
.where(
|
|
212
|
+
and(
|
|
213
|
+
eq(calendarSuggestionTable.accountConfigId, accountConfigId),
|
|
214
|
+
eq(calendarSuggestionTable.suggestionId, suggestionId),
|
|
215
|
+
// The condition is the point: a card the user answered between
|
|
216
|
+
// the producer's read and this write must keep their answer.
|
|
217
|
+
eq(calendarSuggestionTable.state, CalendarSuggestionState.Pending),
|
|
218
|
+
),
|
|
219
|
+
)
|
|
220
|
+
.returning();
|
|
221
|
+
return row ? rowToCalendarSuggestion(row) : null;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
@@ -7,6 +7,7 @@ import { runInTransaction } from "../tx.js";
|
|
|
7
7
|
import { CalendarCollectionRepo } from "./calendar-collection.js";
|
|
8
8
|
import { CalendarEventIndexRepo } from "./calendar-event-index.js";
|
|
9
9
|
import { CalendarObjectRepo } from "./calendar-object.js";
|
|
10
|
+
import { CalendarSuggestionRepo } from "./calendar-suggestion.js";
|
|
10
11
|
|
|
11
12
|
type DB = Db<Record<string, unknown>>;
|
|
12
13
|
|
|
@@ -21,6 +22,7 @@ export class DrizzleCalendarUnitOfWork implements ICalendarUnitOfWork {
|
|
|
21
22
|
calendarCollection: new CalendarCollectionRepo(tx as never),
|
|
22
23
|
calendarObject: new CalendarObjectRepo(tx as never),
|
|
23
24
|
calendarEventIndex: new CalendarEventIndexRepo(tx as never),
|
|
25
|
+
calendarSuggestion: new CalendarSuggestionRepo(tx as never),
|
|
24
26
|
}),
|
|
25
27
|
);
|
|
26
28
|
}
|
package/src/schema.ts
CHANGED
|
@@ -11,6 +11,7 @@ import * as entities from "@remit/drizzle-sqlite-schema";
|
|
|
11
11
|
export const calendarTable = entities.calendarCollections;
|
|
12
12
|
export const calendarObjectTable = entities.calendarObjects;
|
|
13
13
|
export const calendarEventIndexTable = entities.calendarEventIndexes;
|
|
14
|
+
export const calendarSuggestionTable = entities.calendarSuggestions;
|
|
14
15
|
export const filterAnchorTable = entities.filterAnchors;
|
|
15
16
|
export const filterTable = entities.filters;
|
|
16
17
|
export const labelTable = entities.labels;
|