sporades 0.8.1 → 0.8.3

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/CHANGES.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changes
2
2
 
3
+ ## Unreleased - 2026-08-15
4
+
5
+ Changes since v0.8.1.
6
+
7
+ ### 🚀 Features
8
+
9
+ - Add exact pagination and join admission (63f68a0).
10
+
11
+ ### 🐛 Bug Fixes
12
+
13
+ - Resolve npm audit vulnerabilities (a6a4b51).
14
+
3
15
  ## 0.8.1 - 2026-08-14
4
16
 
5
17
  Corrects the incomplete `0.8.0` package release with the merged `main` runtime,
@@ -16,3 +28,4 @@ generated artifacts, and documentation.
16
28
  explicit Team decisions in table and File ACLs. Teams are built in but do
17
29
  not select a current Team or automatically partition Capsule data; Sporades
18
30
  never sends Join-link email. See the [Built-in Teams reference](https://mgscox.github.io/sporades/reference/teams).
31
+
@@ -418,7 +418,7 @@ function writeEnvelope(result, failed = false) {
418
418
  }
419
419
 
420
420
  // src/cli/cli-version.ts
421
- var CLI_VERSION = "0.8.1";
421
+ var CLI_VERSION = "0.8.3";
422
422
 
423
423
  // src/cli/schedule-inspection-envelope.ts
424
424
  function sanitizeScheduleInspectionEnvelope(envelope, invalid) {
package/bin/sporades.js CHANGED
@@ -68,8 +68,8 @@ export const teams = {
68
68
  rename(teamId, name) {
69
69
  return connect().teamsRename(teamId, name);
70
70
  },
71
- listMembers(teamId) {
72
- return connect().teamsListMembers(teamId);
71
+ listMembers(teamId, options = {}) {
72
+ return connect().teamsListMembers(teamId, options);
73
73
  },
74
74
  updateApplicationRoles(teamId, userId, changes) {
75
75
  return connect().teamsUpdateApplicationRoles(teamId, userId, changes);
@@ -1117,7 +1117,7 @@ function createConnection() {
1117
1117
  teamsList() { return request("teams.list"); },
1118
1118
  teamsCreate(name) { return request("teams.create", { name }); },
1119
1119
  teamsRename(teamId, name) { return request("teams.rename", { teamId, name }); },
1120
- teamsListMembers(teamId) { return request("teams.listMembers", { teamId }); },
1120
+ teamsListMembers(teamId, options = {}) { return request("teams.listMembers", { teamId, cursor: options.cursor, limit: options.limit }); },
1121
1121
  teamsUpdateApplicationRoles(teamId, userId, changes) { return request("teams.updateApplicationRoles", { teamId, userId, add: changes?.add, remove: changes?.remove }); },
1122
1122
  teamsCreateJoinLink(teamId, email, options = {}) { return request("teams.createJoinLink", { teamId, email, ttlSeconds: options.ttlSeconds }); },
1123
1123
  teamsListJoinLinks(teamId) { return request("teams.listJoinLinks", { teamId }); },
@@ -8477,6 +8477,7 @@ var INITIAL_TEAM_NAME = "My Team";
8477
8477
  var TEAM_NAME_MAX_BYTES = 80;
8478
8478
  var TEAM_MEMBER_COUNT_MAX = 99;
8479
8479
  var TEAM_MEMBER_LIST_MAX = 100;
8480
+ var TEAM_MEMBER_LIST_DEFAULT = TEAM_MEMBER_LIST_MAX;
8480
8481
  var TEAM_MEMBERSHIP_MAX = 25;
8481
8482
  var TEAM_BOOTSTRAP_RETRY_LIMIT = 5;
8482
8483
  var TEAM_JOIN_LINK_DEFAULT_TTL_SECONDS = 60 * 60 * 24;
@@ -8538,9 +8539,9 @@ function createCurrentUserTeamsApi(database, auth, contextGetter) {
8538
8539
  requireAuth({ auth }, { linked: true });
8539
8540
  return renameCurrentUserTeam(database, auth, teamId, name, contextGetter?.());
8540
8541
  },
8541
- async listMembers(teamId) {
8542
+ async listMembers(teamId, options = {}) {
8542
8543
  requireAuth({ auth }, { linked: true });
8543
- return listTeamMembers(database, auth, teamId);
8544
+ return listTeamMembers(database, auth, teamId, options);
8544
8545
  },
8545
8546
  async updateApplicationRoles(teamId, userId, changes) {
8546
8547
  requireAuth({ auth }, { linked: true });
@@ -8806,6 +8807,7 @@ async function joinCurrentUserTeam(database, auth, code, eventContext) {
8806
8807
  "SELECT [role] FROM [sporades_team_memberships] WHERE [teamId] = ? AND [userId] = ?"
8807
8808
  )).get(row.teamId, auth.userId);
8808
8809
  if (!membership) {
8810
+ await enforceTeamJoinAdmission(database, tx, auth, String(row.teamId));
8809
8811
  await ensureMembershipCounterOnAdapter(tx, auth.userId);
8810
8812
  const claim = await tx.prepare(sql(
8811
8813
  "UPDATE [sporades_team_membership_counters] SET [membershipCount] = [membershipCount] + 1 WHERE [userId] = ? AND [membershipCount] < ?"
@@ -8829,6 +8831,23 @@ async function joinCurrentUserTeam(database, auth, code, eventContext) {
8829
8831
  emitTeamSecurityEvent(database, eventContext, "teams.joined", auth.userId, joined.id, "succeeded", "TEAM_JOINED");
8830
8832
  return { team: joined };
8831
8833
  }
8834
+ async function enforceTeamJoinAdmission(database, tx, auth, teamId) {
8835
+ if (typeof database.teamJoinAdmission !== "function") return;
8836
+ const count = await tx.prepare(tx.dialect.sql(
8837
+ "SELECT COUNT(*) AS [count] FROM [sporades_team_memberships] WHERE [teamId] = ?"
8838
+ )).get(teamId);
8839
+ try {
8840
+ const context = database.createTeamJoinAdmissionContext(tx, auth);
8841
+ const decision = await database.teamJoinAdmission(context, {
8842
+ teamId,
8843
+ userId: auth.userId,
8844
+ currentMemberCount: Number(count?.count ?? 0)
8845
+ });
8846
+ if (decision?.allow !== true) throw teamJoinDenied();
8847
+ } catch {
8848
+ throw teamJoinDenied();
8849
+ }
8850
+ }
8832
8851
  function normalizeTeamJoinEmail(email) {
8833
8852
  const normalized = String(email ?? "").trim().toLowerCase();
8834
8853
  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(normalized)) {
@@ -8949,6 +8968,9 @@ function teamJoinLinkLimitError() {
8949
8968
  function invalidTeamJoinLink() {
8950
8969
  return commandError2("Join link is invalid.", "Use a current Join link for this linked account.", "INVALID_JOIN_LINK");
8951
8970
  }
8971
+ function teamJoinDenied() {
8972
+ return commandError2("Could not join this Team.", "Ask a Team administrator for access.", "TEAM_JOIN_DENIED");
8973
+ }
8952
8974
  async function listCurrentUserTeams(database, auth) {
8953
8975
  requireAuth({ auth }, { linked: true });
8954
8976
  await ensureInitialTeam(database, auth);
@@ -9159,7 +9181,7 @@ async function deleteCurrentUserTeam(database, auth, teamId, eventContext) {
9159
9181
  emitTeamSecurityEvent(database, eventContext, "teams.deleted", auth.userId, teamId, "succeeded", "TEAM_DELETED");
9160
9182
  return { deleted: true };
9161
9183
  }
9162
- async function listTeamMembers(database, auth, teamId) {
9184
+ async function listTeamMembers(database, auth, teamId, options = {}) {
9163
9185
  requireAuth({ auth }, { linked: true });
9164
9186
  if (!isOpaqueTeamId(teamId)) throw teamDenied();
9165
9187
  return withTeamTransaction(database, async (tx) => {
@@ -9168,20 +9190,57 @@ async function listTeamMembers(database, auth, teamId) {
9168
9190
  "SELECT [role] FROM [sporades_team_memberships] WHERE [teamId] = ? AND [userId] = ?"
9169
9191
  )).get(teamId, auth.userId);
9170
9192
  if (callerMembership?.role !== "admin") throw teamDenied();
9193
+ const page = normalizeTeamMemberPage(options);
9194
+ const total = await tx.prepare(sql(
9195
+ "SELECT COUNT(*) AS [count] FROM [sporades_team_memberships] WHERE [teamId] = ?"
9196
+ )).get(teamId);
9197
+ const cursorClause = page.cursor ? "AND ([m].[createdAt] > ? OR ([m].[createdAt] = ? AND [m].[userId] > ?)) " : "";
9198
+ const params = page.cursor ? [teamId, page.cursor.createdAt, page.cursor.createdAt, page.cursor.userId, page.limit + 1] : [teamId, page.limit + 1];
9171
9199
  const rows = await tx.prepare(sql(
9172
- "SELECT [m].[userId], [u].[displayName], [u].[picture], [m].[role] FROM [sporades_team_memberships] [m] JOIN [sporades_auth_users] [u] ON [u].[id] = [m].[userId] WHERE [m].[teamId] = ? ORDER BY [m].[createdAt] ASC, [m].[userId] ASC LIMIT ?"
9173
- )).all(teamId, TEAM_MEMBER_LIST_MAX);
9200
+ "SELECT [m].[userId], [u].[displayName], [u].[picture], [m].[role] , [m].[createdAt] FROM [sporades_team_memberships] [m] JOIN [sporades_auth_users] [u] ON [u].[id] = [m].[userId] WHERE [m].[teamId] = ? " + cursorClause + "ORDER BY [m].[createdAt] ASC, [m].[userId] ASC LIMIT ?"
9201
+ )).all(...params);
9202
+ const hasMore = rows.length > page.limit;
9203
+ const pageRows = hasMore ? rows.slice(0, page.limit) : rows;
9204
+ const last = pageRows.at(-1);
9174
9205
  return {
9175
- members: await Promise.all(rows.map(async (row) => ({
9206
+ members: await Promise.all(pageRows.map(async (row) => ({
9176
9207
  userId: String(row.userId),
9177
9208
  displayName: String(row.displayName),
9178
9209
  picture: typeof row.picture === "string" && row.picture.length > 0 ? row.picture : null,
9179
9210
  role: row.role === "admin" ? "admin" : "member",
9180
9211
  applicationRoles: await activeTeamApplicationRoles(tx, database.teamApplicationRoles, teamId, row.userId)
9181
- })))
9212
+ }))),
9213
+ ...hasMore && last ? { nextCursor: encodeTeamMemberCursor(String(last.createdAt), String(last.userId)) } : {},
9214
+ totalCount: Number(total?.count ?? 0)
9182
9215
  };
9183
9216
  });
9184
9217
  }
9218
+ function normalizeTeamMemberPage(options) {
9219
+ if (!options || typeof options !== "object" || Array.isArray(options)) throw invalidTeamMemberPage();
9220
+ const limit = options.limit === void 0 ? TEAM_MEMBER_LIST_DEFAULT : options.limit;
9221
+ if (!Number.isInteger(limit) || limit < 1 || limit > TEAM_MEMBER_LIST_MAX) throw invalidTeamMemberPage();
9222
+ if (options.cursor === void 0) return { cursor: null, limit };
9223
+ if (typeof options.cursor !== "string" || options.cursor.length < 1 || options.cursor.length > 512) throw invalidTeamMemberPage();
9224
+ try {
9225
+ if (!/^[A-Za-z0-9_-]+$/.test(options.cursor)) throw invalidTeamMemberPage();
9226
+ const cursorBytes = Buffer.from(options.cursor, "base64url");
9227
+ if (cursorBytes.toString("base64url") !== options.cursor) throw invalidTeamMemberPage();
9228
+ const decoded = JSON.parse(cursorBytes.toString("utf8"));
9229
+ if (decoded?.v !== 1 || typeof decoded.createdAt !== "string" || !Number.isFinite(Date.parse(decoded.createdAt)) || typeof decoded.userId !== "string" || decoded.userId.length < 1 || decoded.userId.length > 256) {
9230
+ throw invalidTeamMemberPage();
9231
+ }
9232
+ return { cursor: { createdAt: decoded.createdAt, userId: decoded.userId }, limit };
9233
+ } catch (error) {
9234
+ if (error?.code === "INVALID_TEAM_MEMBER_PAGE") throw error;
9235
+ throw invalidTeamMemberPage();
9236
+ }
9237
+ }
9238
+ function encodeTeamMemberCursor(createdAt, userId) {
9239
+ return Buffer.from(JSON.stringify({ v: 1, createdAt, userId }), "utf8").toString("base64url");
9240
+ }
9241
+ function invalidTeamMemberPage() {
9242
+ return commandError2("Team member page is invalid.", `Use a limit from 1 through ${TEAM_MEMBER_LIST_MAX} and a cursor returned by listMembers().`, "INVALID_TEAM_MEMBER_PAGE");
9243
+ }
9185
9244
  async function ensureInitialTeam(database, auth) {
9186
9245
  if (database.__transactionActive) {
9187
9246
  return ensureInitialTeamOnAdapter(database.adapter, auth.userId);
@@ -14445,7 +14504,10 @@ function quoteIdentifier(identifier) {
14445
14504
  // src/server-runtime-source.ts
14446
14505
  async function openDevDatabase(databasePath, serverSource, serverEnv = {}, config = {}, capsuleDefinition = null, options = {}) {
14447
14506
  if (capsuleDefinition?.teams !== void 0 && (!capsuleDefinition.teams || typeof capsuleDefinition.teams !== "object" || Array.isArray(capsuleDefinition.teams))) {
14448
- throw commandError2("Invalid Capsule Teams declaration.", "Declare teams as { appRoles?: string[] }.", "INVALID_TEAM_APPLICATION_ROLES");
14507
+ throw commandError2("Invalid Capsule Teams declaration.", "Declare teams as { appRoles?: string[], admitJoin?: function }.", "INVALID_TEAM_APPLICATION_ROLES");
14508
+ }
14509
+ if (capsuleDefinition?.teams?.admitJoin !== void 0 && typeof capsuleDefinition.teams.admitJoin !== "function") {
14510
+ throw commandError2("Invalid Capsule Team admission policy.", "Declare teams.admitJoin as a server function.", "INVALID_TEAM_JOIN_ADMISSION");
14449
14511
  }
14450
14512
  const teamApplicationRoles = normalizeTeamApplicationRoles(capsuleDefinition?.teams?.appRoles);
14451
14513
  if (capsuleDefinition?.files !== void 0 && (!capsuleDefinition.files || typeof capsuleDefinition.files !== "object" || Array.isArray(capsuleDefinition.files))) {
@@ -14532,6 +14594,10 @@ async function openDevDatabase(databasePath, serverSource, serverEnv = {}, confi
14532
14594
  passwordResetConfig: resolvePasswordResetConfig(config),
14533
14595
  teamJoinLinkConfig: resolveTeamJoinLinkConfig(config),
14534
14596
  teamApplicationRoles,
14597
+ teamJoinAdmission: capsuleDefinition?.teams?.admitJoin,
14598
+ createTeamJoinAdmissionContext(transactionAdapter, auth) {
14599
+ return createTeamJoinAdmissionContext(createTransactionDatabase(database, transactionAdapter), auth);
14600
+ },
14535
14601
  fileAcl,
14536
14602
  securityPolicy: resolveRuntimeSecurityPolicy(config),
14537
14603
  fileStorage,
@@ -15823,6 +15889,33 @@ function createEndpointDatabaseApi(database, contextGetter = null) {
15823
15889
  database.schema.tables.map((table) => [table.name, createEndpointTableApi(database, table, {}, contextGetter)])
15824
15890
  );
15825
15891
  }
15892
+ function createEndpointReadOnlyDatabaseApi(database, contextGetter = null) {
15893
+ return Object.fromEntries(
15894
+ database.schema.tables.map((table) => [
15895
+ table.name,
15896
+ readOnlyEndpointTableApi(createEndpointTableApi(database, table, {}, contextGetter))
15897
+ ])
15898
+ );
15899
+ }
15900
+ function readOnlyEndpointTableApi(tableApi) {
15901
+ return {
15902
+ where(fieldName, value) {
15903
+ return readOnlyEndpointTableApi(tableApi.where(fieldName, value));
15904
+ },
15905
+ orderBy(fieldName, direction = "asc") {
15906
+ return readOnlyEndpointTableApi(tableApi.orderBy(fieldName, direction));
15907
+ },
15908
+ limit(count) {
15909
+ return readOnlyEndpointTableApi(tableApi.limit(count));
15910
+ },
15911
+ get() {
15912
+ return tableApi.get();
15913
+ },
15914
+ all() {
15915
+ return tableApi.all();
15916
+ }
15917
+ };
15918
+ }
15826
15919
  function createEndpointTableApi(database, table, query = {}, contextGetter = null) {
15827
15920
  return {
15828
15921
  insert(values) {
@@ -16626,7 +16719,7 @@ function createWebSocketHub(getDatabase, trustedRefresh = null) {
16626
16719
  }
16627
16720
  if (message.type === "teams.listMembers") {
16628
16721
  try {
16629
- const data = await listTeamMembers(database, client.session.auth, message.teamId);
16722
+ const data = await listTeamMembers(database, client.session.auth, message.teamId, { cursor: message.cursor, limit: message.limit });
16630
16723
  sendJson(client, { id: message.id ?? null, type: "teams.listMembers.result", data, error: null });
16631
16724
  } catch (error) {
16632
16725
  if (error?.sporadesAuthDenialLogData) emitAuthDeniedLog(database, { data: error.sporadesAuthDenialLogData });
@@ -17426,6 +17519,16 @@ function createMutationContext(database, auth) {
17426
17519
  };
17427
17520
  return context;
17428
17521
  }
17522
+ function createTeamJoinAdmissionContext(database, auth) {
17523
+ const context = {
17524
+ auth,
17525
+ env: database.serverEnv,
17526
+ log: createEndpointLogger(database)
17527
+ };
17528
+ const holder = createContextHolder(context);
17529
+ context.db = createEndpointReadOnlyDatabaseApi(database, () => holder.current);
17530
+ return context;
17531
+ }
17429
17532
  function createCurrentUserJobApi(database, contextGetter) {
17430
17533
  return {
17431
17534
  async enqueue(handlerName, payload, options = {}) {
@@ -23542,7 +23645,7 @@ jobs:
23542
23645
  }
23543
23646
 
23544
23647
  // src/cli/cli-version.ts
23545
- var CLI_VERSION = "0.8.1";
23648
+ var CLI_VERSION = "0.8.3";
23546
23649
 
23547
23650
  // src/cli/sporades.ts
23548
23651
  var SUPPORTED_TEMPLATES = new Set(CLIENT_TEMPLATES);
@@ -1,2 +1,2 @@
1
- export declare const CLI_VERSION = "0.8.1";
1
+ export declare const CLI_VERSION = "0.8.3";
2
2
  //# sourceMappingURL=cli-version.d.ts.map
@@ -1,3 +1,3 @@
1
1
  // Generated by `npm run build` from package.json. Do not edit by hand.
2
- export const CLI_VERSION = "0.8.1";
2
+ export const CLI_VERSION = "0.8.3";
3
3
  //# sourceMappingURL=cli-version.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"server-runtime-source.d.ts","sourceRoot":"","sources":["../src/server-runtime-source.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server-runtime-source.d.ts","sourceRoot":"","sources":["../src/server-runtime-source.ts"],"names":[],"mappings":"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"}
@@ -337,7 +337,10 @@ export * from "./http-runtime.js";
337
337
  // imports what it calls, and esbuild follows the graph.
338
338
  export async function openDevDatabase(databasePath, serverSource, serverEnv = {}, config = {}, capsuleDefinition = null, options = {}) {
339
339
  if (capsuleDefinition?.teams !== undefined && (!capsuleDefinition.teams || typeof capsuleDefinition.teams !== "object" || Array.isArray(capsuleDefinition.teams))) {
340
- throw commandError("Invalid Capsule Teams declaration.", "Declare teams as { appRoles?: string[] }.", "INVALID_TEAM_APPLICATION_ROLES");
340
+ throw commandError("Invalid Capsule Teams declaration.", "Declare teams as { appRoles?: string[], admitJoin?: function }.", "INVALID_TEAM_APPLICATION_ROLES");
341
+ }
342
+ if (capsuleDefinition?.teams?.admitJoin !== undefined && typeof capsuleDefinition.teams.admitJoin !== "function") {
343
+ throw commandError("Invalid Capsule Team admission policy.", "Declare teams.admitJoin as a server function.", "INVALID_TEAM_JOIN_ADMISSION");
341
344
  }
342
345
  const teamApplicationRoles = normalizeTeamApplicationRoles(capsuleDefinition?.teams?.appRoles);
343
346
  if (capsuleDefinition?.files !== undefined && (!capsuleDefinition.files || typeof capsuleDefinition.files !== "object" || Array.isArray(capsuleDefinition.files))) {
@@ -427,6 +430,10 @@ export async function openDevDatabase(databasePath, serverSource, serverEnv = {}
427
430
  passwordResetConfig: resolvePasswordResetConfig(config),
428
431
  teamJoinLinkConfig: resolveTeamJoinLinkConfig(config),
429
432
  teamApplicationRoles,
433
+ teamJoinAdmission: capsuleDefinition?.teams?.admitJoin,
434
+ createTeamJoinAdmissionContext(transactionAdapter, auth) {
435
+ return createTeamJoinAdmissionContext(createTransactionDatabase(database, transactionAdapter), auth);
436
+ },
430
437
  fileAcl,
431
438
  securityPolicy: resolveRuntimeSecurityPolicy(config),
432
439
  fileStorage,
@@ -1778,6 +1785,31 @@ function endpointQueryFromUrl(requestUrl) {
1778
1785
  export function createEndpointDatabaseApi(database, contextGetter = null) {
1779
1786
  return Object.fromEntries(database.schema.tables.map((table) => [table.name, createEndpointTableApi(database, table, {}, contextGetter)]));
1780
1787
  }
1788
+ function createEndpointReadOnlyDatabaseApi(database, contextGetter = null) {
1789
+ return Object.fromEntries(database.schema.tables.map((table) => [
1790
+ table.name,
1791
+ readOnlyEndpointTableApi(createEndpointTableApi(database, table, {}, contextGetter)),
1792
+ ]));
1793
+ }
1794
+ function readOnlyEndpointTableApi(tableApi) {
1795
+ return {
1796
+ where(fieldName, value) {
1797
+ return readOnlyEndpointTableApi(tableApi.where(fieldName, value));
1798
+ },
1799
+ orderBy(fieldName, direction = "asc") {
1800
+ return readOnlyEndpointTableApi(tableApi.orderBy(fieldName, direction));
1801
+ },
1802
+ limit(count) {
1803
+ return readOnlyEndpointTableApi(tableApi.limit(count));
1804
+ },
1805
+ get() {
1806
+ return tableApi.get();
1807
+ },
1808
+ all() {
1809
+ return tableApi.all();
1810
+ },
1811
+ };
1812
+ }
1781
1813
  function createEndpointTableApi(database, table, query = {}, contextGetter = null) {
1782
1814
  return {
1783
1815
  insert(values) {
@@ -2621,7 +2653,7 @@ export function createWebSocketHub(getDatabase, trustedRefresh = null) {
2621
2653
  }
2622
2654
  if (message.type === "teams.listMembers") {
2623
2655
  try {
2624
- const data = await listTeamMembers(database, client.session.auth, message.teamId);
2656
+ const data = await listTeamMembers(database, client.session.auth, message.teamId, { cursor: message.cursor, limit: message.limit });
2625
2657
  sendJson(client, { id: message.id ?? null, type: "teams.listMembers.result", data, error: null });
2626
2658
  }
2627
2659
  catch (error) {
@@ -3462,6 +3494,16 @@ function createMutationContext(database, auth) {
3462
3494
  };
3463
3495
  return context;
3464
3496
  }
3497
+ function createTeamJoinAdmissionContext(database, auth) {
3498
+ const context = {
3499
+ auth,
3500
+ env: database.serverEnv,
3501
+ log: createEndpointLogger(database),
3502
+ };
3503
+ const holder = createContextHolder(context);
3504
+ context.db = createEndpointReadOnlyDatabaseApi(database, () => holder.current);
3505
+ return context;
3506
+ }
3465
3507
  function createCurrentUserJobApi(database, contextGetter) {
3466
3508
  return {
3467
3509
  async enqueue(handlerName, payload, options = {}) {