@fragno-dev/db 0.2.1 → 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 +32 -0
- package/dist/adapters/generic-sql/query/where-builder.js +1 -1
- package/dist/db-fragment-definition-builder.d.ts +27 -89
- package/dist/db-fragment-definition-builder.d.ts.map +1 -1
- package/dist/db-fragment-definition-builder.js +16 -56
- 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 +351 -100
- 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 +431 -263
- 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 +17 -8
- package/dist/query/unit-of-work/unit-of-work.d.ts.map +1 -1
- package/dist/query/unit-of-work/unit-of-work.js +24 -8
- 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 +3 -1
- package/dist/schema/create.d.ts.map +1 -1
- package/dist/schema/create.js +2 -1
- 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/package.json +1 -1
- 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 +58 -248
- 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 -121
- package/src/hooks/hooks.test.ts +248 -256
- 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 +1494 -1464
- package/src/query/unit-of-work/execute-unit-of-work.ts +1685 -590
- package/src/query/unit-of-work/tx-builder.test.ts +1041 -0
- package/src/query/unit-of-work/unit-of-work-coordinator.test.ts +20 -20
- package/src/query/unit-of-work/unit-of-work.test.ts +64 -0
- package/src/query/unit-of-work/unit-of-work.ts +26 -13
- package/src/schema/create.ts +2 -0
- 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,45 +68,56 @@ 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
|
})
|
|
@@ -116,104 +127,128 @@ export const internalFragmentDef = new DatabaseFragmentDefinitionBuilder(
|
|
|
116
127
|
* Get pending hook events for processing.
|
|
117
128
|
* Returns all pending events for the given namespace that are ready to be processed.
|
|
118
129
|
*/
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
// Filter for pending status and events ready for retry
|
|
138
|
-
const now = new Date();
|
|
139
|
-
const ready = events.filter((event) => {
|
|
140
|
-
// FIXME(Wilco): this should be handled by the database query, but there seems to be an issue.
|
|
141
|
-
if (!event.nextRetryAt) {
|
|
142
|
-
return true; // Newly created events (nextRetryAt = null) are ready
|
|
143
|
-
}
|
|
144
|
-
return event.nextRetryAt <= now; // Only include if retry time has passed
|
|
145
|
-
});
|
|
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
|
+
});
|
|
146
148
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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();
|
|
155
159
|
},
|
|
156
160
|
|
|
157
161
|
/**
|
|
158
162
|
* Mark a hook event as completed.
|
|
159
163
|
*/
|
|
160
|
-
markHookCompleted(eventId: FragnoId)
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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();
|
|
165
172
|
},
|
|
166
173
|
|
|
167
174
|
/**
|
|
168
175
|
* Mark a hook event as failed and schedule next retry.
|
|
169
176
|
*/
|
|
170
|
-
markHookFailed(
|
|
171
|
-
eventId: FragnoId,
|
|
172
|
-
error: string,
|
|
173
|
-
attempts: number,
|
|
174
|
-
retryPolicy: RetryPolicy,
|
|
175
|
-
): void {
|
|
176
|
-
const uow = this.uow(internalSchema);
|
|
177
|
-
|
|
177
|
+
markHookFailed(eventId: FragnoId, error: string, attempts: number, retryPolicy: RetryPolicy) {
|
|
178
178
|
const newAttempts = attempts + 1;
|
|
179
179
|
const shouldRetry = retryPolicy.shouldRetry(newAttempts - 1);
|
|
180
180
|
|
|
181
|
-
|
|
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
|
-
|
|
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();
|
|
207
211
|
},
|
|
208
212
|
|
|
209
213
|
/**
|
|
210
214
|
* Mark a hook event as processing (to prevent concurrent execution).
|
|
211
215
|
*/
|
|
212
|
-
markHookProcessing(eventId: FragnoId)
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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();
|
|
217
252
|
},
|
|
218
253
|
});
|
|
219
254
|
})
|
|
@@ -232,16 +267,15 @@ export async function getSchemaVersionFromDatabase(
|
|
|
232
267
|
namespace: string,
|
|
233
268
|
): Promise<number> {
|
|
234
269
|
try {
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
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();
|
|
243
277
|
});
|
|
244
|
-
return
|
|
278
|
+
return setting ? parseInt(setting.value, 10) : 0;
|
|
245
279
|
} catch {
|
|
246
280
|
return 0;
|
|
247
281
|
}
|