etaf/examples/etaf-resource-example.el
2026-08-31 15:18:26 +08:00

148 lines
5.3 KiB
EmacsLisp

;;; etaf-resource-example.el --- Resource lifecycle example -*- lexical-binding: t; -*-
;; SPDX-License-Identifier: GPL-3.0-or-later
;;; Commentary:
;; This example keeps a synchronous Resource inside Component setup, exposes
;; success and error states, and lets Scope disposal own loader cleanup.
;;; Code:
(require 'etaf)
(defconst etaf-resource-example-buffer-name "*ETAF Resource Example*"
"Buffer opened by `etaf-resource-example-open'.")
(defun etaf-resource-example--message (resource)
"Return a display message for RESOURCE."
(pcase (etaf-resource-status resource)
('success (etaf-resource-value resource))
('error (error-message-string (etaf-resource-error resource)))
('loading "Loading service snapshot…")
(_ "Resource has not loaded.")))
(etaf-define-component etaf-resource-example-app ()
"Render a reloadable Resource with visible cleanup and error state."
:setup
(let ((fail-next (etaf-ref nil :name 'etaf-resource-example-fail))
(cleanup-count (etaf-ref 0 :name 'etaf-resource-example-cleanups))
(generation 0)
resource)
(setq resource
(etaf-resource
(lambda ()
(cl-incf generation)
(if (etaf-value fail-next)
(progn
(etaf-set-value fail-next nil)
(error "Synthetic service failure %d" generation))
(etaf-resource-result
(format "Generation %d is healthy." generation)
:cleanup
(lambda ()
(etaf-set-value
cleanup-count
(1+ (etaf-value cleanup-count)))))))
:immediate nil
:name 'etaf-resource-example))
(etaf-on-mounted (lambda () (etaf-resource-load resource)))
(list :resource resource :fail-next fail-next
:cleanup-count cleanup-count))
:view
(column :width '(680) :color "#252A2E" :bgcolor "#F8F5EE"
(box :width '(680) :padding '(1 (18)) :border "#8F432F"
:bgcolor "#FFFDF8" :text-align 'center
(column
(text :color "#8F432F" :font-weight 'bold
"BEST PRACTICE / RESOURCE LIFECYCLE")
(text :font-weight 'bold "Service health")
(text :color "#66706A"
"Loader errors become explicit state; cleanup stays scoped.")))
(box :height 1)
(box :width '(680) :padding '(2 (18))
:border
(if (eq (etaf-resource-status
(plist-get (etaf-state) :resource)) 'success)
"#6D8A73" "#C97252")
:bgcolor
(if (eq (etaf-resource-status
(plist-get (etaf-state) :resource)) 'success)
"#DCEBDD" "#F1D4C9")
:color
(if (eq (etaf-resource-status
(plist-get (etaf-state) :resource)) 'success)
"#24422D" "#6B3020")
:text-align 'center
(column
(text :font-weight 'bold
(expr
(upcase
(symbol-name
(etaf-resource-status (plist-get (etaf-state) :resource))))))
(text
(expr
(etaf-resource-example--message
(plist-get (etaf-state) :resource))))
(text :color "#66706A"
(expr
(format "CLEANUPS %d"
(etaf-value (plist-get (etaf-state) :cleanup-count)))))))
(box :height 1)
(flex :width '(680) :flex-flow '(row wrap) :gap '(1 (12))
(box :flex-grow 1 :flex-shrink 1 :flex-basis '(200)
:min-width '(180) :padding '(1 (12))
:border "#4E7890" :bgcolor "#D9EAF2" :font-weight 'bold
:text-align 'center :ref 'resource-reload :role 'button
:use (list (etaf-focusable))
:on-press
(let ((resource (plist-get (etaf-state) :resource)))
(lambda () (etaf-resource-load resource)))
(text "RELOAD"))
(box :flex-grow 1 :flex-shrink 1 :flex-basis '(200)
:min-width '(180) :padding '(1 (12))
:border "#C97252" :bgcolor "#F1D4C9" :font-weight 'bold
:text-align 'center :ref 'resource-fail :role 'button
:use (list (etaf-focusable))
:on-press
(let ((resource (plist-get (etaf-state) :resource))
(fail-next (plist-get (etaf-state) :fail-next)))
(lambda ()
(etaf-set-value fail-next t)
(etaf-resource-load resource)))
(text "SIMULATE FAILURE")))
(box :height 1)
(box :width '(680) :padding '(1 (16)) :border "#8D887F"
:color "#4D5651" :bgcolor "#EEEAE2"
(text
"Scope rule: reload releases the old value; unmount releases the last one"))))
;;;###autoload
(defun etaf-resource-example-view ()
"Return the Resource lifecycle example View."
(etaf-view (etaf-resource-example-app)))
;;;###autoload
(defun etaf-resource-example-open ()
"Mount the Resource lifecycle example and return its buffer."
(interactive)
(let ((buffer (etaf-mount etaf-resource-example-buffer-name
(etaf-resource-example-view))))
(when (called-interactively-p 'interactive)
(pop-to-buffer buffer))
buffer))
;;;###autoload
(defun etaf-resource-example-close ()
"Unmount and kill the Resource lifecycle example buffer."
(interactive)
(when-let* ((runtime
(etaf-runtime-for-buffer etaf-resource-example-buffer-name)))
(etaf-unmount runtime))
(when-let* ((buffer (get-buffer etaf-resource-example-buffer-name)))
(kill-buffer buffer)))
(provide 'etaf-resource-example)
;;; etaf-resource-example.el ends here