@fragno-dev/db 0.2.0 → 0.2.2
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 +34 -30
- package/CHANGELOG.md +49 -0
- package/dist/adapters/generic-sql/query/where-builder.js +1 -1
- package/dist/db-fragment-definition-builder.d.ts +31 -39
- package/dist/db-fragment-definition-builder.d.ts.map +1 -1
- package/dist/db-fragment-definition-builder.js +20 -16
- package/dist/db-fragment-definition-builder.js.map +1 -1
- package/dist/fragments/internal-fragment.d.ts +94 -8
- package/dist/fragments/internal-fragment.d.ts.map +1 -1
- package/dist/fragments/internal-fragment.js +56 -55
- package/dist/fragments/internal-fragment.js.map +1 -1
- package/dist/hooks/hooks.d.ts +5 -3
- package/dist/hooks/hooks.d.ts.map +1 -1
- package/dist/hooks/hooks.js +38 -37
- package/dist/hooks/hooks.js.map +1 -1
- package/dist/mod.d.ts +3 -3
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +4 -4
- package/dist/mod.js.map +1 -1
- package/dist/query/unit-of-work/execute-unit-of-work.d.ts +367 -80
- package/dist/query/unit-of-work/execute-unit-of-work.d.ts.map +1 -1
- package/dist/query/unit-of-work/execute-unit-of-work.js +448 -148
- package/dist/query/unit-of-work/execute-unit-of-work.js.map +1 -1
- package/dist/query/unit-of-work/unit-of-work.d.ts +35 -11
- package/dist/query/unit-of-work/unit-of-work.d.ts.map +1 -1
- package/dist/query/unit-of-work/unit-of-work.js +49 -19
- package/dist/query/unit-of-work/unit-of-work.js.map +1 -1
- package/dist/query/value-decoding.js +1 -1
- package/dist/schema/create.d.ts +2 -3
- package/dist/schema/create.d.ts.map +1 -1
- package/dist/schema/create.js +2 -5
- package/dist/schema/create.js.map +1 -1
- package/dist/schema/generate-id.d.ts +20 -0
- package/dist/schema/generate-id.d.ts.map +1 -0
- package/dist/schema/generate-id.js +28 -0
- package/dist/schema/generate-id.js.map +1 -0
- package/dist/sql-driver/dialects/durable-object-dialect.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/adapters/drizzle/drizzle-adapter-pglite.test.ts +1 -0
- package/src/adapters/drizzle/drizzle-adapter-sqlite3.test.ts +41 -25
- package/src/adapters/generic-sql/test/generic-drizzle-adapter-sqlite3.test.ts +39 -25
- package/src/db-fragment-definition-builder.test.ts +58 -42
- package/src/db-fragment-definition-builder.ts +78 -88
- package/src/db-fragment-instantiator.test.ts +64 -88
- package/src/db-fragment-integration.test.ts +292 -142
- package/src/fragments/internal-fragment.test.ts +272 -266
- package/src/fragments/internal-fragment.ts +155 -122
- package/src/hooks/hooks.test.ts +268 -264
- package/src/hooks/hooks.ts +74 -63
- package/src/mod.ts +14 -4
- package/src/query/unit-of-work/execute-unit-of-work.test.ts +1582 -998
- package/src/query/unit-of-work/execute-unit-of-work.ts +1746 -343
- package/src/query/unit-of-work/tx-builder.test.ts +1041 -0
- package/src/query/unit-of-work/unit-of-work-coordinator.test.ts +269 -21
- package/src/query/unit-of-work/unit-of-work.test.ts +64 -0
- package/src/query/unit-of-work/unit-of-work.ts +65 -30
- package/src/schema/create.ts +2 -5
- package/src/schema/generate-id.test.ts +57 -0
- package/src/schema/generate-id.ts +38 -0
- package/src/shared/config.ts +0 -10
- package/src/shared/connection-pool.ts +0 -24
- package/src/shared/prisma.ts +0 -45
|
@@ -68,153 +68,187 @@ export const internalFragmentDef = new DatabaseFragmentDefinitionBuilder(
|
|
|
68
68
|
)
|
|
69
69
|
.providesService("settingsService", ({ defineService }) => {
|
|
70
70
|
return defineService({
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
/**
|
|
72
|
+
* Get a setting by namespace and key.
|
|
73
|
+
*/
|
|
74
|
+
get(namespace: string, key: string) {
|
|
75
75
|
const fullKey = `${namespace}.${key}`;
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
return this.serviceTx(internalSchema)
|
|
77
|
+
.retrieve((uow) =>
|
|
78
|
+
uow.findFirst(SETTINGS_TABLE_NAME, (b) =>
|
|
79
|
+
b.whereIndex("unique_key", (eb) => eb("key", "=", fullKey)),
|
|
80
|
+
),
|
|
81
|
+
)
|
|
82
|
+
.transformRetrieve(
|
|
83
|
+
([result]): { id: FragnoId; key: string; value: string } | undefined =>
|
|
84
|
+
result ?? undefined,
|
|
85
|
+
)
|
|
86
|
+
.build();
|
|
81
87
|
},
|
|
82
88
|
|
|
83
|
-
|
|
89
|
+
/**
|
|
90
|
+
* Set a setting value by namespace and key.
|
|
91
|
+
*/
|
|
92
|
+
set(namespace: string, key: string, value: string) {
|
|
84
93
|
const fullKey = `${namespace}.${key}`;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
await uow.mutationPhase;
|
|
94
|
+
return this.serviceTx(internalSchema)
|
|
95
|
+
.retrieve((uow) =>
|
|
96
|
+
uow.findFirst(SETTINGS_TABLE_NAME, (b) =>
|
|
97
|
+
b.whereIndex("unique_key", (eb) => eb("key", "=", fullKey)),
|
|
98
|
+
),
|
|
99
|
+
)
|
|
100
|
+
.transformRetrieve(([result]) => result)
|
|
101
|
+
.mutate(({ uow, retrieveResult }) => {
|
|
102
|
+
if (retrieveResult) {
|
|
103
|
+
uow.update(SETTINGS_TABLE_NAME, retrieveResult.id, (b) => b.set({ value }).check());
|
|
104
|
+
} else {
|
|
105
|
+
uow.create(SETTINGS_TABLE_NAME, {
|
|
106
|
+
key: fullKey,
|
|
107
|
+
value,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
.build();
|
|
104
112
|
},
|
|
105
113
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
114
|
+
/**
|
|
115
|
+
* Delete a setting by ID.
|
|
116
|
+
*/
|
|
117
|
+
delete(id: FragnoId) {
|
|
118
|
+
return this.serviceTx(internalSchema)
|
|
119
|
+
.mutate(({ uow }) => uow.delete(SETTINGS_TABLE_NAME, id))
|
|
120
|
+
.build();
|
|
110
121
|
},
|
|
111
122
|
});
|
|
112
123
|
})
|
|
113
124
|
.providesService("hookService", ({ defineService }) => {
|
|
114
|
-
// TODO(Wilco): re-implement this better
|
|
115
125
|
return defineService({
|
|
116
126
|
/**
|
|
117
127
|
* Get pending hook events for processing.
|
|
118
128
|
* Returns all pending events for the given namespace that are ready to be processed.
|
|
119
129
|
*/
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
// Filter for pending status and events ready for retry
|
|
139
|
-
const now = new Date();
|
|
140
|
-
const ready = events.filter((event) => {
|
|
141
|
-
// FIXME(Wilco): this should be handled by the database query, but there seems to be an issue.
|
|
142
|
-
if (!event.nextRetryAt) {
|
|
143
|
-
return true; // Newly created events (nextRetryAt = null) are ready
|
|
144
|
-
}
|
|
145
|
-
return event.nextRetryAt <= now; // Only include if retry time has passed
|
|
146
|
-
});
|
|
130
|
+
getPendingHookEvents(namespace: string) {
|
|
131
|
+
return this.serviceTx(internalSchema)
|
|
132
|
+
.retrieve((uow) =>
|
|
133
|
+
uow.find("fragno_hooks", (b) =>
|
|
134
|
+
b.whereIndex("idx_namespace_status_retry", (eb) =>
|
|
135
|
+
eb.and(eb("namespace", "=", namespace), eb("status", "=", "pending")),
|
|
136
|
+
),
|
|
137
|
+
),
|
|
138
|
+
)
|
|
139
|
+
.transformRetrieve(([events]) => {
|
|
140
|
+
const now = new Date();
|
|
141
|
+
// FIXME(Wilco): this should be handled by the database query, but there seems to be an issue.
|
|
142
|
+
const ready = events.filter((event) => {
|
|
143
|
+
if (!event.nextRetryAt) {
|
|
144
|
+
return true; // Newly created events (nextRetryAt = null) are ready
|
|
145
|
+
}
|
|
146
|
+
return event.nextRetryAt <= now; // Only include if retry time has passed
|
|
147
|
+
});
|
|
147
148
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
149
|
+
return ready.map((event) => ({
|
|
150
|
+
id: event.id,
|
|
151
|
+
hookName: event.hookName,
|
|
152
|
+
payload: event.payload as unknown,
|
|
153
|
+
attempts: event.attempts,
|
|
154
|
+
maxAttempts: event.maxAttempts,
|
|
155
|
+
idempotencyKey: event.nonce,
|
|
156
|
+
}));
|
|
157
|
+
})
|
|
158
|
+
.build();
|
|
156
159
|
},
|
|
157
160
|
|
|
158
161
|
/**
|
|
159
162
|
* Mark a hook event as completed.
|
|
160
163
|
*/
|
|
161
|
-
markHookCompleted(eventId: FragnoId)
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
164
|
+
markHookCompleted(eventId: FragnoId) {
|
|
165
|
+
return this.serviceTx(internalSchema)
|
|
166
|
+
.mutate(({ uow }) =>
|
|
167
|
+
uow.update("fragno_hooks", eventId, (b) =>
|
|
168
|
+
b.set({ status: "completed", lastAttemptAt: new Date() }).check(),
|
|
169
|
+
),
|
|
170
|
+
)
|
|
171
|
+
.build();
|
|
166
172
|
},
|
|
167
173
|
|
|
168
174
|
/**
|
|
169
175
|
* Mark a hook event as failed and schedule next retry.
|
|
170
176
|
*/
|
|
171
|
-
markHookFailed(
|
|
172
|
-
eventId: FragnoId,
|
|
173
|
-
error: string,
|
|
174
|
-
attempts: number,
|
|
175
|
-
retryPolicy: RetryPolicy,
|
|
176
|
-
): void {
|
|
177
|
-
const uow = this.uow(internalSchema);
|
|
178
|
-
|
|
177
|
+
markHookFailed(eventId: FragnoId, error: string, attempts: number, retryPolicy: RetryPolicy) {
|
|
179
178
|
const newAttempts = attempts + 1;
|
|
180
179
|
const shouldRetry = retryPolicy.shouldRetry(newAttempts - 1);
|
|
181
180
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
.
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
181
|
+
return this.serviceTx(internalSchema)
|
|
182
|
+
.mutate(({ uow }) => {
|
|
183
|
+
if (shouldRetry) {
|
|
184
|
+
const delayMs = retryPolicy.getDelayMs(newAttempts - 1);
|
|
185
|
+
const nextRetryAt = new Date(Date.now() + delayMs);
|
|
186
|
+
uow.update("fragno_hooks", eventId, (b) =>
|
|
187
|
+
b
|
|
188
|
+
.set({
|
|
189
|
+
status: "pending",
|
|
190
|
+
attempts: newAttempts,
|
|
191
|
+
lastAttemptAt: new Date(),
|
|
192
|
+
nextRetryAt,
|
|
193
|
+
error,
|
|
194
|
+
})
|
|
195
|
+
.check(),
|
|
196
|
+
);
|
|
197
|
+
} else {
|
|
198
|
+
uow.update("fragno_hooks", eventId, (b) =>
|
|
199
|
+
b
|
|
200
|
+
.set({
|
|
201
|
+
status: "failed",
|
|
202
|
+
attempts: newAttempts,
|
|
203
|
+
lastAttemptAt: new Date(),
|
|
204
|
+
error,
|
|
205
|
+
})
|
|
206
|
+
.check(),
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
})
|
|
210
|
+
.build();
|
|
208
211
|
},
|
|
209
212
|
|
|
210
213
|
/**
|
|
211
214
|
* Mark a hook event as processing (to prevent concurrent execution).
|
|
212
215
|
*/
|
|
213
|
-
markHookProcessing(eventId: FragnoId)
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
216
|
+
markHookProcessing(eventId: FragnoId) {
|
|
217
|
+
return this.serviceTx(internalSchema)
|
|
218
|
+
.mutate(({ uow }) =>
|
|
219
|
+
uow.update("fragno_hooks", eventId, (b) =>
|
|
220
|
+
b.set({ status: "processing", lastAttemptAt: new Date() }).check(),
|
|
221
|
+
),
|
|
222
|
+
)
|
|
223
|
+
.build();
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Get a hook event by ID (for testing/verification purposes).
|
|
228
|
+
*/
|
|
229
|
+
getHookById(eventId: FragnoId) {
|
|
230
|
+
return this.serviceTx(internalSchema)
|
|
231
|
+
.retrieve((uow) =>
|
|
232
|
+
uow.findFirst("fragno_hooks", (b) =>
|
|
233
|
+
b.whereIndex("primary", (eb) => eb("id", "=", eventId)),
|
|
234
|
+
),
|
|
235
|
+
)
|
|
236
|
+
.transformRetrieve(([result]) => result ?? undefined)
|
|
237
|
+
.build();
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Get all hook events for a namespace (for testing/verification purposes).
|
|
242
|
+
*/
|
|
243
|
+
getHooksByNamespace(namespace: string) {
|
|
244
|
+
return this.serviceTx(internalSchema)
|
|
245
|
+
.retrieve((uow) =>
|
|
246
|
+
uow.find("fragno_hooks", (b) =>
|
|
247
|
+
b.whereIndex("idx_namespace_status_retry", (eb) => eb("namespace", "=", namespace)),
|
|
248
|
+
),
|
|
249
|
+
)
|
|
250
|
+
.transformRetrieve(([events]) => events)
|
|
251
|
+
.build();
|
|
218
252
|
},
|
|
219
253
|
});
|
|
220
254
|
})
|
|
@@ -233,16 +267,15 @@ export async function getSchemaVersionFromDatabase(
|
|
|
233
267
|
namespace: string,
|
|
234
268
|
): Promise<number> {
|
|
235
269
|
try {
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
return version ? parseInt(version.value, 10) : 0;
|
|
270
|
+
const setting = await fragment.inContext(async function () {
|
|
271
|
+
return await this.handlerTx()
|
|
272
|
+
.withServiceCalls(
|
|
273
|
+
() => [fragment.services.settingsService.get(namespace, "schema_version")] as const,
|
|
274
|
+
)
|
|
275
|
+
.transform(({ serviceResult: [result] }) => result)
|
|
276
|
+
.execute();
|
|
244
277
|
});
|
|
245
|
-
return
|
|
278
|
+
return setting ? parseInt(setting.value, 10) : 0;
|
|
246
279
|
} catch {
|
|
247
280
|
return 0;
|
|
248
281
|
}
|