@velum-labs/routekit-accounts 0.16.2 → 0.16.4
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/dist/account-set.js +82 -4
- package/dist/provider.js +5 -2
- package/dist/test/subscription-pool.test.js +119 -4
- package/dist/test/subscription-provider.test.js +2 -0
- package/dist/test/subscription-sdk.test.js +11 -0
- package/dist/types.d.ts +1 -0
- package/dist/wire.d.ts +1 -0
- package/dist/wire.js +1 -0
- package/package.json +5 -5
package/dist/account-set.js
CHANGED
|
@@ -49,6 +49,63 @@ function parsedRateLimitWindow(value, observedAt, source, migration) {
|
|
|
49
49
|
source: windowSource
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
|
+
function parsedResetCredit(value) {
|
|
53
|
+
if (!isRecord(value) || typeof value.id !== "string" || value.id.length === 0) {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
id: value.id,
|
|
58
|
+
...(typeof value.resetType === "string" ? { resetType: value.resetType } : {}),
|
|
59
|
+
...(typeof value.status === "string" ? { status: value.status } : {}),
|
|
60
|
+
...(typeof value.grantedAt === "number" && Number.isFinite(value.grantedAt)
|
|
61
|
+
? { grantedAt: value.grantedAt }
|
|
62
|
+
: {}),
|
|
63
|
+
...(typeof value.expiresAt === "number" && Number.isFinite(value.expiresAt)
|
|
64
|
+
? { expiresAt: value.expiresAt }
|
|
65
|
+
: {}),
|
|
66
|
+
...(typeof value.title === "string" ? { title: value.title } : {}),
|
|
67
|
+
...(typeof value.description === "string" ? { description: value.description } : {})
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function parsedResetCreditSnapshot(value, fallbackObservedAt, migration) {
|
|
71
|
+
if (!isRecord(value) ||
|
|
72
|
+
typeof value.availableCount !== "number" ||
|
|
73
|
+
!Number.isFinite(value.availableCount) ||
|
|
74
|
+
value.availableCount < 0) {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
let observedAt;
|
|
78
|
+
if (typeof value.observedAt === "number" && Number.isFinite(value.observedAt)) {
|
|
79
|
+
observedAt = value.observedAt;
|
|
80
|
+
}
|
|
81
|
+
else if (fallbackObservedAt !== undefined) {
|
|
82
|
+
if (migration !== undefined)
|
|
83
|
+
migration.required = true;
|
|
84
|
+
observedAt = fallbackObservedAt;
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
if (value.credits !== undefined && !Array.isArray(value.credits))
|
|
90
|
+
return undefined;
|
|
91
|
+
const credits = Array.isArray(value.credits)
|
|
92
|
+
? value.credits.flatMap((entry) => {
|
|
93
|
+
const credit = parsedResetCredit(entry);
|
|
94
|
+
return credit === undefined ? [] : [credit];
|
|
95
|
+
})
|
|
96
|
+
: undefined;
|
|
97
|
+
if (Array.isArray(value.credits) &&
|
|
98
|
+
credits !== undefined &&
|
|
99
|
+
credits.length !== value.credits.length &&
|
|
100
|
+
migration !== undefined) {
|
|
101
|
+
migration.required = true;
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
observedAt,
|
|
105
|
+
availableCount: Math.floor(value.availableCount),
|
|
106
|
+
...(credits !== undefined ? { credits } : {})
|
|
107
|
+
};
|
|
108
|
+
}
|
|
52
109
|
function parsedAccountLimits(value, mode, migration) {
|
|
53
110
|
if (!isRecord(value) ||
|
|
54
111
|
!isRecord(value.windows) ||
|
|
@@ -87,13 +144,15 @@ function parsedAccountLimits(value, mode, migration) {
|
|
|
87
144
|
writable: true
|
|
88
145
|
});
|
|
89
146
|
}
|
|
147
|
+
const resetCredits = parsedResetCreditSnapshot(value.resetCredits, value.observedAt, migration);
|
|
90
148
|
return {
|
|
91
149
|
windows,
|
|
92
150
|
observedAt: value.observedAt,
|
|
93
151
|
source: value.source,
|
|
94
152
|
completeness,
|
|
95
153
|
...(typeof value.planType === "string" ? { planType: value.planType } : {}),
|
|
96
|
-
...(isRecord(value.credits) ? { credits: value.credits } : {})
|
|
154
|
+
...(isRecord(value.credits) ? { credits: value.credits } : {}),
|
|
155
|
+
...(resetCredits !== undefined ? { resetCredits } : {})
|
|
97
156
|
};
|
|
98
157
|
}
|
|
99
158
|
function parsedMemberState(value, mode, migration) {
|
|
@@ -183,7 +242,8 @@ function mergeLimits(previous, next, mode) {
|
|
|
183
242
|
});
|
|
184
243
|
}
|
|
185
244
|
}
|
|
186
|
-
|
|
245
|
+
const resetCredits = next.resetCredits ?? previous?.resetCredits;
|
|
246
|
+
const merged = next.completeness === "snapshot"
|
|
187
247
|
? { ...next, windows }
|
|
188
248
|
: {
|
|
189
249
|
...previous,
|
|
@@ -192,6 +252,10 @@ function mergeLimits(previous, next, mode) {
|
|
|
192
252
|
observedAt: next.observedAt,
|
|
193
253
|
source: next.source
|
|
194
254
|
};
|
|
255
|
+
return {
|
|
256
|
+
...merged,
|
|
257
|
+
...(resetCredits !== undefined ? { resetCredits } : {})
|
|
258
|
+
};
|
|
195
259
|
}
|
|
196
260
|
/**
|
|
197
261
|
* Trackers for one state file share mutable state process-wide. A daemon
|
|
@@ -782,7 +846,18 @@ export class SubscriptionAccountSet {
|
|
|
782
846
|
if (this.#provider.fetchResetCredits === undefined) {
|
|
783
847
|
throw new Error(`${this.mode} does not support redeemable rate-limit resets`);
|
|
784
848
|
}
|
|
785
|
-
|
|
849
|
+
const resetCredits = await this.#provider.fetchResetCredits(member.credential, signal);
|
|
850
|
+
const previous = this.#tracker.limits(member.id);
|
|
851
|
+
this.#tracker.update(member.id, {
|
|
852
|
+
...(previous ?? {
|
|
853
|
+
windows: {},
|
|
854
|
+
source: "usage",
|
|
855
|
+
completeness: "partial"
|
|
856
|
+
}),
|
|
857
|
+
resetCredits,
|
|
858
|
+
observedAt: previous?.observedAt ?? resetCredits.observedAt
|
|
859
|
+
});
|
|
860
|
+
return resetCredits;
|
|
786
861
|
}
|
|
787
862
|
async #attachResetCredits(member, limits, signal) {
|
|
788
863
|
if (this.#provider.fetchResetCredits === undefined)
|
|
@@ -792,7 +867,10 @@ export class SubscriptionAccountSet {
|
|
|
792
867
|
return { ...limits, resetCredits };
|
|
793
868
|
}
|
|
794
869
|
catch {
|
|
795
|
-
|
|
870
|
+
// Keep the last authoritative snapshot when the dedicated listing fails.
|
|
871
|
+
// Do not let a weaker embedded usage payload erase known credit rows.
|
|
872
|
+
const previous = this.#tracker.limits(member.id)?.resetCredits;
|
|
873
|
+
return previous === undefined ? limits : { ...limits, resetCredits: previous };
|
|
796
874
|
}
|
|
797
875
|
}
|
|
798
876
|
async #acquire(model, excluded, signal) {
|
package/dist/provider.js
CHANGED
|
@@ -285,7 +285,7 @@ function codexUsageLimits(payload, source = "usage", completeness = "snapshot")
|
|
|
285
285
|
: {}),
|
|
286
286
|
...(typeof rawCredits.balance === "string" ? { balance: rawCredits.balance } : {})
|
|
287
287
|
};
|
|
288
|
-
const resetCredits = codexResetCreditsFromUsage(payload);
|
|
288
|
+
const resetCredits = codexResetCreditsFromUsage(payload, observedAt);
|
|
289
289
|
return {
|
|
290
290
|
windows,
|
|
291
291
|
...(typeof payload.plan_type === "string" ? { planType: payload.plan_type } : {}),
|
|
@@ -296,7 +296,7 @@ function codexUsageLimits(payload, source = "usage", completeness = "snapshot")
|
|
|
296
296
|
completeness
|
|
297
297
|
};
|
|
298
298
|
}
|
|
299
|
-
function codexResetCreditsFromUsage(payload) {
|
|
299
|
+
function codexResetCreditsFromUsage(payload, observedAt) {
|
|
300
300
|
const raw = isRecord(payload.rate_limit_reset_credits)
|
|
301
301
|
? payload.rate_limit_reset_credits
|
|
302
302
|
: isRecord(payload.rateLimitResetCredits)
|
|
@@ -315,6 +315,7 @@ function codexResetCreditsFromUsage(payload) {
|
|
|
315
315
|
.filter((entry) => entry !== undefined)
|
|
316
316
|
: undefined;
|
|
317
317
|
return {
|
|
318
|
+
observedAt,
|
|
318
319
|
availableCount: Math.max(0, Math.floor(count)),
|
|
319
320
|
...(credits !== undefined && credits.length > 0 ? { credits } : {})
|
|
320
321
|
};
|
|
@@ -353,6 +354,7 @@ function parseResetCredit(value) {
|
|
|
353
354
|
};
|
|
354
355
|
}
|
|
355
356
|
function parseResetCreditSnapshot(payload) {
|
|
357
|
+
const observedAt = Date.now() / 1000;
|
|
356
358
|
if (!isRecord(payload))
|
|
357
359
|
throw new Error("Codex reset-credits endpoint returned an invalid payload");
|
|
358
360
|
const rows = Array.isArray(payload.credits)
|
|
@@ -373,6 +375,7 @@ function parseResetCreditSnapshot(payload) {
|
|
|
373
375
|
numeric(payload.availableCount) ??
|
|
374
376
|
available.length;
|
|
375
377
|
return {
|
|
378
|
+
observedAt,
|
|
376
379
|
availableCount: Math.max(0, Math.floor(count)),
|
|
377
380
|
...(credits.length > 0 ? { credits } : {})
|
|
378
381
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
-
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { readFile } from "node:fs/promises";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
@@ -46,7 +46,9 @@ function fakeProvider(state, modelsByToken = {}, mode = "codex") {
|
|
|
46
46
|
};
|
|
47
47
|
},
|
|
48
48
|
async fetchResetCredits() {
|
|
49
|
-
|
|
49
|
+
if (state.failResetCredits === true)
|
|
50
|
+
throw new Error("reset credits unavailable");
|
|
51
|
+
return state.resetCredits ?? { observedAt: Date.now() / 1000, availableCount: 0, credits: [] };
|
|
50
52
|
},
|
|
51
53
|
async consumeResetCredit(_credential, input) {
|
|
52
54
|
state.consumeCalls = (state.consumeCalls ?? 0) + 1;
|
|
@@ -60,12 +62,12 @@ function fakeProvider(state, modelsByToken = {}, mode = "codex") {
|
|
|
60
62
|
source: "usage"
|
|
61
63
|
}
|
|
62
64
|
},
|
|
63
|
-
resetCredits: { availableCount: 0, credits: [] },
|
|
65
|
+
resetCredits: { observedAt: Date.now() / 1000, availableCount: 0, credits: [] },
|
|
64
66
|
observedAt: Date.now() / 1000,
|
|
65
67
|
source: "usage",
|
|
66
68
|
completeness: "snapshot"
|
|
67
69
|
};
|
|
68
|
-
state.resetCredits = { availableCount: 0, credits: [] };
|
|
70
|
+
state.resetCredits = { observedAt: Date.now() / 1000, availableCount: 0, credits: [] };
|
|
69
71
|
}
|
|
70
72
|
return {
|
|
71
73
|
ok: code === "reset",
|
|
@@ -1449,6 +1451,7 @@ test("redeeming a banked reset refreshes windows and clears cooling", async () =
|
|
|
1449
1451
|
}
|
|
1450
1452
|
},
|
|
1451
1453
|
resetCredits: {
|
|
1454
|
+
observedAt,
|
|
1452
1455
|
availableCount: 1,
|
|
1453
1456
|
credits: [
|
|
1454
1457
|
{
|
|
@@ -1463,6 +1466,7 @@ test("redeeming a banked reset refreshes windows and clears cooling", async () =
|
|
|
1463
1466
|
completeness: "snapshot"
|
|
1464
1467
|
},
|
|
1465
1468
|
resetCredits: {
|
|
1469
|
+
observedAt,
|
|
1466
1470
|
availableCount: 1,
|
|
1467
1471
|
credits: [
|
|
1468
1472
|
{
|
|
@@ -1501,3 +1505,114 @@ test("redeeming a banked reset refreshes windows and clears cooling", async () =
|
|
|
1501
1505
|
rmSync(directory, { recursive: true, force: true });
|
|
1502
1506
|
}
|
|
1503
1507
|
});
|
|
1508
|
+
test("dedicated reset refresh preserves stale state on failure and clears on empty", async () => {
|
|
1509
|
+
const directory = mkdtempSync(join(tmpdir(), "routekit-pool-reset-refresh-"));
|
|
1510
|
+
writeMember(directory, "work", { accessToken: "token-work" });
|
|
1511
|
+
const observedAt = Date.now() / 1000;
|
|
1512
|
+
const detailed = {
|
|
1513
|
+
observedAt,
|
|
1514
|
+
availableCount: 1,
|
|
1515
|
+
credits: [{ id: "RateLimitResetCredit_stale", status: "available", title: "Stale reset" }]
|
|
1516
|
+
};
|
|
1517
|
+
const state = {
|
|
1518
|
+
refreshes: 0,
|
|
1519
|
+
resetCredits: detailed,
|
|
1520
|
+
usageLimits: {
|
|
1521
|
+
windows: {
|
|
1522
|
+
primary: {
|
|
1523
|
+
utilization: 0.5,
|
|
1524
|
+
observedAt,
|
|
1525
|
+
source: "usage"
|
|
1526
|
+
}
|
|
1527
|
+
},
|
|
1528
|
+
// Weaker embedded count-only payload must not win when dedicated listing fails.
|
|
1529
|
+
resetCredits: { observedAt, availableCount: 1 },
|
|
1530
|
+
observedAt,
|
|
1531
|
+
source: "usage",
|
|
1532
|
+
completeness: "snapshot"
|
|
1533
|
+
}
|
|
1534
|
+
};
|
|
1535
|
+
const pool = await SubscriptionAccountSet.open(fakeProvider(state), {
|
|
1536
|
+
mode: "codex",
|
|
1537
|
+
source: { kind: "directory", path: directory }
|
|
1538
|
+
});
|
|
1539
|
+
try {
|
|
1540
|
+
await pool.listResetCredits("work");
|
|
1541
|
+
assert.deepEqual(pool.snapshot().members[0]?.limits?.resetCredits, detailed);
|
|
1542
|
+
state.failResetCredits = true;
|
|
1543
|
+
await pool.probe();
|
|
1544
|
+
assert.deepEqual(pool.snapshot().members[0]?.limits?.resetCredits, detailed);
|
|
1545
|
+
state.failResetCredits = false;
|
|
1546
|
+
state.resetCredits = { observedAt: observedAt + 1, availableCount: 0, credits: [] };
|
|
1547
|
+
await pool.listResetCredits("work");
|
|
1548
|
+
assert.deepEqual(pool.snapshot().members[0]?.limits?.resetCredits, state.resetCredits);
|
|
1549
|
+
}
|
|
1550
|
+
finally {
|
|
1551
|
+
await pool.close();
|
|
1552
|
+
rmSync(directory, { recursive: true, force: true });
|
|
1553
|
+
}
|
|
1554
|
+
});
|
|
1555
|
+
test("tracker restores fully validated reset credit details", () => {
|
|
1556
|
+
const directory = mkdtempSync(join(tmpdir(), "routekit-tracker-reset-persist-"));
|
|
1557
|
+
const path = join(directory, ".state.json");
|
|
1558
|
+
const observedAt = 1775000000;
|
|
1559
|
+
writeFileSync(path, JSON.stringify({
|
|
1560
|
+
members: [{
|
|
1561
|
+
id: "work",
|
|
1562
|
+
limits: {
|
|
1563
|
+
windows: {}, observedAt, source: "usage", completeness: "snapshot",
|
|
1564
|
+
resetCredits: {
|
|
1565
|
+
observedAt: observedAt + 1,
|
|
1566
|
+
availableCount: 1,
|
|
1567
|
+
credits: [{
|
|
1568
|
+
id: "RateLimitResetCredit_saved", resetType: "codex_rate_limits",
|
|
1569
|
+
status: "available", grantedAt: observedAt - 10, expiresAt: observedAt + 100,
|
|
1570
|
+
title: "Saved reset", description: "Persisted details"
|
|
1571
|
+
}]
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
}]
|
|
1575
|
+
}));
|
|
1576
|
+
try {
|
|
1577
|
+
assert.deepEqual(new RateLimitTracker(path, "codex").limits("work")?.resetCredits, {
|
|
1578
|
+
observedAt: observedAt + 1, availableCount: 1, credits: [{
|
|
1579
|
+
id: "RateLimitResetCredit_saved", resetType: "codex_rate_limits",
|
|
1580
|
+
status: "available", grantedAt: observedAt - 10, expiresAt: observedAt + 100,
|
|
1581
|
+
title: "Saved reset", description: "Persisted details"
|
|
1582
|
+
}]
|
|
1583
|
+
});
|
|
1584
|
+
}
|
|
1585
|
+
finally {
|
|
1586
|
+
rmSync(directory, { recursive: true, force: true });
|
|
1587
|
+
}
|
|
1588
|
+
});
|
|
1589
|
+
test("tracker migrates legacy reset credits missing observedAt", () => {
|
|
1590
|
+
const directory = mkdtempSync(join(tmpdir(), "routekit-tracker-reset-migrate-"));
|
|
1591
|
+
const path = join(directory, ".state.json");
|
|
1592
|
+
const observedAt = 1775000000;
|
|
1593
|
+
writeFileSync(path, JSON.stringify({
|
|
1594
|
+
members: [{
|
|
1595
|
+
id: "work",
|
|
1596
|
+
limits: {
|
|
1597
|
+
windows: {}, observedAt, source: "usage", completeness: "snapshot",
|
|
1598
|
+
resetCredits: {
|
|
1599
|
+
availableCount: 1,
|
|
1600
|
+
credits: [{ id: "RateLimitResetCredit_legacy", status: "available" }]
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
}]
|
|
1604
|
+
}));
|
|
1605
|
+
try {
|
|
1606
|
+
const tracker = new RateLimitTracker(path, "codex");
|
|
1607
|
+
assert.deepEqual(tracker.limits("work")?.resetCredits, {
|
|
1608
|
+
observedAt,
|
|
1609
|
+
availableCount: 1,
|
|
1610
|
+
credits: [{ id: "RateLimitResetCredit_legacy", status: "available" }]
|
|
1611
|
+
});
|
|
1612
|
+
const persisted = JSON.parse(readFileSync(path, "utf8"));
|
|
1613
|
+
assert.equal(persisted.members[0]?.limits?.resetCredits?.observedAt, observedAt);
|
|
1614
|
+
}
|
|
1615
|
+
finally {
|
|
1616
|
+
rmSync(directory, { recursive: true, force: true });
|
|
1617
|
+
}
|
|
1618
|
+
});
|
|
@@ -109,6 +109,7 @@ test("Codex adapter parses banked reset credits from usage payloads", async () =
|
|
|
109
109
|
sourcePath: "/tmp/codex.json"
|
|
110
110
|
});
|
|
111
111
|
assert.equal(limits.resetCredits?.availableCount, 2);
|
|
112
|
+
assert.equal(typeof limits.resetCredits?.observedAt, "number");
|
|
112
113
|
assert.equal(limits.resetCredits?.credits?.[0]?.id, "RateLimitResetCredit_a");
|
|
113
114
|
assert.equal(limits.windows.primary?.utilization, 0.9);
|
|
114
115
|
}
|
|
@@ -161,6 +162,7 @@ test("Codex adapter lists and consumes banked rate-limit resets", async () => {
|
|
|
161
162
|
sourcePath: "/tmp/codex.json"
|
|
162
163
|
});
|
|
163
164
|
assert.equal(listed.availableCount, 1);
|
|
165
|
+
assert.equal(typeof listed.observedAt, "number");
|
|
164
166
|
assert.equal(listed.credits?.[0]?.id, "RateLimitResetCredit_b");
|
|
165
167
|
const consumed = await provider.consumeResetCredit({
|
|
166
168
|
mode: "codex",
|
|
@@ -114,6 +114,16 @@ test("the usage wire schema round-trips an account-set snapshot", () => {
|
|
|
114
114
|
source: "headers"
|
|
115
115
|
}
|
|
116
116
|
},
|
|
117
|
+
resetCredits: {
|
|
118
|
+
observedAt: 1_776_000_100,
|
|
119
|
+
availableCount: 1,
|
|
120
|
+
credits: [{
|
|
121
|
+
id: "RateLimitResetCredit_wire",
|
|
122
|
+
status: "available",
|
|
123
|
+
title: "Wire reset",
|
|
124
|
+
expiresAt: 1_777_000_000
|
|
125
|
+
}]
|
|
126
|
+
},
|
|
117
127
|
observedAt: 1_776_000_000,
|
|
118
128
|
source: "headers",
|
|
119
129
|
completeness: "partial"
|
|
@@ -128,6 +138,7 @@ test("the usage wire schema round-trips an account-set snapshot", () => {
|
|
|
128
138
|
assert.equal(parsed.accountSets[0]?.members[0]?.lastSelected, true);
|
|
129
139
|
assert.equal(parsed.accountSets[0]?.members[0]?.lastSelectedAt, 1_776_000_000_000);
|
|
130
140
|
assert.equal(parsed.accountSets[0]?.members[0]?.active, true);
|
|
141
|
+
assert.equal(parsed.accountSets[0]?.members[0]?.limits?.resetCredits?.credits?.[0]?.id, "RateLimitResetCredit_wire");
|
|
131
142
|
assert.deepEqual(parsed.accountSets[0]?.members[0]?.readinessReasons, snapshot.members[0]?.readinessReasons);
|
|
132
143
|
const older = structuredClone(snapshot);
|
|
133
144
|
delete older.members[0]?.readinessReasons;
|
package/dist/types.d.ts
CHANGED
package/dist/wire.d.ts
CHANGED
|
@@ -88,6 +88,7 @@ export declare const subscriptionUsageResponseSchema: z.ZodObject<{
|
|
|
88
88
|
balance: z.ZodOptional<z.ZodString>;
|
|
89
89
|
}, z.core.$strip>>;
|
|
90
90
|
resetCredits: z.ZodOptional<z.ZodObject<{
|
|
91
|
+
observedAt: z.ZodNumber;
|
|
91
92
|
availableCount: z.ZodNumber;
|
|
92
93
|
credits: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
93
94
|
id: z.ZodString;
|
package/dist/wire.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velum-labs/routekit-accounts",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.16.
|
|
4
|
+
"version": "0.16.4",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/velum-labs/routekit.git",
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"yaml": "2.9.0",
|
|
30
30
|
"zod": "4.4.3",
|
|
31
|
-
"@velum-labs/routekit-contracts": "0.16.
|
|
32
|
-
"@velum-labs/routekit-gateway": "0.16.
|
|
33
|
-
"@velum-labs/routekit-registry": "0.16.
|
|
34
|
-
"@velum-labs/routekit-runtime": "0.16.
|
|
31
|
+
"@velum-labs/routekit-contracts": "0.16.4",
|
|
32
|
+
"@velum-labs/routekit-gateway": "0.16.4",
|
|
33
|
+
"@velum-labs/routekit-registry": "0.16.4",
|
|
34
|
+
"@velum-labs/routekit-runtime": "0.16.4"
|
|
35
35
|
},
|
|
36
36
|
"keywords": [
|
|
37
37
|
"routekit",
|