From 44d5414bc6d7769862cd66ae8dce8b52a9d93dd6 Mon Sep 17 00:00:00 2001 From: giles Date: Fri, 6 Mar 2026 12:41:38 +0000 Subject: [PATCH] Split boundary.sx: separate language contract from app-specific declarations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit boundary.sx was mixing three concerns in one file: - Core SX I/O primitives (the language contract) - Deployment-specific layout I/O (app architecture) - Per-service page helpers (fully app-specific) Now split into three tiers: 1. shared/sx/ref/boundary.sx — core I/O only (frag, query, current-user, etc.) 2. shared/sx/ref/boundary-app.sx — deployment layout contexts (*-header-ctx, *-ctx) 3. {service}/sx/boundary.sx — per-service page helpers The boundary parser loads all three tiers automatically. Validation error messages now point to the correct file for each tier. Co-Authored-By: Claude Opus 4.6 --- blog/sx/boundary.sx | 41 ++++ events/sx/boundary.sx | 61 ++++++ market/sx/boundary.sx | 21 ++ shared/sx/boundary.py | 11 +- shared/sx/ref/boundary-app.sx | 118 +++++++++++ shared/sx/ref/boundary.sx | 335 +++---------------------------- shared/sx/ref/boundary_parser.py | 133 +++++++++--- sx/sx/boundary.sx | 51 +++++ 8 files changed, 432 insertions(+), 339 deletions(-) create mode 100644 blog/sx/boundary.sx create mode 100644 events/sx/boundary.sx create mode 100644 market/sx/boundary.sx create mode 100644 shared/sx/ref/boundary-app.sx create mode 100644 sx/sx/boundary.sx diff --git a/blog/sx/boundary.sx b/blog/sx/boundary.sx new file mode 100644 index 0000000..30f99d5 --- /dev/null +++ b/blog/sx/boundary.sx @@ -0,0 +1,41 @@ +;; Blog service — page helper declarations. + +(define-page-helper "editor-data" + :params (&key) + :returns "dict" + :service "blog") + +(define-page-helper "editor-page-data" + :params (&key) + :returns "dict" + :service "blog") + +(define-page-helper "post-admin-data" + :params (&key slug) + :returns "dict" + :service "blog") + +(define-page-helper "post-data-data" + :params (&key slug) + :returns "dict" + :service "blog") + +(define-page-helper "post-preview-data" + :params (&key slug) + :returns "dict" + :service "blog") + +(define-page-helper "post-entries-data" + :params (&key slug) + :returns "dict" + :service "blog") + +(define-page-helper "post-settings-data" + :params (&key slug) + :returns "dict" + :service "blog") + +(define-page-helper "post-edit-data" + :params (&key slug) + :returns "dict" + :service "blog") diff --git a/events/sx/boundary.sx b/events/sx/boundary.sx new file mode 100644 index 0000000..9353103 --- /dev/null +++ b/events/sx/boundary.sx @@ -0,0 +1,61 @@ +;; Events service — page helper declarations. + +(define-page-helper "calendar-admin-data" + :params (&key calendar-slug) + :returns "dict" + :service "events") + +(define-page-helper "day-admin-data" + :params (&key calendar-slug year month day) + :returns "dict" + :service "events") + +(define-page-helper "slots-data" + :params (&key calendar-slug) + :returns "dict" + :service "events") + +(define-page-helper "slot-data" + :params (&key calendar-slug slot-id) + :returns "dict" + :service "events") + +(define-page-helper "entry-data" + :params (&key calendar-slug entry-id) + :returns "dict" + :service "events") + +(define-page-helper "entry-admin-data" + :params (&key calendar-slug entry-id year month day) + :returns "dict" + :service "events") + +(define-page-helper "ticket-types-data" + :params (&key calendar-slug entry-id year month day) + :returns "dict" + :service "events") + +(define-page-helper "ticket-type-data" + :params (&key calendar-slug entry-id ticket-type-id year month day) + :returns "dict" + :service "events") + +(define-page-helper "tickets-data" + :params (&key) + :returns "dict" + :service "events") + +(define-page-helper "ticket-detail-data" + :params (&key code) + :returns "dict" + :service "events") + +(define-page-helper "ticket-admin-data" + :params (&key) + :returns "dict" + :service "events") + +(define-page-helper "markets-data" + :params (&key) + :returns "dict" + :service "events") diff --git a/market/sx/boundary.sx b/market/sx/boundary.sx new file mode 100644 index 0000000..d7028c5 --- /dev/null +++ b/market/sx/boundary.sx @@ -0,0 +1,21 @@ +;; Market service — page helper declarations. + +(define-page-helper "all-markets-data" + :params (&key) + :returns "dict" + :service "market") + +(define-page-helper "page-markets-data" + :params (&key slug) + :returns "dict" + :service "market") + +(define-page-helper "page-admin-data" + :params (&key slug) + :returns "dict" + :service "market") + +(define-page-helper "market-home-data" + :params (&key page-slug market-slug) + :returns "dict" + :service "market") diff --git a/shared/sx/boundary.py b/shared/sx/boundary.py index 27cdf48..00f5f26 100644 --- a/shared/sx/boundary.py +++ b/shared/sx/boundary.py @@ -69,22 +69,25 @@ def validate_primitive(name: str) -> None: def validate_io(name: str) -> None: - """Validate that an I/O primitive is declared in boundary.sx.""" + """Validate that an I/O primitive is declared in boundary.sx or boundary-app.sx.""" _load_declarations() assert _DECLARED_IO is not None if name not in _DECLARED_IO: - _report(f"Undeclared I/O primitive: {name!r}. Add to boundary.sx.") + _report( + f"Undeclared I/O primitive: {name!r}. " + f"Add to boundary.sx (core) or boundary-app.sx (deployment)." + ) def validate_helper(service: str, name: str) -> None: - """Validate that a page helper is declared in boundary.sx.""" + """Validate that a page helper is declared in {service}/sx/boundary.sx.""" _load_declarations() assert _DECLARED_HELPERS is not None svc_helpers = _DECLARED_HELPERS.get(service, frozenset()) if name not in svc_helpers: _report( f"Undeclared page helper: {name!r} for service {service!r}. " - f"Add to boundary.sx." + f"Add to {service}/sx/boundary.sx." ) diff --git a/shared/sx/ref/boundary-app.sx b/shared/sx/ref/boundary-app.sx new file mode 100644 index 0000000..213e1c0 --- /dev/null +++ b/shared/sx/ref/boundary-app.sx @@ -0,0 +1,118 @@ +;; ========================================================================== +;; boundary-app.sx — Deployment-specific boundary declarations +;; +;; Layout context I/O primitives for THIS deployment's service architecture. +;; These are NOT part of the SX language contract — a different deployment +;; would declare different layout contexts here. +;; +;; The core SX I/O contract lives in boundary.sx. +;; Per-service page helpers live in {service}/sx/boundary.sx. +;; ========================================================================== + + +;; -------------------------------------------------------------------------- +;; Layout context providers — deployment-specific I/O +;; -------------------------------------------------------------------------- + +;; Shared across all services (root layout) + +(define-io-primitive "root-header-ctx" + :params () + :returns "dict" + :async true + :doc "Dict with root header values (cart-mini, auth-menu, nav-tree, etc.)." + :context :request) + +(define-io-primitive "select-colours" + :params () + :returns "string" + :async true + :doc "Shared select/hover CSS class string." + :context :request) + +(define-io-primitive "account-nav-ctx" + :params () + :returns "any" + :async true + :doc "Account nav fragments, or nil." + :context :request) + +(define-io-primitive "app-rights" + :params () + :returns "dict" + :async true + :doc "User rights dict from g.rights." + :context :request) + +;; Blog service layout + +(define-io-primitive "post-header-ctx" + :params () + :returns "dict" + :async true + :doc "Dict with post-level header values." + :context :request) + +;; Cart service layout + +(define-io-primitive "cart-page-ctx" + :params () + :returns "dict" + :async true + :doc "Dict with cart page header values." + :context :request) + +;; Events service layouts + +(define-io-primitive "events-calendar-ctx" + :params () + :returns "dict" + :async true + :doc "Dict with events calendar header values." + :context :request) + +(define-io-primitive "events-day-ctx" + :params () + :returns "dict" + :async true + :doc "Dict with events day header values." + :context :request) + +(define-io-primitive "events-entry-ctx" + :params () + :returns "dict" + :async true + :doc "Dict with events entry header values." + :context :request) + +(define-io-primitive "events-slot-ctx" + :params () + :returns "dict" + :async true + :doc "Dict with events slot header values." + :context :request) + +(define-io-primitive "events-ticket-type-ctx" + :params () + :returns "dict" + :async true + :doc "Dict with ticket type header values." + :context :request) + +;; Market service layout + +(define-io-primitive "market-header-ctx" + :params () + :returns "dict" + :async true + :doc "Dict with market header data." + :context :request) + +;; Federation service layout + +(define-io-primitive "federation-actor-ctx" + :params () + :returns "dict?" + :async true + :doc "Serialized ActivityPub actor dict or nil." + :context :request) diff --git a/shared/sx/ref/boundary.sx b/shared/sx/ref/boundary.sx index e0d29b3..24f6e39 100644 --- a/shared/sx/ref/boundary.sx +++ b/shared/sx/ref/boundary.sx @@ -1,12 +1,12 @@ ;; ========================================================================== -;; boundary.sx — SX boundary contract +;; boundary.sx — SX language boundary contract ;; -;; Declares everything allowed to cross the host-SX boundary: -;; I/O primitives (Tier 2) and page helpers (Tier 3). +;; Declares the core I/O primitives that any SX host must provide. +;; This is the LANGUAGE contract — not deployment-specific. ;; ;; Pure primitives (Tier 1) are declared in primitives.sx. -;; This file declares what primitives.sx does NOT cover: -;; async/side-effectful host functions that need request context. +;; Deployment-specific I/O (layout contexts) lives in boundary-app.sx. +;; Per-service page helpers live in {service}/sx/boundary.sx. ;; ;; Format: ;; (define-io-primitive "name" @@ -16,13 +16,6 @@ ;; :doc "description" ;; :context :request) ;; -;; (define-page-helper "name" -;; :params (param1 param2) -;; :returns "type" -;; :service "service-name") -;; -;; Bootstrappers read this file and emit frozen sets + validation -;; functions for the target language. ;; ========================================================================== @@ -34,9 +27,11 @@ ;; -------------------------------------------------------------------------- -;; Tier 2: I/O primitives — async, side-effectful, need host context +;; Tier 2: Core I/O primitives — async, side-effectful, need host context ;; -------------------------------------------------------------------------- +;; Cross-service communication + (define-io-primitive "frag" :params (service frag-type &key) :returns "string" @@ -58,6 +53,15 @@ :doc "Call an action on another service via internal HTTP." :context :request) +(define-io-primitive "service" + :params (service-or-method &rest args &key) + :returns "any" + :async true + :doc "Call a domain service method. Two-arg: (service svc method). One-arg: (service method) uses bound handler service." + :context :request) + +;; Request context + (define-io-primitive "current-user" :params () :returns "dict?" @@ -72,13 +76,6 @@ :doc "True if current request has HX-Request header." :context :request) -(define-io-primitive "service" - :params (service-or-method &rest args &key) - :returns "any" - :async true - :doc "Call a domain service method. Two-arg: (service svc method). One-arg: (service method) uses bound handler service." - :context :request) - (define-io-primitive "request-arg" :params (name &rest default) :returns "any" @@ -93,18 +90,11 @@ :doc "Current request path." :context :request) -(define-io-primitive "nav-tree" - :params () - :returns "list" +(define-io-primitive "request-view-args" + :params (key) + :returns "any" :async true - :doc "Navigation tree as list of node dicts." - :context :request) - -(define-io-primitive "get-children" - :params (&key parent-type parent-id) - :returns "list" - :async true - :doc "Fetch child entities for a parent." + :doc "Read a URL view argument from the current request." :context :request) (define-io-primitive "g" @@ -128,6 +118,8 @@ :doc "Raise HTTP error from SX." :context :request) +;; Routing + (define-io-primitive "url-for" :params (endpoint &key) :returns "string" @@ -142,105 +134,23 @@ :doc "Service URL prefix for dev/prod routing." :context :request) -(define-io-primitive "root-header-ctx" +;; Navigation and relations + +(define-io-primitive "nav-tree" :params () - :returns "dict" + :returns "list" :async true - :doc "Dict with root header values (cart-mini, auth-menu, nav-tree, etc.)." + :doc "Navigation tree as list of node dicts." :context :request) -(define-io-primitive "post-header-ctx" - :params () - :returns "dict" +(define-io-primitive "get-children" + :params (&key parent-type parent-id) + :returns "list" :async true - :doc "Dict with post-level header values." + :doc "Fetch child entities for a parent." :context :request) -(define-io-primitive "select-colours" - :params () - :returns "string" - :async true - :doc "Shared select/hover CSS class string." - :context :request) - -(define-io-primitive "account-nav-ctx" - :params () - :returns "any" - :async true - :doc "Account nav fragments, or nil." - :context :request) - -(define-io-primitive "app-rights" - :params () - :returns "dict" - :async true - :doc "User rights dict from g.rights." - :context :request) - -(define-io-primitive "federation-actor-ctx" - :params () - :returns "dict?" - :async true - :doc "Serialized ActivityPub actor dict or nil." - :context :request) - -(define-io-primitive "request-view-args" - :params (key) - :returns "any" - :async true - :doc "Read a URL view argument from the current request." - :context :request) - -(define-io-primitive "cart-page-ctx" - :params () - :returns "dict" - :async true - :doc "Dict with cart page header values." - :context :request) - -(define-io-primitive "events-calendar-ctx" - :params () - :returns "dict" - :async true - :doc "Dict with events calendar header values." - :context :request) - -(define-io-primitive "events-day-ctx" - :params () - :returns "dict" - :async true - :doc "Dict with events day header values." - :context :request) - -(define-io-primitive "events-entry-ctx" - :params () - :returns "dict" - :async true - :doc "Dict with events entry header values." - :context :request) - -(define-io-primitive "events-slot-ctx" - :params () - :returns "dict" - :async true - :doc "Dict with events slot header values." - :context :request) - -(define-io-primitive "events-ticket-type-ctx" - :params () - :returns "dict" - :async true - :doc "Dict with ticket type header values." - :context :request) - -(define-io-primitive "market-header-ctx" - :params () - :returns "dict" - :async true - :doc "Dict with market header data." - :context :request) - -;; Moved from primitives.py — these need host context (infra/config/Quart) +;; Config and host context (sync — no await needed) (define-io-primitive "app-url" :params (service &rest path) @@ -278,185 +188,6 @@ :context :config) -;; -------------------------------------------------------------------------- -;; Tier 3: Page helpers — service-scoped, registered per app -;; -------------------------------------------------------------------------- - -;; SX docs service -(define-page-helper "highlight" - :params (code lang) - :returns "sx-source" - :service "sx") - -(define-page-helper "primitives-data" - :params () - :returns "dict" - :service "sx") - -(define-page-helper "special-forms-data" - :params () - :returns "dict" - :service "sx") - -(define-page-helper "reference-data" - :params (slug) - :returns "dict" - :service "sx") - -(define-page-helper "attr-detail-data" - :params (slug) - :returns "dict" - :service "sx") - -(define-page-helper "header-detail-data" - :params (slug) - :returns "dict" - :service "sx") - -(define-page-helper "event-detail-data" - :params (slug) - :returns "dict" - :service "sx") - -(define-page-helper "read-spec-file" - :params (filename) - :returns "string" - :service "sx") - -(define-page-helper "bootstrapper-data" - :params (target) - :returns "dict" - :service "sx") - -(define-page-helper "bundle-analyzer-data" - :params () - :returns "dict" - :service "sx") - -;; Blog service -(define-page-helper "editor-data" - :params (&key) - :returns "dict" - :service "blog") - -(define-page-helper "editor-page-data" - :params (&key) - :returns "dict" - :service "blog") - -(define-page-helper "post-admin-data" - :params (&key slug) - :returns "dict" - :service "blog") - -(define-page-helper "post-data-data" - :params (&key slug) - :returns "dict" - :service "blog") - -(define-page-helper "post-preview-data" - :params (&key slug) - :returns "dict" - :service "blog") - -(define-page-helper "post-entries-data" - :params (&key slug) - :returns "dict" - :service "blog") - -(define-page-helper "post-settings-data" - :params (&key slug) - :returns "dict" - :service "blog") - -(define-page-helper "post-edit-data" - :params (&key slug) - :returns "dict" - :service "blog") - -;; Events service -(define-page-helper "calendar-admin-data" - :params (&key calendar-slug) - :returns "dict" - :service "events") - -(define-page-helper "day-admin-data" - :params (&key calendar-slug year month day) - :returns "dict" - :service "events") - -(define-page-helper "slots-data" - :params (&key calendar-slug) - :returns "dict" - :service "events") - -(define-page-helper "slot-data" - :params (&key calendar-slug slot-id) - :returns "dict" - :service "events") - -(define-page-helper "entry-data" - :params (&key calendar-slug entry-id) - :returns "dict" - :service "events") - -(define-page-helper "entry-admin-data" - :params (&key calendar-slug entry-id year month day) - :returns "dict" - :service "events") - -(define-page-helper "ticket-types-data" - :params (&key calendar-slug entry-id year month day) - :returns "dict" - :service "events") - -(define-page-helper "ticket-type-data" - :params (&key calendar-slug entry-id ticket-type-id year month day) - :returns "dict" - :service "events") - -(define-page-helper "tickets-data" - :params (&key) - :returns "dict" - :service "events") - -(define-page-helper "ticket-detail-data" - :params (&key code) - :returns "dict" - :service "events") - -(define-page-helper "ticket-admin-data" - :params (&key) - :returns "dict" - :service "events") - -(define-page-helper "markets-data" - :params (&key) - :returns "dict" - :service "events") - -;; Market service -(define-page-helper "all-markets-data" - :params (&key) - :returns "dict" - :service "market") - -(define-page-helper "page-markets-data" - :params (&key slug) - :returns "dict" - :service "market") - -(define-page-helper "page-admin-data" - :params (&key slug) - :returns "dict" - :service "market") - -(define-page-helper "market-home-data" - :params (&key page-slug market-slug) - :returns "dict" - :service "market") - - ;; -------------------------------------------------------------------------- ;; Boundary types — what's allowed to cross the host-SX boundary ;; -------------------------------------------------------------------------- diff --git a/shared/sx/ref/boundary_parser.py b/shared/sx/ref/boundary_parser.py index 3613dd9..d51dee5 100644 --- a/shared/sx/ref/boundary_parser.py +++ b/shared/sx/ref/boundary_parser.py @@ -1,14 +1,23 @@ """ -Parse boundary.sx and primitives.sx to extract declared names. +Parse boundary declarations from multiple sources. + +Three tiers of boundary files: + 1. shared/sx/ref/boundary.sx — core SX language I/O contract + 2. shared/sx/ref/boundary-app.sx — deployment-specific layout I/O + 3. {service}/sx/boundary.sx — per-service page helpers Shared by both bootstrap_py.py and bootstrap_js.py, and used at runtime by the validation module. """ from __future__ import annotations +import glob +import logging import os from typing import Any +logger = logging.getLogger("sx.boundary_parser") + # Allow standalone use (from bootstrappers) or in-project imports try: from shared.sx.parser import parse_all @@ -26,12 +35,28 @@ def _ref_dir() -> str: return os.path.dirname(os.path.abspath(__file__)) +def _project_root() -> str: + """Return the project root (3 levels up from shared/sx/ref/).""" + ref = _ref_dir() + # shared/sx/ref -> shared/sx -> shared -> project root + root = os.path.abspath(os.path.join(ref, "..", "..", "..")) + # In Docker the layout is /app/shared/sx/ref -> /app + if not os.path.isdir(root): + root = os.path.abspath(os.path.join(ref, "..", "..")) + return root + + def _read_file(filename: str) -> str: filepath = os.path.join(_ref_dir(), filename) with open(filepath, encoding="utf-8") as f: return f.read() +def _read_file_path(filepath: str) -> str: + with open(filepath, encoding="utf-8") as f: + return f.read() + + def _extract_keyword_arg(expr: list, key: str) -> Any: """Extract :key value from a flat keyword-arg list.""" for i, item in enumerate(expr): @@ -40,6 +65,51 @@ def _extract_keyword_arg(expr: list, key: str) -> Any: return None +def _extract_declarations( + source: str, +) -> tuple[set[str], dict[str, set[str]]]: + """Extract I/O primitive names and page helper names from boundary source. + + Returns (io_names, {service: helper_names}). + """ + exprs = parse_all(source) + io_names: set[str] = set() + helpers: dict[str, set[str]] = {} + + for expr in exprs: + if not isinstance(expr, list) or not expr: + continue + head = expr[0] + if not isinstance(head, Symbol): + continue + + if head.name == "define-io-primitive": + name = expr[1] + if isinstance(name, str): + io_names.add(name) + + elif head.name == "define-page-helper": + name = expr[1] + service = _extract_keyword_arg(expr, "service") + if isinstance(name, str) and isinstance(service, str): + helpers.setdefault(service, set()).add(name) + + return io_names, helpers + + +def _find_service_boundary_files() -> list[str]: + """Find all {service}/sx/boundary.sx files in the project.""" + root = _project_root() + pattern = os.path.join(root, "*/sx/boundary.sx") + files = glob.glob(pattern) + # Exclude shared/sx/ref/ — that's the core boundary + return [f for f in files if "/shared/" not in f] + + +# --------------------------------------------------------------------------- +# Public API +# --------------------------------------------------------------------------- + def parse_primitives_sx() -> frozenset[str]: """Parse primitives.sx and return frozenset of declared pure primitive names.""" by_module = parse_primitives_by_module() @@ -50,12 +120,7 @@ def parse_primitives_sx() -> frozenset[str]: def parse_primitives_by_module() -> dict[str, frozenset[str]]: - """Parse primitives.sx and return primitives grouped by module. - - Returns: - Dict mapping module name (e.g. "core.arithmetic") to frozenset of - primitive names declared under that module. - """ + """Parse primitives.sx and return primitives grouped by module.""" source = _read_file("primitives.sx") exprs = parse_all(source) modules: dict[str, set[str]] = {} @@ -83,37 +148,40 @@ def parse_primitives_by_module() -> dict[str, frozenset[str]]: def parse_boundary_sx() -> tuple[frozenset[str], dict[str, frozenset[str]]]: - """Parse boundary.sx and return (io_names, {service: helper_names}). + """Parse all boundary sources and return (io_names, {service: helper_names}). - Returns: - io_names: frozenset of declared I/O primitive names - helpers: dict mapping service name to frozenset of helper names + Loads three tiers: + 1. boundary.sx — core language I/O + 2. boundary-app.sx — deployment-specific I/O + 3. {service}/sx/boundary.sx — per-service page helpers """ - source = _read_file("boundary.sx") - exprs = parse_all(source) - io_names: set[str] = set() - helpers: dict[str, set[str]] = {} + all_io: set[str] = set() + all_helpers: dict[str, set[str]] = {} - for expr in exprs: - if not isinstance(expr, list) or not expr: - continue - head = expr[0] - if not isinstance(head, Symbol): - continue + def _merge(source: str, label: str) -> None: + io_names, helpers = _extract_declarations(source) + all_io.update(io_names) + for svc, names in helpers.items(): + all_helpers.setdefault(svc, set()).update(names) + logger.debug("Boundary %s: %d io, %d helpers", label, len(io_names), sum(len(v) for v in helpers.values())) - if head.name == "define-io-primitive": - name = expr[1] - if isinstance(name, str): - io_names.add(name) + # 1. Core language contract + _merge(_read_file("boundary.sx"), "core") - elif head.name == "define-page-helper": - name = expr[1] - service = _extract_keyword_arg(expr, "service") - if isinstance(name, str) and isinstance(service, str): - helpers.setdefault(service, set()).add(name) + # 2. Deployment-specific I/O + app_path = os.path.join(_ref_dir(), "boundary-app.sx") + if os.path.exists(app_path): + _merge(_read_file("boundary-app.sx"), "app") - frozen_helpers = {svc: frozenset(names) for svc, names in helpers.items()} - return frozenset(io_names), frozen_helpers + # 3. Per-service boundary files + for filepath in _find_service_boundary_files(): + try: + _merge(_read_file_path(filepath), filepath) + except Exception as e: + logger.warning("Failed to parse %s: %s", filepath, e) + + frozen_helpers = {svc: frozenset(names) for svc, names in all_helpers.items()} + return frozenset(all_io), frozen_helpers def parse_boundary_types() -> frozenset[str]: @@ -126,7 +194,6 @@ def parse_boundary_types() -> frozenset[str]: and expr[0].name == "define-boundary-types"): type_list = expr[1] if isinstance(type_list, list): - # (list "number" "string" ...) return frozenset( item for item in type_list if isinstance(item, str) diff --git a/sx/sx/boundary.sx b/sx/sx/boundary.sx new file mode 100644 index 0000000..2ca1a8a --- /dev/null +++ b/sx/sx/boundary.sx @@ -0,0 +1,51 @@ +;; SX docs service — page helper declarations. + +(define-page-helper "highlight" + :params (code lang) + :returns "sx-source" + :service "sx") + +(define-page-helper "primitives-data" + :params () + :returns "dict" + :service "sx") + +(define-page-helper "special-forms-data" + :params () + :returns "dict" + :service "sx") + +(define-page-helper "reference-data" + :params (slug) + :returns "dict" + :service "sx") + +(define-page-helper "attr-detail-data" + :params (slug) + :returns "dict" + :service "sx") + +(define-page-helper "header-detail-data" + :params (slug) + :returns "dict" + :service "sx") + +(define-page-helper "event-detail-data" + :params (slug) + :returns "dict" + :service "sx") + +(define-page-helper "read-spec-file" + :params (filename) + :returns "string" + :service "sx") + +(define-page-helper "bootstrapper-data" + :params (target) + :returns "dict" + :service "sx") + +(define-page-helper "bundle-analyzer-data" + :params () + :returns "dict" + :service "sx")