@fuzdev/fuz_app 0.85.1 → 0.87.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/CLAUDE.md +4 -2
- package/dist/actions/perform_action.d.ts.map +1 -1
- package/dist/actions/perform_action.js +8 -4
- package/dist/auth/role_grant_offer_actions.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_actions.js +4 -3
- package/dist/hono_context.d.ts +9 -7
- package/dist/hono_context.d.ts.map +1 -1
- package/dist/http/CLAUDE.md +14 -3
- package/dist/http/pending_effects.d.ts +43 -0
- package/dist/http/pending_effects.d.ts.map +1 -1
- package/dist/http/pending_effects.js +53 -0
- package/dist/http/route_spec.d.ts.map +1 -1
- package/dist/http/route_spec.js +14 -9
- package/dist/testing/CLAUDE.md +6 -3
- package/dist/testing/cross_backend/account_lifecycle.d.ts +5 -5
- package/dist/testing/cross_backend/account_lifecycle.d.ts.map +1 -1
- package/dist/testing/cross_backend/account_lifecycle.js +1 -1
- package/dist/testing/cross_backend/actor_lookup.d.ts +5 -5
- package/dist/testing/cross_backend/actor_lookup.d.ts.map +1 -1
- package/dist/testing/cross_backend/actor_search.d.ts +3 -3
- package/dist/testing/cross_backend/actor_search.d.ts.map +1 -1
- package/dist/testing/cross_backend/app_settings.d.ts +3 -3
- package/dist/testing/cross_backend/app_settings.d.ts.map +1 -1
- package/dist/testing/cross_backend/body_size.d.ts +10 -0
- package/dist/testing/cross_backend/body_size.d.ts.map +1 -0
- package/dist/testing/cross_backend/body_size.js +137 -0
- package/dist/testing/cross_backend/body_size_smuggling.d.ts +10 -0
- package/dist/testing/cross_backend/body_size_smuggling.d.ts.map +1 -0
- package/dist/testing/cross_backend/body_size_smuggling.js +138 -0
- package/dist/testing/cross_backend/capabilities.d.ts +20 -7
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_cross_helpers.d.ts +0 -11
- package/dist/testing/cross_backend/cell_cross_helpers.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_crud.d.ts +2 -2
- package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_crud.js +1 -1
- package/dist/testing/cross_backend/cell_grant_role.d.ts +2 -2
- package/dist/testing/cross_backend/cell_grant_role.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_grant_role.js +1 -1
- package/dist/testing/cross_backend/cell_relations.d.ts +2 -2
- package/dist/testing/cross_backend/cell_relations.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_relations.js +1 -1
- package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
- package/dist/testing/cross_backend/conformance_table.js +8 -6
- package/dist/testing/cross_backend/fact_serving.d.ts +2 -3
- package/dist/testing/cross_backend/fact_serving.d.ts.map +1 -1
- package/dist/testing/cross_backend/origin.d.ts +5 -5
- package/dist/testing/cross_backend/origin.d.ts.map +1 -1
- package/dist/testing/cross_backend/ready.d.ts +2 -7
- package/dist/testing/cross_backend/ready.d.ts.map +1 -1
- package/dist/testing/cross_backend/setup.d.ts +28 -0
- package/dist/testing/cross_backend/setup.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_backdoor.d.ts +2 -2
- package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
- package/dist/testing/cross_backend/ts_spine_backend_config.d.ts +5 -2
- package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
- package/dist/testing/cross_backend/ts_spine_backend_config.js +5 -2
- package/package.json +1 -1
package/dist/actions/CLAUDE.md
CHANGED
|
@@ -506,8 +506,10 @@ Per-message side-effect queues: `pending_effects` (eager) drains via
|
|
|
506
506
|
handlers via `emit_after_commit`) drains via `flush_post_commit_effects`.
|
|
507
507
|
Both flush in the same `try/finally` that releases the request controller,
|
|
508
508
|
so fire-and-forget audit / notification effects pushed by the handler
|
|
509
|
-
complete (or reject visibly) before the next message dispatches.
|
|
510
|
-
|
|
509
|
+
complete (or reject visibly) before the next message dispatches. The
|
|
510
|
+
deferred queue is **discarded on rollback** before it reaches that flush (a
|
|
511
|
+
rolled-back message fires no post-commit effect). See `http/CLAUDE.md`
|
|
512
|
+
§Pending Effects.
|
|
511
513
|
|
|
512
514
|
**Lifecycle hooks.** `on_socket_open({ws, connection_id, identity, notify, signal})`
|
|
513
515
|
fires after `transport.add_connection` but before the first message;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"perform_action.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/perform_action.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAGH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AACpD,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAEjD,OAAO,EAGN,KAAK,cAAc,EACnB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAC,KAAK,cAAc,EAAC,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"perform_action.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/perform_action.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAGH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AACpD,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAEjD,OAAO,EAGN,KAAK,cAAc,EACnB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAC,KAAK,cAAc,EAAC,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AAGpC,OAAO,EAEN,KAAK,gBAAgB,EAErB,KAAK,kBAAkB,EACvB,MAAM,oBAAoB,CAAC;AAY5B,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAEpD,OAAO,KAAK,EAA+B,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAE7E;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IAClC,kEAAkE;IAClE,MAAM,EAAE,SAAS,CAAC;IAClB,mGAAmG;IACnG,UAAU,EAAE,OAAO,CAAC;IACpB,sDAAsD;IACtD,UAAU,EAAE,gBAAgB,CAAC;IAC7B,yDAAyD;IACzD,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,uEAAuE;IACvE,eAAe,EAAE,cAAc,GAAG,IAAI,CAAC;IACvC,qEAAqE;IACrE,SAAS,EAAE,MAAM,CAAC;IAClB,oGAAoG;IACpG,MAAM,EAAE,WAAW,CAAC;IACpB,sFAAsF;IACtF,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAClD,uDAAuD;IACvD,aAAa,CAAC,EAAE,IAAI,CAAC;IACrB;;;;OAIG;IACH,MAAM,CAAC,EAAE;QAAC,eAAe,EAAE,cAAc,GAAG,IAAI,CAAA;KAAC,CAAC;CAClD;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IACjC,gGAAgG;IAChG,EAAE,EAAE,EAAE,CAAC;IACP;;;OAGG;IACH,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACtC;;;OAGG;IACH,mBAAmB,EAAE,KAAK,CAAC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACvD,gDAAgD;IAChD,GAAG,EAAE,MAAM,CAAC;IACZ,kEAAkE;IAClE,sBAAsB,EAAE,WAAW,GAAG,IAAI,CAAC;IAC3C,uEAAuE;IACvE,2BAA2B,EAAE,WAAW,GAAG,IAAI,CAAC;CAChD;AAED;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAC5B;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAC,GAC7B;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,kBAAkB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAC,CAAC;AAE9D;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,GAC1B,OAAO,kBAAkB,EACzB,MAAM,iBAAiB,KACrB,OAAO,CAAC,mBAAmB,CA6J7B,CAAC;AAoFF;;;GAGG;AACH,eAAO,MAAM,iCAAiC,GAC7C,IAAI,gBAAgB,EACpB,QAAQ,mBAAmB,KACzB;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,gBAAgB,CAAA;CAAC,GAAG,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAC,GAAG;IAAC,KAAK,EAAE,kBAAkB,CAAA;CAAC,CAK5F,CAAC"}
|
|
@@ -41,6 +41,7 @@ import { DEV } from 'esm-env';
|
|
|
41
41
|
import { apply_authorization_phase, has_any_scoped_role, } from '../auth/request_context.js';
|
|
42
42
|
import {} from '../hono_context.js';
|
|
43
43
|
import { is_void_schema } from '../http/schema_helpers.js';
|
|
44
|
+
import { dispatch_with_post_commit_rollback } from '../http/pending_effects.js';
|
|
44
45
|
import { JSONRPC_VERSION, } from '../http/jsonrpc.js';
|
|
45
46
|
import { jsonrpc_error_messages, jsonrpc_error_code_to_http_status, http_status_to_jsonrpc_error_code, JSONRPC_ERROR_CODES, } from '../http/jsonrpc_errors.js';
|
|
46
47
|
import { ERROR_AUTHENTICATION_REQUIRED, ERROR_INSUFFICIENT_PERMISSIONS, ERROR_CREDENTIAL_TYPE_REQUIRED, } from '../http/error_schemas.js';
|
|
@@ -159,11 +160,14 @@ export const perform_action = async (input, deps) => {
|
|
|
159
160
|
}
|
|
160
161
|
return { kind: 'ok', result: output };
|
|
161
162
|
};
|
|
163
|
+
// Dispatch — transaction for mutations, pool for reads. Wrapped so a thrown
|
|
164
|
+
// handler discards the post-commit effects it queued (`emit_after_commit`):
|
|
165
|
+
// its transaction rolled back, so those effects must not announce state that
|
|
166
|
+
// never committed. The eager `pending_effects` queue survives rollback
|
|
167
|
+
// (attempt audits). See `dispatch_with_post_commit_rollback` (canonical
|
|
168
|
+
// contract) and docs/security.md §"Post-commit WS fan-out".
|
|
162
169
|
try {
|
|
163
|
-
|
|
164
|
-
return await db.transaction((tx) => execute(tx));
|
|
165
|
-
}
|
|
166
|
-
return await execute(db);
|
|
170
|
+
return await dispatch_with_post_commit_rollback(post_commit_effects, () => use_transaction ? db.transaction((tx) => execute(tx)) : execute(db));
|
|
167
171
|
}
|
|
168
172
|
catch (err) {
|
|
169
173
|
// Duck-type check: Error with numeric `code` signals a JSON-RPC error.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role_grant_offer_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_offer_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,OAAO,EAGN,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EAIN,KAAK,gBAAgB,EACrB,MAAM,kBAAkB,CAAC;AA0B1B,OAAO,EAA4C,KAAK,cAAc,EAAC,MAAM,sBAAsB,CAAC;AACpG,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAEhD,OAAO,EAON,KAAK,kBAAkB,EACvB,MAAM,qCAAqC,CAAC;AAiC7C;;;;;;;;GAQG;AACH,MAAM,MAAM,6BAA6B,GAAG,CAC3C,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAAC,EACrE,IAAI,EAAE,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAAC,EACnC,GAAG,EAAE,aAAa,KACd,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEhC,qDAAqD;AACrD,MAAM,WAAW,2BAA2B;IAC3C;;;;;OAKG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,0FAA0F;IAC1F,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,6BAA6B,CAAC;CAC1C;AA6BD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,yBAAyB,EAAE,6BAavC,CAAC;AAIF;;;;;;;GAOG;AACH,eAAO,MAAM,+BAA+B,GAC3C,MAAM,IAAI,CAAC,gBAAgB,EAAE,KAAK,GAAG,OAAO,CAAC,GAAG;IAC/C,mBAAmB,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;CAChD,EACD,UAAS,2BAAgC,KACvC,KAAK,CAAC,SAAS,
|
|
1
|
+
{"version":3,"file":"role_grant_offer_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_offer_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,OAAO,EAGN,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EAIN,KAAK,gBAAgB,EACrB,MAAM,kBAAkB,CAAC;AA0B1B,OAAO,EAA4C,KAAK,cAAc,EAAC,MAAM,sBAAsB,CAAC;AACpG,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAEhD,OAAO,EAON,KAAK,kBAAkB,EACvB,MAAM,qCAAqC,CAAC;AAiC7C;;;;;;;;GAQG;AACH,MAAM,MAAM,6BAA6B,GAAG,CAC3C,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAAC,EACrE,IAAI,EAAE,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAAC,EACnC,GAAG,EAAE,aAAa,KACd,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEhC,qDAAqD;AACrD,MAAM,WAAW,2BAA2B;IAC3C;;;;;OAKG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,0FAA0F;IAC1F,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,6BAA6B,CAAC;CAC1C;AA6BD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,yBAAyB,EAAE,6BAavC,CAAC;AAIF;;;;;;;GAOG;AACH,eAAO,MAAM,+BAA+B,GAC3C,MAAM,IAAI,CAAC,gBAAgB,EAAE,KAAK,GAAG,OAAO,CAAC,GAAG;IAC/C,mBAAmB,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;CAChD,EACD,UAAS,2BAAgC,KACvC,KAAK,CAAC,SAAS,CAodjB,CAAC"}
|
|
@@ -248,9 +248,10 @@ export const create_role_grant_offer_actions = (deps, options = {}) => {
|
|
|
248
248
|
}));
|
|
249
249
|
// Audit events are written in-transaction by query_accept_offer; wire
|
|
250
250
|
// them through `audit.notify` post-commit so SSE/WS broadcasts fire.
|
|
251
|
-
// WS notifications
|
|
252
|
-
// grantor sees "accepted" and each superseded grantor sees
|
|
253
|
-
//
|
|
251
|
+
// WS notifications ride the same deferred post-commit thunk so the
|
|
252
|
+
// grantor sees "accepted" and each superseded grantor sees "supersede"
|
|
253
|
+
// only once the accept has durably committed — and never if it rolls
|
|
254
|
+
// back (the dispatch site discards `post_commit_effects` on rollback).
|
|
254
255
|
emit_after_commit(ctx, () => {
|
|
255
256
|
fan_out_audit_events(result.audit_events, audit);
|
|
256
257
|
if (notification_sender && grantor_account_id) {
|
package/dist/hono_context.d.ts
CHANGED
|
@@ -99,13 +99,15 @@ declare module 'hono' {
|
|
|
99
99
|
*/
|
|
100
100
|
pending_effects: Array<Promise<void>>;
|
|
101
101
|
/**
|
|
102
|
-
* Post-commit thunks pushed via `emit_after_commit(ctx, fn)`. The
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
102
|
+
* Post-commit thunks pushed via `emit_after_commit(ctx, fn)`. The flush
|
|
103
|
+
* middleware invokes each thunk after the handler returns — never inline
|
|
104
|
+
* — so notifications (WS sends, etc.) cannot fire mid-transaction; the
|
|
105
|
+
* queue is also discarded when the handler's transaction rolls back, so a
|
|
106
|
+
* thunk never fires for state that never committed. Full contract (the
|
|
107
|
+
* eager-`pending_effects` contrast, the discard mechanism, the flush
|
|
108
|
+
* safety net) lives on `dispatch_with_post_commit_rollback` /
|
|
109
|
+
* `emit_after_commit` in `http/pending_effects.ts`. Producers do not push
|
|
110
|
+
* raw thunks directly. Initialized by `create_app_server`; in test mode
|
|
109
111
|
* (`await_pending_effects: true`), every thunk completes before the
|
|
110
112
|
* response returns.
|
|
111
113
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hono_context.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/hono_context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAO9D;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,mDAInB,CAAC;AAEX,yDAAyD;AACzD,eAAO,MAAM,cAAc;;;;EAA2B,CAAC;AACvD,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,0DAA0D;AAC1D,eAAO,MAAM,mBAAmB,oBAAoB,CAAC;AAErD,qEAAqE;AACrE,eAAO,MAAM,qBAAqB,sBAAsB,CAAC;AAEzD;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,oBAAoB,CAAC;AAEhD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,uBAAuB,wBAAwB,CAAC;AAE7D,OAAO,QAAQ,MAAM,CAAC;IACrB,UAAU,kBAAkB;QAC3B,+DAA+D;QAC/D,SAAS,EAAE,MAAM,CAAC;QAClB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;QAC/B,eAAe,EAAE,cAAc,GAAG,IAAI,CAAC;QACvC,eAAe,EAAE,OAAO,CAAC;QACzB,gBAAgB,EAAE,OAAO,CAAC;QAC1B,eAAe,EAAE,OAAO,CAAC;QACzB,2FAA2F;QAC3F,eAAe,EAAE,cAAc,GAAG,IAAI,CAAC;QACvC;;;;;WAKG;QACH,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;QAC/B;;;;;WAKG;QACH,uBAAuB,EAAE,MAAM,GAAG,IAAI,CAAC;QACvC;;;;;;WAMG;QACH,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;QACjC;;;;;;;WAOG;QACH,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QACtC
|
|
1
|
+
{"version":3,"file":"hono_context.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/hono_context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAO9D;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,mDAInB,CAAC;AAEX,yDAAyD;AACzD,eAAO,MAAM,cAAc;;;;EAA2B,CAAC;AACvD,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,0DAA0D;AAC1D,eAAO,MAAM,mBAAmB,oBAAoB,CAAC;AAErD,qEAAqE;AACrE,eAAO,MAAM,qBAAqB,sBAAsB,CAAC;AAEzD;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,oBAAoB,CAAC;AAEhD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,uBAAuB,wBAAwB,CAAC;AAE7D,OAAO,QAAQ,MAAM,CAAC;IACrB,UAAU,kBAAkB;QAC3B,+DAA+D;QAC/D,SAAS,EAAE,MAAM,CAAC;QAClB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;QAC/B,eAAe,EAAE,cAAc,GAAG,IAAI,CAAC;QACvC,eAAe,EAAE,OAAO,CAAC;QACzB,gBAAgB,EAAE,OAAO,CAAC;QAC1B,eAAe,EAAE,OAAO,CAAC;QACzB,2FAA2F;QAC3F,eAAe,EAAE,cAAc,GAAG,IAAI,CAAC;QACvC;;;;;WAKG;QACH,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;QAC/B;;;;;WAKG;QACH,uBAAuB,EAAE,MAAM,GAAG,IAAI,CAAC;QACvC;;;;;;WAMG;QACH,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;QACjC;;;;;;;WAOG;QACH,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QACtC;;;;;;;;;;;;WAYG;QACH,mBAAmB,EAAE,KAAK,CAAC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QACvD;;;;WAIG;QACH,mBAAmB,EAAE,OAAO,CAAC;KAC7B;CACD"}
|
package/dist/http/CLAUDE.md
CHANGED
|
@@ -29,7 +29,7 @@ effects, see ../../../docs/architecture.md.
|
|
|
29
29
|
- `http/jsonrpc_helpers.ts` — message builders, type guards, input/result normalizers.
|
|
30
30
|
- `http/common_routes.ts` — health check + readiness probe (`/ready` schema-drift deploy gate) + authenticated server-status + surface route specs.
|
|
31
31
|
- `http/db_routes.ts` — generic keeper-only table browser route specs (public schema).
|
|
32
|
-
- `http/pending_effects.ts` — `emit_after_commit` + `flush_pending_effects` + `flush_post_commit_effects` + `EmitAfterCommitContext`.
|
|
32
|
+
- `http/pending_effects.ts` — `emit_after_commit` + `dispatch_with_post_commit_rollback` (the shared rollback-discard wrapper both dispatch sites use) + `flush_pending_effects` + `flush_post_commit_effects` + `EmitAfterCommitContext`.
|
|
33
33
|
|
|
34
34
|
## Route Spec System
|
|
35
35
|
|
|
@@ -427,7 +427,7 @@ this work that's already started"; thunk pushers say "run this after the
|
|
|
427
427
|
handler returns" — and burying both behind one field made
|
|
428
428
|
`c.var.pending_effects.push(x)` ambiguous at the call site. Splitting turns the field name into the contract.
|
|
429
429
|
|
|
430
|
-
### Why `emit_after_commit` defers
|
|
430
|
+
### Why `emit_after_commit` defers — and discards on rollback
|
|
431
431
|
|
|
432
432
|
The thunk shape is **load-bearing for correctness**. Pushing
|
|
433
433
|
`Promise.resolve().then(fn)` onto an eager queue — what `emit_after_commit`
|
|
@@ -437,6 +437,17 @@ would leak a notification for state that never landed. The thunk defers
|
|
|
437
437
|
the work to flush time; the `try/finally` in the flush middleware runs
|
|
438
438
|
after the handler (and any wrapping transaction) returns.
|
|
439
439
|
|
|
440
|
+
Deferral alone isn't enough: the flush runs whether the handler returned or
|
|
441
|
+
threw, so a thrown handler (rolling back its transaction) would still fire the
|
|
442
|
+
thunk it queued. So the contract is **two-sided**: both dispatch sites
|
|
443
|
+
(`http/route_spec.ts`, `actions/perform_action.ts`) wrap their handler in the
|
|
444
|
+
shared `dispatch_with_post_commit_rollback` helper, which discards
|
|
445
|
+
`post_commit_effects` on a handler throw — `emit_after_commit` thus reads as
|
|
446
|
+
**"run iff the wrapping transaction commits."** The eager `pending_effects`
|
|
447
|
+
queue is the deliberate opposite (survives rollback — attempt audits). The full
|
|
448
|
+
two-sided contract + Rust-twin parity note live on the helper's TSDoc in
|
|
449
|
+
`http/pending_effects.ts`.
|
|
450
|
+
|
|
440
451
|
```typescript
|
|
441
452
|
emit_after_commit(ctx, () => notification_sender.send_to_account(account_id, msg));
|
|
442
453
|
```
|
|
@@ -449,7 +460,7 @@ and any side effect that must run only after the transaction commits.
|
|
|
449
460
|
|
|
450
461
|
- **The flush owns the safety net.** `flush_post_commit_effects` wraps every thunk in `try/catch` and routes errors through `ctx.log.error`, so one failing send cannot starve sibling effects in the same batch nor corrupt the already-committed response. Per-thunk `try/catch` inside `emit_after_commit` would skip directly-pushed thunks (e.g. tests); centralizing the wrap in the flush closes that gap.
|
|
451
462
|
- **Test mode (`await_pending_effects: true`) flushes both queues.** Eager: `await flush_pending_effects(pending_effects, log)`. Deferred: `await flush_post_commit_effects(post_commit_effects, log)`. Both complete before the response returns. Production mode wraps the same helpers in `void ...` and threads `on_effect_error` into `flush_pending_effects`'s `on_rejection` callback for fan-out.
|
|
452
|
-
- **
|
|
463
|
+
- **Drain at the outer flush; discard at the dispatch site.** The successful-path _drain_ of both queues stays at the outer flush middleware (`server/app_server.ts`) + the per-message WS flush — adjacent, one location, no per-wrapper drain timing. What the dispatch sites (route-spec wrapper / `perform_action`) own is the _rollback discard_, via the shared `dispatch_with_post_commit_rollback` helper: on a handler throw they truncate `post_commit_effects` before the outer flush ever sees it, so the flush only drains effects from a committed handler. The eager `pending_effects` queue is never truncated — it drains regardless (attempt audits survive rollback).
|
|
453
464
|
- Structurally satisfied by both `RouteContext` (HTTP) and `ActionContext` (RPC + WS) — they share the `{log, post_commit_effects}` shape, which is why this helper lives in `http/` rather than `actions/` or `auth/`.
|
|
454
465
|
|
|
455
466
|
WS sends are **not** wrapped by `create_validated_broadcaster` (that only
|
|
@@ -17,6 +17,15 @@
|
|
|
17
17
|
* returns. Used for WS sends and any work that must observe a committed
|
|
18
18
|
* transaction.
|
|
19
19
|
*
|
|
20
|
+
* **Discard on rollback.** A `post_commit_effects` thunk is discarded if the
|
|
21
|
+
* handler's transaction rolls back (a thrown handler) — it must not announce
|
|
22
|
+
* state that never committed. Both dispatch sites enforce this by wrapping
|
|
23
|
+
* their handler in `dispatch_with_post_commit_rollback` (see its TSDoc below
|
|
24
|
+
* for the full two-sided contract and the Rust-twin parity note). The eager
|
|
25
|
+
* `pending_effects` queue is the deliberate opposite: its pool writes run
|
|
26
|
+
* outside the transaction and intentionally **survive** rollback (attempt
|
|
27
|
+
* audits).
|
|
28
|
+
*
|
|
20
29
|
* The split exists because the two shapes encode different contracts:
|
|
21
30
|
* eager pushers are saying "wait for this work that's already started";
|
|
22
31
|
* thunk pushers are saying "run this after the handler returns." Burying
|
|
@@ -55,11 +64,45 @@ export interface EmitAfterCommitContext {
|
|
|
55
64
|
* The flush owns the per-thunk `try/catch` + `log.error` so any
|
|
56
65
|
* directly-pushed thunk (tests included) cannot escape the safety net.
|
|
57
66
|
*
|
|
67
|
+
* Deferral is only half the contract: a queued thunk is also **discarded if
|
|
68
|
+
* the handler's transaction rolls back** — via `dispatch_with_post_commit_rollback`
|
|
69
|
+
* (see its TSDoc). So `fn` runs iff the wrapping transaction commits, never for
|
|
70
|
+
* state that rolled back. Rollback-resilient writes (attempt audits that must
|
|
71
|
+
* land even when the handler fails) belong on the eager `pending_effects` queue
|
|
72
|
+
* instead.
|
|
73
|
+
*
|
|
58
74
|
* @param ctx - context carrying `log` and the `post_commit_effects` queue
|
|
59
75
|
* @param fn - side effect to run after commit; may return `void` or `Promise<void>`
|
|
60
76
|
* @mutates `ctx.post_commit_effects` - appends `fn` verbatim
|
|
61
77
|
*/
|
|
62
78
|
export declare const emit_after_commit: (ctx: EmitAfterCommitContext, fn: () => void | Promise<void>) => void;
|
|
79
|
+
/**
|
|
80
|
+
* Run a handler dispatch (the handler plus its wrapping `db.transaction`),
|
|
81
|
+
* discarding any `post_commit_effects` it queued via `emit_after_commit` if it
|
|
82
|
+
* throws. A thrown handler rolls back its transaction, so firing those deferred
|
|
83
|
+
* effects would announce state that never committed. On any throw the queue is
|
|
84
|
+
* truncated back to its pre-dispatch depth — **not** cleared, so entries a
|
|
85
|
+
* surrounding scope pre-seeded survive — and the error re-thrown unchanged.
|
|
86
|
+
*
|
|
87
|
+
* The eager `pending_effects` queue is deliberately never touched here: its
|
|
88
|
+
* pool writes run outside the transaction and intentionally survive rollback
|
|
89
|
+
* (attempt audits). `emit_after_commit` thus reads as "run iff the wrapping
|
|
90
|
+
* transaction commits."
|
|
91
|
+
*
|
|
92
|
+
* Both dispatch sites — the REST route wrapper (`http/route_spec.ts`) and the
|
|
93
|
+
* action dispatcher (`actions/perform_action.ts`, the RPC + WS path) — wrap
|
|
94
|
+
* their handler call in this so the discard contract lives in one place. The
|
|
95
|
+
* Rust `fuz_actions` spine pins the same contract.
|
|
96
|
+
*
|
|
97
|
+
* `post_commit_effects` is `undefined` when a handler runs without the
|
|
98
|
+
* app-server pending-effects middleware (bare route / dispatch harnesses):
|
|
99
|
+
* absent ⇒ nothing queued ⇒ nothing to discard.
|
|
100
|
+
*
|
|
101
|
+
* @param post_commit_effects - the deferred queue to truncate on throw, or `undefined`
|
|
102
|
+
* @param dispatch - invokes the handler (and any wrapping transaction)
|
|
103
|
+
* @mutates `post_commit_effects` - truncated to its pre-dispatch depth on throw
|
|
104
|
+
*/
|
|
105
|
+
export declare const dispatch_with_post_commit_rollback: <T>(post_commit_effects: Array<() => void | Promise<void>> | undefined, dispatch: () => T | Promise<T>) => Promise<Awaited<T>>;
|
|
63
106
|
/**
|
|
64
107
|
* Drain an eager `pending_effects` queue: `Promise.allSettled` the
|
|
65
108
|
* in-flight handles, route every rejection through `log.error`, and
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pending_effects.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/pending_effects.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"pending_effects.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/pending_effects.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,mBAAmB,EAAE,KAAK,CAAC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;CACvD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,iBAAiB,GAC7B,KAAK,sBAAsB,EAC3B,IAAI,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAC5B,IAEF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,kCAAkC,GAAU,CAAC,EACzD,qBAAqB,KAAK,CAAC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,SAAS,EAClE,UAAU,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,KAC5B,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAQpB,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,qBAAqB,GACjC,SAAS,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,EACrC,KAAK,MAAM,EACX,eAAe,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,KACtC,OAAO,CAAC,IAAI,CASd,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,GACrC,SAAS,aAAa,CAAC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,EAClD,KAAK,MAAM,KACT,OAAO,CAAC,IAAI,CAiBd,CAAC"}
|
|
@@ -17,6 +17,15 @@
|
|
|
17
17
|
* returns. Used for WS sends and any work that must observe a committed
|
|
18
18
|
* transaction.
|
|
19
19
|
*
|
|
20
|
+
* **Discard on rollback.** A `post_commit_effects` thunk is discarded if the
|
|
21
|
+
* handler's transaction rolls back (a thrown handler) — it must not announce
|
|
22
|
+
* state that never committed. Both dispatch sites enforce this by wrapping
|
|
23
|
+
* their handler in `dispatch_with_post_commit_rollback` (see its TSDoc below
|
|
24
|
+
* for the full two-sided contract and the Rust-twin parity note). The eager
|
|
25
|
+
* `pending_effects` queue is the deliberate opposite: its pool writes run
|
|
26
|
+
* outside the transaction and intentionally **survive** rollback (attempt
|
|
27
|
+
* audits).
|
|
28
|
+
*
|
|
20
29
|
* The split exists because the two shapes encode different contracts:
|
|
21
30
|
* eager pushers are saying "wait for this work that's already started";
|
|
22
31
|
* thunk pushers are saying "run this after the handler returns." Burying
|
|
@@ -45,6 +54,13 @@
|
|
|
45
54
|
* The flush owns the per-thunk `try/catch` + `log.error` so any
|
|
46
55
|
* directly-pushed thunk (tests included) cannot escape the safety net.
|
|
47
56
|
*
|
|
57
|
+
* Deferral is only half the contract: a queued thunk is also **discarded if
|
|
58
|
+
* the handler's transaction rolls back** — via `dispatch_with_post_commit_rollback`
|
|
59
|
+
* (see its TSDoc). So `fn` runs iff the wrapping transaction commits, never for
|
|
60
|
+
* state that rolled back. Rollback-resilient writes (attempt audits that must
|
|
61
|
+
* land even when the handler fails) belong on the eager `pending_effects` queue
|
|
62
|
+
* instead.
|
|
63
|
+
*
|
|
48
64
|
* @param ctx - context carrying `log` and the `post_commit_effects` queue
|
|
49
65
|
* @param fn - side effect to run after commit; may return `void` or `Promise<void>`
|
|
50
66
|
* @mutates `ctx.post_commit_effects` - appends `fn` verbatim
|
|
@@ -52,6 +68,43 @@
|
|
|
52
68
|
export const emit_after_commit = (ctx, fn) => {
|
|
53
69
|
ctx.post_commit_effects.push(fn);
|
|
54
70
|
};
|
|
71
|
+
/**
|
|
72
|
+
* Run a handler dispatch (the handler plus its wrapping `db.transaction`),
|
|
73
|
+
* discarding any `post_commit_effects` it queued via `emit_after_commit` if it
|
|
74
|
+
* throws. A thrown handler rolls back its transaction, so firing those deferred
|
|
75
|
+
* effects would announce state that never committed. On any throw the queue is
|
|
76
|
+
* truncated back to its pre-dispatch depth — **not** cleared, so entries a
|
|
77
|
+
* surrounding scope pre-seeded survive — and the error re-thrown unchanged.
|
|
78
|
+
*
|
|
79
|
+
* The eager `pending_effects` queue is deliberately never touched here: its
|
|
80
|
+
* pool writes run outside the transaction and intentionally survive rollback
|
|
81
|
+
* (attempt audits). `emit_after_commit` thus reads as "run iff the wrapping
|
|
82
|
+
* transaction commits."
|
|
83
|
+
*
|
|
84
|
+
* Both dispatch sites — the REST route wrapper (`http/route_spec.ts`) and the
|
|
85
|
+
* action dispatcher (`actions/perform_action.ts`, the RPC + WS path) — wrap
|
|
86
|
+
* their handler call in this so the discard contract lives in one place. The
|
|
87
|
+
* Rust `fuz_actions` spine pins the same contract.
|
|
88
|
+
*
|
|
89
|
+
* `post_commit_effects` is `undefined` when a handler runs without the
|
|
90
|
+
* app-server pending-effects middleware (bare route / dispatch harnesses):
|
|
91
|
+
* absent ⇒ nothing queued ⇒ nothing to discard.
|
|
92
|
+
*
|
|
93
|
+
* @param post_commit_effects - the deferred queue to truncate on throw, or `undefined`
|
|
94
|
+
* @param dispatch - invokes the handler (and any wrapping transaction)
|
|
95
|
+
* @mutates `post_commit_effects` - truncated to its pre-dispatch depth on throw
|
|
96
|
+
*/
|
|
97
|
+
export const dispatch_with_post_commit_rollback = async (post_commit_effects, dispatch) => {
|
|
98
|
+
const depth = post_commit_effects?.length ?? 0;
|
|
99
|
+
try {
|
|
100
|
+
return await dispatch();
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
if (post_commit_effects)
|
|
104
|
+
post_commit_effects.length = depth;
|
|
105
|
+
throw err;
|
|
106
|
+
}
|
|
107
|
+
};
|
|
55
108
|
/**
|
|
56
109
|
* Drain an eager `pending_effects` queue: `Promise.allSettled` the
|
|
57
110
|
* in-flight handles, route every rejection through `log.error`, and
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route_spec.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/route_spec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAC,OAAO,EAAW,IAAI,EAAE,iBAAiB,EAAC,MAAM,MAAM,CAAC;AACpE,OAAO,KAAK,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAE3B,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AACpC,OAAO,EACN,KAAK,iBAAiB,EACtB,KAAK,YAAY,EAKjB,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"route_spec.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/route_spec.ts"],"names":[],"mappings":"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"}
|
package/dist/http/route_spec.js
CHANGED
|
@@ -16,6 +16,7 @@ import { DEV } from 'esm-env';
|
|
|
16
16
|
import { ERROR_INVALID_JSON_BODY, ERROR_INVALID_REQUEST_BODY, ERROR_INVALID_ROUTE_PARAMS, ERROR_INVALID_QUERY_PARAMS, } from './error_schemas.js';
|
|
17
17
|
import { ThrownJsonrpcError, jsonrpc_error_code_to_http_status, jsonrpc_error_code_to_name, } from './jsonrpc_errors.js';
|
|
18
18
|
import { is_null_schema, merge_error_schemas } from './schema_helpers.js';
|
|
19
|
+
import { dispatch_with_post_commit_rollback } from './pending_effects.js';
|
|
19
20
|
import { assert_route_auth_acting_biconditional } from './auth_shape.js';
|
|
20
21
|
export function get_route_input(c, _schema) {
|
|
21
22
|
return c.get('validated_input');
|
|
@@ -321,17 +322,21 @@ export const apply_route_specs = (app, specs, resolve_auth_guards, log, db, auth
|
|
|
321
322
|
// Step 1: adapt RouteHandler → Handler (construct RouteContext, call spec.handler)
|
|
322
323
|
const use_transaction = spec.transaction ?? spec.method !== 'GET';
|
|
323
324
|
const inner = spec.handler;
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
325
|
+
// Discard post-commit effects a handler queued (`emit_after_commit`) if it
|
|
326
|
+
// throws — a thrown handler rolls back its wrapping transaction, so firing
|
|
327
|
+
// those effects would announce state that never committed. The eager
|
|
328
|
+
// `pending_effects` queue (attempt audits, run outside the transaction) is
|
|
329
|
+
// untouched. Shared with the action dispatcher (actions/perform_action.ts)
|
|
330
|
+
// via `dispatch_with_post_commit_rollback`.
|
|
331
|
+
let handler = (c) => {
|
|
332
|
+
const route_context = {
|
|
327
333
|
pending_effects: c.var.pending_effects,
|
|
328
334
|
post_commit_effects: c.var.post_commit_effects,
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
db,
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
});
|
|
335
|
+
};
|
|
336
|
+
return dispatch_with_post_commit_rollback(c.var.post_commit_effects, () => use_transaction
|
|
337
|
+
? db.transaction(async (tx) => inner(c, { db: tx, ...route_context }))
|
|
338
|
+
: inner(c, { db, ...route_context }));
|
|
339
|
+
};
|
|
335
340
|
// Step 2: output validation
|
|
336
341
|
handler = wrap_output_validation(handler, spec.output, merged_errors, log);
|
|
337
342
|
// Step 3: error catch layer
|
package/dist/testing/CLAUDE.md
CHANGED
|
@@ -1043,9 +1043,12 @@ stays **off** `create_spine_surface_spec`, and these dedicated suites are the
|
|
|
1043
1043
|
cell validators (`describe_standard_cross_process_tests`' generic round-trip
|
|
1044
1044
|
never sees them). Both parse every success response against the verb's Zod
|
|
1045
1045
|
**output** schema, so a TS↔Rust envelope drift fails the suite — not just a
|
|
1046
|
-
payload-field drift. Call-site primitives (`
|
|
1047
|
-
`expect_output`
|
|
1048
|
-
`cross_backend/
|
|
1046
|
+
payload-field drift. Call-site primitives (`cross_rpc_call` / `error_reason` /
|
|
1047
|
+
`expect_output`) live in `cross_backend/cell_cross_helpers.ts`; the shared
|
|
1048
|
+
options shape is `RpcPathCrossSuiteOptions` from `cross_backend/setup.ts` (the
|
|
1049
|
+
neutral base every RPC-dispatched imperative cross suite aliases — origin,
|
|
1050
|
+
body-size, actor lookup/search, account lifecycle, app settings, testing
|
|
1051
|
+
backdoor, cell).
|
|
1049
1052
|
|
|
1050
1053
|
- **`describe_cell_crud_cross_tests`** (gates on `capabilities.cell_crud`) —
|
|
1051
1054
|
the create → get → update → delete → list lifecycle threading the id, plus
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import '../assert_dev_env.js';
|
|
2
|
-
import {
|
|
2
|
+
import type { RpcPathCrossSuiteOptions } from './setup.js';
|
|
3
3
|
/**
|
|
4
|
-
* Options for the account-lifecycle parity suite.
|
|
5
|
-
*
|
|
6
|
-
* `
|
|
4
|
+
* Options for the account-lifecycle parity suite. The standard
|
|
5
|
+
* RPC-dispatched cross-suite shape (`setup_test` / `capabilities` /
|
|
6
|
+
* `rpc_path`); aliases `RpcPathCrossSuiteOptions` rather than duplicating.
|
|
7
7
|
*/
|
|
8
|
-
export type AccountLifecycleCrossTestOptions =
|
|
8
|
+
export type AccountLifecycleCrossTestOptions = RpcPathCrossSuiteOptions;
|
|
9
9
|
export declare const describe_account_lifecycle_cross_tests: (options: AccountLifecycleCrossTestOptions) => void;
|
|
10
10
|
//# sourceMappingURL=account_lifecycle.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"account_lifecycle.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/account_lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"account_lifecycle.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/account_lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAkC9B,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,YAAY,CAAC;AAGzD;;;;GAIG;AACH,MAAM,MAAM,gCAAgC,GAAG,wBAAwB,CAAC;AAExE,eAAO,MAAM,sCAAsC,GAClD,SAAS,gCAAgC,KACvC,IA+TF,CAAC"}
|
|
@@ -21,7 +21,7 @@ import { describe, assert } from 'vitest';
|
|
|
21
21
|
import { AccountDeleteOutput, AccountUndeleteOutput, AccountPurgeOutput, AdminAccountListOutput, AuditLogListOutput, ERROR_CANNOT_DELETE_KEEPER, } from '../../auth/admin_action_specs.js';
|
|
22
22
|
import { ERROR_ACCOUNT_NOT_FOUND, ERROR_AUTHENTICATION_REQUIRED } from '../../http/error_schemas.js';
|
|
23
23
|
import { test_if } from './capabilities.js';
|
|
24
|
-
import { cross_rpc_call, error_reason, expect_output
|
|
24
|
+
import { cross_rpc_call, error_reason, expect_output } from './cell_cross_helpers.js';
|
|
25
25
|
import { SPINE_RPC_PATH } from './default_spine_surface.js';
|
|
26
26
|
export const describe_account_lifecycle_cross_tests = (options) => {
|
|
27
27
|
const { setup_test, capabilities } = options;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import '../assert_dev_env.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { RpcPathCrossSuiteOptions } from './setup.js';
|
|
3
3
|
/**
|
|
4
|
-
* Options for the actor-lookup parity suite.
|
|
5
|
-
*
|
|
6
|
-
* `
|
|
4
|
+
* Options for the actor-lookup parity suite. The standard RPC-dispatched
|
|
5
|
+
* cross-suite shape (`setup_test` / `capabilities` / `rpc_path`); aliases
|
|
6
|
+
* the shared `RpcPathCrossSuiteOptions` rather than minting a duplicate.
|
|
7
7
|
*/
|
|
8
|
-
export type ActorLookupCrossTestOptions =
|
|
8
|
+
export type ActorLookupCrossTestOptions = RpcPathCrossSuiteOptions;
|
|
9
9
|
export declare const describe_actor_lookup_cross_tests: (options: ActorLookupCrossTestOptions) => void;
|
|
10
10
|
//# sourceMappingURL=actor_lookup.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actor_lookup.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/actor_lookup.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAqC9B,OAAO,KAAK,EAAC,
|
|
1
|
+
{"version":3,"file":"actor_lookup.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/actor_lookup.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAqC9B,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,YAAY,CAAC;AAGzD;;;;GAIG;AACH,MAAM,MAAM,2BAA2B,GAAG,wBAAwB,CAAC;AASnE,eAAO,MAAM,iCAAiC,GAAI,SAAS,2BAA2B,KAAG,IAkDxF,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import '../assert_dev_env.js';
|
|
2
|
-
import type {
|
|
3
|
-
/** Options for the actor-search parity suite (
|
|
4
|
-
export type ActorSearchCrossTestOptions =
|
|
2
|
+
import type { RpcPathCrossSuiteOptions } from './setup.js';
|
|
3
|
+
/** Options for the actor-search parity suite (the standard RPC-dispatched shape). */
|
|
4
|
+
export type ActorSearchCrossTestOptions = RpcPathCrossSuiteOptions;
|
|
5
5
|
export declare const describe_actor_search_cross_tests: (options: ActorSearchCrossTestOptions) => void;
|
|
6
6
|
//# sourceMappingURL=actor_search.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actor_search.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/actor_search.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA2C9B,OAAO,KAAK,EAAC,
|
|
1
|
+
{"version":3,"file":"actor_search.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/actor_search.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA2C9B,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,YAAY,CAAC;AAGzD,qFAAqF;AACrF,MAAM,MAAM,2BAA2B,GAAG,wBAAwB,CAAC;AASnE,eAAO,MAAM,iCAAiC,GAAI,SAAS,2BAA2B,KAAG,IAyDxF,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import '../assert_dev_env.js';
|
|
2
|
-
import type {
|
|
3
|
-
/** Options for the app-settings effect suite (
|
|
4
|
-
export type AppSettingsCrossTestOptions =
|
|
2
|
+
import type { RpcPathCrossSuiteOptions } from './setup.js';
|
|
3
|
+
/** Options for the app-settings effect suite (the standard RPC-dispatched shape). */
|
|
4
|
+
export type AppSettingsCrossTestOptions = RpcPathCrossSuiteOptions;
|
|
5
5
|
export declare const describe_app_settings_cross_tests: (options: AppSettingsCrossTestOptions) => void;
|
|
6
6
|
//# sourceMappingURL=app_settings.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app_settings.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/app_settings.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAsC9B,OAAO,KAAK,EAAC,
|
|
1
|
+
{"version":3,"file":"app_settings.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/app_settings.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAsC9B,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,YAAY,CAAC;AAGzD,qFAAqF;AACrF,MAAM,MAAM,2BAA2B,GAAG,wBAAwB,CAAC;AAiBnE,eAAO,MAAM,iCAAiC,GAAI,SAAS,2BAA2B,KAAG,IA8DxF,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
import type { RpcPathCrossSuiteOptions } from './setup.js';
|
|
3
|
+
/**
|
|
4
|
+
* Options for the body-size parity suite — the standard RPC-dispatched
|
|
5
|
+
* cross-suite shape (`setup_test` / `capabilities` / `rpc_path`); aliases the
|
|
6
|
+
* shared `RpcPathCrossSuiteOptions` rather than minting a duplicate.
|
|
7
|
+
*/
|
|
8
|
+
export type BodySizeCrossTestOptions = RpcPathCrossSuiteOptions;
|
|
9
|
+
export declare const describe_body_size_cross_tests: (options: BodySizeCrossTestOptions) => void;
|
|
10
|
+
//# sourceMappingURL=body_size.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"body_size.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/body_size.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA6D9B,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,YAAY,CAAC;AAGzD;;;;GAIG;AACH,MAAM,MAAM,wBAAwB,GAAG,wBAAwB,CAAC;AAmDhE,eAAO,MAAM,8BAA8B,GAAI,SAAS,wBAAwB,KAAG,IA0ClF,CAAC"}
|