sporades 0.8.6 โ 0.8.7
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 +14 -85
- package/README.md +2 -0
- package/bin/sporades-host-helper.js +4 -1
- package/bin/sporades.js +170 -49
- package/dist/cli/cli-version.d.ts +1 -1
- package/dist/cli/cli-version.js +1 -1
- package/dist/database-runtime.d.ts +1 -0
- package/dist/database-runtime.d.ts.map +1 -1
- package/dist/database-runtime.js +29 -11
- package/dist/database-runtime.js.map +1 -1
- package/dist/generated-source-manifest.json +2 -2
- package/dist/server-runtime-source.d.ts +1 -0
- package/dist/server-runtime-source.d.ts.map +1 -1
- package/dist/server-runtime-source.js +93 -20
- package/dist/server-runtime-source.js.map +1 -1
- package/dist/teams-runtime.d.ts.map +1 -1
- package/dist/teams-runtime.js +37 -19
- package/dist/teams-runtime.js.map +1 -1
- package/package.json +1 -1
- package/src/types/server.d.ts +3 -3
package/CHANGES.md
CHANGED
|
@@ -1,103 +1,32 @@
|
|
|
1
1
|
# Changes
|
|
2
2
|
|
|
3
|
-
## Unreleased - 2026-08-
|
|
3
|
+
## Unreleased - 2026-08-18
|
|
4
4
|
|
|
5
|
-
Changes since v0.8.
|
|
5
|
+
Changes since v0.8.6.
|
|
6
6
|
|
|
7
7
|
### ๐ Features
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
- Add conflict-targeted idempotent table inserts (bd2aeaa).
|
|
11
|
-
- Expose privileged Team inspections (16d784a).
|
|
12
|
-
- Declare unique Capsule table constraints (f638b36).
|
|
13
|
-
- Expose exact Team membership counts (6893db7).
|
|
9
|
+
- Add transaction-bound trusted reads (9d23493).
|
|
14
10
|
|
|
15
11
|
### ๐ Bug Fixes
|
|
16
12
|
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
- Validate all schedule cursors (bf3dea0).
|
|
25
|
-
- Arm schedule recovery after commit (da43e54).
|
|
26
|
-
- Persist schedule upgrade lineage (54d664e).
|
|
27
|
-
- Close schedule handoff and root reentry races (281029a).
|
|
28
|
-
- Serialize legacy schedule upgrades (9e87fb1).
|
|
29
|
-
- Publish schedule incarnations atomically (78da563).
|
|
30
|
-
- Make schedule generations authoritative (9005b1e).
|
|
31
|
-
- Bind schedules to declaration generations (b478ae7).
|
|
32
|
-
- Make schedule claims transactional (960f2c6).
|
|
33
|
-
- Seal retained job and libsql lifecycle (c5dc235).
|
|
34
|
-
- Recover future leases and schedules safely (fa08688).
|
|
35
|
-
- Bound job lifecycle time domains (89149c7).
|
|
36
|
-
- Close pre-handler job shutdown races (ffdca81).
|
|
37
|
-
- Make runtime shutdown failure atomic (1958f3b).
|
|
38
|
-
- Harden transactional lifecycle cleanup (e408f2d).
|
|
39
|
-
- Defer transactional job cancellation (2d3cf3e).
|
|
40
|
-
- Linearize team reads and job claims (1aa5391).
|
|
41
|
-
- Wake retained jobs on startup (0a66d61).
|
|
42
|
-
- Settle transactional log writes (43e1b2b).
|
|
43
|
-
- Revoke settled transaction adapters (bef9821).
|
|
44
|
-
- Abort jobs through the root runtime (5a43040).
|
|
45
|
-
- Settle jobs before lifecycle shutdown (51d1d9d).
|
|
46
|
-
- Settle job shutdown safely (9d38300).
|
|
47
|
-
- Await runtime closure during shutdown (ff688f1).
|
|
48
|
-
- Settle scheduled Jobs before runtime shutdown (7e7204e).
|
|
49
|
-
- Scope unique migration error translation (9af3b24).
|
|
50
|
-
- Preserve non-unique migration errors (2cfb207).
|
|
51
|
-
- Fix chained transaction release and async insert type (386ea84).
|
|
52
|
-
- Fix async insertOrIgnore documentation (a41f2ac).
|
|
53
|
-
- Guard privileged join-link reads (dea4ebc).
|
|
54
|
-
- Close privileged Team inspection races (d53136c).
|
|
55
|
-
- Redact privileged Team join metadata (ad77482).
|
|
56
|
-
- Defer existing table unique changes (e49477d).
|
|
57
|
-
|
|
58
|
-
### ๐ง Improvements
|
|
59
|
-
|
|
60
|
-
- Ignore Macos fluff files (d4ae934).
|
|
13
|
+
- Keep admission logs in transaction (5a9d012).
|
|
14
|
+
- Revoke trusted reads at settlement (dca0fea).
|
|
15
|
+
- Reject trusted reads from snapshots (9abe441).
|
|
16
|
+
- Bind Join cancellation to commit (e37058d).
|
|
17
|
+
- Close Team admission boundary races (b12dbde).
|
|
18
|
+
- Trust Team Join admission reads (32b65ee).
|
|
19
|
+
- Close trusted-read review gaps (da0c453).
|
|
61
20
|
|
|
62
21
|
### ๐ Documentation
|
|
63
22
|
|
|
64
|
-
-
|
|
65
|
-
-
|
|
66
|
-
- Record swarm cleanup (7233572).
|
|
67
|
-
- Close combined platform contract swarm (a1862c8).
|
|
68
|
-
- Record final combined contract package proof (4c44bf8).
|
|
69
|
-
- Record combined contract proof evidence (ac1c48c).
|
|
70
|
-
- Record accepted unique migration contract (6c5e484).
|
|
71
|
-
- Record accepted idempotent insert contract (19e7b63).
|
|
72
|
-
- Align shipped Job lifecycle reference (9b7820f).
|
|
73
|
-
- Document atomic Job enqueue lifecycle (9dbeb8a).
|
|
74
|
-
- Reject nested database transaction scopes (3cc98b6).
|
|
75
|
-
- Record accepted platform contract tickets (6ef6d1c).
|
|
76
|
-
- Close unique constraint contract gaps (20baabb).
|
|
77
|
-
- Prove unique constraint boundaries across adapters (f6d3517).
|
|
78
|
-
- Cover immutable unique schema boundary (4fb81d9).
|
|
79
|
-
- Initialize platform contracts swarm ledger (61816a1).
|
|
80
|
-
- Add Team subscription platform tickets (494a086).
|
|
23
|
+
- Complete trusted Join admission ticket (0347c0e).
|
|
24
|
+
- Complete trusted-read foundation ticket (2a24b7b).
|
|
81
25
|
|
|
82
26
|
### ๐งช Tests
|
|
83
27
|
|
|
84
|
-
-
|
|
85
|
-
-
|
|
86
|
-
- Preserve PostgreSQL quoting recorder transactions (85d2266).
|
|
87
|
-
- Cover unrelated rebuild constraint failures (4605ec5).
|
|
88
|
-
- Isolate Node 24 SQLite server tests (8542eb9).
|
|
89
|
-
- Harden failed mutation cleanup (d43a5ab).
|
|
90
|
-
- Bound transaction handler context lifetime (67dccb4).
|
|
91
|
-
- Defer runtime Job dispatch until commit (0a04afb).
|
|
92
|
-
- Persist Jobs inside handler transactions (b59004f).
|
|
93
|
-
- Reject captured transaction reentry and drop rolled-back jobs (952309e).
|
|
94
|
-
- Replace async-local transaction ownership (95cbb85).
|
|
95
|
-
- Gate transaction connections and track mutation writes (20cda07).
|
|
96
|
-
- Serialize shared database connection transactions (425e4f0).
|
|
97
|
-
- Isolate composite unique conformance (75ace57).
|
|
98
|
-
- Cover Team count membership lifecycle (80d6cd7).
|
|
99
|
-
- Accept cross-realm query arrays (55b87a6).
|
|
100
|
-
- Reject query argument array subclasses (2171b61).
|
|
28
|
+
- Restore PostgreSQL SQL quoting audit (c1fcf7c).
|
|
29
|
+
- Tidy up test (f643360).
|
|
101
30
|
|
|
102
31
|
## 0.8.5 - 2026-08-15
|
|
103
32
|
|
package/README.md
CHANGED
|
@@ -54,6 +54,8 @@ sporades host push --restart
|
|
|
54
54
|
- **Built-in collaboration, explicitly authorized**: runtime-owned Teams,
|
|
55
55
|
email-bound Join links, membership application roles, and explicit Team ACL
|
|
56
56
|
decisionsโwithout a hidden current Team or automatic data partitioning.
|
|
57
|
+
Trusted `teams.admitJoin` policies may make atomic seat decisions from
|
|
58
|
+
read-only app state hidden by the joining user's ordinary row ACLs.
|
|
57
59
|
- **Realtime everything**: configuration free - automatic notifications, even for Postgress and AWS-S3
|
|
58
60
|
|
|
59
61
|
## Documentation
|
|
@@ -253,6 +253,7 @@ var nodeCryptoModule2 = process.getBuiltinModule("node:crypto");
|
|
|
253
253
|
var TEAM_JOIN_LINK_DEFAULT_TTL_SECONDS = 60 * 60 * 24;
|
|
254
254
|
var TEAM_JOIN_LINK_MIN_TTL_SECONDS = 5 * 60;
|
|
255
255
|
var TEAM_JOIN_LINK_MAX_TTL_SECONDS = 60 * 60 * 24 * 7;
|
|
256
|
+
var transactionBeforeCommitChecks = Symbol.for("sporades.database.transactionBeforeCommitChecks");
|
|
256
257
|
|
|
257
258
|
// src/auth-runtime.ts
|
|
258
259
|
var nodeCryptoModule3 = process.getBuiltinModule("node:crypto");
|
|
@@ -265,8 +266,10 @@ var PASSWORD_RESET_MAX_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
|
265
266
|
var nodeCryptoModule4 = process.getBuiltinModule("node:crypto");
|
|
266
267
|
var nodeFsModule = process.getBuiltinModule("node:fs");
|
|
267
268
|
var transactionOperations = Symbol.for("sporades.database.transactionOperations");
|
|
269
|
+
var transactionBeforeCommitChecks2 = Symbol.for("sporades.database.transactionBeforeCommitChecks");
|
|
268
270
|
|
|
269
271
|
// src/server-runtime-source.ts
|
|
272
|
+
var trustedReadTransactionAdapter = Symbol("sporades.trustedReadTransactionAdapter");
|
|
270
273
|
function logPayloadMaxBytes(config = {}) {
|
|
271
274
|
const configured = Number(config.logs?.payloadMaxBytes ?? config.logging?.payloadMaxBytes);
|
|
272
275
|
return Number.isInteger(configured) && configured > 0 ? configured : 4096;
|
|
@@ -422,7 +425,7 @@ function writeEnvelope(result, failed = false) {
|
|
|
422
425
|
}
|
|
423
426
|
|
|
424
427
|
// src/cli/cli-version.ts
|
|
425
|
-
var CLI_VERSION = "0.8.
|
|
428
|
+
var CLI_VERSION = "0.8.7";
|
|
426
429
|
|
|
427
430
|
// src/cli/schedule-inspection-envelope.ts
|
|
428
431
|
function sanitizeScheduleInspectionEnvelope(envelope, invalid) {
|
package/bin/sporades.js
CHANGED
|
@@ -8842,6 +8842,7 @@ var TEAM_JOIN_LINK_MAX_OUTSTANDING = 20;
|
|
|
8842
8842
|
var TEAM_JOIN_LINK_CREATION_MAX_PER_HOUR = 10;
|
|
8843
8843
|
var TEAM_JOIN_LINK_PRUNE_LIMIT = 100;
|
|
8844
8844
|
var TEAM_JOIN_LINK_SECRET_ID = "v1";
|
|
8845
|
+
var transactionBeforeCommitChecks = Symbol.for("sporades.database.transactionBeforeCommitChecks");
|
|
8845
8846
|
var TEAM_APPLICATION_ROLE_MAX = 32;
|
|
8846
8847
|
var TEAM_APPLICATION_ROLE_PATCH_MAX = 16;
|
|
8847
8848
|
var TEAM_APPLICATION_ROLE_PATTERN = /^[a-z][a-z0-9-]{0,31}$/;
|
|
@@ -9195,8 +9196,11 @@ async function validateTeamJoinLink(database, auth, code) {
|
|
|
9195
9196
|
async function joinCurrentUserTeam(database, auth, code, eventContext) {
|
|
9196
9197
|
let joined;
|
|
9197
9198
|
let deniedTeamId = null;
|
|
9199
|
+
let auditUserId = null;
|
|
9198
9200
|
try {
|
|
9199
9201
|
requireAuth({ auth }, { linked: true });
|
|
9202
|
+
const joiningUserId = String(auth.userId);
|
|
9203
|
+
auditUserId = joiningUserId;
|
|
9200
9204
|
const parsed = parseTeamJoinCode(code);
|
|
9201
9205
|
if (!parsed) throw invalidTeamJoinLink();
|
|
9202
9206
|
joined = await withTeamTransaction(database, async (tx) => {
|
|
@@ -9226,7 +9230,7 @@ async function joinCurrentUserTeam(database, auth, code, eventContext) {
|
|
|
9226
9230
|
if (!team) throw invalidTeamJoinLink();
|
|
9227
9231
|
const attachedEmails = await tx.prepare(sql(
|
|
9228
9232
|
"SELECT [email] FROM [sporades_auth_email_credentials] WHERE [userId] = ? UNION ALL SELECT [email] FROM [sporades_auth_identities] WHERE [userId] = ? AND [email] IS NOT NULL"
|
|
9229
|
-
)).all(
|
|
9233
|
+
)).all(joiningUserId, joiningUserId);
|
|
9230
9234
|
const targetEmail = normalizeTeamJoinIdentityEmail(row.email);
|
|
9231
9235
|
if (!attachedEmails.some((identity) => normalizeTeamJoinIdentityEmail(identity.email) === targetEmail)) {
|
|
9232
9236
|
deniedTeamId = String(team.id);
|
|
@@ -9236,12 +9240,12 @@ async function joinCurrentUserTeam(database, auth, code, eventContext) {
|
|
|
9236
9240
|
"SELECT [userId] FROM [sporades_team_join_link_redemptions] WHERE [joinLinkId] = ?"
|
|
9237
9241
|
)).get(row.id);
|
|
9238
9242
|
if (row.consumedAt) {
|
|
9239
|
-
if (redemption?.userId !==
|
|
9243
|
+
if (redemption?.userId !== joiningUserId) throw invalidTeamJoinLink();
|
|
9240
9244
|
const membership2 = await tx.prepare(sql(
|
|
9241
9245
|
"SELECT [role] FROM [sporades_team_memberships] WHERE [teamId] = ? AND [userId] = ?"
|
|
9242
|
-
)).get(row.teamId,
|
|
9246
|
+
)).get(row.teamId, joiningUserId);
|
|
9243
9247
|
if (!membership2) throw invalidTeamJoinLink();
|
|
9244
|
-
await ensureInitialTeamOnAdapter(tx,
|
|
9248
|
+
await ensureInitialTeamOnAdapter(tx, joiningUserId);
|
|
9245
9249
|
const count2 = await tx.prepare(sql("SELECT COUNT(*) AS [count] FROM [sporades_team_memberships] WHERE [teamId] = ?")).get(row.teamId);
|
|
9246
9250
|
return teamSummary({ id: team.id, name: team.name, role: membership2.role, memberCount: Number(count2?.count ?? 0) });
|
|
9247
9251
|
}
|
|
@@ -9252,23 +9256,24 @@ async function joinCurrentUserTeam(database, auth, code, eventContext) {
|
|
|
9252
9256
|
if (Number(consumed?.changes ?? 0) !== 1) throw invalidTeamJoinLink();
|
|
9253
9257
|
await tx.prepare(sql(
|
|
9254
9258
|
"INSERT INTO [sporades_team_join_link_redemptions] ([joinLinkId], [teamId], [userId], [createdAt]) VALUES (?, ?, ?, ?)"
|
|
9255
|
-
)).run(row.id, row.teamId,
|
|
9256
|
-
await ensureInitialTeamOnAdapter(tx,
|
|
9259
|
+
)).run(row.id, row.teamId, joiningUserId, now);
|
|
9260
|
+
await ensureInitialTeamOnAdapter(tx, joiningUserId);
|
|
9257
9261
|
let membership = await tx.prepare(sql(
|
|
9258
9262
|
"SELECT [role] FROM [sporades_team_memberships] WHERE [teamId] = ? AND [userId] = ?"
|
|
9259
|
-
)).get(row.teamId,
|
|
9263
|
+
)).get(row.teamId, joiningUserId);
|
|
9260
9264
|
if (!membership) {
|
|
9261
|
-
await enforceTeamJoinAdmission(database, tx, auth, String(row.teamId));
|
|
9262
|
-
|
|
9265
|
+
await enforceTeamJoinAdmission(database, tx, auth, joiningUserId, String(row.teamId), eventContext?.signal);
|
|
9266
|
+
registerTeamJoinCancellationBeforeCommit(tx, eventContext?.signal);
|
|
9267
|
+
await ensureMembershipCounterOnAdapter(tx, joiningUserId);
|
|
9263
9268
|
const claim = await tx.prepare(sql(
|
|
9264
9269
|
"UPDATE [sporades_team_membership_counters] SET [membershipCount] = [membershipCount] + 1 WHERE [userId] = ? AND [membershipCount] < ?"
|
|
9265
|
-
)).run(
|
|
9270
|
+
)).run(joiningUserId, TEAM_MEMBERSHIP_MAX);
|
|
9266
9271
|
if (Number(claim?.changes ?? 0) !== 1) {
|
|
9267
9272
|
throw commandError2("Team limit reached.", `A user can belong to at most ${TEAM_MEMBERSHIP_MAX} Teams.`, "TEAM_LIMIT_REACHED");
|
|
9268
9273
|
}
|
|
9269
9274
|
await tx.prepare(sql(
|
|
9270
9275
|
"INSERT INTO [sporades_team_memberships] ([teamId], [userId], [role], [createdAt]) VALUES (?, ?, 'member', ?)"
|
|
9271
|
-
)).run(row.teamId,
|
|
9276
|
+
)).run(row.teamId, joiningUserId, now);
|
|
9272
9277
|
membership = { role: "member" };
|
|
9273
9278
|
}
|
|
9274
9279
|
await releaseTeamJoinLinkCapacity(tx, String(row.teamId));
|
|
@@ -9276,29 +9281,40 @@ async function joinCurrentUserTeam(database, auth, code, eventContext) {
|
|
|
9276
9281
|
return teamSummary({ id: team.id, name: team.name, role: membership.role, memberCount: Number(count?.count ?? 0) });
|
|
9277
9282
|
});
|
|
9278
9283
|
} catch (error) {
|
|
9279
|
-
emitTeamSecurityEvent(database, eventContext, "teams.joinLink.join", auth?.userId, deniedTeamId, "denied", String(error?.code ?? "INVALID_JOIN_LINK"));
|
|
9284
|
+
emitTeamSecurityEvent(database, eventContext, "teams.joinLink.join", auditUserId ?? auth?.userId, deniedTeamId, "denied", String(error?.code ?? "INVALID_JOIN_LINK"));
|
|
9280
9285
|
throw error;
|
|
9281
9286
|
}
|
|
9282
|
-
emitTeamSecurityEvent(database, eventContext, "teams.joined",
|
|
9287
|
+
emitTeamSecurityEvent(database, eventContext, "teams.joined", auditUserId, joined.id, "succeeded", "TEAM_JOINED");
|
|
9283
9288
|
return { team: joined };
|
|
9284
9289
|
}
|
|
9285
|
-
async function enforceTeamJoinAdmission(database, tx, auth, teamId) {
|
|
9286
|
-
if (typeof database.
|
|
9290
|
+
async function enforceTeamJoinAdmission(database, tx, auth, joiningUserId, teamId, signal) {
|
|
9291
|
+
if (typeof database.runTeamJoinAdmission !== "function") return;
|
|
9287
9292
|
const count = await tx.prepare(tx.dialect.sql(
|
|
9288
9293
|
"SELECT COUNT(*) AS [count] FROM [sporades_team_memberships] WHERE [teamId] = ?"
|
|
9289
9294
|
)).get(teamId);
|
|
9290
9295
|
try {
|
|
9291
|
-
const
|
|
9292
|
-
const decision = await database.teamJoinAdmission(context, {
|
|
9296
|
+
const decision = await database.runTeamJoinAdmission(tx, auth, {
|
|
9293
9297
|
teamId,
|
|
9294
|
-
userId:
|
|
9298
|
+
userId: joiningUserId,
|
|
9295
9299
|
currentMemberCount: Number(count?.count ?? 0)
|
|
9296
|
-
});
|
|
9300
|
+
}, signal);
|
|
9297
9301
|
if (decision?.allow !== true) throw teamJoinDenied();
|
|
9298
9302
|
} catch {
|
|
9299
9303
|
throw teamJoinDenied();
|
|
9300
9304
|
}
|
|
9301
9305
|
}
|
|
9306
|
+
function throwIfTeamJoinCancelled(signal) {
|
|
9307
|
+
if (signal?.aborted) throw teamJoinDenied();
|
|
9308
|
+
}
|
|
9309
|
+
function registerTeamJoinCancellationBeforeCommit(transactionAdapter, signal) {
|
|
9310
|
+
if (!signal) return;
|
|
9311
|
+
let checks = transactionAdapter[transactionBeforeCommitChecks];
|
|
9312
|
+
if (!Object.prototype.hasOwnProperty.call(transactionAdapter, transactionBeforeCommitChecks)) {
|
|
9313
|
+
checks = [];
|
|
9314
|
+
Object.defineProperty(transactionAdapter, transactionBeforeCommitChecks, { value: checks });
|
|
9315
|
+
}
|
|
9316
|
+
checks.push(() => throwIfTeamJoinCancelled(signal));
|
|
9317
|
+
}
|
|
9302
9318
|
function normalizeTeamJoinEmail(email) {
|
|
9303
9319
|
const normalized = String(email ?? "").trim().toLowerCase();
|
|
9304
9320
|
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(normalized)) {
|
|
@@ -13282,8 +13298,14 @@ async function rejectNestedTransactionScope() {
|
|
|
13282
13298
|
"Keep mutation work inside a single Sporades mutation transaction."
|
|
13283
13299
|
);
|
|
13284
13300
|
}
|
|
13285
|
-
var
|
|
13286
|
-
function
|
|
13301
|
+
var transactionScopes = /* @__PURE__ */ new WeakMap();
|
|
13302
|
+
function isActiveTransactionScopedAdapter(value, owner) {
|
|
13303
|
+
const scope = value && typeof value === "object" ? transactionScopes.get(value) : void 0;
|
|
13304
|
+
return Boolean(
|
|
13305
|
+
scope && scope.kind === "transaction" && (owner === void 0 || scope.owner === owner)
|
|
13306
|
+
);
|
|
13307
|
+
}
|
|
13308
|
+
function createTransactionScopedAdapter(adapter, operations, owner, kind) {
|
|
13287
13309
|
let active = true;
|
|
13288
13310
|
const assertActive = () => {
|
|
13289
13311
|
if (!active) throw commandError2(
|
|
@@ -13317,16 +13339,24 @@ function createTransactionScopedAdapter(adapter, operations = {}) {
|
|
|
13317
13339
|
withTransaction: rejectNestedTransactionScope,
|
|
13318
13340
|
withReadOnlySnapshot: rejectNestedTransactionScope
|
|
13319
13341
|
});
|
|
13320
|
-
|
|
13321
|
-
|
|
13342
|
+
transactionScopes.set(scopedAdapter, {
|
|
13343
|
+
revoke: () => {
|
|
13344
|
+
active = false;
|
|
13345
|
+
},
|
|
13346
|
+
owner,
|
|
13347
|
+
kind
|
|
13322
13348
|
});
|
|
13323
13349
|
return scopedAdapter;
|
|
13324
13350
|
}
|
|
13325
13351
|
function revokeTransactionScopedAdapter(adapter) {
|
|
13326
|
-
|
|
13327
|
-
|
|
13352
|
+
transactionScopes.get(adapter)?.revoke();
|
|
13353
|
+
transactionScopes.delete(adapter);
|
|
13328
13354
|
}
|
|
13329
13355
|
var transactionOperations = Symbol.for("sporades.database.transactionOperations");
|
|
13356
|
+
var transactionBeforeCommitChecks2 = Symbol.for("sporades.database.transactionBeforeCommitChecks");
|
|
13357
|
+
async function runTransactionBeforeCommitChecks(transactionAdapter) {
|
|
13358
|
+
for (const check of transactionAdapter[transactionBeforeCommitChecks2] ?? []) await check();
|
|
13359
|
+
}
|
|
13330
13360
|
async function createRuntimeDatabaseAdapter(databasePath, serverEnv = {}, config = {}) {
|
|
13331
13361
|
if (config.services?.database?.engine === "libsql" && serverEnv.SPORADES_SERVICE_DATABASE_ENGINE === "libsql" && serverEnv.SPORADES_SERVICE_DATABASE_URL) {
|
|
13332
13362
|
return await createLibsqlDatabaseAdapter({
|
|
@@ -14106,13 +14136,14 @@ async function createSqliteDatabaseAdapter(databasePath, options = {}) {
|
|
|
14106
14136
|
async withTransaction(fn) {
|
|
14107
14137
|
return await connectionGate.runTransaction(async () => {
|
|
14108
14138
|
const ownerOperations = typeof this[transactionOperations] === "function" ? this[transactionOperations]() : { exec: this.exec.bind(this), prepare: this.prepare.bind(this) };
|
|
14109
|
-
const transactionAdapter = createTransactionScopedAdapter(this, ownerOperations);
|
|
14139
|
+
const transactionAdapter = createTransactionScopedAdapter(this, ownerOperations, this, "transaction");
|
|
14110
14140
|
const transactionExec = ownerOperations.exec;
|
|
14111
14141
|
await transactionExec("BEGIN");
|
|
14112
14142
|
try {
|
|
14113
14143
|
let result;
|
|
14114
14144
|
try {
|
|
14115
14145
|
result = await fn(transactionAdapter);
|
|
14146
|
+
await runTransactionBeforeCommitChecks(transactionAdapter);
|
|
14116
14147
|
} finally {
|
|
14117
14148
|
revokeTransactionScopedAdapter(transactionAdapter);
|
|
14118
14149
|
}
|
|
@@ -14127,7 +14158,7 @@ async function createSqliteDatabaseAdapter(databasePath, options = {}) {
|
|
|
14127
14158
|
async withReadOnlySnapshot(fn) {
|
|
14128
14159
|
return await connectionGate.runTransaction(async () => {
|
|
14129
14160
|
const ownerOperations = typeof this[transactionOperations] === "function" ? this[transactionOperations]() : { exec: this.exec.bind(this), prepare: this.prepare.bind(this) };
|
|
14130
|
-
const ownerAdapter = createTransactionScopedAdapter(this, ownerOperations);
|
|
14161
|
+
const ownerAdapter = createTransactionScopedAdapter(this, ownerOperations, this, "snapshot");
|
|
14131
14162
|
const transactionExec = ownerOperations.exec;
|
|
14132
14163
|
await transactionExec("BEGIN");
|
|
14133
14164
|
await transactionExec("PRAGMA query_only = ON");
|
|
@@ -14250,10 +14281,11 @@ async function createPostgresDatabaseAdapter(options) {
|
|
|
14250
14281
|
return await connectionGate.runTransaction(async () => {
|
|
14251
14282
|
await rawQuery("BEGIN");
|
|
14252
14283
|
try {
|
|
14253
|
-
const transactionAdapter = createTransactionScopedAdapter(adapter, createOperations(runDirectly));
|
|
14284
|
+
const transactionAdapter = createTransactionScopedAdapter(adapter, createOperations(runDirectly), adapter, "transaction");
|
|
14254
14285
|
let result;
|
|
14255
14286
|
try {
|
|
14256
14287
|
result = await fn(transactionAdapter);
|
|
14288
|
+
await runTransactionBeforeCommitChecks(transactionAdapter);
|
|
14257
14289
|
} finally {
|
|
14258
14290
|
revokeTransactionScopedAdapter(transactionAdapter);
|
|
14259
14291
|
}
|
|
@@ -14270,7 +14302,7 @@ async function createPostgresDatabaseAdapter(options) {
|
|
|
14270
14302
|
},
|
|
14271
14303
|
async withReadOnlySnapshot(fn) {
|
|
14272
14304
|
return await connectionGate.runTransaction(async () => {
|
|
14273
|
-
const transactionAdapter = createTransactionScopedAdapter(adapter, createOperations(runDirectly));
|
|
14305
|
+
const transactionAdapter = createTransactionScopedAdapter(adapter, createOperations(runDirectly), adapter, "snapshot");
|
|
14274
14306
|
await rawQuery("BEGIN TRANSACTION READ ONLY");
|
|
14275
14307
|
try {
|
|
14276
14308
|
let result;
|
|
@@ -14758,13 +14790,14 @@ async function createLibsqlDatabaseAdapter(options) {
|
|
|
14758
14790
|
const transactionAdapter = createTransactionScopedAdapter({
|
|
14759
14791
|
...adapter,
|
|
14760
14792
|
...createOperations(transaction, runDirectly)
|
|
14761
|
-
});
|
|
14793
|
+
}, {}, adapter, "transaction");
|
|
14762
14794
|
activeTransactions.add(transaction);
|
|
14763
14795
|
try {
|
|
14764
14796
|
await libsqlExecute({ endpoint, authToken, transaction, sql: "BEGIN", params: [], close: false });
|
|
14765
14797
|
let result;
|
|
14766
14798
|
try {
|
|
14767
14799
|
result = await fn(transactionAdapter);
|
|
14800
|
+
await runTransactionBeforeCommitChecks(transactionAdapter);
|
|
14768
14801
|
} finally {
|
|
14769
14802
|
revokeTransactionScopedAdapter(transactionAdapter);
|
|
14770
14803
|
}
|
|
@@ -14786,7 +14819,7 @@ async function createLibsqlDatabaseAdapter(options) {
|
|
|
14786
14819
|
return await connectionGate.runTransaction(async () => {
|
|
14787
14820
|
assertLibsqlOpen(closed);
|
|
14788
14821
|
const transaction = { baton: null, baseUrl: endpoint };
|
|
14789
|
-
const snapshotAdapter = createTransactionScopedAdapter({ ...adapter, ...createOperations(transaction, runDirectly) });
|
|
14822
|
+
const snapshotAdapter = createTransactionScopedAdapter({ ...adapter, ...createOperations(transaction, runDirectly) }, {}, adapter, "snapshot");
|
|
14790
14823
|
activeTransactions.add(transaction);
|
|
14791
14824
|
try {
|
|
14792
14825
|
await libsqlExecute({ endpoint, authToken, transaction, sql: "BEGIN", params: [], close: false });
|
|
@@ -15257,6 +15290,8 @@ function quoteIdentifier(identifier) {
|
|
|
15257
15290
|
|
|
15258
15291
|
// src/server-runtime-source.ts
|
|
15259
15292
|
var mutationResultsWithWrites = /* @__PURE__ */ new WeakSet();
|
|
15293
|
+
var trustedReadPurposes = /* @__PURE__ */ new Set(["teams.join-admission"]);
|
|
15294
|
+
var trustedReadTransactionAdapter = Symbol("sporades.trustedReadTransactionAdapter");
|
|
15260
15295
|
async function shutdownAndCloseDatabase(database) {
|
|
15261
15296
|
let shutdownError;
|
|
15262
15297
|
let closeError;
|
|
@@ -15487,10 +15522,24 @@ async function openDevDatabase(databasePath, serverSource, serverEnv = {}, confi
|
|
|
15487
15522
|
passwordResetConfig: resolvePasswordResetConfig(config),
|
|
15488
15523
|
teamJoinLinkConfig: resolveTeamJoinLinkConfig(config),
|
|
15489
15524
|
teamApplicationRoles,
|
|
15490
|
-
|
|
15491
|
-
|
|
15492
|
-
|
|
15493
|
-
|
|
15525
|
+
runTeamJoinAdmission: typeof capsuleDefinition?.teams?.admitJoin === "function" ? async function(transactionAdapter, auth, input, signal) {
|
|
15526
|
+
const rootDatabase = this.__rootDatabase ?? this;
|
|
15527
|
+
const trustedTransaction = this[trustedReadTransactionAdapter] ?? transactionAdapter;
|
|
15528
|
+
const admissionDatabase = createTransactionDatabase(rootDatabase, trustedTransaction);
|
|
15529
|
+
try {
|
|
15530
|
+
return await withTrustedRead(rootDatabase, {
|
|
15531
|
+
transaction: trustedTransaction,
|
|
15532
|
+
purpose: "teams.join-admission",
|
|
15533
|
+
subject: { teamId: input.teamId, userId: input.userId },
|
|
15534
|
+
signal
|
|
15535
|
+
}, (trustedDb) => capsuleDefinition.teams.admitJoin(
|
|
15536
|
+
createTeamJoinAdmissionContext(admissionDatabase, auth, trustedDb),
|
|
15537
|
+
input
|
|
15538
|
+
));
|
|
15539
|
+
} finally {
|
|
15540
|
+
await drainPendingLogWrites(admissionDatabase);
|
|
15541
|
+
}
|
|
15542
|
+
} : void 0,
|
|
15494
15543
|
fileAcl,
|
|
15495
15544
|
securityPolicy: resolveRuntimeSecurityPolicy(config),
|
|
15496
15545
|
fileStorage,
|
|
@@ -17381,6 +17430,7 @@ function createTransactionDatabase(database, transactionAdapter, writeState) {
|
|
|
17381
17430
|
adapter,
|
|
17382
17431
|
sqlite: adapter,
|
|
17383
17432
|
__transactionActive: true,
|
|
17433
|
+
[trustedReadTransactionAdapter]: transactionAdapter,
|
|
17384
17434
|
__rootDatabase: database.__rootDatabase ?? database,
|
|
17385
17435
|
__pendingLogWrites: pendingLogWrites
|
|
17386
17436
|
};
|
|
@@ -17569,32 +17619,104 @@ function createEndpointDatabaseApi(database, contextGetter = null) {
|
|
|
17569
17619
|
database.schema.tables.map((table) => [table.name, createEndpointTableApi(database, table, {}, contextGetter)])
|
|
17570
17620
|
);
|
|
17571
17621
|
}
|
|
17572
|
-
function createEndpointReadOnlyDatabaseApi(database, contextGetter = null) {
|
|
17622
|
+
function createEndpointReadOnlyDatabaseApi(database, contextGetter = null, assertActive = () => {
|
|
17623
|
+
}) {
|
|
17573
17624
|
return Object.fromEntries(
|
|
17574
17625
|
database.schema.tables.map((table) => [
|
|
17575
17626
|
table.name,
|
|
17576
|
-
readOnlyEndpointTableApi(createEndpointTableApi(database, table, {}, contextGetter))
|
|
17627
|
+
readOnlyEndpointTableApi(createEndpointTableApi(database, table, {}, contextGetter), assertActive)
|
|
17577
17628
|
])
|
|
17578
17629
|
);
|
|
17579
17630
|
}
|
|
17580
|
-
function readOnlyEndpointTableApi(tableApi) {
|
|
17631
|
+
function readOnlyEndpointTableApi(tableApi, assertActive) {
|
|
17581
17632
|
return {
|
|
17582
17633
|
where(fieldName, value) {
|
|
17583
|
-
|
|
17634
|
+
assertActive();
|
|
17635
|
+
return readOnlyEndpointTableApi(tableApi.where(fieldName, value), assertActive);
|
|
17584
17636
|
},
|
|
17585
17637
|
orderBy(fieldName, direction = "asc") {
|
|
17586
|
-
|
|
17638
|
+
assertActive();
|
|
17639
|
+
return readOnlyEndpointTableApi(tableApi.orderBy(fieldName, direction), assertActive);
|
|
17587
17640
|
},
|
|
17588
17641
|
limit(count) {
|
|
17589
|
-
|
|
17642
|
+
assertActive();
|
|
17643
|
+
return readOnlyEndpointTableApi(tableApi.limit(count), assertActive);
|
|
17590
17644
|
},
|
|
17591
17645
|
get() {
|
|
17592
|
-
|
|
17646
|
+
assertActive();
|
|
17647
|
+
return trustedReadResult(tableApi.get(), assertActive);
|
|
17593
17648
|
},
|
|
17594
17649
|
all() {
|
|
17595
|
-
|
|
17650
|
+
assertActive();
|
|
17651
|
+
return trustedReadResult(tableApi.all(), assertActive);
|
|
17652
|
+
}
|
|
17653
|
+
};
|
|
17654
|
+
}
|
|
17655
|
+
function trustedReadResult(value, assertActive) {
|
|
17656
|
+
return Promise.resolve(value).then((result) => {
|
|
17657
|
+
assertActive();
|
|
17658
|
+
return result;
|
|
17659
|
+
});
|
|
17660
|
+
}
|
|
17661
|
+
async function withTrustedRead(database, options, callback) {
|
|
17662
|
+
if (!isActiveTransactionScopedAdapter(options?.transaction, database?.adapter)) {
|
|
17663
|
+
throw commandError2(
|
|
17664
|
+
"Trusted app-database reads require an active transaction.",
|
|
17665
|
+
"Start the trusted policy from the runtime-owned transition transaction.",
|
|
17666
|
+
"TRUSTED_READ_TRANSACTION_REQUIRED"
|
|
17667
|
+
);
|
|
17668
|
+
}
|
|
17669
|
+
if (!trustedReadPurposes.has(options?.purpose)) {
|
|
17670
|
+
throw commandError2(
|
|
17671
|
+
"Trusted app-database read purpose is invalid.",
|
|
17672
|
+
"Use a runtime-owned trusted policy purpose.",
|
|
17673
|
+
"INVALID_TRUSTED_READ_PURPOSE"
|
|
17674
|
+
);
|
|
17675
|
+
}
|
|
17676
|
+
const signal = options?.signal;
|
|
17677
|
+
const abortError = () => commandError2(
|
|
17678
|
+
"Trusted app-database read was aborted.",
|
|
17679
|
+
"Retry the runtime-owned trusted policy if cancellation was not intended.",
|
|
17680
|
+
"TRUSTED_READ_ABORTED"
|
|
17681
|
+
);
|
|
17682
|
+
if (signal?.aborted) throw abortError();
|
|
17683
|
+
const transactionDatabase = createTransactionDatabase(database, options.transaction);
|
|
17684
|
+
let active = true;
|
|
17685
|
+
const assertActive = () => {
|
|
17686
|
+
if (!active) {
|
|
17687
|
+
throw commandError2(
|
|
17688
|
+
"Trusted app-database read access is no longer active.",
|
|
17689
|
+
"Start a new runtime-owned trusted policy callback before reading app data.",
|
|
17690
|
+
"TRUSTED_READ_ACCESS_INACTIVE"
|
|
17691
|
+
);
|
|
17596
17692
|
}
|
|
17693
|
+
if (signal?.aborted) throw abortError();
|
|
17597
17694
|
};
|
|
17695
|
+
const context = {
|
|
17696
|
+
subject: options.subject,
|
|
17697
|
+
purpose: options.purpose,
|
|
17698
|
+
signal
|
|
17699
|
+
};
|
|
17700
|
+
grantPrivilegedDbAccess(context);
|
|
17701
|
+
const holder = createContextHolder(context);
|
|
17702
|
+
const db = createEndpointReadOnlyDatabaseApi(transactionDatabase, () => holder.current, assertActive);
|
|
17703
|
+
try {
|
|
17704
|
+
try {
|
|
17705
|
+
const result = await callback(db);
|
|
17706
|
+
assertActive();
|
|
17707
|
+
return result;
|
|
17708
|
+
} catch {
|
|
17709
|
+
if (signal?.aborted) throw abortError();
|
|
17710
|
+
throw commandError2(
|
|
17711
|
+
"Trusted app-database read failed.",
|
|
17712
|
+
"The runtime-owned trusted policy could not be evaluated.",
|
|
17713
|
+
"TRUSTED_READ_FAILED"
|
|
17714
|
+
);
|
|
17715
|
+
}
|
|
17716
|
+
} finally {
|
|
17717
|
+
active = false;
|
|
17718
|
+
revokePrivilegedDbAccess(context);
|
|
17719
|
+
}
|
|
17598
17720
|
}
|
|
17599
17721
|
function createEndpointTableApi(database, table, query = {}, contextGetter = null) {
|
|
17600
17722
|
return {
|
|
@@ -19376,14 +19498,13 @@ function createMutationContext(database, auth) {
|
|
|
19376
19498
|
};
|
|
19377
19499
|
return context;
|
|
19378
19500
|
}
|
|
19379
|
-
function createTeamJoinAdmissionContext(database, auth) {
|
|
19501
|
+
function createTeamJoinAdmissionContext(database, auth, trustedDb) {
|
|
19380
19502
|
const context = {
|
|
19381
|
-
auth,
|
|
19503
|
+
auth: Object.freeze({ ...auth }),
|
|
19382
19504
|
env: database.serverEnv,
|
|
19383
|
-
log: createEndpointLogger(database)
|
|
19505
|
+
log: createEndpointLogger(database),
|
|
19506
|
+
db: trustedDb
|
|
19384
19507
|
};
|
|
19385
|
-
const holder = createContextHolder(context);
|
|
19386
|
-
context.db = createEndpointReadOnlyDatabaseApi(database, () => holder.current);
|
|
19387
19508
|
return context;
|
|
19388
19509
|
}
|
|
19389
19510
|
function deferOrScheduleJobDispatch(database, queueDatabase, context = void 0) {
|
|
@@ -25655,7 +25776,7 @@ jobs:
|
|
|
25655
25776
|
}
|
|
25656
25777
|
|
|
25657
25778
|
// src/cli/cli-version.ts
|
|
25658
|
-
var CLI_VERSION = "0.8.
|
|
25779
|
+
var CLI_VERSION = "0.8.7";
|
|
25659
25780
|
|
|
25660
25781
|
// src/cli/sporades.ts
|
|
25661
25782
|
var SUPPORTED_TEMPLATES = new Set(CLIENT_TEMPLATES);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const CLI_VERSION = "0.8.
|
|
1
|
+
export declare const CLI_VERSION = "0.8.7";
|
|
2
2
|
//# sourceMappingURL=cli-version.d.ts.map
|
package/dist/cli/cli-version.js
CHANGED
|
@@ -2,6 +2,7 @@ import type { PathLike } from "node:fs";
|
|
|
2
2
|
type LooseRecord = Record<string, any>;
|
|
3
3
|
type RuntimeConfig = LooseRecord;
|
|
4
4
|
type RuntimeEnv = Record<string, string | undefined>;
|
|
5
|
+
export declare function isActiveTransactionScopedAdapter(value: any, owner?: any): boolean;
|
|
5
6
|
export declare function createRuntimeDatabaseAdapter(databasePath: any, serverEnv?: RuntimeEnv, config?: RuntimeConfig): Promise<LooseRecord>;
|
|
6
7
|
export declare function createRuntimeInspectionAdapter(databasePath: any, serverEnv?: RuntimeEnv, config?: RuntimeConfig): Promise<LooseRecord | null>;
|
|
7
8
|
export declare function createDatabaseDialect(spec: LooseRecord): LooseRecord;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database-runtime.d.ts","sourceRoot":"","sources":["../src/database-runtime.ts"],"names":[],"mappings":"AAiKA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAuBxC,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AACvC,KAAK,aAAa,GAAG,WAAW,CAAC;AACjC,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"database-runtime.d.ts","sourceRoot":"","sources":["../src/database-runtime.ts"],"names":[],"mappings":"AAiKA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAuBxC,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AACvC,KAAK,aAAa,GAAG,WAAW,CAAC;AACjC,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAkErD,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,CAAC,EAAE,GAAG,WAOvE;AAqDD,wBAAsB,4BAA4B,CAAC,YAAY,EAAE,GAAG,EAAE,SAAS,GAAE,UAAe,EAAE,MAAM,GAAE,aAAkB,GAAG,OAAO,CAAC,WAAW,CAAC,CAqBlJ;AAED,wBAAsB,8BAA8B,CAAC,YAAY,EAAE,GAAG,EAAE,SAAS,GAAE,UAAe,EAAE,MAAM,GAAE,aAAkB,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAS3J;AAkBD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,WAAW,GAAG,WAAW,CAyBpE;AA2BD,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,WAAW,GAAG,WAAW,CAa1E;AAMD,wBAAgB,sBAAsB,gBAMrC;AAED,wBAAgB,wBAAwB,gBAgBvC;AAED,wBAAgB,sBAAsB,gBASrC;AAGD,wBAAgB,qBAAqB,gBAoCpC;AAED,wBAAgB,uBAAuB,gBAmDtC;AAKD,wBAAgB,kCAAkC,CAAC,OAAO,EAAE,WAAW,GAAG,WAAW,CA0qBpF;AAED,wBAAsB,2BAA2B,CAAC,YAAY,EAAE,QAAQ,EAAE,OAAO,GAAE,WAAgB,wBAwFlG;AAED,wBAAsB,6BAA6B,CAAC,OAAO,EAAE;IAAE,GAAG,EAAE,GAAG,CAAC;CAAE,wBA+HzE;AAED,wBAAsB,wBAAwB,CAAC,GAAG,EAAE,GAAG;;eA+FxC,MAAM;;;;;;GAyEpB;AAyMD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,GAAE,GAAG,EAAO,UA2E/D;AAMD,wBAAsB,2BAA2B,CAAC,OAAO,EAAE;IAAE,GAAG,EAAE,GAAG,CAAC;IAAC,SAAS,EAAE,GAAG,CAAC;CAAE;;;;wBAiF1D,CAAC,kBAAkB,EAAE,WAAW,KAAK,GAAG;6BA8BnC,CAAC,OAAO,EAAE,WAAW,KAAK,GAAG;;cAtFlD,MAAM;iBAUH,MAAM;uBAGA,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE;uBAQtB,SAAS,EAAE;uBAGX,MAAM,EAAE;;;GAuG9B;AA4VD,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE,SAAS,MAAa,OAM7F;AAyED,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE;IAAE,OAAO,EAAE,GAAG,CAAC;IAAC,MAAM,EAAE,GAAG,CAAC;CAAE,gBAEhF;AAED,wBAAsB,YAAY,CAAC,QAAQ,EAAE;IAAE,OAAO,EAAE,GAAG,CAAC;IAAC,MAAM,EAAE,GAAG,CAAC;CAAE,gBAE1E;AAED,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE;IAAE,OAAO,EAAE,GAAG,CAAC;IAAC,MAAM,EAAE,GAAG,CAAC;CAAE,EAAE,GAAG,EAAE,GAAG,gBAExF;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,GAAG,YAuE1C;AAED,wBAAgB,eAAe,CAAC,UAAU,EAAE,GAAG,UAE9C"}
|