@vllnt/convex-idempotency 0.1.0-canary.6abc175

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.
Files changed (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +127 -0
  3. package/dist/client/index.d.ts +135 -0
  4. package/dist/client/index.d.ts.map +1 -0
  5. package/dist/client/index.js +117 -0
  6. package/dist/client/index.js.map +1 -0
  7. package/dist/client/types.d.ts +78 -0
  8. package/dist/client/types.d.ts.map +1 -0
  9. package/dist/client/types.js +3 -0
  10. package/dist/client/types.js.map +1 -0
  11. package/dist/component/_generated/api.d.ts +40 -0
  12. package/dist/component/_generated/api.d.ts.map +1 -0
  13. package/dist/component/_generated/api.js +31 -0
  14. package/dist/component/_generated/api.js.map +1 -0
  15. package/dist/component/_generated/component.d.ts +65 -0
  16. package/dist/component/_generated/component.d.ts.map +1 -0
  17. package/dist/component/_generated/component.js +11 -0
  18. package/dist/component/_generated/component.js.map +1 -0
  19. package/dist/component/_generated/dataModel.d.ts +46 -0
  20. package/dist/component/_generated/dataModel.d.ts.map +1 -0
  21. package/dist/component/_generated/dataModel.js +11 -0
  22. package/dist/component/_generated/dataModel.js.map +1 -0
  23. package/dist/component/_generated/server.d.ts +121 -0
  24. package/dist/component/_generated/server.d.ts.map +1 -0
  25. package/dist/component/_generated/server.js +78 -0
  26. package/dist/component/_generated/server.js.map +1 -0
  27. package/dist/component/convex.config.d.ts +3 -0
  28. package/dist/component/convex.config.d.ts.map +1 -0
  29. package/dist/component/convex.config.js +7 -0
  30. package/dist/component/convex.config.js.map +1 -0
  31. package/dist/component/crons.d.ts +16 -0
  32. package/dist/component/crons.d.ts.map +1 -0
  33. package/dist/component/crons.js +19 -0
  34. package/dist/component/crons.js.map +1 -0
  35. package/dist/component/mutations.d.ts +91 -0
  36. package/dist/component/mutations.d.ts.map +1 -0
  37. package/dist/component/mutations.js +177 -0
  38. package/dist/component/mutations.js.map +1 -0
  39. package/dist/component/queries.d.ts +9 -0
  40. package/dist/component/queries.d.ts.map +1 -0
  41. package/dist/component/queries.js +22 -0
  42. package/dist/component/queries.js.map +1 -0
  43. package/dist/component/schema.d.ts +26 -0
  44. package/dist/component/schema.d.ts.map +1 -0
  45. package/dist/component/schema.js +21 -0
  46. package/dist/component/schema.js.map +1 -0
  47. package/dist/component/validators.d.ts +80 -0
  48. package/dist/component/validators.d.ts.map +1 -0
  49. package/dist/component/validators.js +42 -0
  50. package/dist/component/validators.js.map +1 -0
  51. package/dist/shared.d.ts +17 -0
  52. package/dist/shared.d.ts.map +1 -0
  53. package/dist/shared.js +17 -0
  54. package/dist/shared.js.map +1 -0
  55. package/package.json +96 -0
  56. package/src/client/index.ts +251 -0
  57. package/src/client/types.ts +87 -0
  58. package/src/component/_generated/api.ts +56 -0
  59. package/src/component/_generated/component.ts +67 -0
  60. package/src/component/_generated/dataModel.ts +60 -0
  61. package/src/component/_generated/server.ts +156 -0
  62. package/src/component/convex.config.ts +9 -0
  63. package/src/component/crons.ts +23 -0
  64. package/src/component/mutations.ts +195 -0
  65. package/src/component/queries.ts +24 -0
  66. package/src/component/schema.ts +21 -0
  67. package/src/component/validators.ts +56 -0
  68. package/src/shared.ts +21 -0
  69. package/src/test.ts +12 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 bntvllnt
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,127 @@
1
+ <!-- Badges -->
2
+ [![convex-component](https://img.shields.io/badge/convex-component-EE342F.svg)](https://www.convex.dev/components)
3
+ [![npm](https://img.shields.io/npm/v/@vllnt/convex-idempotency.svg)](https://www.npmjs.com/package/@vllnt/convex-idempotency)
4
+ [![CI](https://github.com/vllnt/convex-idempotency/actions/workflows/ci.yml/badge.svg)](https://github.com/vllnt/convex-idempotency/actions/workflows/ci.yml)
5
+ [![license](https://img.shields.io/npm/l/@vllnt/convex-idempotency.svg)](./LICENSE)
6
+
7
+ # @vllnt/convex-idempotency
8
+
9
+ Exactly-once idempotency key ledger for retried operations, as a Convex component.
10
+
11
+ ```ts
12
+ const idem = new Idempotency(components.idempotency);
13
+ const claim = await idem.begin(ctx, requestId); // mints a claim, or short-circuits a replay
14
+ if (claim.state === "done") return claim.result; // replayed — skip the work
15
+ // ... run the work ...
16
+ await idem.complete(ctx, requestId, result); // record the outcome for next time
17
+ ```
18
+
19
+ Record an idempotency `key` with a grace TTL; on a replay short-circuit and return the prior outcome
20
+ instead of re-running the work. Domain-neutral: payment intents, webhook deliveries, queue consumers,
21
+ double-submit guards — any operation that must run **at most once** per key.
22
+
23
+ ## Features
24
+
25
+ - **Exactly-once** per `(scope, key)` — `begin` mints an inflight claim that rides the mutation transaction; a concurrent retry sees `inflight` with a `retryAfterMs` backoff hint.
26
+ - **Replay** — once `complete` records an outcome, a later `begin` returns `{ state: "done", result }` for a short-circuit.
27
+ - **Split TTLs** — a short **inflight lease** (default 60s) so a crashed worker's claim self-heals, and a longer **done grace** (default 24h) after which a key may be re-minted.
28
+ - **Lost-claim detection** — `complete` returns `{ recorded: true } | { recorded: false, reason }` so a host knows when its work finished but the row was gone. Opt into `upsertOnMissing`.
29
+ - **Server-sourced time** — expiry is read from the server clock; a caller can't supply `now`, so an adversarial clock can't force a key to look live or expired.
30
+ - **TTL validation** — non-positive or infinite TTLs throw `INVALID_TTL` before any write.
31
+ - **Typed result** — `Idempotency<TResult>` types the stored outcome; a `resultValidator` narrows it at the boundary.
32
+ - **Scopes** — global by default, or namespace per tenant / operation type.
33
+ - **Bounded purge + cron** — a daily cron sweeps expired keys in batches and self-reschedules until clean.
34
+
35
+ ## Installation
36
+
37
+ ```bash
38
+ pnpm add @vllnt/convex-idempotency
39
+ ```
40
+
41
+ Peer dependency: `convex@^1.41.0`.
42
+
43
+ ## Usage
44
+
45
+ ```ts
46
+ // convex/convex.config.ts
47
+ import { defineApp } from "convex/server";
48
+ import idempotency from "@vllnt/convex-idempotency/convex.config";
49
+
50
+ const app = defineApp();
51
+ app.use(idempotency);
52
+ export default app;
53
+ ```
54
+
55
+ ```ts
56
+ // convex/charge.ts — host owns auth; pass an opaque idempotency key in.
57
+ import { components } from "./_generated/api";
58
+ import { mutation } from "./_generated/server";
59
+ import { v } from "convex/values";
60
+ import { Idempotency } from "@vllnt/convex-idempotency";
61
+
62
+ const idem = new Idempotency<{ chargeId: string }>(components.idempotency, {
63
+ resultValidator: v.object({ chargeId: v.string() }).parse, // narrow at the boundary
64
+ });
65
+
66
+ export const charge = mutation({
67
+ args: { requestId: v.string(), amount: v.number() },
68
+ handler: async (ctx, { requestId, amount }) => {
69
+ const claim = await idem.begin(ctx, requestId);
70
+ if (claim.state === "done") return claim.result; // typed replay
71
+ if (claim.state === "inflight")
72
+ throw new Error(`retry in ${claim.retryAfterMs}ms`); // backoff hint
73
+ const result = { chargeId: await doCharge(amount) }; // state === "fresh"
74
+ const done = await idem.complete(ctx, requestId, result);
75
+ if (!done.recorded) console.warn("claim lost:", done.reason); // work ran, row gone
76
+ return result;
77
+ },
78
+ });
79
+ ```
80
+
81
+ ## API Reference
82
+
83
+ | Method | Kind | Result |
84
+ |--------|------|--------|
85
+ | `begin(ctx, key, opts?)` | mutation | `{ state: "fresh" } \| { state: "inflight"; expiresAt; retryAfterMs } \| { state: "done"; result? }` |
86
+ | `complete(ctx, key, result?, opts?)` | mutation | `{ recorded: true } \| { recorded: false; reason: "missing" \| "expired" \| "already_done" }` |
87
+ | `get(ctx, key, scope?)` | query | `{ status, result?, expiresAt } \| null` |
88
+ | `purge(ctx, opts?)` | mutation | `number` (keys removed in the first bounded pass) |
89
+
90
+ Full reference: [docs/API.md](docs/API.md).
91
+
92
+ ## React
93
+
94
+ Backend-only — no `./react` entry. Pure infra dedup with no user-facing reactive surface.
95
+
96
+ ## Security
97
+
98
+ - Auth-agnostic — the host resolves identity and decides who may run an operation.
99
+ - Tables sandboxed — reached only through the exported functions.
100
+ - Server-sourced expiry — a skewed client clock can't hijack a replay or bypass dedup; `key` / `scope` / `result` stay opaque.
101
+
102
+ See [docs/API.md](docs/API.md).
103
+
104
+ ## Testing
105
+
106
+ ```bash
107
+ pnpm test # single run
108
+ pnpm test:coverage # enforced 100% on covered files
109
+ ```
110
+
111
+ Tests run against the real component runtime via `convex-test` (`@edge-runtime/vm`), not mocks.
112
+
113
+ ## Contributing
114
+
115
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
116
+
117
+ ## Author
118
+
119
+ Built by [bntvllnt](https://github.com/bntvllnt) · [bntvllnt.com](https://bntvllnt.com) · [X @bntvllnt](https://x.com/bntvllnt)
120
+
121
+ Part of the [@vllnt](https://github.com/vllnt) Convex component fleet — [vllnt.com](https://vllnt.com)
122
+
123
+ If this is useful, [sponsor the work](https://github.com/sponsors/bntvllnt).
124
+
125
+ ## License
126
+
127
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,135 @@
1
+ import type { FunctionArgs, FunctionReference, FunctionReturnType } from "convex/server";
2
+ import type { BeginResult, CompleteResult, IdempotencyOptions, KeyState, ResultParser } from "./types.js";
3
+ /**
4
+ * The component's raw `begin` return, before the client narrows `result`. The
5
+ * stored outcome is opaque here (`unknown`); the {@link Idempotency} client runs
6
+ * the host `resultValidator` over it at its typed boundary.
7
+ */
8
+ type RawBegin = {
9
+ state: "fresh";
10
+ } | {
11
+ state: "inflight";
12
+ expiresAt: number;
13
+ retryAfterMs: number;
14
+ } | {
15
+ state: "done";
16
+ result?: unknown;
17
+ };
18
+ /**
19
+ * The idempotency component's function references, as exposed on the host via
20
+ * `components.idempotency`. The host's stored outcome type is opaque here
21
+ * (`unknown`); the {@link Idempotency} client narrows it to `TResult` at its
22
+ * own typed boundary.
23
+ */
24
+ export interface IdempotencyComponent {
25
+ mutations: {
26
+ begin: FunctionReference<"mutation", "internal", {
27
+ key: string;
28
+ scope: string;
29
+ inflightTtlMs: number;
30
+ }, RawBegin>;
31
+ complete: FunctionReference<"mutation", "internal", {
32
+ key: string;
33
+ scope: string;
34
+ result?: unknown;
35
+ doneTtlMs: number;
36
+ upsertOnMissing: boolean;
37
+ }, CompleteResult>;
38
+ purge: FunctionReference<"mutation", "internal", {
39
+ before?: number;
40
+ batch: number;
41
+ }, number>;
42
+ };
43
+ queries: {
44
+ get: FunctionReference<"query", "internal", {
45
+ key: string;
46
+ scope: string;
47
+ }, {
48
+ status: "inflight" | "done";
49
+ result?: unknown;
50
+ expiresAt: number;
51
+ } | null>;
52
+ };
53
+ }
54
+ interface RunQueryCtx {
55
+ runQuery<Q extends FunctionReference<"query", "internal">>(reference: Q, args: FunctionArgs<Q>): Promise<FunctionReturnType<Q>>;
56
+ }
57
+ interface RunMutationCtx {
58
+ runMutation<M extends FunctionReference<"mutation", "internal">>(reference: M, args: FunctionArgs<M>): Promise<FunctionReturnType<M>>;
59
+ }
60
+ /** Per-call overrides for `scope` and the inflight lease. */
61
+ interface BeginOptions {
62
+ scope?: string;
63
+ inflightTtlMs?: number;
64
+ }
65
+ /** Per-call overrides for `scope`, the done grace window, and lost-claim upsert. */
66
+ interface CompleteOptions {
67
+ scope?: string;
68
+ doneTtlMs?: number;
69
+ upsertOnMissing?: boolean;
70
+ }
71
+ /**
72
+ * Consumer-facing client for the exactly-once idempotency key ledger. The host
73
+ * owns meaning and auth; it passes an opaque `key` and an optional `scope`, and
74
+ * stores an arbitrary `TResult` outcome that replays return verbatim. Pass a
75
+ * `resultValidator` to narrow the opaque stored value to `TResult` at the
76
+ * boundary — there is no unchecked cast.
77
+ *
78
+ * @typeParam TResult - The host's stored outcome type (defaults to `unknown`).
79
+ *
80
+ * @example
81
+ * ```ts
82
+ * const idem = new Idempotency(components.idempotency, {
83
+ * resultValidator: v.object({ chargeId: v.string() }).parse,
84
+ * });
85
+ * const r = await idem.begin(ctx, requestId);
86
+ * if (r.state === "done") return r.result; // typed replay
87
+ * if (r.state === "inflight") throw new Error(`retry in ${r.retryAfterMs}ms`);
88
+ * const out = await doWork(); // r.state === "fresh"
89
+ * const done = await idem.complete(ctx, requestId, out);
90
+ * if (!done.recorded) log.warn("claim lost", done.reason); // work ran, row gone
91
+ * ```
92
+ */
93
+ export declare class Idempotency<TResult = unknown> {
94
+ private readonly component;
95
+ private readonly defaultScope;
96
+ private readonly defaultInflightTtlMs;
97
+ private readonly defaultDoneTtlMs;
98
+ private readonly defaultUpsertOnMissing;
99
+ private readonly resultValidator;
100
+ constructor(component: IdempotencyComponent, options?: IdempotencyOptions<TResult>);
101
+ private scopeOf;
102
+ /**
103
+ * Narrow an opaque stored `result` to `TResult` via the host validator. Absent
104
+ * values pass through untouched; with no validator the value is returned as-is
105
+ * (the host accepted the unchecked-type tradeoff by omitting one).
106
+ */
107
+ private parseResult;
108
+ /**
109
+ * Claim `key`. `fresh` mints an inflight key (do the work, then `complete`);
110
+ * `inflight` means a concurrent attempt holds it — back off `retryAfterMs`;
111
+ * `done` returns the validated recorded `result` for a short-circuit replay.
112
+ */
113
+ begin(ctx: RunMutationCtx, key: string, opts?: BeginOptions): Promise<BeginResult<TResult>>;
114
+ /**
115
+ * Mark `key` `done`, recording `result` and extending the done grace window.
116
+ * Returns a discriminated outcome: `recorded: true` on success, or
117
+ * `recorded: false` with a `reason` when the claim was lost (missing, expired,
118
+ * or already done) so the host can react to dropped work. `result` is validated
119
+ * against the host validator before it is stored.
120
+ */
121
+ complete(ctx: RunMutationCtx, key: string, result?: TResult, opts?: CompleteOptions): Promise<CompleteResult>;
122
+ /** The stored state for `key`, or `null` if no key is held in the scope. */
123
+ get(ctx: RunQueryCtx, key: string, scope?: string): Promise<KeyState<TResult> | null>;
124
+ /**
125
+ * Delete expired keys in bounded batches, oldest first. `before` defaults to
126
+ * the server clock; `batch` caps each pass and the sweep self-reschedules until
127
+ * the tail is clean. Returns the count removed in the first pass.
128
+ */
129
+ purge(ctx: RunMutationCtx, opts?: {
130
+ before?: number;
131
+ batch?: number;
132
+ }): Promise<number>;
133
+ }
134
+ export type { BeginResult, CompleteResult, IdempotencyOptions, KeyState, ResultParser, };
135
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,117 @@
1
+ import { DEFAULT_DONE_TTL_MS, DEFAULT_INFLIGHT_TTL_MS, DEFAULT_PURGE_BATCH, DEFAULT_SCOPE, } from "../shared.js";
2
+ /**
3
+ * Consumer-facing client for the exactly-once idempotency key ledger. The host
4
+ * owns meaning and auth; it passes an opaque `key` and an optional `scope`, and
5
+ * stores an arbitrary `TResult` outcome that replays return verbatim. Pass a
6
+ * `resultValidator` to narrow the opaque stored value to `TResult` at the
7
+ * boundary — there is no unchecked cast.
8
+ *
9
+ * @typeParam TResult - The host's stored outcome type (defaults to `unknown`).
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * const idem = new Idempotency(components.idempotency, {
14
+ * resultValidator: v.object({ chargeId: v.string() }).parse,
15
+ * });
16
+ * const r = await idem.begin(ctx, requestId);
17
+ * if (r.state === "done") return r.result; // typed replay
18
+ * if (r.state === "inflight") throw new Error(`retry in ${r.retryAfterMs}ms`);
19
+ * const out = await doWork(); // r.state === "fresh"
20
+ * const done = await idem.complete(ctx, requestId, out);
21
+ * if (!done.recorded) log.warn("claim lost", done.reason); // work ran, row gone
22
+ * ```
23
+ */
24
+ export class Idempotency {
25
+ component;
26
+ defaultScope;
27
+ defaultInflightTtlMs;
28
+ defaultDoneTtlMs;
29
+ defaultUpsertOnMissing;
30
+ resultValidator;
31
+ constructor(component, options = {}) {
32
+ this.component = component;
33
+ this.defaultScope = options.defaultScope ?? DEFAULT_SCOPE;
34
+ this.defaultInflightTtlMs =
35
+ options.defaultInflightTtlMs ?? DEFAULT_INFLIGHT_TTL_MS;
36
+ this.defaultDoneTtlMs = options.defaultDoneTtlMs ?? DEFAULT_DONE_TTL_MS;
37
+ this.defaultUpsertOnMissing = options.upsertOnMissing ?? false;
38
+ this.resultValidator = options.resultValidator;
39
+ }
40
+ scopeOf(scope) {
41
+ return scope ?? this.defaultScope;
42
+ }
43
+ /**
44
+ * Narrow an opaque stored `result` to `TResult` via the host validator. Absent
45
+ * values pass through untouched; with no validator the value is returned as-is
46
+ * (the host accepted the unchecked-type tradeoff by omitting one).
47
+ */
48
+ parseResult(value) {
49
+ if (value === undefined) {
50
+ return undefined;
51
+ }
52
+ if (this.resultValidator === undefined) {
53
+ return value;
54
+ }
55
+ return this.resultValidator(value);
56
+ }
57
+ /**
58
+ * Claim `key`. `fresh` mints an inflight key (do the work, then `complete`);
59
+ * `inflight` means a concurrent attempt holds it — back off `retryAfterMs`;
60
+ * `done` returns the validated recorded `result` for a short-circuit replay.
61
+ */
62
+ async begin(ctx, key, opts = {}) {
63
+ const r = await ctx.runMutation(this.component.mutations.begin, {
64
+ key,
65
+ scope: this.scopeOf(opts.scope),
66
+ inflightTtlMs: opts.inflightTtlMs ?? this.defaultInflightTtlMs,
67
+ });
68
+ if (r.state === "done") {
69
+ return { state: "done", result: this.parseResult(r.result) };
70
+ }
71
+ return r;
72
+ }
73
+ /**
74
+ * Mark `key` `done`, recording `result` and extending the done grace window.
75
+ * Returns a discriminated outcome: `recorded: true` on success, or
76
+ * `recorded: false` with a `reason` when the claim was lost (missing, expired,
77
+ * or already done) so the host can react to dropped work. `result` is validated
78
+ * against the host validator before it is stored.
79
+ */
80
+ complete(ctx, key, result, opts = {}) {
81
+ const validated = result === undefined ? undefined : this.parseResult(result);
82
+ return ctx.runMutation(this.component.mutations.complete, {
83
+ key,
84
+ scope: this.scopeOf(opts.scope),
85
+ result: validated,
86
+ doneTtlMs: opts.doneTtlMs ?? this.defaultDoneTtlMs,
87
+ upsertOnMissing: opts.upsertOnMissing ?? this.defaultUpsertOnMissing,
88
+ });
89
+ }
90
+ /** The stored state for `key`, or `null` if no key is held in the scope. */
91
+ async get(ctx, key, scope) {
92
+ const row = await ctx.runQuery(this.component.queries.get, {
93
+ key,
94
+ scope: this.scopeOf(scope),
95
+ });
96
+ if (row === null) {
97
+ return null;
98
+ }
99
+ return {
100
+ status: row.status,
101
+ result: this.parseResult(row.result),
102
+ expiresAt: row.expiresAt,
103
+ };
104
+ }
105
+ /**
106
+ * Delete expired keys in bounded batches, oldest first. `before` defaults to
107
+ * the server clock; `batch` caps each pass and the sweep self-reschedules until
108
+ * the tail is clean. Returns the count removed in the first pass.
109
+ */
110
+ purge(ctx, opts = {}) {
111
+ return ctx.runMutation(this.component.mutations.purge, {
112
+ before: opts.before,
113
+ batch: opts.batch ?? DEFAULT_PURGE_BATCH,
114
+ });
115
+ }
116
+ }
117
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ /** Public TypeScript surface for the idempotency client. */
2
+ /**
3
+ * Validates and narrows an opaque stored `result` to the host's `TResult` at the
4
+ * client boundary. Receives the raw value the component replayed (`unknown`) and
5
+ * MUST return a typed `TResult` or throw. A `convex/values` validator's `.parse`
6
+ * (or a Zod `.parse`) fits directly; omit it to keep results unvalidated.
7
+ *
8
+ * @typeParam TResult - The host's stored outcome type.
9
+ */
10
+ export type ResultParser<TResult> = (value: unknown) => TResult;
11
+ /** Outcome of {@link Idempotency.begin} for a key. */
12
+ export type BeginResult<TResult> = {
13
+ /** The key was just minted; do the work, then call `complete`. */
14
+ state: "fresh";
15
+ } | {
16
+ /** Another attempt holds the key; do not re-run. */
17
+ state: "inflight";
18
+ /** Absolute ms timestamp when the inflight lease frees. */
19
+ expiresAt: number;
20
+ /** How long to back off (ms) before retrying — the crashed-worker self-heal window. */
21
+ retryAfterMs: number;
22
+ } | {
23
+ /** A prior attempt already completed; `result` holds its outcome. */
24
+ state: "done";
25
+ /** The recorded outcome (absent if completed without one). */
26
+ result?: TResult;
27
+ };
28
+ /** Outcome of {@link Idempotency.complete} — discriminated so a lost claim is detectable. */
29
+ export type CompleteResult = {
30
+ /** The outcome was written to the ledger. */
31
+ recorded: true;
32
+ } | {
33
+ /** The claim was lost; the work ran but its ledger row was gone. */
34
+ recorded: false;
35
+ /**
36
+ * `missing` — no key for this id/scope. `expired` — the inflight lease
37
+ * lapsed before completion. `already_done` — a prior attempt won the race.
38
+ */
39
+ reason: "missing" | "expired" | "already_done";
40
+ };
41
+ /** Construction options for the {@link Idempotency} client. */
42
+ export interface IdempotencyOptions<TResult> {
43
+ /** Namespace applied when a call omits `scope`. Default `"global"`. */
44
+ defaultScope?: string;
45
+ /**
46
+ * Inflight lease (ms) applied by `begin` when a call omits `inflightTtlMs`.
47
+ * Short by design so a crashed worker's claim self-heals. Default `60_000`.
48
+ */
49
+ defaultInflightTtlMs?: number;
50
+ /**
51
+ * Grace window (ms) applied by `complete` when a call omits `doneTtlMs` —
52
+ * how long a recorded outcome replays before the key may be re-minted.
53
+ * Default `86_400_000` (24h).
54
+ */
55
+ defaultDoneTtlMs?: number;
56
+ /**
57
+ * When set, `complete` upserts the key as `done` even if its inflight row had
58
+ * vanished (`missing`/`expired`) — recording finished work rather than dropping
59
+ * it. Default `false`. Overridable per `complete` call.
60
+ */
61
+ upsertOnMissing?: boolean;
62
+ /**
63
+ * Validates/narrows a stored `result` to `TResult` at the boundary. Applied to
64
+ * the `result` returned by `begin` (`done` replay) and `get`, and to the
65
+ * `result` passed into `complete` before it is stored. Throws on a mismatch.
66
+ */
67
+ resultValidator?: ResultParser<TResult>;
68
+ }
69
+ /** A key's stored state, as returned by {@link Idempotency.get}. */
70
+ export interface KeyState<TResult> {
71
+ /** `inflight` while an attempt holds the key, `done` once completed. */
72
+ status: "inflight" | "done";
73
+ /** The recorded outcome, present only when `status` is `done`. */
74
+ result?: TResult;
75
+ /** Absolute ms timestamp after which the key may be re-minted. */
76
+ expiresAt: number;
77
+ }
78
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/client/types.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAE5D;;;;;;;GAOG;AACH,MAAM,MAAM,YAAY,CAAC,OAAO,IAAI,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;AAEhE,sDAAsD;AACtD,MAAM,MAAM,WAAW,CAAC,OAAO,IAC3B;IACE,kEAAkE;IAClE,KAAK,EAAE,OAAO,CAAC;CAChB,GACD;IACE,oDAAoD;IACpD,KAAK,EAAE,UAAU,CAAC;IAClB,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,uFAAuF;IACvF,YAAY,EAAE,MAAM,CAAC;CACtB,GACD;IACE,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAC;IACd,8DAA8D;IAC9D,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAEN,6FAA6F;AAC7F,MAAM,MAAM,cAAc,GACtB;IACE,6CAA6C;IAC7C,QAAQ,EAAE,IAAI,CAAC;CAChB,GACD;IACE,oEAAoE;IACpE,QAAQ,EAAE,KAAK,CAAC;IAChB;;;OAGG;IACH,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,cAAc,CAAC;CAChD,CAAC;AAEN,+DAA+D;AAC/D,MAAM,WAAW,kBAAkB,CAAC,OAAO;IACzC,uEAAuE;IACvE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,eAAe,CAAC,EAAE,YAAY,CAAC,OAAO,CAAC,CAAC;CACzC;AAED,oEAAoE;AACpE,MAAM,WAAW,QAAQ,CAAC,OAAO;IAC/B,wEAAwE;IACxE,MAAM,EAAE,UAAU,GAAG,MAAM,CAAC;IAC5B,kEAAkE;IAClE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,kEAAkE;IAClE,SAAS,EAAE,MAAM,CAAC;CACnB"}
@@ -0,0 +1,3 @@
1
+ /** Public TypeScript surface for the idempotency client. */
2
+ export {};
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/client/types.ts"],"names":[],"mappings":"AAAA,4DAA4D"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Generated `api` utility.
3
+ *
4
+ * THIS CODE IS AUTOMATICALLY GENERATED.
5
+ *
6
+ * To regenerate, run `npx convex dev`.
7
+ * @module
8
+ */
9
+ import type * as crons from "../crons.js";
10
+ import type * as mutations from "../mutations.js";
11
+ import type * as queries from "../queries.js";
12
+ import type * as validators from "../validators.js";
13
+ import type { ApiFromModules, FilterApi, FunctionReference } from "convex/server";
14
+ declare const fullApi: ApiFromModules<{
15
+ crons: typeof crons;
16
+ mutations: typeof mutations;
17
+ queries: typeof queries;
18
+ validators: typeof validators;
19
+ }>;
20
+ /**
21
+ * A utility for referencing Convex functions in your app's public API.
22
+ *
23
+ * Usage:
24
+ * ```js
25
+ * const myFunctionReference = api.myModule.myFunction;
26
+ * ```
27
+ */
28
+ export declare const api: FilterApi<typeof fullApi, FunctionReference<any, "public">>;
29
+ /**
30
+ * A utility for referencing Convex functions in your app's internal API.
31
+ *
32
+ * Usage:
33
+ * ```js
34
+ * const myFunctionReference = internal.myModule.myFunction;
35
+ * ```
36
+ */
37
+ export declare const internal: FilterApi<typeof fullApi, FunctionReference<any, "internal">>;
38
+ export declare const components: {};
39
+ export {};
40
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../src/component/_generated/api.ts"],"names":[],"mappings":"AACA;;;;;;;GAOG;AAEH,OAAO,KAAK,KAAK,KAAK,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,KAAK,SAAS,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,KAAK,OAAO,MAAM,eAAe,CAAC;AAC9C,OAAO,KAAK,KAAK,UAAU,MAAM,kBAAkB,CAAC;AAEpD,OAAO,KAAK,EACV,cAAc,EACd,SAAS,EACT,iBAAiB,EAClB,MAAM,eAAe,CAAC;AAGvB,QAAA,MAAM,OAAO,EAAE,cAAc,CAAC;IAC5B,KAAK,EAAE,OAAO,KAAK,CAAC;IACpB,SAAS,EAAE,OAAO,SAAS,CAAC;IAC5B,OAAO,EAAE,OAAO,OAAO,CAAC;IACxB,UAAU,EAAE,OAAO,UAAU,CAAC;CAC/B,CAAiB,CAAC;AAEnB;;;;;;;GAOG;AACH,eAAO,MAAM,GAAG,EAAE,SAAS,CACzB,OAAO,OAAO,EACd,iBAAiB,CAAC,GAAG,EAAE,QAAQ,CAAC,CACjB,CAAC;AAElB;;;;;;;GAOG;AACH,eAAO,MAAM,QAAQ,EAAE,SAAS,CAC9B,OAAO,OAAO,EACd,iBAAiB,CAAC,GAAG,EAAE,UAAU,CAAC,CACnB,CAAC;AAElB,eAAO,MAAM,UAAU,EAAqC,EAAE,CAAC"}
@@ -0,0 +1,31 @@
1
+ /* eslint-disable */
2
+ /**
3
+ * Generated `api` utility.
4
+ *
5
+ * THIS CODE IS AUTOMATICALLY GENERATED.
6
+ *
7
+ * To regenerate, run `npx convex dev`.
8
+ * @module
9
+ */
10
+ import { anyApi, componentsGeneric } from "convex/server";
11
+ const fullApi = anyApi;
12
+ /**
13
+ * A utility for referencing Convex functions in your app's public API.
14
+ *
15
+ * Usage:
16
+ * ```js
17
+ * const myFunctionReference = api.myModule.myFunction;
18
+ * ```
19
+ */
20
+ export const api = anyApi;
21
+ /**
22
+ * A utility for referencing Convex functions in your app's internal API.
23
+ *
24
+ * Usage:
25
+ * ```js
26
+ * const myFunctionReference = internal.myModule.myFunction;
27
+ * ```
28
+ */
29
+ export const internal = anyApi;
30
+ export const components = componentsGeneric();
31
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../../../src/component/_generated/api.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB;;;;;;;GAOG;AAYH,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAE1D,MAAM,OAAO,GAKR,MAAa,CAAC;AAEnB;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,GAAG,GAGZ,MAAa,CAAC;AAElB;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,QAAQ,GAGjB,MAAa,CAAC;AAElB,MAAM,CAAC,MAAM,UAAU,GAAG,iBAAiB,EAAmB,CAAC"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Generated `ComponentApi` utility.
3
+ *
4
+ * THIS CODE IS AUTOMATICALLY GENERATED.
5
+ *
6
+ * To regenerate, run `npx convex dev`.
7
+ * @module
8
+ */
9
+ import type { FunctionReference } from "convex/server";
10
+ /**
11
+ * A utility for referencing a Convex component's exposed API.
12
+ *
13
+ * Useful when expecting a parameter like `components.myComponent`.
14
+ * Usage:
15
+ * ```ts
16
+ * async function myFunction(ctx: QueryCtx, component: ComponentApi) {
17
+ * return ctx.runQuery(component.someFile.someQuery, { ...args });
18
+ * }
19
+ * ```
20
+ */
21
+ export type ComponentApi<Name extends string | undefined = string | undefined> = {
22
+ mutations: {
23
+ begin: FunctionReference<"mutation", "internal", {
24
+ inflightTtlMs: number;
25
+ key: string;
26
+ scope: string;
27
+ }, {
28
+ state: "fresh";
29
+ } | {
30
+ expiresAt: number;
31
+ retryAfterMs: number;
32
+ state: "inflight";
33
+ } | {
34
+ result?: any;
35
+ state: "done";
36
+ }, Name>;
37
+ complete: FunctionReference<"mutation", "internal", {
38
+ doneTtlMs: number;
39
+ key: string;
40
+ result?: any;
41
+ scope: string;
42
+ upsertOnMissing: boolean;
43
+ }, {
44
+ recorded: true;
45
+ } | {
46
+ reason: "missing" | "expired" | "already_done";
47
+ recorded: false;
48
+ }, Name>;
49
+ purge: FunctionReference<"mutation", "internal", {
50
+ batch: number;
51
+ before?: number;
52
+ }, number, Name>;
53
+ };
54
+ queries: {
55
+ get: FunctionReference<"query", "internal", {
56
+ key: string;
57
+ scope: string;
58
+ }, null | {
59
+ expiresAt: number;
60
+ result?: any;
61
+ status: "inflight" | "done";
62
+ }, Name>;
63
+ };
64
+ };
65
+ //# sourceMappingURL=component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../../../src/component/_generated/component.ts"],"names":[],"mappings":"AACA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEvD;;;;;;;;;;GAUG;AACH,MAAM,MAAM,YAAY,CAAC,IAAI,SAAS,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,IAC3E;IACE,SAAS,EAAE;QACT,KAAK,EAAE,iBAAiB,CACtB,UAAU,EACV,UAAU,EACV;YAAE,aAAa,EAAE,MAAM,CAAC;YAAC,GAAG,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,EACnD;YAAE,KAAK,EAAE,OAAO,CAAA;SAAE,GAClB;YAAE,SAAS,EAAE,MAAM,CAAC;YAAC,YAAY,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,UAAU,CAAA;SAAE,GAC9D;YAAE,MAAM,CAAC,EAAE,GAAG,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,EACjC,IAAI,CACL,CAAC;QACF,QAAQ,EAAE,iBAAiB,CACzB,UAAU,EACV,UAAU,EACV;YACE,SAAS,EAAE,MAAM,CAAC;YAClB,GAAG,EAAE,MAAM,CAAC;YACZ,MAAM,CAAC,EAAE,GAAG,CAAC;YACb,KAAK,EAAE,MAAM,CAAC;YACd,eAAe,EAAE,OAAO,CAAC;SAC1B,EACC;YAAE,QAAQ,EAAE,IAAI,CAAA;SAAE,GAClB;YAAE,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,cAAc,CAAC;YAAC,QAAQ,EAAE,KAAK,CAAA;SAAE,EACrE,IAAI,CACL,CAAC;QACF,KAAK,EAAE,iBAAiB,CACtB,UAAU,EACV,UAAU,EACV;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,MAAM,CAAC,EAAE,MAAM,CAAA;SAAE,EAClC,MAAM,EACN,IAAI,CACL,CAAC;KACH,CAAC;IACF,OAAO,EAAE;QACP,GAAG,EAAE,iBAAiB,CACpB,OAAO,EACP,UAAU,EACV;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,EAC9B,IAAI,GAAG;YAAE,SAAS,EAAE,MAAM,CAAC;YAAC,MAAM,CAAC,EAAE,GAAG,CAAC;YAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAAA;SAAE,EACvE,IAAI,CACL,CAAC;KACH,CAAC;CACH,CAAC"}