@capxul/sdk 4.20.0-beta.4 → 4.20.0-beta.40
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/README.md +282 -32
- package/dist/access-6VLRaqRP.mjs +26274 -0
- package/dist/index-DTc8HIBm.d.mts +40284 -0
- package/dist/index.d.mts +2 -653
- package/dist/index.mjs +121 -268
- package/dist/node/index.d.mts +9 -9
- package/dist/node/index.mjs +29 -38
- package/dist/organizations-DItJHTNM.mjs +12693 -0
- package/dist/permission-approvals-C2oLa3jf.mjs +138 -0
- package/dist/production-Dy8n4ZBY.mjs +5488 -0
- package/dist/testing/index.d.mts +32 -15
- package/dist/testing/index.mjs +315 -152
- package/package.json +51 -18
- package/dist/OAuthBearerAuthClient-C-ip-z8M.d.mts +0 -628
- package/dist/OAuthBearerAuthClient-DWkofxdZ.mjs +0 -2488
- package/dist/production-BMfmzwHq.mjs +0 -17131
- package/dist/production-CJwxgI8u.d.mts +0 -3141
package/dist/testing/index.mjs
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { Effect, Result, Semaphore } from "effect";
|
|
1
|
+
import { $r as toAccountId, Bi as InMemoryAuthCacheAdapter, Ci as toAddress, Ei as absurd, Fi as authClientPortFromPromiseAdapter, Gi as toEpochMs, Gn as ClockError, Ji as toEmail, Jn as readClockNow, Ki as toEpochSeconds, Kn as ClockPortTag, Lt as CONFIGURED_MONEY_ASSETS, M as wireChainId, Mi as toAllowedOrigin, Ni as toAppId, O as convexCallErrorFromCapxul, Ot as deriveCapxulSafeAddress, Pi as toPublishableKey, Q as assetAmountFromRaw, Sn as toHandle, Ti as toWeiAmount, Wi as toDurationMs, Xi as toSessionToken, Yi as toJwtToken, Zi as Errors, _ as accountFromAccountPositions, f as smartAccountErrorFromCapxul, na as CapxulError, nr as redactTelemetryEvent, qi as toAuthUserId, qn as clockFailure, rt as toRaw, vn as toCountryCode, w as accountReadErrorFromCapxul, wi as toChainId, wn as validateHandle, yn as toKycTier, zr as toOrgId } from "../access-6VLRaqRP.mjs";
|
|
2
|
+
import { a as cloneTelemetryEvent, c as identityErrorFromCapxul, l as bootstrapErrorFromCapxul, n as createCapxulClientWithSignerControls, o as cloneTelemetryProps, r as assembleCapxulClient, s as IdentityPortTag } from "../production-Dy8n4ZBY.mjs";
|
|
3
|
+
import { Effect, Layer, Result, Semaphore } from "effect";
|
|
5
4
|
import { getFunctionName } from "convex/server";
|
|
6
5
|
//#region src/testing/production-client.ts
|
|
7
6
|
/** Create the one production client with per-client controls at its signing boundaries. */
|
|
@@ -100,7 +99,123 @@ function armSignerFaults(plan = {}) {
|
|
|
100
99
|
};
|
|
101
100
|
}
|
|
102
101
|
//#endregion
|
|
102
|
+
//#region src/testing/organization-setup.ts
|
|
103
|
+
/** Exact backend setup shape for SDK tests. */
|
|
104
|
+
function organizationSetupFixture(orgId, status = "ready", overrides = {}) {
|
|
105
|
+
return {
|
|
106
|
+
orgId: toOrgId(orgId),
|
|
107
|
+
status,
|
|
108
|
+
completedAt: status === "ready" || status === "rechecking" ? 100 : null,
|
|
109
|
+
lastCheckedAt: null,
|
|
110
|
+
nextCheckAt: null,
|
|
111
|
+
issue: null,
|
|
112
|
+
lastSendIssue: null,
|
|
113
|
+
recovery: status === "awaitingAuthorization" ? "authorize" : "none",
|
|
114
|
+
...overrides
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/** In-memory setup transport for packed consumer tests. */
|
|
118
|
+
function createTestOrganizationSetup() {
|
|
119
|
+
const rows = /* @__PURE__ */ new Map();
|
|
120
|
+
const handles = /* @__PURE__ */ new Map();
|
|
121
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
122
|
+
const set = (setup) => {
|
|
123
|
+
rows.set(String(setup.orgId), setup);
|
|
124
|
+
for (const listener of listeners.get(String(setup.orgId)) ?? []) listener({
|
|
125
|
+
status: "value",
|
|
126
|
+
value: setup
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
const get = async (orgId) => {
|
|
130
|
+
const value = rows.get(String(orgId));
|
|
131
|
+
return value === void 0 ? {
|
|
132
|
+
ok: false,
|
|
133
|
+
error: Errors.invalidInput("orgId", "Organization setup was not found")
|
|
134
|
+
} : {
|
|
135
|
+
ok: true,
|
|
136
|
+
value
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
return {
|
|
140
|
+
set,
|
|
141
|
+
ops: {
|
|
142
|
+
listMine: async (input = {}) => {
|
|
143
|
+
const all = [...rows.values()].filter((row) => row.completedAt === null);
|
|
144
|
+
const offset = input.cursor === void 0 ? 0 : Number(input.cursor);
|
|
145
|
+
const limit = input.limit ?? 50;
|
|
146
|
+
if (!Number.isSafeInteger(offset) || offset < 0 || !Number.isSafeInteger(limit) || limit < 1 || limit > 50) return {
|
|
147
|
+
ok: false,
|
|
148
|
+
error: Errors.invalidInput("organizationSetupPage", "Invalid cursor or limit")
|
|
149
|
+
};
|
|
150
|
+
const page = all.slice(offset, offset + limit);
|
|
151
|
+
const next = offset + page.length;
|
|
152
|
+
return {
|
|
153
|
+
ok: true,
|
|
154
|
+
value: {
|
|
155
|
+
items: page,
|
|
156
|
+
nextCursor: next < all.length ? String(next) : null
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
},
|
|
160
|
+
begin: async (draft) => {
|
|
161
|
+
const orgId = handles.get(draft.handle) ?? toOrgId(`org_test_${draft.handle}`);
|
|
162
|
+
handles.set(draft.handle, orgId);
|
|
163
|
+
const setup = rows.get(String(orgId)) ?? organizationSetupFixture(orgId, "awaitingAuthorization");
|
|
164
|
+
set(setup);
|
|
165
|
+
return {
|
|
166
|
+
ok: true,
|
|
167
|
+
value: {
|
|
168
|
+
orgId,
|
|
169
|
+
setup
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
},
|
|
173
|
+
get,
|
|
174
|
+
resume: get,
|
|
175
|
+
authorize: async (orgId) => {
|
|
176
|
+
const current = await get(orgId);
|
|
177
|
+
if (!current.ok || current.value.status !== "awaitingAuthorization") return current;
|
|
178
|
+
const setup = organizationSetupFixture(orgId, "processing", { nextCheckAt: 1 });
|
|
179
|
+
set(setup);
|
|
180
|
+
return {
|
|
181
|
+
ok: true,
|
|
182
|
+
value: setup
|
|
183
|
+
};
|
|
184
|
+
},
|
|
185
|
+
subscribe: async (orgId, listener) => {
|
|
186
|
+
const key = String(orgId);
|
|
187
|
+
const group = listeners.get(key) ?? /* @__PURE__ */ new Set();
|
|
188
|
+
listeners.set(key, group);
|
|
189
|
+
group.add(listener);
|
|
190
|
+
listener({ status: "loading" });
|
|
191
|
+
const current = rows.get(key);
|
|
192
|
+
if (current !== void 0) listener({
|
|
193
|
+
status: "value",
|
|
194
|
+
value: current
|
|
195
|
+
});
|
|
196
|
+
let active = true;
|
|
197
|
+
return {
|
|
198
|
+
ok: true,
|
|
199
|
+
value: () => {
|
|
200
|
+
if (!active) return;
|
|
201
|
+
active = false;
|
|
202
|
+
group.delete(listener);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
},
|
|
206
|
+
invalidateSubscriptions: () => {
|
|
207
|
+
for (const group of listeners.values()) group.clear();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
//#endregion
|
|
103
213
|
//#region src/testing/account/InMemoryAccountReadAdapter.ts
|
|
214
|
+
function fixtureAsset(symbol) {
|
|
215
|
+
const asset = CONFIGURED_MONEY_ASSETS.find((row) => row.symbol === symbol);
|
|
216
|
+
if (asset === void 0) throw new Error(`no configured settlement asset ${symbol}`);
|
|
217
|
+
return asset;
|
|
218
|
+
}
|
|
104
219
|
var InMemoryAccountReadAdapter = class {
|
|
105
220
|
#deps;
|
|
106
221
|
#rawBalance;
|
|
@@ -108,26 +223,29 @@ var InMemoryAccountReadAdapter = class {
|
|
|
108
223
|
this.#deps = deps;
|
|
109
224
|
this.#rawBalance = deps.rawBalance ?? "0";
|
|
110
225
|
}
|
|
226
|
+
#asset() {
|
|
227
|
+
return fixtureAsset(this.#deps.assetSymbol ?? "USDC");
|
|
228
|
+
}
|
|
111
229
|
readBalance(_input) {
|
|
112
230
|
const scriptedFailure = this.#deps.failures?.readBalance;
|
|
113
231
|
if (scriptedFailure !== void 0) return Effect.fail(accountReadErrorFromCapxul("readBalance", scriptedFailure));
|
|
114
232
|
if (this.#deps.account !== void 0) return Effect.succeed(cloneAccount(this.#deps.account));
|
|
115
|
-
const
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
233
|
+
const asset = this.#asset();
|
|
234
|
+
const balance = assetAmountFromRaw(toWeiAmount(this.#rawBalance), asset).value;
|
|
235
|
+
return Effect.succeed(accountFromAccountPositions(toAccountId(this.#deps.accountId ?? "account_01TEST000000000000000000"), [{
|
|
236
|
+
asset: asset.assetId,
|
|
237
|
+
symbol: asset.symbol,
|
|
238
|
+
decimals: asset.decimals,
|
|
239
|
+
balance,
|
|
240
|
+
available: balance
|
|
241
|
+
}]));
|
|
123
242
|
}
|
|
124
243
|
fundFromFaucet(input) {
|
|
125
244
|
const scriptedFailure = this.#deps.failures?.fundFromFaucet;
|
|
126
245
|
if (scriptedFailure !== void 0) return Effect.fail(accountReadErrorFromCapxul("fundFromFaucet", scriptedFailure));
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const minted = BigInt(toWei(input.amount));
|
|
246
|
+
const asset = this.#asset();
|
|
247
|
+
if (input.amount.asset !== asset.assetId) return Effect.fail(accountReadErrorFromCapxul("fundFromFaucet", Errors.invalidInput("amount.asset", "must match adapter config")));
|
|
248
|
+
const minted = toRaw(input.amount, asset);
|
|
131
249
|
this.#rawBalance = (BigInt(this.#rawBalance) + minted).toString();
|
|
132
250
|
return Effect.succeed({ txHash: "0x" + "f".repeat(64) });
|
|
133
251
|
}
|
|
@@ -135,36 +253,68 @@ var InMemoryAccountReadAdapter = class {
|
|
|
135
253
|
function cloneAccount(account) {
|
|
136
254
|
return {
|
|
137
255
|
id: account.id,
|
|
138
|
-
|
|
139
|
-
|
|
256
|
+
balances: account.balances.map((position) => ({ ...position })),
|
|
257
|
+
balance: account.balance === null ? null : { ...account.balance },
|
|
258
|
+
available: account.available === null ? null : { ...account.available }
|
|
140
259
|
};
|
|
141
260
|
}
|
|
142
261
|
//#endregion
|
|
143
|
-
//#region src/testing/clock/
|
|
144
|
-
|
|
145
|
-
* Event-discipline test clock. Time moves only when a release primitive is
|
|
146
|
-
* invoked. `tickOne()` releases EXACTLY ONE pending sleep (the earliest
|
|
147
|
-
* deadline, FIFO at ties) and snaps `now()` to that deadline. `advance(ms)`
|
|
148
|
-
* composes repeated `tickOne()` calls over the resulting window — used to
|
|
149
|
-
* keep the cross-adapter conformance harness uniform with the distance model
|
|
150
|
-
* of `VirtualClockAdapter`. See `packages/sdk/docs/architecture.md` and the
|
|
151
|
-
* slice spec `docs/slices/clock-adapters.md`.
|
|
152
|
-
*/
|
|
153
|
-
var ManualClockAdapter = class ManualClockAdapter {
|
|
262
|
+
//#region src/testing/clock/sleep-queue.ts
|
|
263
|
+
var SleepQueueClock = class {
|
|
154
264
|
currentTime = 0;
|
|
155
|
-
|
|
265
|
+
pending = [];
|
|
156
266
|
seqCounter = 0;
|
|
157
|
-
constructor(deps) {}
|
|
158
267
|
now = Effect.sync(() => toEpochMs(this.currentTime));
|
|
159
268
|
sleep(duration) {
|
|
160
269
|
return Effect.promise(() => new Promise((resolve) => {
|
|
161
|
-
this.
|
|
270
|
+
this.enqueue({
|
|
162
271
|
deadline: this.currentTime + duration,
|
|
163
272
|
resolve,
|
|
164
273
|
seq: this.seqCounter++
|
|
165
274
|
});
|
|
166
275
|
}));
|
|
167
276
|
}
|
|
277
|
+
/** Move time forward by `ms`, releasing every sleep that falls due. */
|
|
278
|
+
advance(ms) {
|
|
279
|
+
return Effect.runPromise(this.advanceEffect(toDurationMs(ms)));
|
|
280
|
+
}
|
|
281
|
+
/** This clock as the cross-adapter conformance harness drives it. */
|
|
282
|
+
underTest() {
|
|
283
|
+
return {
|
|
284
|
+
clock: this,
|
|
285
|
+
advance: (ms) => this.advance(ms)
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
enqueue(entry) {
|
|
289
|
+
this.pending.push(entry);
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
/** The clock in context when it is a `kind`; otherwise a ClockError naming the layer `helper` needs. */
|
|
293
|
+
function requireClock(kind, helper, layer) {
|
|
294
|
+
return Effect.gen(function* () {
|
|
295
|
+
const clock = yield* ClockPortTag;
|
|
296
|
+
if (clock instanceof kind) return clock;
|
|
297
|
+
return yield* new ClockError({
|
|
298
|
+
operation: "sleep",
|
|
299
|
+
cause: /* @__PURE__ */ new Error(`${helper} requires ${layer}`)
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
//#endregion
|
|
304
|
+
//#region src/testing/clock/ManualClockAdapter.ts
|
|
305
|
+
/**
|
|
306
|
+
* Event-discipline test clock. Time moves only when a release primitive is
|
|
307
|
+
* invoked. `tickOne()` releases EXACTLY ONE pending sleep (the earliest
|
|
308
|
+
* deadline, FIFO at ties) and snaps `now()` to that deadline. `advance(ms)`
|
|
309
|
+
* composes repeated `tickOne()` calls over the resulting window — used to
|
|
310
|
+
* keep the cross-adapter conformance harness uniform with the distance model
|
|
311
|
+
* of `VirtualClockAdapter`. See `packages/sdk/docs/architecture.md` and the
|
|
312
|
+
* slice spec `docs/slices/clock-adapters.md`.
|
|
313
|
+
*/
|
|
314
|
+
var ManualClockAdapter = class ManualClockAdapter extends SleepQueueClock {
|
|
315
|
+
constructor(deps) {
|
|
316
|
+
super();
|
|
317
|
+
}
|
|
168
318
|
/**
|
|
169
319
|
* Release exactly one pending sleep — the earliest deadline, FIFO at ties.
|
|
170
320
|
* Snaps `now()` forward to that sleep's deadline. No-op if the queue is
|
|
@@ -174,39 +324,33 @@ var ManualClockAdapter = class ManualClockAdapter {
|
|
|
174
324
|
return Effect.runPromise(this.tickOneEffect());
|
|
175
325
|
}
|
|
176
326
|
tickOneEffect() {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const next = self.queue.splice(nextIndex, 1)[0];
|
|
182
|
-
if (next.deadline > self.currentTime) self.currentTime = next.deadline;
|
|
327
|
+
return Effect.gen({ self: this }, function* () {
|
|
328
|
+
if (this.pending.length === 0) return;
|
|
329
|
+
const next = this.pending.splice(this.findEarliestIndex(), 1)[0];
|
|
330
|
+
if (next.deadline > this.currentTime) this.currentTime = next.deadline;
|
|
183
331
|
next.resolve();
|
|
184
332
|
yield* Effect.yieldNow;
|
|
185
333
|
});
|
|
186
334
|
}
|
|
187
335
|
/**
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
336
|
+
* Releases every sleep whose deadline falls inside
|
|
337
|
+
* `[currentTime, currentTime + ms]` via repeated `tickOne()`, then snaps
|
|
338
|
+
* `now()` to `currentTime + ms`. The composition is the seam that lets the
|
|
339
|
+
* harness drive Manual and Virtual identically.
|
|
192
340
|
*/
|
|
193
|
-
advance(ms) {
|
|
194
|
-
return Effect.runPromise(this.advanceEffect(toDurationMs(ms)));
|
|
195
|
-
}
|
|
196
341
|
advanceEffect(duration) {
|
|
197
|
-
|
|
198
|
-
return Effect.gen(function* () {
|
|
342
|
+
return Effect.gen({ self: this }, function* () {
|
|
199
343
|
yield* Effect.yieldNow;
|
|
200
|
-
const target =
|
|
201
|
-
while (
|
|
202
|
-
|
|
344
|
+
const target = this.currentTime + duration;
|
|
345
|
+
while (this.pending.length > 0 && this.earliestDeadline() <= target) yield* this.tickOneEffect();
|
|
346
|
+
this.currentTime = target;
|
|
203
347
|
});
|
|
204
348
|
}
|
|
205
349
|
findEarliestIndex() {
|
|
206
350
|
let bestIndex = 0;
|
|
207
|
-
let best = this.
|
|
208
|
-
for (let i = 1; i < this.
|
|
209
|
-
const candidate = this.
|
|
351
|
+
let best = this.pending[0];
|
|
352
|
+
for (let i = 1; i < this.pending.length; i++) {
|
|
353
|
+
const candidate = this.pending[i];
|
|
210
354
|
if (candidate.deadline < best.deadline || candidate.deadline === best.deadline && candidate.seq < best.seq) {
|
|
211
355
|
best = candidate;
|
|
212
356
|
bestIndex = i;
|
|
@@ -215,21 +359,14 @@ var ManualClockAdapter = class ManualClockAdapter {
|
|
|
215
359
|
return bestIndex;
|
|
216
360
|
}
|
|
217
361
|
earliestDeadline() {
|
|
218
|
-
return this.
|
|
362
|
+
return this.pending[this.findEarliestIndex()].deadline;
|
|
219
363
|
}
|
|
220
364
|
static forConformance() {
|
|
221
|
-
return new
|
|
222
|
-
}
|
|
223
|
-
};
|
|
224
|
-
var ManualClockUnderTest = class {
|
|
225
|
-
clock;
|
|
226
|
-
constructor(clock) {
|
|
227
|
-
this.clock = clock;
|
|
228
|
-
}
|
|
229
|
-
advance(ms) {
|
|
230
|
-
return this.clock.advance(ms);
|
|
365
|
+
return new ManualClockAdapter({}).underTest();
|
|
231
366
|
}
|
|
232
367
|
};
|
|
368
|
+
Layer.sync(ClockPortTag, () => new ManualClockAdapter({}));
|
|
369
|
+
requireClock(ManualClockAdapter, "tickManualClockOnce", "ManualClockLayer").pipe(Effect.flatMap((clock) => clock.tickOneEffect()));
|
|
233
370
|
//#endregion
|
|
234
371
|
//#region src/testing/auth-client/InMemoryAuthClientAdapter.ts
|
|
235
372
|
const DEFAULT_OTP_TTL_MS = toDurationMs(3e5);
|
|
@@ -471,7 +608,12 @@ async function runAuthCacheEffect(effect, operation) {
|
|
|
471
608
|
const result = await Effect.runPromise(Effect.result(effect));
|
|
472
609
|
if (Result.isFailure(result)) return {
|
|
473
610
|
ok: false,
|
|
474
|
-
error: Errors.
|
|
611
|
+
error: Errors.providerUnavailable({
|
|
612
|
+
provider: "auth-cache",
|
|
613
|
+
operation,
|
|
614
|
+
mode: "unknown",
|
|
615
|
+
cause: result.failure
|
|
616
|
+
})
|
|
475
617
|
};
|
|
476
618
|
return {
|
|
477
619
|
ok: true,
|
|
@@ -509,6 +651,47 @@ function fail$2(kind, error) {
|
|
|
509
651
|
return Effect.fail(bootstrapErrorFromCapxul(kind, error));
|
|
510
652
|
}
|
|
511
653
|
//#endregion
|
|
654
|
+
//#region src/testing/convex-call/subscribers.ts
|
|
655
|
+
/**
|
|
656
|
+
* Register `subscriber` under `path` and return its unsubscribe, which removes
|
|
657
|
+
* it once (a second call does nothing) and drops a path left with no one.
|
|
658
|
+
*/
|
|
659
|
+
function subscribeAt(subscribers, path, subscriber) {
|
|
660
|
+
let subs = subscribers.get(path);
|
|
661
|
+
if (subs === void 0) {
|
|
662
|
+
subs = /* @__PURE__ */ new Set();
|
|
663
|
+
subscribers.set(path, subs);
|
|
664
|
+
}
|
|
665
|
+
subs.add(subscriber);
|
|
666
|
+
let active = true;
|
|
667
|
+
return () => {
|
|
668
|
+
if (!active) return;
|
|
669
|
+
active = false;
|
|
670
|
+
const live = subscribers.get(path);
|
|
671
|
+
if (live === void 0) return;
|
|
672
|
+
live.delete(subscriber);
|
|
673
|
+
if (live.size === 0) subscribers.delete(path);
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
//#endregion
|
|
677
|
+
//#region src/testing/convex-call/wire-reply.ts
|
|
678
|
+
/**
|
|
679
|
+
* The Convex wire, as the in-memory test doubles stand in for it. A scripted or
|
|
680
|
+
* registered value crosses where a real reply would, and the caller's function
|
|
681
|
+
* reference names its type, exactly as `ConvexClient` types a reply it cannot
|
|
682
|
+
* check. These are the only places the doubles make that claim.
|
|
683
|
+
*/
|
|
684
|
+
function wireReply(_fn, value) {
|
|
685
|
+
return value;
|
|
686
|
+
}
|
|
687
|
+
/** A scripted snapshot for a subscription on `fn`: only an `ok` value carries the reply type. */
|
|
688
|
+
function wireSnapshot(fn, snapshot) {
|
|
689
|
+
return snapshot.status === "ok" ? {
|
|
690
|
+
status: "ok",
|
|
691
|
+
value: wireReply(fn, snapshot.value)
|
|
692
|
+
} : snapshot;
|
|
693
|
+
}
|
|
694
|
+
//#endregion
|
|
512
695
|
//#region src/testing/convex-call/ConvexCallStubAdapter.ts
|
|
513
696
|
var ConvexCallStubAdapter = class {
|
|
514
697
|
#script;
|
|
@@ -517,43 +700,30 @@ var ConvexCallStubAdapter = class {
|
|
|
517
700
|
this.#script = deps.script;
|
|
518
701
|
}
|
|
519
702
|
query(fn, _args) {
|
|
520
|
-
return this.#resolve(
|
|
703
|
+
return this.#resolve(fn);
|
|
521
704
|
}
|
|
522
705
|
mutation(fn, _args) {
|
|
523
|
-
return this.#resolve(
|
|
706
|
+
return this.#resolve(fn);
|
|
524
707
|
}
|
|
525
708
|
action(fn, _args) {
|
|
526
|
-
return this.#resolve(
|
|
709
|
+
return this.#resolve(fn);
|
|
527
710
|
}
|
|
528
711
|
subscribe(fn, _args, callback) {
|
|
529
712
|
return Effect.sync(() => {
|
|
530
713
|
const path = getFunctionName(fn);
|
|
531
|
-
const cb = callback;
|
|
714
|
+
const cb = (snapshot) => callback(wireSnapshot(fn, snapshot));
|
|
532
715
|
cb({ status: "loading" });
|
|
533
716
|
const sequence = this.#script.subscriptions?.get(path);
|
|
534
717
|
const subscriber = {
|
|
535
718
|
callback: cb,
|
|
536
719
|
cursor: 0
|
|
537
720
|
};
|
|
538
|
-
|
|
539
|
-
if (subs === void 0) {
|
|
540
|
-
subs = /* @__PURE__ */ new Set();
|
|
541
|
-
this.#subscribers.set(path, subs);
|
|
542
|
-
}
|
|
543
|
-
subs.add(subscriber);
|
|
721
|
+
const unsubscribe = subscribeAt(this.#subscribers, path, subscriber);
|
|
544
722
|
if (sequence !== void 0 && sequence.length > 0) {
|
|
545
723
|
cb(sequence[0]);
|
|
546
724
|
subscriber.cursor = 1;
|
|
547
725
|
}
|
|
548
|
-
|
|
549
|
-
return () => {
|
|
550
|
-
if (!active) return;
|
|
551
|
-
active = false;
|
|
552
|
-
const liveSubs = this.#subscribers.get(path);
|
|
553
|
-
if (liveSubs === void 0) return;
|
|
554
|
-
liveSubs.delete(subscriber);
|
|
555
|
-
if (liveSubs.size === 0) this.#subscribers.delete(path);
|
|
556
|
-
};
|
|
726
|
+
return unsubscribe;
|
|
557
727
|
});
|
|
558
728
|
}
|
|
559
729
|
advanceSubscription(path) {
|
|
@@ -568,17 +738,26 @@ var ConvexCallStubAdapter = class {
|
|
|
568
738
|
sub.callback(next);
|
|
569
739
|
}
|
|
570
740
|
}
|
|
571
|
-
#resolve(
|
|
741
|
+
#resolve(fn) {
|
|
742
|
+
const path = getFunctionName(fn);
|
|
572
743
|
const failure = this.#script.failures?.get(path);
|
|
573
744
|
if (failure !== void 0) return Effect.fail(mapFailure(path, failure));
|
|
574
|
-
if (this.#script.responses?.has(path)) return Effect.succeed(this.#script.responses.get(path));
|
|
575
|
-
return Effect.fail(convexCallErrorFromCapxul(path, Errors.
|
|
745
|
+
if (this.#script.responses?.has(path)) return Effect.succeed(wireReply(fn, this.#script.responses.get(path)));
|
|
746
|
+
return Effect.fail(convexCallErrorFromCapxul(path, Errors.providerUnavailable({
|
|
747
|
+
provider: "convex",
|
|
748
|
+
operation: path,
|
|
749
|
+
mode: "unknown"
|
|
750
|
+
})));
|
|
576
751
|
}
|
|
577
752
|
};
|
|
578
753
|
function mapFailure(operation, failure) {
|
|
579
754
|
if (failure instanceof CapxulError) return convexCallErrorFromCapxul(operation, failure);
|
|
580
|
-
|
|
581
|
-
|
|
755
|
+
return convexCallErrorFromCapxul(operation, Errors.providerUnavailable({
|
|
756
|
+
provider: "convex",
|
|
757
|
+
operation,
|
|
758
|
+
mode: "unknown",
|
|
759
|
+
cause: failure
|
|
760
|
+
}));
|
|
582
761
|
}
|
|
583
762
|
//#endregion
|
|
584
763
|
//#region src/testing/identity/InMemoryIdentityAdapter.ts
|
|
@@ -587,15 +766,15 @@ var InMemoryIdentityAdapter = class {
|
|
|
587
766
|
#clock;
|
|
588
767
|
constructor(deps) {
|
|
589
768
|
this.#clock = deps.clock;
|
|
769
|
+
for (const [authUserId, profile] of deps.profiles ?? []) this.#identities.set(authUserId, cloneProfile(profile));
|
|
590
770
|
}
|
|
591
771
|
loadByAuthUserId(authUserId) {
|
|
592
772
|
return Effect.sync(() => cloneProfileOrNull(this.#identities.get(authUserId) ?? null));
|
|
593
773
|
}
|
|
594
774
|
create(input) {
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
const now = yield* self.#now("create.clock");
|
|
775
|
+
return Effect.gen({ self: this }, function* () {
|
|
776
|
+
if (this.#identities.has(input.authUserId)) return yield* fail$1("create", Errors.invalidInput("authUserId", "already exists"));
|
|
777
|
+
const now = yield* this.#now("create.clock");
|
|
599
778
|
const profile = {
|
|
600
779
|
authUserId: input.authUserId,
|
|
601
780
|
email: input.email,
|
|
@@ -609,16 +788,15 @@ var InMemoryIdentityAdapter = class {
|
|
|
609
788
|
createdAt: now,
|
|
610
789
|
updatedAt: now
|
|
611
790
|
};
|
|
612
|
-
|
|
791
|
+
this.#identities.set(input.authUserId, cloneProfile(profile));
|
|
613
792
|
return cloneProfile(profile);
|
|
614
793
|
});
|
|
615
794
|
}
|
|
616
795
|
update(input) {
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
const existing = self.#identities.get(input.authUserId);
|
|
796
|
+
return Effect.gen({ self: this }, function* () {
|
|
797
|
+
const existing = this.#identities.get(input.authUserId);
|
|
620
798
|
if (existing === void 0) return yield* fail$1("update", Errors.profileNotFound(input.authUserId));
|
|
621
|
-
const now = yield*
|
|
799
|
+
const now = yield* this.#now("update.clock");
|
|
622
800
|
const updated = {
|
|
623
801
|
authUserId: existing.authUserId,
|
|
624
802
|
email: existing.email,
|
|
@@ -632,15 +810,14 @@ var InMemoryIdentityAdapter = class {
|
|
|
632
810
|
imageUrl: existing.imageUrl,
|
|
633
811
|
updatedAt: now
|
|
634
812
|
};
|
|
635
|
-
|
|
813
|
+
this.#identities.set(input.authUserId, cloneProfile(updated));
|
|
636
814
|
return cloneProfile(updated);
|
|
637
815
|
});
|
|
638
816
|
}
|
|
639
817
|
completeOnboarding(input) {
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
const
|
|
643
|
-
const existing = self.#identities.get(input.authUserId);
|
|
818
|
+
return Effect.gen({ self: this }, function* () {
|
|
819
|
+
const now = yield* this.#now("completeOnboarding.clock");
|
|
820
|
+
const existing = this.#identities.get(input.authUserId);
|
|
644
821
|
const profile = {
|
|
645
822
|
authUserId: input.authUserId,
|
|
646
823
|
email: existing?.email ?? input.email,
|
|
@@ -654,14 +831,16 @@ var InMemoryIdentityAdapter = class {
|
|
|
654
831
|
createdAt: existing?.createdAt ?? now,
|
|
655
832
|
updatedAt: now
|
|
656
833
|
};
|
|
657
|
-
|
|
834
|
+
this.#identities.set(input.authUserId, cloneProfile(profile));
|
|
658
835
|
return cloneProfile(profile);
|
|
659
836
|
});
|
|
660
837
|
}
|
|
661
838
|
#now(operation) {
|
|
662
|
-
|
|
839
|
+
if (this.#clock === void 0) return Effect.succeed(toEpochMs(0));
|
|
840
|
+
return this.#clock.now.pipe(Effect.mapError((cause) => identityErrorFromCapxul(operation, clockFailure(cause), cause)));
|
|
663
841
|
}
|
|
664
842
|
};
|
|
843
|
+
Layer.effect(IdentityPortTag, Effect.map(ClockPortTag, (clock) => new InMemoryIdentityAdapter({ clock })));
|
|
665
844
|
function fail$1(operation, error) {
|
|
666
845
|
return Effect.fail(identityErrorFromCapxul(operation, error));
|
|
667
846
|
}
|
|
@@ -686,8 +865,6 @@ function cloneProfileOrNull(profile) {
|
|
|
686
865
|
//#endregion
|
|
687
866
|
//#region src/testing/smart-account/smart-account-derivation.ts
|
|
688
867
|
const SUPPORTED_CHAIN_IDS = /* @__PURE__ */ new Set([84532, 8453]);
|
|
689
|
-
toAddress("0xa6b71e26c5e0845f74c812102ca7114b6a896ab2");
|
|
690
|
-
keccak256("0x");
|
|
691
868
|
function isSupportedSmartAccountChain(chainId) {
|
|
692
869
|
return SUPPORTED_CHAIN_IDS.has(wireChainId(chainId));
|
|
693
870
|
}
|
|
@@ -708,6 +885,7 @@ var InMemorySmartAccountAdapter = class {
|
|
|
708
885
|
constructor(deps) {
|
|
709
886
|
this.#clock = deps.clock;
|
|
710
887
|
this.#failures = deps.failures;
|
|
888
|
+
for (const [authUserId, account] of deps.accounts ?? []) this.#accounts.set(authUserId, cloneSmartAccount(account));
|
|
711
889
|
}
|
|
712
890
|
loadByAuthUserId(authUserId) {
|
|
713
891
|
const scriptedFailure = this.#failures?.loadByAuthUserId;
|
|
@@ -726,12 +904,11 @@ var InMemorySmartAccountAdapter = class {
|
|
|
726
904
|
provision(input) {
|
|
727
905
|
const scriptedFailure = this.#failures?.provision;
|
|
728
906
|
if (scriptedFailure !== void 0) return fail("provision", scriptedFailure);
|
|
729
|
-
|
|
730
|
-
return Effect.gen(function* () {
|
|
907
|
+
return Effect.gen({ self: this }, function* () {
|
|
731
908
|
if (!isSupportedSmartAccountChain(input.chainId)) return yield* fail("provision", Errors.invalidInput("chainId", `unsupported chainId ${String(input.chainId)}`));
|
|
732
|
-
const existing =
|
|
909
|
+
const existing = this.#accounts.get(input.authUserId);
|
|
733
910
|
if (existing !== void 0) return cloneSmartAccount(existing);
|
|
734
|
-
const now = yield*
|
|
911
|
+
const now = yield* this.#now("provision.clock");
|
|
735
912
|
const account = {
|
|
736
913
|
authUserId: input.authUserId,
|
|
737
914
|
signerAddress: null,
|
|
@@ -741,9 +918,9 @@ var InMemorySmartAccountAdapter = class {
|
|
|
741
918
|
claimedAt: null,
|
|
742
919
|
createdAt: now
|
|
743
920
|
};
|
|
744
|
-
|
|
921
|
+
this.#accounts.set(input.authUserId, cloneSmartAccount(account));
|
|
745
922
|
return cloneSmartAccount(account);
|
|
746
|
-
}).pipe(
|
|
923
|
+
}).pipe(this.#provisionMutex.withPermits(1));
|
|
747
924
|
}
|
|
748
925
|
promoteDeployedAt(authUserId, deployedAt) {
|
|
749
926
|
const existing = this.#accounts.get(authUserId);
|
|
@@ -755,27 +932,27 @@ var InMemorySmartAccountAdapter = class {
|
|
|
755
932
|
});
|
|
756
933
|
}
|
|
757
934
|
confirmDeployment(input) {
|
|
758
|
-
const
|
|
759
|
-
return
|
|
760
|
-
|
|
935
|
+
const scriptedFailure = this.#failures?.confirmDeployment;
|
|
936
|
+
if (scriptedFailure !== void 0) return fail("confirmDeployment", scriptedFailure);
|
|
937
|
+
return Effect.gen({ self: this }, function* () {
|
|
938
|
+
const existing = this.#accounts.get(input.authUserId);
|
|
761
939
|
if (existing === void 0) return yield* fail("confirmDeployment", Errors.profileNotFound(String(input.authUserId)));
|
|
762
940
|
if (existing.smartAccountAddress !== toAddress(input.safeAddress)) return yield* fail("confirmDeployment", Errors.invalidInput("safeAddress", "does not match backend smart-account row"));
|
|
763
941
|
if (existing.deployedAt !== null) return cloneSmartAccount(existing);
|
|
764
|
-
const now = yield*
|
|
942
|
+
const now = yield* this.#now("confirmDeployment.clock");
|
|
765
943
|
const updated = {
|
|
766
944
|
...existing,
|
|
767
945
|
deployedAt: now
|
|
768
946
|
};
|
|
769
|
-
|
|
947
|
+
this.#accounts.set(input.authUserId, cloneSmartAccount(updated));
|
|
770
948
|
return cloneSmartAccount(updated);
|
|
771
|
-
}).pipe(
|
|
949
|
+
}).pipe(this.#provisionMutex.withPermits(1));
|
|
772
950
|
}
|
|
773
951
|
claim(input) {
|
|
774
952
|
const scriptedFailure = this.#failures?.claim;
|
|
775
953
|
if (scriptedFailure !== void 0) return fail("claim", scriptedFailure);
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
const existing = self.#accounts.get(input.authUserId);
|
|
954
|
+
return Effect.gen({ self: this }, function* () {
|
|
955
|
+
const existing = this.#accounts.get(input.authUserId);
|
|
779
956
|
if (existing === void 0) return yield* fail("claim", Errors.invalidInput("account", "no provisioned smart account for this user"));
|
|
780
957
|
if (existing.chainId !== input.chainId) return yield* fail("claim", Errors.invalidInput("chainId", "does not match backend smart-account row"));
|
|
781
958
|
const newOwner = toAddress(input.signerAddress);
|
|
@@ -783,19 +960,20 @@ var InMemorySmartAccountAdapter = class {
|
|
|
783
960
|
if (existing.signerAddress !== newOwner) return yield* fail("claim", Errors.invalidInput("signerAddress", "account already claimed by a different signer"));
|
|
784
961
|
return cloneSmartAccount(existing);
|
|
785
962
|
}
|
|
786
|
-
const now = yield*
|
|
963
|
+
const now = yield* this.#now("claim.clock");
|
|
787
964
|
const updated = {
|
|
788
965
|
...existing,
|
|
789
966
|
signerAddress: newOwner,
|
|
790
967
|
claimedAt: now,
|
|
791
968
|
deployedAt: existing.deployedAt ?? now
|
|
792
969
|
};
|
|
793
|
-
|
|
970
|
+
this.#accounts.set(input.authUserId, cloneSmartAccount(updated));
|
|
794
971
|
return cloneSmartAccount(updated);
|
|
795
|
-
}).pipe(
|
|
972
|
+
}).pipe(this.#provisionMutex.withPermits(1));
|
|
796
973
|
}
|
|
797
974
|
#now(operation) {
|
|
798
|
-
|
|
975
|
+
if (this.#clock === void 0) return Effect.succeed(toEpochMs(0));
|
|
976
|
+
return this.#clock.now.pipe(Effect.mapError((cause) => smartAccountErrorFromCapxul(operation, clockFailure(cause), cause)));
|
|
799
977
|
}
|
|
800
978
|
};
|
|
801
979
|
function fail(operation, error) {
|
|
@@ -825,7 +1003,7 @@ var RecordingTelemetryAdapter = class {
|
|
|
825
1003
|
this.#rawMode = options.rawMode === true;
|
|
826
1004
|
}
|
|
827
1005
|
get events() {
|
|
828
|
-
return this.#events.map(
|
|
1006
|
+
return this.#events.map(cloneTelemetryEvent);
|
|
829
1007
|
}
|
|
830
1008
|
get operations() {
|
|
831
1009
|
return this.#operations.map(cloneOperation);
|
|
@@ -836,7 +1014,7 @@ var RecordingTelemetryAdapter = class {
|
|
|
836
1014
|
this.#events.push(recorded);
|
|
837
1015
|
this.#operations.push({
|
|
838
1016
|
type: "emit",
|
|
839
|
-
event:
|
|
1017
|
+
event: cloneTelemetryEvent(recorded)
|
|
840
1018
|
});
|
|
841
1019
|
});
|
|
842
1020
|
}
|
|
@@ -866,17 +1044,11 @@ var RecordingTelemetryAdapter = class {
|
|
|
866
1044
|
this.#operations.length = 0;
|
|
867
1045
|
}
|
|
868
1046
|
};
|
|
869
|
-
function cloneEvent(event) {
|
|
870
|
-
return event.props === void 0 ? { name: event.name } : {
|
|
871
|
-
name: event.name,
|
|
872
|
-
props: cloneProps(event.props)
|
|
873
|
-
};
|
|
874
|
-
}
|
|
875
1047
|
function cloneOperation(operation) {
|
|
876
1048
|
switch (operation.type) {
|
|
877
1049
|
case "emit": return {
|
|
878
1050
|
type: "emit",
|
|
879
|
-
event:
|
|
1051
|
+
event: cloneTelemetryEvent(operation.event)
|
|
880
1052
|
};
|
|
881
1053
|
case "identify": return {
|
|
882
1054
|
type: "identify",
|
|
@@ -887,14 +1059,15 @@ function cloneOperation(operation) {
|
|
|
887
1059
|
input: cloneGroupInput(operation.input)
|
|
888
1060
|
};
|
|
889
1061
|
case "reset": return { type: "reset" };
|
|
1062
|
+
default: return absurd(operation);
|
|
890
1063
|
}
|
|
891
1064
|
}
|
|
892
1065
|
function cloneIdentifyInput(input) {
|
|
893
1066
|
return {
|
|
894
1067
|
distinctId: input.distinctId,
|
|
895
1068
|
...input.anonDistinctId === void 0 ? {} : { anonDistinctId: input.anonDistinctId },
|
|
896
|
-
...input.traits === void 0 ? {} : { traits:
|
|
897
|
-
...input.properties === void 0 ? {} : { properties:
|
|
1069
|
+
...input.traits === void 0 ? {} : { traits: cloneTelemetryProps(input.traits) },
|
|
1070
|
+
...input.properties === void 0 ? {} : { properties: cloneTelemetryProps(input.properties) }
|
|
898
1071
|
};
|
|
899
1072
|
}
|
|
900
1073
|
function cloneGroupInput(input) {
|
|
@@ -904,22 +1077,9 @@ function cloneGroupInput(input) {
|
|
|
904
1077
|
} : {
|
|
905
1078
|
groupType: input.groupType,
|
|
906
1079
|
groupKey: input.groupKey,
|
|
907
|
-
properties:
|
|
1080
|
+
properties: cloneTelemetryProps(input.properties)
|
|
908
1081
|
};
|
|
909
1082
|
}
|
|
910
|
-
function cloneProps(props) {
|
|
911
|
-
const cloned = {};
|
|
912
|
-
for (const [key, value] of Object.entries(props)) cloned[key] = cloneTelemetryValue(value);
|
|
913
|
-
return cloned;
|
|
914
|
-
}
|
|
915
|
-
function cloneTelemetryValue(value) {
|
|
916
|
-
if (Array.isArray(value)) return value.map(cloneTelemetryValue);
|
|
917
|
-
if (value === null || typeof value !== "object") return value;
|
|
918
|
-
if (Object.getPrototypeOf(value) !== Object.prototype) return value;
|
|
919
|
-
const cloned = {};
|
|
920
|
-
for (const [key, nested] of Object.entries(value)) cloned[key] = cloneTelemetryValue(nested);
|
|
921
|
-
return cloned;
|
|
922
|
-
}
|
|
923
1083
|
//#endregion
|
|
924
1084
|
//#region src/testing/index.ts
|
|
925
1085
|
const TEST_BOOTSTRAP = {
|
|
@@ -947,6 +1107,7 @@ function createCapxulTestClient(options = {}) {
|
|
|
947
1107
|
});
|
|
948
1108
|
const identity = new InMemoryIdentityAdapter({ clock });
|
|
949
1109
|
const telemetry = new RecordingTelemetryAdapter();
|
|
1110
|
+
const organizationSetup = createTestOrganizationSetup();
|
|
950
1111
|
const ports = {
|
|
951
1112
|
authClient: authClientPortFromPromiseAdapter(authClient),
|
|
952
1113
|
authCache,
|
|
@@ -963,6 +1124,7 @@ function createCapxulTestClient(options = {}) {
|
|
|
963
1124
|
bootstrap: TEST_BOOTSTRAP,
|
|
964
1125
|
authCache,
|
|
965
1126
|
requirement: options.requirement ?? "none",
|
|
1127
|
+
organizationSetup: organizationSetup.ops,
|
|
966
1128
|
...options.failureObservation === void 0 ? {} : { failureObservation: options.failureObservation }
|
|
967
1129
|
});
|
|
968
1130
|
const transitions = [];
|
|
@@ -986,6 +1148,7 @@ function createCapxulTestClient(options = {}) {
|
|
|
986
1148
|
return telemetry.operations;
|
|
987
1149
|
},
|
|
988
1150
|
peekOtp: (email) => authClient.peekOtpForTesting(toEmail(email)),
|
|
1151
|
+
organizationSetup: { set: organizationSetup.set },
|
|
989
1152
|
seedIdentity: async (input) => {
|
|
990
1153
|
const authUserId = toAuthUserId(input.authUserId);
|
|
991
1154
|
const email = toEmail(input.email);
|
|
@@ -1006,4 +1169,4 @@ function createCapxulTestClient(options = {}) {
|
|
|
1006
1169
|
};
|
|
1007
1170
|
}
|
|
1008
1171
|
//#endregion
|
|
1009
|
-
export { ControlledSignerFault, armSignerFaults, createCapxulProductionTestClient, createCapxulTestClient, signerFaultCause };
|
|
1172
|
+
export { ControlledSignerFault, armSignerFaults, createCapxulProductionTestClient, createCapxulTestClient, organizationSetupFixture, signerFaultCause };
|