experimental-a2 0.18.0 → 0.18.1
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/CHANGELOG.md +11 -0
- package/dist/{actor-Bjce9_5U.d.ts → actor-C7b-1arn.d.ts} +3 -3
- package/dist/{actor-Bjce9_5U.d.ts.map → actor-C7b-1arn.d.ts.map} +1 -1
- package/dist/actor-client.d.ts +1 -1
- package/dist/actor-react.d.ts +1 -1
- package/dist/actor-react.js +1 -1
- package/dist/actor.d.ts +1 -1
- package/dist/actor.js +2 -2
- package/dist/{ai-DrUaMyRt.d.ts → ai-BMLlI2Ff.d.ts} +2 -2
- package/dist/{ai-DrUaMyRt.d.ts.map → ai-BMLlI2Ff.d.ts.map} +1 -1
- package/dist/{ai-BpOGm-zB.js → ai-D6mGlO8j.js} +2 -2
- package/dist/{ai-BpOGm-zB.js.map → ai-D6mGlO8j.js.map} +1 -1
- package/dist/ai-client.d.ts +1 -1
- package/dist/ai-client.js +2 -2
- package/dist/ai-fx.d.ts +1 -1
- package/dist/ai-fx.d.ts.map +1 -1
- package/dist/ai-fx.js +92 -34
- package/dist/ai-fx.js.map +1 -1
- package/dist/{ai-harness-DvsPeJeB.d.ts → ai-harness-B2suP2Qh.d.ts} +2 -2
- package/dist/{ai-harness-DvsPeJeB.d.ts.map → ai-harness-B2suP2Qh.d.ts.map} +1 -1
- package/dist/ai-pi.d.ts +1 -1
- package/dist/ai-pi.d.ts.map +1 -1
- package/dist/ai-pi.js +59 -13
- package/dist/ai-pi.js.map +1 -1
- package/dist/ai-react.d.ts +1 -1
- package/dist/ai-react.js +2 -2
- package/dist/ai-server.d.ts +3 -3
- package/dist/ai-server.d.ts.map +1 -1
- package/dist/ai-server.js +10 -6
- package/dist/ai-server.js.map +1 -1
- package/dist/ai-tool-input-DmSqMUQi.js +40 -0
- package/dist/ai-tool-input-DmSqMUQi.js.map +1 -0
- package/dist/ai.d.ts +1 -1
- package/dist/ai.js +1 -1
- package/dist/{client-BReKqwZC.js → client-BhHLoNKF.js} +3 -3
- package/dist/{client-BReKqwZC.js.map → client-BhHLoNKF.js.map} +1 -1
- package/dist/client.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/{internal-B2T3U4Rq.js → internal-CscSY7K_.js} +4 -2
- package/dist/{internal-B2T3U4Rq.js.map → internal-CscSY7K_.js.map} +1 -1
- package/dist/react.js +1 -1
- package/dist/scheduler-qstash.d.ts +2 -2
- package/dist/scheduler-qstash.js +3 -3
- package/dist/scheduler-qstash.js.map +1 -1
- package/dist/scheduler-vercel.d.ts +7 -7
- package/dist/scheduler-vercel.d.ts.map +1 -1
- package/dist/scheduler-vercel.js +9 -10
- package/dist/scheduler-vercel.js.map +1 -1
- package/dist/{server-Dw_k_7kO.js → server-D-Y4pqwu.js} +37 -11
- package/dist/server-D-Y4pqwu.js.map +1 -0
- package/dist/{server-En7cbi3v.d.ts → server-DYAATFuT.d.ts} +11 -5
- package/dist/server-DYAATFuT.d.ts.map +1 -0
- package/dist/server.d.ts +2 -2
- package/dist/server.js +1 -1
- package/dist/{session-clear-Bo-XTFEy.d.ts → session-clear-C-QwTXV1.d.ts} +2 -2
- package/dist/session-clear-C-QwTXV1.d.ts.map +1 -0
- package/dist/{store-Djdv0bnZ.d.ts → store-CyLUy86v.d.ts} +23 -4
- package/dist/store-CyLUy86v.d.ts.map +1 -0
- package/dist/store-N8PXxDAS.js.map +1 -1
- package/dist/store-memory.d.ts +1 -1
- package/dist/store-memory.js +25 -6
- package/dist/store-memory.js.map +1 -1
- package/dist/{store-polling-B5FcwJfo.js → store-polling-7WJV0YWP.js} +2 -2
- package/dist/{store-polling-B5FcwJfo.js.map → store-polling-7WJV0YWP.js.map} +1 -1
- package/dist/store-postgres.d.ts +1 -1
- package/dist/store-postgres.d.ts.map +1 -1
- package/dist/store-postgres.js +55 -25
- package/dist/store-postgres.js.map +1 -1
- package/dist/{store-presence-polling-BAwS8RWn.js → store-presence-polling-B1ixeqIS.js} +3 -3
- package/dist/{store-presence-polling-BAwS8RWn.js.map → store-presence-polling-B1ixeqIS.js.map} +1 -1
- package/dist/store-redis-http.d.ts +1 -1
- package/dist/store-redis-http.js +3 -3
- package/dist/{store-redis-notify-BRITti_o.js → store-redis-notify-CM0AfLY0.js} +88 -37
- package/dist/store-redis-notify-CM0AfLY0.js.map +1 -0
- package/dist/store-redis.d.ts +1 -1
- package/dist/store-redis.js +1 -1
- package/dist/store-sqlite.d.ts +1 -1
- package/dist/store-sqlite.js +35 -20
- package/dist/store-sqlite.js.map +1 -1
- package/dist/{wire-rSUglYo8.js → wire-BKxH1vbP.js} +2 -2
- package/dist/{wire-rSUglYo8.js.map → wire-BKxH1vbP.js.map} +1 -1
- package/docs/agents/03-conversations.mdx +2 -2
- package/docs/index.mdx +3 -3
- package/docs/reference/02-server.mdx +12 -7
- package/docs/reference/04-ai.mdx +16 -7
- package/docs/reference/05-adapters.mdx +4 -4
- package/docs/server/01-handlers.mdx +1 -1
- package/docs/server/03-cancellation.mdx +3 -2
- package/docs/server/05-production.mdx +2 -1
- package/docs/server/06-guarantees.mdx +20 -11
- package/examples/playground/DEPLOY.md +2 -4
- package/examples/playground/app/a0/[appId]/page.tsx +2 -8
- package/examples/playground/app/a0/devtools.tsx +2 -8
- package/examples/playground/app/a0/server.ts +8 -19
- package/examples/playground/package.json +1 -1
- package/package.json +1 -1
- package/src/ai-agent.ts +10 -3
- package/src/ai-fx.ts +126 -47
- package/src/ai-pi.ts +74 -15
- package/src/ai-tool-input.ts +45 -0
- package/src/internal.ts +6 -0
- package/src/scheduler-qstash.ts +5 -5
- package/src/scheduler-vercel.ts +14 -15
- package/src/server.ts +58 -12
- package/src/store-memory.ts +50 -9
- package/src/store-postgres.ts +79 -28
- package/src/store-redis-core.ts +112 -31
- package/src/store-sqlite.ts +61 -28
- package/src/store.ts +22 -3
- package/dist/server-Dw_k_7kO.js.map +0 -1
- package/dist/server-En7cbi3v.d.ts.map +0 -1
- package/dist/session-clear-Bo-XTFEy.d.ts.map +0 -1
- package/dist/store-Djdv0bnZ.d.ts.map +0 -1
- package/dist/store-redis-notify-BRITti_o.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store-polling-
|
|
1
|
+
{"version":3,"file":"store-polling-7WJV0YWP.js","names":[],"sources":["../src/stream-activity.ts","../src/store-polling.ts"],"sourcesContent":["export type StreamActivity = {\n readonly idle: boolean\n setIdle(idle: boolean): void\n activate(): void\n subscribe(listener: (idle: boolean) => void): () => void\n}\n\nexport const streamActivities: WeakMap<object, StreamActivity> = new WeakMap()\n\nexport function createStreamActivity(wake: () => void): StreamActivity {\n let idle = false\n const listeners = new Set<(idle: boolean) => void>()\n const setIdle = (next: boolean): void => {\n if (idle === next) return\n idle = next\n for (const listener of listeners) listener(idle)\n }\n return {\n get idle() {\n return idle\n },\n setIdle,\n activate() {\n setIdle(false)\n wake()\n },\n subscribe(listener) {\n listeners.add(listener)\n return () => listeners.delete(listener)\n },\n }\n}\n","/**\n * The shared polling stream — how poll-based backends (sqlite,\n * postgres) implement `A2Store.stream`.\n *\n * The cadence is activity-adaptive: while events are flowing, polls\n * run at a tight floor (25ms) so streams feel pushed — a token\n * generation reads smoothly instead of clumping at the idle interval;\n * every empty poll doubles the gap until the idle ceiling. The cadence\n * is owned (POLL_TIMINGS in internal.ts), not a public option: the\n * floor already keeps active streams smooth, so tuning it is a cost\n * decision a2 makes, not one it delegates. Correctness never depends\n * on the cadence — each poll is a catch-up read (`index > last`, in\n * order), so nothing can be missed, only arrive a beat later.\n */\n\nimport { POLL_TIMINGS } from './internal.ts'\nimport type { Event } from './store.ts'\nimport { createStreamActivity, streamActivities } from './stream-activity.ts'\n\n/** One cancellable sleep — the injection seam for cadence tests. */\nexport type Sleeper = { promise: Promise<void>; cancel: () => void }\n\n/** Unref'd cancellable timer — shared with the redis safety read. */\nexport const defaultSleep = (ms: number): Sleeper => {\n let timer: ReturnType<typeof setTimeout> | undefined\n let wake: (() => void) | undefined\n const promise = new Promise<void>((resolve) => {\n wake = resolve // assigned synchronously by the executor\n timer = setTimeout(resolve, ms)\n // Don't hold the process open for an idle poll.\n ;(timer as { unref?: () => void }).unref?.()\n })\n return {\n promise,\n cancel: () => {\n clearTimeout(timer)\n wake?.()\n },\n }\n}\n\n// Read the seam lazily — tests shrink POLL_TIMINGS in their setup.\nconst ceiling = (): number => POLL_TIMINGS.idleCeilingMs\nconst floor = (): number =>\n Math.min(POLL_TIMINGS.activeFloorMs, POLL_TIMINGS.idleCeilingMs)\n\nexport type PollingStreamOptions = {\n /** Deliver events strictly after this index (exclusive). */\n startAfter?: number\n /** Injectable sleep — tests assert the cadence without wall-clock waits. */\n sleep?: (ms: number) => Sleeper\n}\n\n/**\n * Build the `stream()` iterable from a backend's catch-up read. The\n * iterable ends when the consumer calls `return()` (a disconnecting\n * SSE client), waking any pending sleep immediately.\n */\nexport function pollingStream(\n readAfter: (afterIndex: number) => Event[] | Promise<Event[]>,\n options: PollingStreamOptions,\n): AsyncIterable<Event> {\n const startAfter = options.startAfter ?? 0\n const sleep = options.sleep ?? defaultSleep\n\n return {\n [Symbol.asyncIterator](): AsyncIterator<Event> {\n let last = startAfter\n let buffer: Array<Event | undefined> = []\n let position = 0\n let closed = false\n let pending: Sleeper | null = null\n let interval = floor()\n let activityVersion = 0\n const activity = createStreamActivity(() => {\n if (closed) return\n activityVersion += 1\n interval = floor()\n pending?.cancel()\n })\n\n const iterator: AsyncIterator<Event> = {\n async next(): Promise<IteratorResult<Event>> {\n for (;;) {\n if (closed) return { value: undefined, done: true }\n try {\n if (position === buffer.length) {\n // oxlint-disable-next-line no-await-in-loop -- poll loop\n const rows = await readAfter(last)\n if (closed) return { value: undefined, done: true }\n buffer = rows\n position = 0\n }\n } catch (error) {\n if (closed) return { value: undefined, done: true }\n throw error\n }\n if (closed) return { value: undefined, done: true }\n const row = buffer[position]\n if (row) {\n // Activity: tighten back to the floor.\n activity.activate()\n last = row.index\n buffer[position] = undefined\n position += 1\n if (position === buffer.length) {\n buffer = []\n position = 0\n }\n return { value: row, done: false }\n }\n // Empty poll: wait, then back off toward the idle ceiling.\n activity.setIdle(interval === ceiling())\n const version = activityVersion\n pending = sleep(interval)\n // oxlint-disable-next-line no-await-in-loop -- poll loop\n await pending.promise\n pending = null\n if (version === activityVersion) {\n interval = Math.min(interval * 2, ceiling())\n }\n }\n },\n async return(): Promise<IteratorResult<Event>> {\n closed = true\n buffer = []\n position = 0\n pending?.cancel()\n return { value: undefined, done: true }\n },\n }\n streamActivities.set(iterator, activity)\n return iterator\n },\n }\n}\n"],"mappings":";;AAOA,MAAa,mCAAoD,IAAI,QAAQ;AAE7E,SAAgB,qBAAqB,MAAkC;CACrE,IAAI,OAAO;CACX,MAAM,4BAAY,IAAI,IAA6B;CACnD,MAAM,WAAW,SAAwB;EACvC,IAAI,SAAS,MAAM;EACnB,OAAO;EACP,KAAK,MAAM,YAAY,WAAW,SAAS,IAAI;CACjD;CACA,OAAO;EACL,IAAI,OAAO;GACT,OAAO;EACT;EACA;EACA,WAAW;GACT,QAAQ,KAAK;GACb,KAAK;EACP;EACA,UAAU,UAAU;GAClB,UAAU,IAAI,QAAQ;GACtB,aAAa,UAAU,OAAO,QAAQ;EACxC;CACF;AACF;;;;;;;;;;;;;;;;;;ACRA,MAAa,gBAAgB,OAAwB;CACnD,IAAI;CACJ,IAAI;CAOJ,OAAO;EACL,SAAA,IAPkB,SAAe,YAAY;GAC7C,OAAO;GACP,QAAQ,WAAW,SAAS,EAAE;GAE7B,MAAkC,QAAQ;EAC7C,CAEQ;EACN,cAAc;GACZ,aAAa,KAAK;GAClB,OAAO;EACT;CACF;AACF;AAGA,MAAM,gBAAwB,aAAa;AAC3C,MAAM,cACJ,KAAK,IAAI,aAAa,eAAe,aAAa,aAAa;;;;;;AAcjE,SAAgB,cACd,WACA,SACsB;CACtB,MAAM,aAAa,QAAQ,cAAc;CACzC,MAAM,QAAQ,QAAQ,SAAS;CAE/B,OAAO,EACL,CAAC,OAAO,iBAAuC;EAC7C,IAAI,OAAO;EACX,IAAI,SAAmC,CAAC;EACxC,IAAI,WAAW;EACf,IAAI,SAAS;EACb,IAAI,UAA0B;EAC9B,IAAI,WAAW,MAAM;EACrB,IAAI,kBAAkB;EACtB,MAAM,WAAW,2BAA2B;GAC1C,IAAI,QAAQ;GACZ,mBAAmB;GACnB,WAAW,MAAM;GACjB,SAAS,OAAO;EAClB,CAAC;EAED,MAAM,WAAiC;GACrC,MAAM,OAAuC;IAC3C,SAAS;KACP,IAAI,QAAQ,OAAO;MAAE,OAAO,KAAA;MAAW,MAAM;KAAK;KAClD,IAAI;MACF,IAAI,aAAa,OAAO,QAAQ;OAE9B,MAAM,OAAO,MAAM,UAAU,IAAI;OACjC,IAAI,QAAQ,OAAO;QAAE,OAAO,KAAA;QAAW,MAAM;OAAK;OAClD,SAAS;OACT,WAAW;MACb;KACF,SAAS,OAAO;MACd,IAAI,QAAQ,OAAO;OAAE,OAAO,KAAA;OAAW,MAAM;MAAK;MAClD,MAAM;KACR;KACA,IAAI,QAAQ,OAAO;MAAE,OAAO,KAAA;MAAW,MAAM;KAAK;KAClD,MAAM,MAAM,OAAO;KACnB,IAAI,KAAK;MAEP,SAAS,SAAS;MAClB,OAAO,IAAI;MACX,OAAO,YAAY,KAAA;MACnB,YAAY;MACZ,IAAI,aAAa,OAAO,QAAQ;OAC9B,SAAS,CAAC;OACV,WAAW;MACb;MACA,OAAO;OAAE,OAAO;OAAK,MAAM;MAAM;KACnC;KAEA,SAAS,QAAQ,aAAa,QAAQ,CAAC;KACvC,MAAM,UAAU;KAChB,UAAU,MAAM,QAAQ;KAExB,MAAM,QAAQ;KACd,UAAU;KACV,IAAI,YAAY,iBACd,WAAW,KAAK,IAAI,WAAW,GAAG,QAAQ,CAAC;IAE/C;GACF;GACA,MAAM,SAAyC;IAC7C,SAAS;IACT,SAAS,CAAC;IACV,WAAW;IACX,SAAS,OAAO;IAChB,OAAO;KAAE,OAAO,KAAA;KAAW,MAAM;IAAK;GACxC;EACF;EACA,iBAAiB,IAAI,UAAU,QAAQ;EACvC,OAAO;CACT,EACF;AACF"}
|
package/dist/store-postgres.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as Clock, s as IdSource, t as A2Store } from "./store-
|
|
1
|
+
import { r as Clock, s as IdSource, t as A2Store } from "./store-CyLUy86v.js";
|
|
2
2
|
//#region src/store-postgres.d.ts
|
|
3
3
|
/** The result shape this backend reads: just rows. */
|
|
4
4
|
type PostgresQueryResult = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store-postgres.d.ts","names":[],"sources":["../src/store-postgres.ts"],"mappings":";;;KAuDY;EAAwB,MAAM;;;KAG9B;EACV,MAAM,cAAc,qBAAqB,QAAQ;EACjD;;;;;;;;KASU;EACV,MAAM,cAAc,qBAAqB,QAAQ;EACjD,YAAY,QAAQ;EACpB,QAAQ;;KAGE;;EAEV;;EAEA;;EAEA,SAAS;;EAET,QAAQ;;EAER,MAAM;;KAGI,gBAAgB;;;EAG1B,SAAS;;
|
|
1
|
+
{"version":3,"file":"store-postgres.d.ts","names":[],"sources":["../src/store-postgres.ts"],"mappings":";;;KAuDY;EAAwB,MAAM;;;KAG9B;EACV,MAAM,cAAc,qBAAqB,QAAQ;EACjD;;;;;;;;KASU;EACV,MAAM,cAAc,qBAAqB,QAAQ;EACjD,YAAY,QAAQ;EACpB,QAAQ;;KAGE;;EAEV;;EAEA;;EAEA,SAAS;;EAET,QAAQ;;EAER,MAAM;;KAGI,gBAAgB;;;EAG1B,SAAS;;iBAiSK,SAAS,UAAS,uBAA4B"}
|
package/dist/store-postgres.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { i as A2Error, r as sessionClearedError, s as supersededAttemptError, t as CLEAR_EVENT_TYPE } from "./session-clear-DGI4jTQ5.js";
|
|
2
|
-
import { _ as nullProtoRecord, r as NOTIFY_TIMINGS } from "./internal-
|
|
3
|
-
import { n as pollingStream, t as defaultSleep } from "./store-polling-
|
|
2
|
+
import { _ as nullProtoRecord, r as NOTIFY_TIMINGS } from "./internal-CscSY7K_.js";
|
|
3
|
+
import { n as pollingStream, t as defaultSleep } from "./store-polling-7WJV0YWP.js";
|
|
4
4
|
import { r as platformWaitUntil } from "./platform-B4TnJtWu.js";
|
|
5
5
|
import { t as retryableLazy } from "./retryable-lazy-DZWmHpii.js";
|
|
6
6
|
import { t as idempotentReplay } from "./idempotent-replay-JdgMQRRm.js";
|
|
7
7
|
import { n as SYSTEM_CLOCK, t as RANDOM_IDS } from "./store-N8PXxDAS.js";
|
|
8
8
|
import { t as decodeReturnedEventIds } from "./store-codec-DTG0Ftek.js";
|
|
9
|
-
import { t as pollPresence } from "./store-presence-polling-
|
|
9
|
+
import { t as pollPresence } from "./store-presence-polling-B1ixeqIS.js";
|
|
10
10
|
import { createHash } from "node:crypto";
|
|
11
11
|
//#region src/postgres-notifications.ts
|
|
12
12
|
const postgresNotificationChannel = ({ kind, sessionId }) => `a2_${kind}_${createHash("md5").update(sessionId).digest("hex")}`;
|
|
@@ -694,6 +694,19 @@ const createListener = ({ pg, connectionString }) => postgresNotifications(async
|
|
|
694
694
|
}
|
|
695
695
|
};
|
|
696
696
|
});
|
|
697
|
+
/** A dead-lettered event's snapshot pins, and history no pin keeps. */
|
|
698
|
+
const releasePins = async ({ query, sessionId, index }) => {
|
|
699
|
+
await query(`delete from a2_snapshot_pins
|
|
700
|
+
where session_id = $1 and event_index = $2`, [sessionId, index]);
|
|
701
|
+
await query(`delete from a2_snapshot_history history
|
|
702
|
+
where history.session_id = $1
|
|
703
|
+
and not exists (
|
|
704
|
+
select 1 from a2_snapshot_pins pin
|
|
705
|
+
where pin.session_id = history.session_id
|
|
706
|
+
and pin.reducer_name = history.reducer_name
|
|
707
|
+
and pin.up_to_index = history.up_to_index
|
|
708
|
+
)`, [sessionId]);
|
|
709
|
+
};
|
|
697
710
|
function postgres(options = {}) {
|
|
698
711
|
const clock = options.clock ?? SYSTEM_CLOCK;
|
|
699
712
|
const generateId = options.ids ?? RANDOM_IDS;
|
|
@@ -1095,10 +1108,12 @@ function postgres(options = {}) {
|
|
|
1095
1108
|
return rows.map(toEvent);
|
|
1096
1109
|
});
|
|
1097
1110
|
},
|
|
1098
|
-
async claimAvailable({ sessionId, holder, ttlMs, expiresAtMs, excludeIndexes = [] }) {
|
|
1111
|
+
async claimAvailable({ sessionId, holder, ttlMs, expiresAtMs, excludeIndexes = [], maxFailures }) {
|
|
1099
1112
|
return wrap(() => withTx(async (query) => {
|
|
1100
1113
|
const now = clock.now().getTime();
|
|
1101
1114
|
const expiresAt = expiresAtMs ?? now + ttlMs;
|
|
1115
|
+
const exhausted = `(claimed.claim_holder is not null
|
|
1116
|
+
and claimed.failure_count + 1 >= $6::int)`;
|
|
1102
1117
|
const { rows } = await query(`with eligible as materialized (
|
|
1103
1118
|
select event.idx
|
|
1104
1119
|
from a2_events event
|
|
@@ -1113,10 +1128,16 @@ function postgres(options = {}) {
|
|
|
1113
1128
|
)
|
|
1114
1129
|
update a2_events claimed set
|
|
1115
1130
|
attempt_count = claimed.attempt_count + 1,
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1131
|
+
failure_count = claimed.failure_count
|
|
1132
|
+
+ case when claimed.claim_holder is null then 0 else 1 end,
|
|
1133
|
+
failed_at = case when ${exhausted} then $2::bigint else claimed.failed_at end,
|
|
1134
|
+
first_claimed_at = case when ${exhausted}
|
|
1135
|
+
then claimed.first_claimed_at
|
|
1136
|
+
else coalesce(claimed.first_claimed_at, $2::bigint) end,
|
|
1137
|
+
last_claimed_at = case when ${exhausted}
|
|
1138
|
+
then claimed.last_claimed_at else $2::bigint end,
|
|
1139
|
+
claim_holder = case when ${exhausted} then null else $3::text end,
|
|
1140
|
+
claim_expires_at = case when ${exhausted} then null else $4::bigint end
|
|
1120
1141
|
from eligible
|
|
1121
1142
|
where claimed.session_id = $1 and claimed.idx = eligible.idx
|
|
1122
1143
|
returning claimed.*, ${clearIndexOf("$1")} as clear_index`, [
|
|
@@ -1124,8 +1145,14 @@ function postgres(options = {}) {
|
|
|
1124
1145
|
now,
|
|
1125
1146
|
holder,
|
|
1126
1147
|
expiresAt,
|
|
1127
|
-
[...excludeIndexes]
|
|
1148
|
+
[...excludeIndexes],
|
|
1149
|
+
maxFailures ?? null
|
|
1128
1150
|
]);
|
|
1151
|
+
for (const row of rows) if (row["failed_at"] !== null) await releasePins({
|
|
1152
|
+
query,
|
|
1153
|
+
sessionId,
|
|
1154
|
+
index: asNumber(row["idx"])
|
|
1155
|
+
});
|
|
1129
1156
|
if (rows.length > 0) return {
|
|
1130
1157
|
outcome: "claimed",
|
|
1131
1158
|
events: rows.map((row) => {
|
|
@@ -1142,7 +1169,12 @@ function postgres(options = {}) {
|
|
|
1142
1169
|
and failed_at is null
|
|
1143
1170
|
and lane_ready
|
|
1144
1171
|
and idx > ${clearIndexOf("$1")}
|
|
1145
|
-
and claim_expires_at > $2
|
|
1172
|
+
and claim_expires_at > $2
|
|
1173
|
+
and not (idx = any($3::bigint[]))`, [
|
|
1174
|
+
sessionId,
|
|
1175
|
+
now,
|
|
1176
|
+
[...excludeIndexes]
|
|
1177
|
+
]);
|
|
1146
1178
|
const retryAt = activeRows[0]?.["retry_at"];
|
|
1147
1179
|
return retryAt === null || retryAt === void 0 ? { outcome: "settled" } : {
|
|
1148
1180
|
outcome: "busy",
|
|
@@ -1336,7 +1368,8 @@ function postgres(options = {}) {
|
|
|
1336
1368
|
};
|
|
1337
1369
|
}));
|
|
1338
1370
|
},
|
|
1339
|
-
async failAttempt({ sessionId, index, attempt, error, maxFailures }) {
|
|
1371
|
+
async failAttempt({ sessionId, index, attempt, error, maxFailures, retryAfterMs }) {
|
|
1372
|
+
const now = clock.now().getTime();
|
|
1340
1373
|
return wrap(() => withTx(async (query) => {
|
|
1341
1374
|
const { rows } = await query(`with current as materialized (
|
|
1342
1375
|
select e.* from a2_events e
|
|
@@ -1354,7 +1387,10 @@ function postgres(options = {}) {
|
|
|
1354
1387
|
else e.failed_at
|
|
1355
1388
|
end,
|
|
1356
1389
|
claim_holder = null,
|
|
1357
|
-
claim_expires_at =
|
|
1390
|
+
claim_expires_at = case
|
|
1391
|
+
when e.failure_count + 1 >= $5 then null
|
|
1392
|
+
else $7::bigint
|
|
1393
|
+
end
|
|
1358
1394
|
from current
|
|
1359
1395
|
where e.session_id = $1
|
|
1360
1396
|
and e.idx = $2
|
|
@@ -1393,7 +1429,8 @@ function postgres(options = {}) {
|
|
|
1393
1429
|
attempt,
|
|
1394
1430
|
error,
|
|
1395
1431
|
maxFailures,
|
|
1396
|
-
|
|
1432
|
+
now,
|
|
1433
|
+
retryAfterMs === void 0 ? null : now + retryAfterMs
|
|
1397
1434
|
]);
|
|
1398
1435
|
const row = rows[0];
|
|
1399
1436
|
if (row?.["outcome"] === "cleared") return {
|
|
@@ -1411,18 +1448,11 @@ function postgres(options = {}) {
|
|
|
1411
1448
|
};
|
|
1412
1449
|
throw new TypeError(`no event at index ${index} in session '${sessionId}'`);
|
|
1413
1450
|
}
|
|
1414
|
-
if (row["outcome"] === "dead_lettered") {
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
and not exists (
|
|
1420
|
-
select 1 from a2_snapshot_pins pin
|
|
1421
|
-
where pin.session_id = history.session_id
|
|
1422
|
-
and pin.reducer_name = history.reducer_name
|
|
1423
|
-
and pin.up_to_index = history.up_to_index
|
|
1424
|
-
)`, [sessionId]);
|
|
1425
|
-
}
|
|
1451
|
+
if (row["outcome"] === "dead_lettered") await releasePins({
|
|
1452
|
+
query,
|
|
1453
|
+
sessionId,
|
|
1454
|
+
index
|
|
1455
|
+
});
|
|
1426
1456
|
return {
|
|
1427
1457
|
outcome: row["outcome"],
|
|
1428
1458
|
failureCount: asNumber(row["failure_count"])
|