@effect-agent/storage-sqlite 0.1.0-beta.42 → 0.1.0-beta.44

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.
@@ -81,6 +81,7 @@ const decodeRecord = Effect.fn("SqliteScheduleStore.decodeRecord")(function* (
81
81
  const record = yield* Schema.decodeEffect(Schema.fromJsonString(ScheduleRecord))(
82
82
  row.record_json,
83
83
  ).pipe(Effect.mapError(() => corrupt("decode schedule")));
84
+
84
85
  if (
85
86
  record.owner.tenantId !== row.tenant_id ||
86
87
  record.owner.ownerId !== row.owner_id ||
@@ -89,6 +90,7 @@ const decodeRecord = Effect.fn("SqliteScheduleStore.decodeRecord")(function* (
89
90
  ) {
90
91
  return yield* corrupt("decode schedule identity");
91
92
  }
93
+
92
94
  return record;
93
95
  });
94
96
 
@@ -127,6 +129,7 @@ const makeScheduleStore = Effect.gen(function* () {
127
129
  AND owner_id = ${key.owner.ownerId}
128
130
  AND schedule_id = ${key.scheduleId}
129
131
  `.pipe(Effect.mapError(() => unavailable(operation)));
132
+
130
133
  return yield* decodeRows(Schema.Array(ScheduleRow), rows, operation);
131
134
  });
132
135
 
@@ -135,8 +138,10 @@ const makeScheduleStore = Effect.gen(function* () {
135
138
  operation: string,
136
139
  ): Effect.fn.Return<ScheduleRecord | null, ScheduleStorageError> {
137
140
  const rows = yield* readRows(key, operation);
141
+
138
142
  if (rows.length === 0) return null;
139
143
  if (rows.length !== 1) return yield* corrupt(operation);
144
+
140
145
  return yield* decodeRecord(rows[0]);
141
146
  });
142
147
 
@@ -145,19 +150,23 @@ const makeScheduleStore = Effect.gen(function* () {
145
150
  const operation = "insert schedule";
146
151
  const canonical = yield* decodeInput(operation, ScheduleRecord, record);
147
152
  const recordJson = yield* encodeRecord(canonical);
153
+
148
154
  const result = yield* sql
149
155
  .withTransaction(
150
156
  Effect.gen(function* () {
151
157
  const existing = yield* readOne(canonical, operation);
158
+
152
159
  if (existing !== null) {
153
160
  if (existing.creationFingerprint === canonical.creationFingerprint) {
154
161
  return { record: existing, inserted: false } as const;
155
162
  }
163
+
156
164
  return yield* ScheduleConflict.make({
157
165
  reason: "creation",
158
166
  key: { owner: canonical.owner, scheduleId: canonical.scheduleId },
159
167
  });
160
168
  }
169
+
161
170
  const rawCounts = yield* sql<Record<string, unknown>>`
162
171
  SELECT COUNT(*) AS schedule_count
163
172
  FROM effect_agent_schedules
@@ -166,7 +175,9 @@ const makeScheduleStore = Effect.gen(function* () {
166
175
  AND (json_extract(record_json, '$.pending') IS NOT NULL OR
167
176
  (json_extract(record_json, '$.state') != 'cancelled' AND json_extract(record_json, '$.nextAtMillis') IS NOT NULL))
168
177
  `.pipe(Effect.mapError(() => unavailable(operation)));
178
+
169
179
  const counts = yield* decodeRows(Schema.Array(ScheduleCountRow), rawCounts, operation);
180
+
170
181
  if (counts.length !== 1) return yield* corrupt(operation);
171
182
  if (counts[0].schedule_count >= ownerLimit) {
172
183
  return yield* ScheduleCapacityError.make({ limit: ownerLimit });
@@ -183,11 +194,14 @@ const makeScheduleStore = Effect.gen(function* () {
183
194
  ${recordJson}
184
195
  )
185
196
  `.pipe(Effect.mapError(() => unavailable(operation)));
197
+
186
198
  return { record: canonical, inserted: true } as const;
187
199
  }),
188
200
  )
189
201
  .pipe(Effect.catchTag("SqlError", () => Effect.fail(unavailable(operation))));
202
+
190
203
  if (result.inserted) yield* scheduleFailpoint.hit("schedule:insert:after");
204
+
191
205
  return result.record;
192
206
  },
193
207
  );
@@ -195,6 +209,7 @@ const makeScheduleStore = Effect.gen(function* () {
195
209
  const get: ScheduleStore["Service"]["get"] = Effect.fn("SqliteScheduleStore.get")(
196
210
  function* (key) {
197
211
  const decodedKey = yield* decodeInput("get schedule", ScheduleKey, key);
212
+
198
213
  return yield* readOne(decodedKey, "get schedule");
199
214
  },
200
215
  );
@@ -204,6 +219,7 @@ const makeScheduleStore = Effect.gen(function* () {
204
219
  ): Effect.fn.Return<SchedulePage, ScheduleStorageError> {
205
220
  const operation = "list schedules";
206
221
  const decodedRequest = yield* decodeInput(operation, SchedulePageRequest, request);
222
+
207
223
  const rows =
208
224
  decodedRequest.after === undefined
209
225
  ? yield* sql<Record<string, unknown>>`
@@ -223,10 +239,12 @@ const makeScheduleStore = Effect.gen(function* () {
223
239
  ORDER BY schedule_id
224
240
  LIMIT ${decodedRequest.limit + 1}
225
241
  `.pipe(Effect.mapError(() => unavailable(operation)));
242
+
226
243
  const decoded = yield* decodeRows(Schema.Array(ScheduleRow), rows, operation);
227
244
  const records = yield* Effect.forEach(decoded, decodeRecord);
228
245
  const hasNext = records.length > decodedRequest.limit;
229
246
  const items = hasNext ? records.slice(0, decodedRequest.limit) : records;
247
+
230
248
  return { items, next: hasNext ? (items.at(-1)?.scheduleId ?? null) : null };
231
249
  });
232
250
 
@@ -235,14 +253,18 @@ const makeScheduleStore = Effect.gen(function* () {
235
253
  const operation = "change schedule";
236
254
  const decodedKey = yield* decodeInput(operation, ScheduleKey, key);
237
255
  const decodedChange = yield* decodeInput(operation, ScheduleChange, change);
256
+
238
257
  const result = yield* sql
239
258
  .withTransaction(
240
259
  Effect.gen(function* () {
241
260
  const current = yield* readOne(decodedKey, operation);
261
+
242
262
  if (current === null) return yield* ScheduleNotFound.make({ key: decodedKey });
243
263
  const transition = applyScheduleChange(current, decodedChange);
264
+
244
265
  if (Result.isFailure(transition)) return yield* transition.failure;
245
266
  const next = transition.success;
267
+
246
268
  if (!scheduleUsesCapacity(current) && scheduleUsesCapacity(next)) {
247
269
  const rawCounts = yield* sql<Record<string, unknown>>`
248
270
  SELECT COUNT(*) AS schedule_count FROM effect_agent_schedules
@@ -250,17 +272,20 @@ const makeScheduleStore = Effect.gen(function* () {
250
272
  AND (json_extract(record_json, '$.pending') IS NOT NULL OR
251
273
  (json_extract(record_json, '$.state') != 'cancelled' AND json_extract(record_json, '$.nextAtMillis') IS NOT NULL))
252
274
  `.pipe(Effect.mapError(() => unavailable(operation)));
275
+
253
276
  const counts = yield* decodeRows(
254
277
  Schema.Array(ScheduleCountRow),
255
278
  rawCounts,
256
279
  operation,
257
280
  );
281
+
258
282
  if (counts.length !== 1) return yield* corrupt(operation);
259
283
  if (counts[0].schedule_count >= ownerLimit)
260
284
  return yield* ScheduleCapacityError.make({ limit: ownerLimit });
261
285
  }
262
286
  if (next === current) return { record: current, changed: false } as const;
263
287
  const recordJson = yield* encodeRecord(next);
288
+
264
289
  yield* scheduleFailpoint.hit(`schedule:${decodedChange._tag.toLowerCase()}:before`);
265
290
  yield* sql`
266
291
  UPDATE effect_agent_schedules
@@ -269,13 +294,16 @@ const makeScheduleStore = Effect.gen(function* () {
269
294
  AND owner_id = ${decodedKey.owner.ownerId}
270
295
  AND schedule_id = ${decodedKey.scheduleId}
271
296
  `.pipe(Effect.mapError(() => unavailable(operation)));
297
+
272
298
  return { record: next, changed: true } as const;
273
299
  }),
274
300
  )
275
301
  .pipe(Effect.catchTag("SqlError", () => Effect.fail(unavailable(operation))));
302
+
276
303
  if (result.changed) {
277
304
  yield* scheduleFailpoint.hit(`schedule:${decodedChange._tag.toLowerCase()}:after`);
278
305
  }
306
+
279
307
  return result.record;
280
308
  },
281
309
  );
@@ -287,20 +315,24 @@ const makeScheduleStore = Effect.gen(function* () {
287
315
  after?: ScheduleDueCursor,
288
316
  ) {
289
317
  const operation = "query due schedules";
318
+
290
319
  const decodedOwner =
291
320
  owner === undefined ? undefined : yield* decodeInput(operation, ScheduleOwner, owner);
321
+
292
322
  const cursor =
293
323
  after === undefined
294
324
  ? undefined
295
325
  : yield* Schema.decodeUnknownEffect(ScheduleDueCursor)(after).pipe(
296
326
  Effect.mapError(() => corrupt(operation)),
297
327
  );
328
+
298
329
  const continuation =
299
330
  cursor === undefined
300
331
  ? sql`1 = 1`
301
332
  : sql`
302
333
  (deadline_at_millis, tenant_id, owner_id, schedule_id) >
303
334
  (${cursor.deadlineAtMillis}, ${cursor.owner.tenantId}, ${cursor.owner.ownerId}, ${cursor.scheduleId})`;
335
+
304
336
  const rows =
305
337
  decodedOwner === undefined
306
338
  ? yield* sql<Record<string, unknown>>`
@@ -319,7 +351,9 @@ const makeScheduleStore = Effect.gen(function* () {
319
351
  ORDER BY deadline_at_millis, schedule_id
320
352
  LIMIT ${limit}
321
353
  `.pipe(Effect.mapError(() => unavailable(operation)));
354
+
322
355
  const decoded = yield* decodeRows(Schema.Array(ScheduleDueRow), rows, operation);
356
+
323
357
  return decoded.map((row) => ({
324
358
  owner: { tenantId: row.tenant_id, ownerId: row.owner_id },
325
359
  scheduleId: row.schedule_id,
@@ -331,8 +365,10 @@ const makeScheduleStore = Effect.gen(function* () {
331
365
  "SqliteScheduleStore.nextDeadline",
332
366
  )(function* (owner?: ScheduleOwner) {
333
367
  const operation = "query next schedule deadline";
368
+
334
369
  const decodedOwner =
335
370
  owner === undefined ? undefined : yield* decodeInput(operation, ScheduleOwner, owner);
371
+
336
372
  const rows =
337
373
  decodedOwner === undefined
338
374
  ? yield* sql<Record<string, unknown>>`
@@ -347,8 +383,11 @@ const makeScheduleStore = Effect.gen(function* () {
347
383
  AND owner_id = ${decodedOwner.ownerId}
348
384
  AND deadline_at_millis IS NOT NULL
349
385
  `.pipe(Effect.mapError(() => unavailable(operation)));
386
+
350
387
  const decoded = yield* decodeRows(Schema.Array(ScheduleDeadlineRow), rows, operation);
388
+
351
389
  if (decoded.length !== 1) return yield* corrupt(operation);
390
+
352
391
  return decoded[0].deadline_at_millis;
353
392
  });
354
393
 
@@ -19,6 +19,7 @@ export class SqliteStorageFailpointTestControl extends Context.Service<
19
19
  static readonly layer = Layer.effectContext(
20
20
  Effect.gen(function* () {
21
21
  const handler = yield* Ref.make<SqliteStorageFailpointHandler>(noFailpoint);
22
+
22
23
  return Context.make(
23
24
  SqliteStorageFailpoint,
24
25
  SqliteStorageFailpoint.of({