@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.
- package/LICENSE +21 -0
- package/README.md +127 -0
- package/dist/client/index.d.ts +135 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +117 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/types.d.ts +78 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +3 -0
- package/dist/client/types.js.map +1 -0
- package/dist/component/_generated/api.d.ts +40 -0
- package/dist/component/_generated/api.d.ts.map +1 -0
- package/dist/component/_generated/api.js +31 -0
- package/dist/component/_generated/api.js.map +1 -0
- package/dist/component/_generated/component.d.ts +65 -0
- package/dist/component/_generated/component.d.ts.map +1 -0
- package/dist/component/_generated/component.js +11 -0
- package/dist/component/_generated/component.js.map +1 -0
- package/dist/component/_generated/dataModel.d.ts +46 -0
- package/dist/component/_generated/dataModel.d.ts.map +1 -0
- package/dist/component/_generated/dataModel.js +11 -0
- package/dist/component/_generated/dataModel.js.map +1 -0
- package/dist/component/_generated/server.d.ts +121 -0
- package/dist/component/_generated/server.d.ts.map +1 -0
- package/dist/component/_generated/server.js +78 -0
- package/dist/component/_generated/server.js.map +1 -0
- package/dist/component/convex.config.d.ts +3 -0
- package/dist/component/convex.config.d.ts.map +1 -0
- package/dist/component/convex.config.js +7 -0
- package/dist/component/convex.config.js.map +1 -0
- package/dist/component/crons.d.ts +16 -0
- package/dist/component/crons.d.ts.map +1 -0
- package/dist/component/crons.js +19 -0
- package/dist/component/crons.js.map +1 -0
- package/dist/component/mutations.d.ts +91 -0
- package/dist/component/mutations.d.ts.map +1 -0
- package/dist/component/mutations.js +177 -0
- package/dist/component/mutations.js.map +1 -0
- package/dist/component/queries.d.ts +9 -0
- package/dist/component/queries.d.ts.map +1 -0
- package/dist/component/queries.js +22 -0
- package/dist/component/queries.js.map +1 -0
- package/dist/component/schema.d.ts +26 -0
- package/dist/component/schema.d.ts.map +1 -0
- package/dist/component/schema.js +21 -0
- package/dist/component/schema.js.map +1 -0
- package/dist/component/validators.d.ts +80 -0
- package/dist/component/validators.d.ts.map +1 -0
- package/dist/component/validators.js +42 -0
- package/dist/component/validators.js.map +1 -0
- package/dist/shared.d.ts +17 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +17 -0
- package/dist/shared.js.map +1 -0
- package/package.json +96 -0
- package/src/client/index.ts +251 -0
- package/src/client/types.ts +87 -0
- package/src/component/_generated/api.ts +56 -0
- package/src/component/_generated/component.ts +67 -0
- package/src/component/_generated/dataModel.ts +60 -0
- package/src/component/_generated/server.ts +156 -0
- package/src/component/convex.config.ts +9 -0
- package/src/component/crons.ts +23 -0
- package/src/component/mutations.ts +195 -0
- package/src/component/queries.ts +24 -0
- package/src/component/schema.ts +21 -0
- package/src/component/validators.ts +56 -0
- package/src/shared.ts +21 -0
- 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
|
+
[](https://www.convex.dev/components)
|
|
3
|
+
[](https://www.npmjs.com/package/@vllnt/convex-idempotency)
|
|
4
|
+
[](https://github.com/vllnt/convex-idempotency/actions/workflows/ci.yml)
|
|
5
|
+
[](./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":"AAYA,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACvB,mBAAmB,EACnB,aAAa,GACd,MAAM,cAAc,CAAC;AAsFtB;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,OAAO,WAAW;IAQH;IAPF,YAAY,CAAS;IACrB,oBAAoB,CAAS;IAC7B,gBAAgB,CAAS;IACzB,sBAAsB,CAAU;IAChC,eAAe,CAAoC;IAEpE,YACmB,SAA+B,EAChD,UAAuC,EAAE;QADxB,cAAS,GAAT,SAAS,CAAsB;QAGhD,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,aAAa,CAAC;QAC1D,IAAI,CAAC,oBAAoB;YACvB,OAAO,CAAC,oBAAoB,IAAI,uBAAuB,CAAC;QAC1D,IAAI,CAAC,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,IAAI,mBAAmB,CAAC;QACxE,IAAI,CAAC,sBAAsB,GAAG,OAAO,CAAC,eAAe,IAAI,KAAK,CAAC;QAC/D,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC;IACjD,CAAC;IAEO,OAAO,CAAC,KAAyB;QACvC,OAAO,KAAK,IAAI,IAAI,CAAC,YAAY,CAAC;IACpC,CAAC;IAED;;;;OAIG;IACK,WAAW,CAAC,KAAc;QAChC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,IAAI,IAAI,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;YACvC,OAAO,KAAgB,CAAC;QAC1B,CAAC;QACD,OAAO,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;IACrC,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,KAAK,CACT,GAAmB,EACnB,GAAW,EACX,OAAqB,EAAE;QAEvB,MAAM,CAAC,GAAa,MAAM,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,KAAK,EAAE;YACxE,GAAG;YACH,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC;YAC/B,aAAa,EAAE,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC,oBAAoB;SAC/D,CAAC,CAAC;QACH,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM,EAAE,CAAC;YACvB,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;QAC/D,CAAC;QACD,OAAO,CAAC,CAAC;IACX,CAAC;IAED;;;;;;OAMG;IACH,QAAQ,CACN,GAAmB,EACnB,GAAW,EACX,MAAgB,EAChB,OAAwB,EAAE;QAE1B,MAAM,SAAS,GACb,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QAC9D,OAAO,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,QAAQ,EAAE;YACxD,GAAG;YACH,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC;YAC/B,MAAM,EAAE,SAAS;YACjB,SAAS,EAAE,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,gBAAgB;YAClD,eAAe,EAAE,IAAI,CAAC,eAAe,IAAI,IAAI,CAAC,sBAAsB;SACrE,CAAC,CAAC;IACL,CAAC;IAED,4EAA4E;IAC5E,KAAK,CAAC,GAAG,CACP,GAAgB,EAChB,GAAW,EACX,KAAc;QAEd,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,EAAE;YACzD,GAAG;YACH,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC;SAC3B,CAAC,CAAC;QACH,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACjB,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO;YACL,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC;YACpC,SAAS,EAAE,GAAG,CAAC,SAAS;SACzB,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACH,KAAK,CACH,GAAmB,EACnB,OAA4C,EAAE;QAE9C,OAAO,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,KAAK,EAAE;YACrD,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,mBAAmB;SACzC,CAAC,CAAC;IACL,CAAC;CACF"}
|
|
@@ -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 @@
|
|
|
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"}
|