spfn 0.3.0-beta.14 → 0.3.0-beta.16

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/README.md CHANGED
@@ -635,6 +635,12 @@ spfn ops list --module ledger # just that module's comma
635
635
  spfn ops call ledger.compact --yes # effect=destructive needs this
636
636
  ```
637
637
 
638
+ An app's own command can carry an effect and a summary too, declared on its route with
639
+ `.effect()` and `.summary()`; `spfn ops list` and `--describe` show them, and an app command
640
+ announcing `read` or `write` needs no `--yes`. A CLI at 0.3.0-beta.15 or older does not
641
+ know app commands carry them: it warns that such a command "names no usable module" and
642
+ asks for `--yes` on it, so upgrade the CLI together with `@spfn/core`.
643
+
638
644
  `spfn ops call` refuses a command the app declared `effect: destructive` unless `--yes` is
639
645
  given. It refuses the same way when the app announced module metadata this CLI could not
640
646
  validate: the effect is then unknown rather than absent, and an unknown effect is not
@@ -685,6 +691,41 @@ Because the package is the app's, it is resolved **from the directory the comman
685
691
  Run `spfn ops token` from the app's root; running it elsewhere reports the package as
686
692
  missing, and the message names the directory it looked in.
687
693
 
694
+ #### Can I call a surface from my own code?
695
+
696
+ Yes — the client `spfn ops` runs on is a library export, `spfn/surface`, and it reaches any
697
+ namespace a [`defineSurface`](../core/README.md#can-a-second-surface-sit-beside-_ops)
698
+ serves, not only `/_ops`:
699
+
700
+ ```typescript
701
+ import { createSurfaceClient } from 'spfn/surface';
702
+
703
+ const client = createSurfaceClient({
704
+ appUrl: 'https://app.example',
705
+ namespace: '/_app', // manifest at /_app/_manifest
706
+ token: async () => issueToken(), // string | () => Promise<string>
707
+ onWarning: message => console.error(message), // dropped commands; default: silent
708
+ });
709
+
710
+ const manifest = await client.manifest();
711
+ const usage = client.describe(manifest, 'pageAdd'); // the --describe text
712
+ const answer = await client.call('pageAdd', { params: {}, query: {}, body: { title: 'Hello' } });
713
+ // answer: { status, body } — the app's answer, relayed
714
+ ```
715
+
716
+ Each command carries the `effect` and `summary` the app declared, so your code can tell a
717
+ read from a write before it calls; `call()` itself makes no decision on them. It keeps
718
+ every check `spfn ops` makes: the manifest's shape, each command's path staying under
719
+ `<namespace>/`, module and command metadata, and path parameters that would decode into a dot
720
+ segment. `appUrl` must be https (`http` only against a loopback host) — refused when the
721
+ client is created, with a thrown `Error`. A manifest request that does not answer 200
722
+ throws a `SurfaceManifestError` carrying the `status`. The library prints nothing and does
723
+ not load the CLI's terminal stack; `spfn ops` is this client on `/_ops` with its own
724
+ printer.
725
+
726
+ The `spfn` package has an `exports` map: `spfn` (the CLI's index), `spfn/surface`, and
727
+ `spfn/package.json`. No other path inside the package is importable.
728
+
688
729
  ### `spfn kit`
689
730
 
690
731
  Install, verify and update a **Superfunction Kit** — a licensed product that ships as a