Skip to content

feat(affinescript): step (a) — design lock-in for Node-bound packages#16

Merged
hyperpolymath merged 2 commits intomainfrom
affinescript-node-design
May 4, 2026
Merged

feat(affinescript): step (a) — design lock-in for Node-bound packages#16
hyperpolymath merged 2 commits intomainfrom
affinescript-node-design

Conversation

@hyperpolymath
Copy link
Copy Markdown
Owner

Summary

Step (a) of the migration plan: AffineScript .affine design files for all 5 Node-bound packages, written alongside the JS fallback (PR #14). Bodies remain TODO until the Node-target lands (affinescript#35) — the type-level contract is the design lock-in.

Conformance: every file follows the locked AffineScript conventions (see feedback_affinescript_conventions.md resolution log). Q1=canonical face, Q2=.affine extension, Q3=domain-specific effect names, Q4=per-package Externs.affine shim (interim, pending .affex compatibility filesystem).

What's in this PR

14 new .affine files across the 5 Node-bound packages:

Package Files Effects declared
scanner Externs, Scanner, Index Browser, Fs, Clock
core Externs, Arangodb, Index Db, Process
github-action Externs, Action, Index Action, Github
cli Externs, Cli, Index IO, Process, Fs, Path, Commander, Spinner, Style, TableBuilder
monitoring-api Externs, Server Net, Joi, Dotenv, IO, Process, Clock (+ re-declared Db / Scanner from cross-pkg shims)

Key playbook re-decompositions captured at the type level

Per the migration playbook Cardinal Rule (re-decompose, don't transliterate):

  • Module-level singletons (server.ts's top-level db / scanner) → owned AppState threaded as ref parameter through route handlers — kills the service-locator pattern the playbook flags.
  • try/catch around every handler → handler effect rows (Net + Db + Joi + Clock + …) make impurity visible at the signature.
  • **Tagged-template aql\…`queries** → bind-vars-formDb.query[B](db, str, bindVars) -> Cursor` per the playbook recommendation.
  • 'A' | 'AA' | 'AAA' literal-string unions → AffineScript sum types (A | AA | AAA).
  • TS class with private fields + methods (e.g. ArangoDBService) → owned Service record + standalone fns taking ref Service / mut Service.
  • React.forwardRef class (in PR #15) → standalone make_button fn returning own Element.

Caveats

  • Bodies are stubs. Each fn has the right signature + effect row but the body is // TODO: implement when Node-target lands plus let _ = …; ()-style placeholders. This is the agreed shape of "step (a) design lock-in" — type-level contract captured, runtime impl a separate session.
  • Cross-package types (e.g. monitoring-api consuming core's Service and Db effect, or scanner's ScanResult) are currently re-declared as opaque extern type + a slim re-declared effect in each consumer. When .affex lands, those re-decls collapse to use Core / use Scanner and the duplication disappears.
  • Stdlib-style helpers (String.join, List.fold, Int.to_string, etc.) are referenced but not yet pinned to a specific stdlib path — to settle when the language's stdlib path scheme is finalised.
  • pub visibility on top-level let / type is used per stdlib/Core.affine precedent; if the canonical face has different export semantics in some constructs, those are renamings.

How this fits the four PRs

Step Branch PR What it gives
(d) js-strip-types #14 TS → JS via ts-blank-space; Node packages run today
(c) react-affinescript-pilot #15 Button.affine pilot; Modal deferred
(a) affinescript-node-design this PR .affine design for the 5 Node-bound packages

PR #14 keeps the runtime working today; PR #15 + this PR capture the AffineScript design while context is fresh. The two layers (.js + .affine) coexist until the Node-target lands; at that point the .js files retire and .affine becomes canonical.

Test plan

  • AffineScript compile-check via affinescript check <file> once a working environment is set up — couldn't run in this session
  • Specify .affex per the parking-lot entry; collapse cross-package type re-decls
  • Implement TODO-bodies once Node-target lands (multi-session)
  • Modal port (deferred from PR feat(react): pilot AffineScript port of Button per migration playbook #15)

🤖 Generated with Claude Code

…ckages

Per the migration plan d→c→a, this is step (a): write AffineScript .affine
sources alongside the JS fallback (PR #14) for each Node-bound package, so
the playbook re-decomposition is captured while context is fresh. Bodies
remain TODO until the Node-target lands (affinescript#35); the type-level
contract is the design lock-in.

All files conform to the locked AffineScript conventions
(see feedback_affinescript_conventions.md):
  - Q1 face = canonical (// comments, fn keyword, brace blocks, -{Effects}-> arrow)
  - Q2 ext = .affine
  - Q3 effects = domain-specific (DOM/Fs/Process/Net/Db/IO/Action per the table)
  - Q4 cross-pkg = per-package Externs.affine shim (interim, pending .affex)

14 new .affine files across the 5 packages:

  scanner/
    Externs.affine    Browser + Fs + Clock effects, opaque puppeteer/playwright types
    Scanner.affine    ScanOptions/ScanResult records + make_scanner + scan
    Index.affine      re-exports

  core/
    Externs.affine    Db + Process effects, opaque arangojs types
    Arangodb.affine   Site/Scan/Violation/WcagCriterion/Organization records;
                      Service owned record; 7 query fns matching arangodb.ts;
                      tagged-template aql lifted to bind-vars per the playbook
    Index.affine      re-exports

  github-action/
    Externs.affine    Action + Github effects, opaque Octokit/Context types
    Action.affine     parse_wcag_level, generate_summary, post_pr_comment, run
    Index.affine      re-export run

  cli/
    Externs.affine    IO/Process/Fs/Path/Commander/Spinner/Style/TableBuilder effects
    Cli.affine        scan_action, ci_action, batch_action, build_program, main
    Index.affine      re-export main

  monitoring-api/
    Externs.affine    Net + Joi + Dotenv + IO + Process + Clock effects;
                      Express App/Router/Req/Res opaque
    Server.affine     AppState (replaces module-level db/scanner singletons),
                      respond_error helper, 13 route handlers (one per
                      route in src/routes/*.ts), boot()

Key playbook re-decompositions captured at the type level:
  * Module-level `db` / `scanner` singletons → owned AppState threaded as ref
  * try/catch around handlers → effect rows make impurity visible
  * `'A' | 'AA' | 'AAA'` string unions → AffineScript sum types
  * tagged-template aql → bind-vars Db.query
  * forwardRef class (in React.affine, prior PR) → standalone fn with own return

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector
Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@hyperpolymath hyperpolymath merged commit 9363f1c into main May 4, 2026
18 of 34 checks passed
@hyperpolymath hyperpolymath deleted the affinescript-node-design branch May 4, 2026 07:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant