@capxul/sdk 4.20.0-beta.3 → 4.20.0-beta.31
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 +252 -33
- package/dist/access-B-2qmxF_.mjs +23227 -0
- package/dist/index-DidkO1aY.d.mts +34071 -0
- package/dist/index.d.mts +2 -653
- package/dist/index.mjs +84 -235
- package/dist/node/index.d.mts +9 -9
- package/dist/node/index.mjs +29 -38
- package/dist/organizations-wucUfgJr.mjs +12546 -0
- package/dist/permission-approvals-BLI_uadD.mjs +138 -0
- package/dist/production-2L76NWS5.mjs +4882 -0
- package/dist/testing/index.d.mts +32 -15
- package/dist/testing/index.mjs +296 -146
- package/package.json +48 -15
- package/dist/OAuthBearerAuthClient-C-ip-z8M.d.mts +0 -628
- package/dist/OAuthBearerAuthClient-DWkofxdZ.mjs +0 -2488
- package/dist/production-CJwxgI8u.d.mts +0 -3141
- package/dist/production-M4dWz5g4.mjs +0 -17131
package/dist/testing/index.mjs
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { D as
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { Effect, Result, Semaphore } from "effect";
|
|
1
|
+
import { Ai as toEpochMs, D as convexCallErrorFromCapxul, Et as CONFIGURED_MONEY_ASSETS, Fi as toSessionToken, Fr as absurd, Ii as Errors, Li as CapxulError, Mi as toAuthUserId, Mr as toAddress, Ni as toEmail, Nr as toChainId, Ot as deriveCapxulSafeAddress, Pi as toJwtToken, Pr as toWeiAmount, Qn as toOrgId, Ti as InMemoryAuthCacheAdapter, Z as assetAmountFromRaw, _ as accountFromAccountPositions, _i as toAllowedOrigin, ai as ClockPortTag, bi as authClientPortFromPromiseAdapter, f as smartAccountErrorFromCapxul, fn as toCountryCode, gn as toHandle, ii as ClockError, j as wireChainId, ji as toEpochSeconds, ki as toDurationMs, nt as toRaw, oi as readClockNow, pn as toKycTier, ur as toAccountId, vi as toAppId, vn as validateHandle, w as accountReadErrorFromCapxul, yi as toPublishableKey, yn as redactTelemetryEvent } from "../access-B-2qmxF_.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-2L76NWS5.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);
|
|
@@ -509,6 +646,47 @@ function fail$2(kind, error) {
|
|
|
509
646
|
return Effect.fail(bootstrapErrorFromCapxul(kind, error));
|
|
510
647
|
}
|
|
511
648
|
//#endregion
|
|
649
|
+
//#region src/testing/convex-call/subscribers.ts
|
|
650
|
+
/**
|
|
651
|
+
* Register `subscriber` under `path` and return its unsubscribe, which removes
|
|
652
|
+
* it once (a second call does nothing) and drops a path left with no one.
|
|
653
|
+
*/
|
|
654
|
+
function subscribeAt(subscribers, path, subscriber) {
|
|
655
|
+
let subs = subscribers.get(path);
|
|
656
|
+
if (subs === void 0) {
|
|
657
|
+
subs = /* @__PURE__ */ new Set();
|
|
658
|
+
subscribers.set(path, subs);
|
|
659
|
+
}
|
|
660
|
+
subs.add(subscriber);
|
|
661
|
+
let active = true;
|
|
662
|
+
return () => {
|
|
663
|
+
if (!active) return;
|
|
664
|
+
active = false;
|
|
665
|
+
const live = subscribers.get(path);
|
|
666
|
+
if (live === void 0) return;
|
|
667
|
+
live.delete(subscriber);
|
|
668
|
+
if (live.size === 0) subscribers.delete(path);
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
//#endregion
|
|
672
|
+
//#region src/testing/convex-call/wire-reply.ts
|
|
673
|
+
/**
|
|
674
|
+
* The Convex wire, as the in-memory test doubles stand in for it. A scripted or
|
|
675
|
+
* registered value crosses where a real reply would, and the caller's function
|
|
676
|
+
* reference names its type, exactly as `ConvexClient` types a reply it cannot
|
|
677
|
+
* check. These are the only places the doubles make that claim.
|
|
678
|
+
*/
|
|
679
|
+
function wireReply(_fn, value) {
|
|
680
|
+
return value;
|
|
681
|
+
}
|
|
682
|
+
/** A scripted snapshot for a subscription on `fn`: only an `ok` value carries the reply type. */
|
|
683
|
+
function wireSnapshot(fn, snapshot) {
|
|
684
|
+
return snapshot.status === "ok" ? {
|
|
685
|
+
status: "ok",
|
|
686
|
+
value: wireReply(fn, snapshot.value)
|
|
687
|
+
} : snapshot;
|
|
688
|
+
}
|
|
689
|
+
//#endregion
|
|
512
690
|
//#region src/testing/convex-call/ConvexCallStubAdapter.ts
|
|
513
691
|
var ConvexCallStubAdapter = class {
|
|
514
692
|
#script;
|
|
@@ -517,43 +695,30 @@ var ConvexCallStubAdapter = class {
|
|
|
517
695
|
this.#script = deps.script;
|
|
518
696
|
}
|
|
519
697
|
query(fn, _args) {
|
|
520
|
-
return this.#resolve(
|
|
698
|
+
return this.#resolve(fn);
|
|
521
699
|
}
|
|
522
700
|
mutation(fn, _args) {
|
|
523
|
-
return this.#resolve(
|
|
701
|
+
return this.#resolve(fn);
|
|
524
702
|
}
|
|
525
703
|
action(fn, _args) {
|
|
526
|
-
return this.#resolve(
|
|
704
|
+
return this.#resolve(fn);
|
|
527
705
|
}
|
|
528
706
|
subscribe(fn, _args, callback) {
|
|
529
707
|
return Effect.sync(() => {
|
|
530
708
|
const path = getFunctionName(fn);
|
|
531
|
-
const cb = callback;
|
|
709
|
+
const cb = (snapshot) => callback(wireSnapshot(fn, snapshot));
|
|
532
710
|
cb({ status: "loading" });
|
|
533
711
|
const sequence = this.#script.subscriptions?.get(path);
|
|
534
712
|
const subscriber = {
|
|
535
713
|
callback: cb,
|
|
536
714
|
cursor: 0
|
|
537
715
|
};
|
|
538
|
-
|
|
539
|
-
if (subs === void 0) {
|
|
540
|
-
subs = /* @__PURE__ */ new Set();
|
|
541
|
-
this.#subscribers.set(path, subs);
|
|
542
|
-
}
|
|
543
|
-
subs.add(subscriber);
|
|
716
|
+
const unsubscribe = subscribeAt(this.#subscribers, path, subscriber);
|
|
544
717
|
if (sequence !== void 0 && sequence.length > 0) {
|
|
545
718
|
cb(sequence[0]);
|
|
546
719
|
subscriber.cursor = 1;
|
|
547
720
|
}
|
|
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
|
-
};
|
|
721
|
+
return unsubscribe;
|
|
557
722
|
});
|
|
558
723
|
}
|
|
559
724
|
advanceSubscription(path) {
|
|
@@ -568,10 +733,11 @@ var ConvexCallStubAdapter = class {
|
|
|
568
733
|
sub.callback(next);
|
|
569
734
|
}
|
|
570
735
|
}
|
|
571
|
-
#resolve(
|
|
736
|
+
#resolve(fn) {
|
|
737
|
+
const path = getFunctionName(fn);
|
|
572
738
|
const failure = this.#script.failures?.get(path);
|
|
573
739
|
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));
|
|
740
|
+
if (this.#script.responses?.has(path)) return Effect.succeed(wireReply(fn, this.#script.responses.get(path)));
|
|
575
741
|
return Effect.fail(convexCallErrorFromCapxul(path, Errors.providerError("convex", path, "unknown function")));
|
|
576
742
|
}
|
|
577
743
|
};
|
|
@@ -587,15 +753,15 @@ var InMemoryIdentityAdapter = class {
|
|
|
587
753
|
#clock;
|
|
588
754
|
constructor(deps) {
|
|
589
755
|
this.#clock = deps.clock;
|
|
756
|
+
for (const [authUserId, profile] of deps.profiles ?? []) this.#identities.set(authUserId, cloneProfile(profile));
|
|
590
757
|
}
|
|
591
758
|
loadByAuthUserId(authUserId) {
|
|
592
759
|
return Effect.sync(() => cloneProfileOrNull(this.#identities.get(authUserId) ?? null));
|
|
593
760
|
}
|
|
594
761
|
create(input) {
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
const now = yield* self.#now("create.clock");
|
|
762
|
+
return Effect.gen({ self: this }, function* () {
|
|
763
|
+
if (this.#identities.has(input.authUserId)) return yield* fail$1("create", Errors.invalidInput("authUserId", "already exists"));
|
|
764
|
+
const now = yield* this.#now("create.clock");
|
|
599
765
|
const profile = {
|
|
600
766
|
authUserId: input.authUserId,
|
|
601
767
|
email: input.email,
|
|
@@ -609,16 +775,15 @@ var InMemoryIdentityAdapter = class {
|
|
|
609
775
|
createdAt: now,
|
|
610
776
|
updatedAt: now
|
|
611
777
|
};
|
|
612
|
-
|
|
778
|
+
this.#identities.set(input.authUserId, cloneProfile(profile));
|
|
613
779
|
return cloneProfile(profile);
|
|
614
780
|
});
|
|
615
781
|
}
|
|
616
782
|
update(input) {
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
const existing = self.#identities.get(input.authUserId);
|
|
783
|
+
return Effect.gen({ self: this }, function* () {
|
|
784
|
+
const existing = this.#identities.get(input.authUserId);
|
|
620
785
|
if (existing === void 0) return yield* fail$1("update", Errors.profileNotFound(input.authUserId));
|
|
621
|
-
const now = yield*
|
|
786
|
+
const now = yield* this.#now("update.clock");
|
|
622
787
|
const updated = {
|
|
623
788
|
authUserId: existing.authUserId,
|
|
624
789
|
email: existing.email,
|
|
@@ -632,15 +797,14 @@ var InMemoryIdentityAdapter = class {
|
|
|
632
797
|
imageUrl: existing.imageUrl,
|
|
633
798
|
updatedAt: now
|
|
634
799
|
};
|
|
635
|
-
|
|
800
|
+
this.#identities.set(input.authUserId, cloneProfile(updated));
|
|
636
801
|
return cloneProfile(updated);
|
|
637
802
|
});
|
|
638
803
|
}
|
|
639
804
|
completeOnboarding(input) {
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
const
|
|
643
|
-
const existing = self.#identities.get(input.authUserId);
|
|
805
|
+
return Effect.gen({ self: this }, function* () {
|
|
806
|
+
const now = yield* this.#now("completeOnboarding.clock");
|
|
807
|
+
const existing = this.#identities.get(input.authUserId);
|
|
644
808
|
const profile = {
|
|
645
809
|
authUserId: input.authUserId,
|
|
646
810
|
email: existing?.email ?? input.email,
|
|
@@ -654,14 +818,16 @@ var InMemoryIdentityAdapter = class {
|
|
|
654
818
|
createdAt: existing?.createdAt ?? now,
|
|
655
819
|
updatedAt: now
|
|
656
820
|
};
|
|
657
|
-
|
|
821
|
+
this.#identities.set(input.authUserId, cloneProfile(profile));
|
|
658
822
|
return cloneProfile(profile);
|
|
659
823
|
});
|
|
660
824
|
}
|
|
661
825
|
#now(operation) {
|
|
826
|
+
if (this.#clock === void 0) return Effect.succeed(toEpochMs(0));
|
|
662
827
|
return this.#clock.now.pipe(Effect.mapError((cause) => identityErrorFromCapxul(operation, Errors.providerError("clock", "now", cause), cause)));
|
|
663
828
|
}
|
|
664
829
|
};
|
|
830
|
+
Layer.effect(IdentityPortTag, Effect.map(ClockPortTag, (clock) => new InMemoryIdentityAdapter({ clock })));
|
|
665
831
|
function fail$1(operation, error) {
|
|
666
832
|
return Effect.fail(identityErrorFromCapxul(operation, error));
|
|
667
833
|
}
|
|
@@ -686,8 +852,6 @@ function cloneProfileOrNull(profile) {
|
|
|
686
852
|
//#endregion
|
|
687
853
|
//#region src/testing/smart-account/smart-account-derivation.ts
|
|
688
854
|
const SUPPORTED_CHAIN_IDS = /* @__PURE__ */ new Set([84532, 8453]);
|
|
689
|
-
toAddress("0xa6b71e26c5e0845f74c812102ca7114b6a896ab2");
|
|
690
|
-
keccak256("0x");
|
|
691
855
|
function isSupportedSmartAccountChain(chainId) {
|
|
692
856
|
return SUPPORTED_CHAIN_IDS.has(wireChainId(chainId));
|
|
693
857
|
}
|
|
@@ -708,6 +872,7 @@ var InMemorySmartAccountAdapter = class {
|
|
|
708
872
|
constructor(deps) {
|
|
709
873
|
this.#clock = deps.clock;
|
|
710
874
|
this.#failures = deps.failures;
|
|
875
|
+
for (const [authUserId, account] of deps.accounts ?? []) this.#accounts.set(authUserId, cloneSmartAccount(account));
|
|
711
876
|
}
|
|
712
877
|
loadByAuthUserId(authUserId) {
|
|
713
878
|
const scriptedFailure = this.#failures?.loadByAuthUserId;
|
|
@@ -726,12 +891,11 @@ var InMemorySmartAccountAdapter = class {
|
|
|
726
891
|
provision(input) {
|
|
727
892
|
const scriptedFailure = this.#failures?.provision;
|
|
728
893
|
if (scriptedFailure !== void 0) return fail("provision", scriptedFailure);
|
|
729
|
-
|
|
730
|
-
return Effect.gen(function* () {
|
|
894
|
+
return Effect.gen({ self: this }, function* () {
|
|
731
895
|
if (!isSupportedSmartAccountChain(input.chainId)) return yield* fail("provision", Errors.invalidInput("chainId", `unsupported chainId ${String(input.chainId)}`));
|
|
732
|
-
const existing =
|
|
896
|
+
const existing = this.#accounts.get(input.authUserId);
|
|
733
897
|
if (existing !== void 0) return cloneSmartAccount(existing);
|
|
734
|
-
const now = yield*
|
|
898
|
+
const now = yield* this.#now("provision.clock");
|
|
735
899
|
const account = {
|
|
736
900
|
authUserId: input.authUserId,
|
|
737
901
|
signerAddress: null,
|
|
@@ -741,9 +905,9 @@ var InMemorySmartAccountAdapter = class {
|
|
|
741
905
|
claimedAt: null,
|
|
742
906
|
createdAt: now
|
|
743
907
|
};
|
|
744
|
-
|
|
908
|
+
this.#accounts.set(input.authUserId, cloneSmartAccount(account));
|
|
745
909
|
return cloneSmartAccount(account);
|
|
746
|
-
}).pipe(
|
|
910
|
+
}).pipe(this.#provisionMutex.withPermits(1));
|
|
747
911
|
}
|
|
748
912
|
promoteDeployedAt(authUserId, deployedAt) {
|
|
749
913
|
const existing = this.#accounts.get(authUserId);
|
|
@@ -755,27 +919,27 @@ var InMemorySmartAccountAdapter = class {
|
|
|
755
919
|
});
|
|
756
920
|
}
|
|
757
921
|
confirmDeployment(input) {
|
|
758
|
-
const
|
|
759
|
-
return
|
|
760
|
-
|
|
922
|
+
const scriptedFailure = this.#failures?.confirmDeployment;
|
|
923
|
+
if (scriptedFailure !== void 0) return fail("confirmDeployment", scriptedFailure);
|
|
924
|
+
return Effect.gen({ self: this }, function* () {
|
|
925
|
+
const existing = this.#accounts.get(input.authUserId);
|
|
761
926
|
if (existing === void 0) return yield* fail("confirmDeployment", Errors.profileNotFound(String(input.authUserId)));
|
|
762
927
|
if (existing.smartAccountAddress !== toAddress(input.safeAddress)) return yield* fail("confirmDeployment", Errors.invalidInput("safeAddress", "does not match backend smart-account row"));
|
|
763
928
|
if (existing.deployedAt !== null) return cloneSmartAccount(existing);
|
|
764
|
-
const now = yield*
|
|
929
|
+
const now = yield* this.#now("confirmDeployment.clock");
|
|
765
930
|
const updated = {
|
|
766
931
|
...existing,
|
|
767
932
|
deployedAt: now
|
|
768
933
|
};
|
|
769
|
-
|
|
934
|
+
this.#accounts.set(input.authUserId, cloneSmartAccount(updated));
|
|
770
935
|
return cloneSmartAccount(updated);
|
|
771
|
-
}).pipe(
|
|
936
|
+
}).pipe(this.#provisionMutex.withPermits(1));
|
|
772
937
|
}
|
|
773
938
|
claim(input) {
|
|
774
939
|
const scriptedFailure = this.#failures?.claim;
|
|
775
940
|
if (scriptedFailure !== void 0) return fail("claim", scriptedFailure);
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
const existing = self.#accounts.get(input.authUserId);
|
|
941
|
+
return Effect.gen({ self: this }, function* () {
|
|
942
|
+
const existing = this.#accounts.get(input.authUserId);
|
|
779
943
|
if (existing === void 0) return yield* fail("claim", Errors.invalidInput("account", "no provisioned smart account for this user"));
|
|
780
944
|
if (existing.chainId !== input.chainId) return yield* fail("claim", Errors.invalidInput("chainId", "does not match backend smart-account row"));
|
|
781
945
|
const newOwner = toAddress(input.signerAddress);
|
|
@@ -783,18 +947,19 @@ var InMemorySmartAccountAdapter = class {
|
|
|
783
947
|
if (existing.signerAddress !== newOwner) return yield* fail("claim", Errors.invalidInput("signerAddress", "account already claimed by a different signer"));
|
|
784
948
|
return cloneSmartAccount(existing);
|
|
785
949
|
}
|
|
786
|
-
const now = yield*
|
|
950
|
+
const now = yield* this.#now("claim.clock");
|
|
787
951
|
const updated = {
|
|
788
952
|
...existing,
|
|
789
953
|
signerAddress: newOwner,
|
|
790
954
|
claimedAt: now,
|
|
791
955
|
deployedAt: existing.deployedAt ?? now
|
|
792
956
|
};
|
|
793
|
-
|
|
957
|
+
this.#accounts.set(input.authUserId, cloneSmartAccount(updated));
|
|
794
958
|
return cloneSmartAccount(updated);
|
|
795
|
-
}).pipe(
|
|
959
|
+
}).pipe(this.#provisionMutex.withPermits(1));
|
|
796
960
|
}
|
|
797
961
|
#now(operation) {
|
|
962
|
+
if (this.#clock === void 0) return Effect.succeed(toEpochMs(0));
|
|
798
963
|
return this.#clock.now.pipe(Effect.mapError((cause) => smartAccountErrorFromCapxul(operation, Errors.providerError("clock", "now", cause), cause)));
|
|
799
964
|
}
|
|
800
965
|
};
|
|
@@ -825,7 +990,7 @@ var RecordingTelemetryAdapter = class {
|
|
|
825
990
|
this.#rawMode = options.rawMode === true;
|
|
826
991
|
}
|
|
827
992
|
get events() {
|
|
828
|
-
return this.#events.map(
|
|
993
|
+
return this.#events.map(cloneTelemetryEvent);
|
|
829
994
|
}
|
|
830
995
|
get operations() {
|
|
831
996
|
return this.#operations.map(cloneOperation);
|
|
@@ -836,7 +1001,7 @@ var RecordingTelemetryAdapter = class {
|
|
|
836
1001
|
this.#events.push(recorded);
|
|
837
1002
|
this.#operations.push({
|
|
838
1003
|
type: "emit",
|
|
839
|
-
event:
|
|
1004
|
+
event: cloneTelemetryEvent(recorded)
|
|
840
1005
|
});
|
|
841
1006
|
});
|
|
842
1007
|
}
|
|
@@ -866,17 +1031,11 @@ var RecordingTelemetryAdapter = class {
|
|
|
866
1031
|
this.#operations.length = 0;
|
|
867
1032
|
}
|
|
868
1033
|
};
|
|
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
1034
|
function cloneOperation(operation) {
|
|
876
1035
|
switch (operation.type) {
|
|
877
1036
|
case "emit": return {
|
|
878
1037
|
type: "emit",
|
|
879
|
-
event:
|
|
1038
|
+
event: cloneTelemetryEvent(operation.event)
|
|
880
1039
|
};
|
|
881
1040
|
case "identify": return {
|
|
882
1041
|
type: "identify",
|
|
@@ -887,14 +1046,15 @@ function cloneOperation(operation) {
|
|
|
887
1046
|
input: cloneGroupInput(operation.input)
|
|
888
1047
|
};
|
|
889
1048
|
case "reset": return { type: "reset" };
|
|
1049
|
+
default: return absurd(operation);
|
|
890
1050
|
}
|
|
891
1051
|
}
|
|
892
1052
|
function cloneIdentifyInput(input) {
|
|
893
1053
|
return {
|
|
894
1054
|
distinctId: input.distinctId,
|
|
895
1055
|
...input.anonDistinctId === void 0 ? {} : { anonDistinctId: input.anonDistinctId },
|
|
896
|
-
...input.traits === void 0 ? {} : { traits:
|
|
897
|
-
...input.properties === void 0 ? {} : { properties:
|
|
1056
|
+
...input.traits === void 0 ? {} : { traits: cloneTelemetryProps(input.traits) },
|
|
1057
|
+
...input.properties === void 0 ? {} : { properties: cloneTelemetryProps(input.properties) }
|
|
898
1058
|
};
|
|
899
1059
|
}
|
|
900
1060
|
function cloneGroupInput(input) {
|
|
@@ -904,22 +1064,9 @@ function cloneGroupInput(input) {
|
|
|
904
1064
|
} : {
|
|
905
1065
|
groupType: input.groupType,
|
|
906
1066
|
groupKey: input.groupKey,
|
|
907
|
-
properties:
|
|
1067
|
+
properties: cloneTelemetryProps(input.properties)
|
|
908
1068
|
};
|
|
909
1069
|
}
|
|
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
1070
|
//#endregion
|
|
924
1071
|
//#region src/testing/index.ts
|
|
925
1072
|
const TEST_BOOTSTRAP = {
|
|
@@ -947,6 +1094,7 @@ function createCapxulTestClient(options = {}) {
|
|
|
947
1094
|
});
|
|
948
1095
|
const identity = new InMemoryIdentityAdapter({ clock });
|
|
949
1096
|
const telemetry = new RecordingTelemetryAdapter();
|
|
1097
|
+
const organizationSetup = createTestOrganizationSetup();
|
|
950
1098
|
const ports = {
|
|
951
1099
|
authClient: authClientPortFromPromiseAdapter(authClient),
|
|
952
1100
|
authCache,
|
|
@@ -963,6 +1111,7 @@ function createCapxulTestClient(options = {}) {
|
|
|
963
1111
|
bootstrap: TEST_BOOTSTRAP,
|
|
964
1112
|
authCache,
|
|
965
1113
|
requirement: options.requirement ?? "none",
|
|
1114
|
+
organizationSetup: organizationSetup.ops,
|
|
966
1115
|
...options.failureObservation === void 0 ? {} : { failureObservation: options.failureObservation }
|
|
967
1116
|
});
|
|
968
1117
|
const transitions = [];
|
|
@@ -986,6 +1135,7 @@ function createCapxulTestClient(options = {}) {
|
|
|
986
1135
|
return telemetry.operations;
|
|
987
1136
|
},
|
|
988
1137
|
peekOtp: (email) => authClient.peekOtpForTesting(toEmail(email)),
|
|
1138
|
+
organizationSetup: { set: organizationSetup.set },
|
|
989
1139
|
seedIdentity: async (input) => {
|
|
990
1140
|
const authUserId = toAuthUserId(input.authUserId);
|
|
991
1141
|
const email = toEmail(input.email);
|
|
@@ -1006,4 +1156,4 @@ function createCapxulTestClient(options = {}) {
|
|
|
1006
1156
|
};
|
|
1007
1157
|
}
|
|
1008
1158
|
//#endregion
|
|
1009
|
-
export { ControlledSignerFault, armSignerFaults, createCapxulProductionTestClient, createCapxulTestClient, signerFaultCause };
|
|
1159
|
+
export { ControlledSignerFault, armSignerFaults, createCapxulProductionTestClient, createCapxulTestClient, organizationSetupFixture, signerFaultCause };
|