From c2525a137bbe4244f7b69d930527420d12f84bce Mon Sep 17 00:00:00 2001 From: Kinneyzhang Date: Thu, 27 Aug 2026 22:49:21 +0800 Subject: [PATCH] feat: expose transaction activity boundary --- README.md | 2 +- README_CN.md | 2 +- docs/API-REFERENCE.md | 7 +++++++ tests/tp-binding-tests.el | 9 +++++++++ tp-reactive.el | 5 +++++ 5 files changed, 23 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4cd283b..339fdd5 100644 --- a/README.md +++ b/README.md @@ -174,7 +174,7 @@ cycles instead of spinning. | Core inspection and debug | `tp-debug-*`, `tp-intervals`, `tp-intervals-map`, `tp-plist`, `tp-text-snapshot`, `tp-empty-p` | | Property policy and declarations | `tp-define-property-policy`, `tp-register-text-property`, `tp-text-declarations`, `tp-computed`, `tp-resolve-value`, `tp-merge-declarations`, `tp-define-style`, `tp-style-declarations` | | Static recipes | `define-tp`/`tp-define-layer`, `define-tps`/`define-tp-group`/`tp-define-group`, layer/group queries, undefine/reset/describe | -| Signals and bindings | signal create/read/peek/set/dispose, binding install/read/dispose, `tp-variable-signal`, `tp-with-transaction`, `tp-transaction-participate`, counters/reset | +| Signals and bindings | signal create/read/peek/set/dispose, binding install/read/dispose, `tp-variable-signal`, `tp-with-transaction`, `tp-transaction-participate`, read-only `tp-transaction-active-p`, counters/reset | | Objects and plans | plan/result constructors, `tp-object-ensure`, retain/reuse, fragment/content-range attachment, resolve, mounted/mounts | | Host ranges | `tp-range-anchor-create`, `tp-range-anchor-live-p`, `tp-object-attach-range`, `tp-range-rebase` | | Surfaces | mount/update/scoped update, materialize, live/revision/client-state, at-point, report/report-summary/inspect, unmount | diff --git a/README_CN.md b/README_CN.md index b2ff614..e5a49dc 100644 --- a/README_CN.md +++ b/README_CN.md @@ -171,7 +171,7 @@ effect 的 input/version tuple,重复或超过图规模上限时停止循环 | Core inspection 与 debug | `tp-debug-*`、`tp-intervals`、`tp-intervals-map`、`tp-plist`、`tp-text-snapshot`、`tp-empty-p` | | Property policy 与 declaration | `tp-define-property-policy`、`tp-register-text-property`、`tp-text-declarations`、`tp-computed`、`tp-resolve-value`、`tp-merge-declarations`、`tp-define-style`、`tp-style-declarations` | | 静态 recipe | `define-tp`/`tp-define-layer`、`define-tps`/`define-tp-group`/`tp-define-group`、layer/group 查询、undefine/reset/describe | -| Signal 与 binding | signal create/read/peek/set/dispose、binding install/read/dispose、`tp-variable-signal`、`tp-with-transaction`、`tp-transaction-participate`、counter/reset | +| Signal 与 binding | signal create/read/peek/set/dispose、binding install/read/dispose、`tp-variable-signal`、`tp-with-transaction`、`tp-transaction-participate`、只读 `tp-transaction-active-p`、counter/reset | | Object 与 plan | plan/result constructor、`tp-object-ensure`、retain/reuse、fragment/content-range attach、resolve、mounted/mounts | | Host range | `tp-range-anchor-create`、`tp-range-anchor-live-p`、`tp-object-attach-range`、`tp-range-rebase` | | Surface | mount/update/scoped update、materialize、live/revision/client-state、at-point、report/report-summary/inspect、unmount | diff --git a/docs/API-REFERENCE.md b/docs/API-REFERENCE.md index c5e34eb..80a7e0f 100644 --- a/docs/API-REFERENCE.md +++ b/docs/API-REFERENCE.md @@ -231,6 +231,8 @@ tp-binding-dispose 释放单个 binding。 (lambda () (my-publish)) (lambda () (my-rollback))) +(tp-transaction-active-p) + (tp-variable-signal 'my-variable) (tp-variable-signal 'my-buffer-variable some-buffer) (tp-reactive-counters) @@ -244,6 +246,11 @@ publish 在 surface publication 后、source commit 前运行,失败时按逆 rollback。tp-variable-signal 用 Emacs variable watcher 适配全局或指定 Buffer 的变量,不是旧的 $variable API。 +tp-transaction-active-p 是只读边界查询:只在当前 dynamic extent 已进入 +或加入 TP transaction 时返回严格的 t,否则返回 nil。它不暴露 transaction +对象、participant 或内部状态,调用方只能用它在 mutation 前拒绝不支持的 +嵌套事务边界。 + ETAF registers one opaque participant for its immutable generation and Ebox client state. Its publish is paired with rollback across TP final accept; the ETAF runtime separately records effect input/version tuples and reports a diff --git a/tests/tp-binding-tests.el b/tests/tp-binding-tests.el index 0c8676c..75be5a2 100644 --- a/tests/tp-binding-tests.el +++ b/tests/tp-binding-tests.el @@ -160,6 +160,15 @@ (should (= calls 2)) (should (= (plist-get (tp-reactive-counters) :recomputed) 1))))) +(ert-deftest tp-binding-test-public-transaction-state-follows-dynamic-extent () + "The public transaction predicate is true only inside joined transactions." + (should-not (tp-transaction-active-p)) + (tp-with-transaction + (should (tp-transaction-active-p)) + (tp-with-transaction + (should (tp-transaction-active-p)))) + (should-not (tp-transaction-active-p))) + (ert-deftest tp-binding-test-transaction-commits-signals-in-first-touch-order () "Touched signals commit once in first-touch order, including net reverts." (tp-binding-test--isolated diff --git a/tp-reactive.el b/tp-reactive.el index 86d2b6e..d29e2a5 100644 --- a/tp-reactive.el +++ b/tp-reactive.el @@ -68,6 +68,11 @@ :subscription-added 0 :subscription-removed 0)) (defvar tp--transaction-active nil) + +;;;###autoload +(defun tp-transaction-active-p () + "Return non-nil while the current dynamic extent is in a TP transaction." + (and tp--transaction-active t)) (defvar tp--transaction-signal-values nil) (defvar tp--transaction-signals nil) (defvar tp--transaction-dirty-set nil)