@dszp/netsapiens-lib 0.1.0
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/LICENSE +21 -0
- package/README.md +103 -0
- package/dist/html.d.ts +51 -0
- package/dist/html.js +332 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +22 -0
- package/dist/jwt.d.ts +174 -0
- package/dist/jwt.js +290 -0
- package/dist/mermaid.d.ts +14 -0
- package/dist/mermaid.js +0 -0
- package/dist/model.d.ts +80 -0
- package/dist/model.js +10 -0
- package/dist/nsClient.d.ts +79 -0
- package/dist/nsClient.js +205 -0
- package/dist/policy.d.ts +49 -0
- package/dist/policy.js +39 -0
- package/dist/principal.d.ts +52 -0
- package/dist/principal.js +45 -0
- package/dist/raster.d.ts +21 -0
- package/dist/raster.js +81 -0
- package/dist/resolver.d.ts +58 -0
- package/dist/resolver.js +1092 -0
- package/dist/sensitivity.d.ts +32 -0
- package/dist/sensitivity.js +30 -0
- package/dist/themes.d.ts +71 -0
- package/dist/themes.js +104 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 David Szpunar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# @dszp/netsapiens-lib
|
|
2
|
+
|
|
3
|
+
Portable, **Node-free** NetSapiens toolkit. The same code runs unchanged in a Cloudflare Worker, in
|
|
4
|
+
Node, or in the browser — it uses only Web APIs (`fetch`, `atob`, `TextDecoder`, `crypto.subtle`),
|
|
5
|
+
never `node:*`.
|
|
6
|
+
|
|
7
|
+
Three capabilities, one dependency-free package:
|
|
8
|
+
|
|
9
|
+
- **Read-only NS API v2 client** — `NsClient` (bearer auth, injectable `fetch`) +
|
|
10
|
+
`fetchDomainSnapshot(client, domain)` which assembles a routing-relevant domain snapshot.
|
|
11
|
+
- **JWT (`ns_t`) validation** — `verify()` (cheap local format gate → cached live `/jwt` check) and
|
|
12
|
+
`validateJwtFormat()`. Pluggable `VerdictCache` (inject the Workers Cache API / KV / DO;
|
|
13
|
+
`MemoryVerdictCache` for dev). Anti-overload by design — a bad/expired token never hits the server.
|
|
14
|
+
- **Call-flow resolver + renderers** — `resolveFlow(snapshot, ref)` walks a NetSapiens domain snapshot
|
|
15
|
+
into a normalized `FlowGraph`; `toMermaid()` renders it to a Mermaid flowchart; `renderGalleryHtml()`
|
|
16
|
+
/ `renderFlowCards()` return HTML strings the caller can place anywhere.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
npm install @dszp/netsapiens-lib # or: pnpm add / yarn add
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
ESM-only, zero runtime dependencies, ships its own types.
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { resolveFlow, toMermaid, renderGalleryHtml, verify, NsClient } from '@dszp/netsapiens-lib';
|
|
30
|
+
|
|
31
|
+
const graph = resolveFlow(snapshot, { kind: 'did', ref: '13175550100' });
|
|
32
|
+
const mermaid = toMermaid(graph);
|
|
33
|
+
const html = renderGalleryHtml(snapshot.meta.domain, [graph]);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### What `NsClient` covers
|
|
37
|
+
|
|
38
|
+
`NsClient` is deliberately **not** an enumeration of endpoints — it has exactly one method:
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
client.get<T>(path, query?) // any GET under https://{server}/ns-api/v2
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
That's the whole surface. Any v2 read is reachable (`/domains`, `/domains/{d}/users`,
|
|
45
|
+
`/domains/{d}/users/{ext}/devices`, …) without this library needing to know about it, and one choke
|
|
46
|
+
point is what makes the read-only property below checkable rather than a promise. NetSapiens versions
|
|
47
|
+
drift; consult your server's own `/ns-api/apidoc/` for the paths it offers.
|
|
48
|
+
|
|
49
|
+
Two composites are provided because they're multi-read and worth getting right once:
|
|
50
|
+
|
|
51
|
+
| Function | Reads |
|
|
52
|
+
|---|---|
|
|
53
|
+
| `listDomains(client)` | `/domains` → `{domain, description, locked}[]` |
|
|
54
|
+
| `fetchDomainSnapshot(client, domain, opts?)` | `/domains/{d}` plus, in parallel, `timeframes`, `users`, `callqueues`, `phonenumbers`, `autoattendants` — then per-user `answerrules`. Individual reads fail **soft** (a missing collection yields `[]`, not a thrown snapshot). |
|
|
55
|
+
|
|
56
|
+
The snapshot is the routing subset — what `resolveFlow()` needs. It is not a full domain export.
|
|
57
|
+
|
|
58
|
+
### Read-only by charter
|
|
59
|
+
|
|
60
|
+
`NsClient` exposes **`get()` and nothing else**, and `verify()` only ever issues `GET /jwt`. That is a
|
|
61
|
+
deliberate boundary, not a missing feature: this library is built for tools that visualize and audit a
|
|
62
|
+
NetSapiens domain, where "it cannot possibly write" is a property worth having structurally rather
|
|
63
|
+
than by convention. Writes belong in a separate, explicitly-reviewed client. If a write surface is
|
|
64
|
+
added here later it will be a distinct class, never new methods on `NsClient`.
|
|
65
|
+
|
|
66
|
+
### Configuration binds to *your* deployment
|
|
67
|
+
|
|
68
|
+
Two values are required and have no defaults, on purpose — a default would silently bind you to
|
|
69
|
+
someone else's portal:
|
|
70
|
+
|
|
71
|
+
- `NsClient({ server })` — your NS API host, e.g. `api.example.com`.
|
|
72
|
+
- `verify(token, { expectedIss })` — the Manager Portal host that issues your `ns_t`, e.g.
|
|
73
|
+
`manage.example.com`. Pass an array when one backend is fronted by several portal hostnames
|
|
74
|
+
(exact match, no wildcards), or `validateIss: false` to opt out deliberately.
|
|
75
|
+
|
|
76
|
+
`aud` defaults to `"ns"` because that value is fixed by the NetSapiens platform and true for everyone.
|
|
77
|
+
|
|
78
|
+
## Develop
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
pnpm install
|
|
82
|
+
pnpm build # tsc → dist/
|
|
83
|
+
pnpm test # the offline suite — green with no credentials, no setup
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The build (`tsconfig.json`) omits `@types/node` on purpose: a stray `node:*` import fails the build,
|
|
87
|
+
which is how the Node-free guarantee is enforced.
|
|
88
|
+
|
|
89
|
+
`pnpm test:ns <snapshot.json>` is separate and not part of `pnpm test`: it needs a real domain
|
|
90
|
+
snapshot, which is customer data and correctly absent from this repo.
|
|
91
|
+
|
|
92
|
+
## Docs
|
|
93
|
+
|
|
94
|
+
- **[ARCHITECTURE.md](./ARCHITECTURE.md)** — module boundaries, why the live `/jwt` call is the
|
|
95
|
+
signature authority, the Mermaid rendering traps, and the NetSapiens routing model the resolver
|
|
96
|
+
decodes.
|
|
97
|
+
- **[CONTRIBUTING.md](./CONTRIBUTING.md)** — the rules: fictional fixtures, no deployment-binding
|
|
98
|
+
defaults, doc comments are published API, Node-free.
|
|
99
|
+
- **[CHANGELOG.md](./CHANGELOG.md)**
|
|
100
|
+
|
|
101
|
+
## License
|
|
102
|
+
|
|
103
|
+
[MIT](./LICENSE) © David Szpunar
|
package/dist/html.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FlowGraph[] -> a self-contained gallery HTML string. Portable (no Node deps): the caller
|
|
3
|
+
* decides where the string goes — a file (CLI), an HTTP response (Worker), or embedded in
|
|
4
|
+
* ns-onboard's review page / build-preview. Mermaid renders client-side from a CDN.
|
|
5
|
+
*/
|
|
6
|
+
import type { FlowGraph } from './model.js';
|
|
7
|
+
import { type FlowTheme } from './mermaid.js';
|
|
8
|
+
/** Stable, collision-safe DOM id for a flow card so a host page can deep-link one diagram. */
|
|
9
|
+
export declare function flowAnchorId(g: FlowGraph): string;
|
|
10
|
+
export interface CardOptions {
|
|
11
|
+
/** Themed rendering (light/dark palette + `look: neo` + a card anchor id). OMIT for the
|
|
12
|
+
* legacy dark card with no frontmatter and no id — the Worker relies on that being unchanged. */
|
|
13
|
+
theme?: FlowTheme;
|
|
14
|
+
/** Render the card as a collapsible `<details>` (gallery navigation). */
|
|
15
|
+
collapsible?: boolean;
|
|
16
|
+
/** When collapsible, start expanded. */
|
|
17
|
+
open?: boolean;
|
|
18
|
+
/** When collapsible, add a "↑ top" link in the summary. */
|
|
19
|
+
backToTop?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface GalleryOptions extends CardOptions {
|
|
22
|
+
/** Load Mermaid from this URL (CDN by default). Override to self-host / inline for CSP. */
|
|
23
|
+
mermaidSrc?: string;
|
|
24
|
+
/** Extra note shown under the title. */
|
|
25
|
+
subtitle?: string;
|
|
26
|
+
/** Brand accent (hex) for subtle highlights — links, the "Legend:" lead, the card's top rule, and
|
|
27
|
+
* the pan/zoom controls' hover. Themed path only; defaults to the theme's link color. Pass your
|
|
28
|
+
* own brand color here (from your host's config) rather than baking one into a theme, e.g.
|
|
29
|
+
* `accent: '#1a6bb0'`. */
|
|
30
|
+
accent?: string;
|
|
31
|
+
/** Themed galleries only: anchor ids ({@link flowAnchorId}) to render pre-expanded. Cards not in
|
|
32
|
+
* the set render collapsed. A themed gallery is always collapsible with a table-of-contents. */
|
|
33
|
+
expand?: Set<string>;
|
|
34
|
+
}
|
|
35
|
+
/** Render one flow as a gallery `<section>` card. Themed cards carry an `id` anchor
|
|
36
|
+
* ({@link flowAnchorId}); the legacy (no-theme) card is byte-identical to the original. */
|
|
37
|
+
export declare function renderFlowCard(g: FlowGraph, opts?: CardOptions): string;
|
|
38
|
+
/** Render the gallery `<section>` cards only (no page chrome) — for embedding in another page. */
|
|
39
|
+
export declare function renderFlowCards(graphs: FlowGraph[], opts?: CardOptions): string;
|
|
40
|
+
/**
|
|
41
|
+
* The `<script>` tags a host page needs to render embedded `.mermaid` blocks itself
|
|
42
|
+
* (e.g. ns-onboard's review report). `securityLevel: 'strict'` is MANDATORY — it is the
|
|
43
|
+
* second escaping layer `mermaid.ts` depends on; do not make it caller-configurable.
|
|
44
|
+
*/
|
|
45
|
+
export declare function mermaidBootstrap(opts?: {
|
|
46
|
+
theme?: FlowTheme;
|
|
47
|
+
mermaidSrc?: string;
|
|
48
|
+
}): string;
|
|
49
|
+
/** Full standalone HTML document for a set of flows. */
|
|
50
|
+
export declare function renderGalleryHtml(domain: string, graphs: FlowGraph[], opts?: GalleryOptions): string;
|
|
51
|
+
//# sourceMappingURL=html.d.ts.map
|
package/dist/html.js
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FlowGraph[] -> a self-contained gallery HTML string. Portable (no Node deps): the caller
|
|
3
|
+
* decides where the string goes — a file (CLI), an HTTP response (Worker), or embedded in
|
|
4
|
+
* ns-onboard's review page / build-preview. Mermaid renders client-side from a CDN.
|
|
5
|
+
*/
|
|
6
|
+
import { toMermaid } from './mermaid.js';
|
|
7
|
+
// Pinned Mermaid build + Subresource Integrity. A floating `mermaid@11` tag lets jsDelivr serve whatever
|
|
8
|
+
// the latest 11.x is with NO integrity guarantee — a compromised/substituted CDN response would execute in
|
|
9
|
+
// the gallery (and, before the modal iframe was sandboxed, could reach the portal's ns_t). Pin an exact
|
|
10
|
+
// version + SRI hash so the browser refuses any bytes that don't match. Bump BOTH together — recompute:
|
|
11
|
+
// curl -s https://cdn.jsdelivr.net/npm/mermaid@<v>/dist/mermaid.min.js | openssl dgst -sha384 -binary | openssl base64 -A
|
|
12
|
+
const MERMAID_VERSION = '11.16.0';
|
|
13
|
+
const MERMAID_CDN = `https://cdn.jsdelivr.net/npm/mermaid@${MERMAID_VERSION}/dist/mermaid.min.js`;
|
|
14
|
+
const MERMAID_SRI = 'sha384-T/0lMUdJpd2S1ZHtRiofG3htU3xPCrFVeAQ1UUE2TJwlEJSV5NUwn30kP28n238E';
|
|
15
|
+
/** Build the Mermaid `<script>` tag. For the pinned jsDelivr build (the default) attach `integrity` +
|
|
16
|
+
* `crossorigin` so the browser rejects a substituted CDN payload. A caller-supplied self-hosted `src`
|
|
17
|
+
* is emitted WITHOUT SRI — the caller owns that origin's integrity, and its bytes won't match this hash. */
|
|
18
|
+
function mermaidScriptTag(src) {
|
|
19
|
+
const url = src ?? MERMAID_CDN;
|
|
20
|
+
const sri = url === MERMAID_CDN ? ` integrity="${MERMAID_SRI}" crossorigin="anonymous"` : '';
|
|
21
|
+
return `<script src="${escapeHtml(url)}"${sri}></script>`;
|
|
22
|
+
}
|
|
23
|
+
/** Left-align multi-line node content (agent / device / queue lists) + edge labels so they read as
|
|
24
|
+
* lists, not centered blobs. Hosts include this wherever `.mermaid` diagrams render. */
|
|
25
|
+
const FLOW_LABEL_CSS = `.mermaid g.agents div, .mermaid g.agents span, .mermaid g.agents p,
|
|
26
|
+
.mermaid g.devices div, .mermaid g.devices span, .mermaid g.devices p,
|
|
27
|
+
.mermaid g.queue div, .mermaid g.queue span, .mermaid g.queue p { text-align:left !important; }
|
|
28
|
+
.mermaid .edgeLabel div, .mermaid .edgeLabel span, .mermaid .edgeLabel p { text-align:left !important; }
|
|
29
|
+
/* Edge-label chips — keep them TIGHT. Mermaid gives the label foreignObject a tall line-height and a
|
|
30
|
+
semi-transparent .labelBkg; once overflow:visible stops the right-edge clip (it under-measures width
|
|
31
|
+
under look:neo, so "press 2" got cut), that block showed as an oversized blocky highlight and short
|
|
32
|
+
labels even wrapped. So: drop the block bg, force single-line (explicit <br/> in multi-option labels
|
|
33
|
+
still breaks), and render the text as a compact rounded chip with a little space before/after.
|
|
34
|
+
mermaid.ts drops the old trailing-nbsp slack in favor of this. */
|
|
35
|
+
.mermaid g.edgeLabel foreignObject { overflow:visible; }
|
|
36
|
+
.mermaid .edgeLabel .labelBkg { background:transparent !important; }
|
|
37
|
+
.mermaid .edgeLabel foreignObject > div { white-space:nowrap !important; line-height:1.35 !important; max-width:none !important; }
|
|
38
|
+
.mermaid span.edgeLabel { display:inline-block; padding:1px 7px; border-radius:4px; line-height:1.35; white-space:nowrap; }`;
|
|
39
|
+
/** Shared flowchart layout config. Node clipping is prevented by nbsp label slack in mermaid.ts,
|
|
40
|
+
* not by padding — so this keeps Mermaid's default node padding (a larger override made wide nodes
|
|
41
|
+
* like AAs balloon). */
|
|
42
|
+
const FLOWCHART_CFG = `htmlLabels:true, curve:'basis', nodeSpacing:45, rankSpacing:55`;
|
|
43
|
+
function escapeHtml(t) {
|
|
44
|
+
return String(t).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
45
|
+
}
|
|
46
|
+
function cap(t) {
|
|
47
|
+
return t.charAt(0).toUpperCase() + t.slice(1);
|
|
48
|
+
}
|
|
49
|
+
/** Stable, collision-safe DOM id for a flow card so a host page can deep-link one diagram. */
|
|
50
|
+
export function flowAnchorId(g) {
|
|
51
|
+
return `flow-${g.entity.kind}-${String(g.entity.ref).replace(/[^A-Za-z0-9_-]/g, '-')}`;
|
|
52
|
+
}
|
|
53
|
+
/** Render one flow as a gallery `<section>` card. Themed cards carry an `id` anchor
|
|
54
|
+
* ({@link flowAnchorId}); the legacy (no-theme) card is byte-identical to the original. */
|
|
55
|
+
export function renderFlowCard(g, opts = {}) {
|
|
56
|
+
const notes = g.notes.length ? `<ul class="cf-notes">${g.notes.map((n) => `<li>${escapeHtml(n)}</li>`).join('')}</ul>` : '';
|
|
57
|
+
const mermaid = `<div class="mermaid">${escapeHtml(toMermaid(g, opts.theme ? { theme: opts.theme } : undefined))}</div>`;
|
|
58
|
+
const title = `${escapeHtml(g.entity.kind === 'did' ? 'DID' : cap(g.entity.kind))}: ${escapeHtml(g.entity.label)}`;
|
|
59
|
+
if (opts.collapsible) {
|
|
60
|
+
const back = opts.backToTop ? ` <a class="cf-top" href="#cf-top" onclick="event.stopPropagation()">↑ top</a>` : '';
|
|
61
|
+
return `<details class="cf-card"${opts.theme ? ` id="${flowAnchorId(g)}"` : ''}${opts.open ? ' open' : ''}>
|
|
62
|
+
<summary><span class="cf-title">${title}</span><span class="cf-meta"> · ${g.nodes.length} nodes · ${g.edges.length} edges</span>${back}</summary>
|
|
63
|
+
${mermaid}
|
|
64
|
+
${notes}
|
|
65
|
+
</details>`;
|
|
66
|
+
}
|
|
67
|
+
const idAttr = opts.theme ? ` id="${flowAnchorId(g)}"` : '';
|
|
68
|
+
return `<section class="cf-card"${idAttr}>
|
|
69
|
+
<h2>${title}</h2>
|
|
70
|
+
<div class="cf-meta">${g.nodes.length} nodes · ${g.edges.length} edges</div>
|
|
71
|
+
${mermaid}
|
|
72
|
+
${notes}
|
|
73
|
+
</section>`;
|
|
74
|
+
}
|
|
75
|
+
/** Render the gallery `<section>` cards only (no page chrome) — for embedding in another page. */
|
|
76
|
+
export function renderFlowCards(graphs, opts = {}) {
|
|
77
|
+
return graphs.map((g) => renderFlowCard(g, opts)).join('\n');
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The `<script>` tags a host page needs to render embedded `.mermaid` blocks itself
|
|
81
|
+
* (e.g. ns-onboard's review report). `securityLevel: 'strict'` is MANDATORY — it is the
|
|
82
|
+
* second escaping layer `mermaid.ts` depends on; do not make it caller-configurable.
|
|
83
|
+
*/
|
|
84
|
+
export function mermaidBootstrap(opts = {}) {
|
|
85
|
+
const mermaidTheme = opts.theme === 'light' ? 'base' : 'dark';
|
|
86
|
+
return `<style>${FLOW_LABEL_CSS}</style>
|
|
87
|
+
${mermaidScriptTag(opts.mermaidSrc)}
|
|
88
|
+
<script>mermaid.initialize({ startOnLoad:true, theme:'${mermaidTheme}', securityLevel:'strict', flowchart:{ ${FLOWCHART_CFG} } });</script>`;
|
|
89
|
+
}
|
|
90
|
+
/** Full standalone HTML document for a set of flows. */
|
|
91
|
+
export function renderGalleryHtml(domain, graphs, opts = {}) {
|
|
92
|
+
const mermaidSrc = opts.mermaidSrc ?? MERMAID_CDN;
|
|
93
|
+
const subtitle = opts.subtitle ?? `resolved from snapshot · ${graphs.length} flows`;
|
|
94
|
+
// Themed path (light for ns-onboard, or explicit dark-neo). No theme → the original dark
|
|
95
|
+
// document below, byte-identical (the Worker depends on this).
|
|
96
|
+
if (opts.theme)
|
|
97
|
+
return themedGalleryHtml(domain, graphs, mermaidSrc, subtitle, opts.theme, opts.expand, opts.accent);
|
|
98
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8">
|
|
99
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
100
|
+
<title>Call Flows — ${escapeHtml(domain)}</title>
|
|
101
|
+
${mermaidScriptTag(mermaidSrc)}
|
|
102
|
+
<style>
|
|
103
|
+
:root { color-scheme: dark; }
|
|
104
|
+
body { background:#0f1115; color:#e6e6e6; font:15px/1.5 system-ui,sans-serif; margin:0; padding:24px; }
|
|
105
|
+
h1 { font-size:14px; font-weight:600; color:#8a94a6; margin:0 0 2px; }
|
|
106
|
+
.cf-sub { color:#e6e6e6; margin:0 0 24px; font-size:18px; font-weight:700; }
|
|
107
|
+
.cf-card { background:#161a22; border:1px solid #232a36; border-radius:12px; padding:18px 20px; margin:0 0 22px; }
|
|
108
|
+
.cf-card h2 { font-size:16px; margin:0 0 2px; }
|
|
109
|
+
.cf-meta { color:#7b8494; font-size:12px; margin-bottom:12px; }
|
|
110
|
+
.mermaid { background:#0c0e12; border-radius:8px; padding:14px; overflow:auto; text-align:center; }
|
|
111
|
+
${FLOW_LABEL_CSS}
|
|
112
|
+
.cf-notes { margin:12px 0 0; padding-left:18px; color:#c9a24a; font-size:12.5px; }
|
|
113
|
+
.cf-notes li { margin:2px 0; }
|
|
114
|
+
.cf-legend { display:flex; align-items:center; flex-wrap:wrap; gap:10px; margin:0 0 22px; font-size:12px; color:#aab; }
|
|
115
|
+
.cf-legend span { background:#161a22; border:1px solid #232a36; border-radius:6px; padding:3px 8px; }
|
|
116
|
+
.cf-legend-lead { font-weight:700; }
|
|
117
|
+
.mermaid { cursor:zoom-in; }
|
|
118
|
+
.cf-lightbox { position:fixed; inset:0; background:rgba(6,8,12,.95); display:none; z-index:9999; padding:20px; cursor:zoom-out; }
|
|
119
|
+
.cf-lightbox.open { display:block; }
|
|
120
|
+
.cf-lightbox-inner { width:100%; height:100%; overflow:auto; display:flex; align-items:flex-start; justify-content:center; }
|
|
121
|
+
.cf-lightbox-inner svg { max-width:none !important; height:auto; }
|
|
122
|
+
.cf-lightbox-hint { position:fixed; top:10px; right:16px; color:#8a94a6; font-size:12px; pointer-events:none; }
|
|
123
|
+
</style></head>
|
|
124
|
+
<body>
|
|
125
|
+
<h1>Call Flow Diagram — ${escapeHtml(domain)}</h1>
|
|
126
|
+
<p class="cf-sub">${escapeHtml(subtitle)}</p>
|
|
127
|
+
<div class="cf-legend">
|
|
128
|
+
<b class="cf-legend-lead">Legend:</b><span>📞 DID</span><span>🕒 time-of-day</span><span>👤 user</span><span>📱 ring devices</span>
|
|
129
|
+
<span>📋 queue</span><span>👥 agents</span><span>🔀 auto attendant</span><span>🔊 prompt</span>
|
|
130
|
+
<span>📭 voicemail</span><span>☎️ external</span>
|
|
131
|
+
</div>
|
|
132
|
+
${renderFlowCards(graphs)}
|
|
133
|
+
<div id="cf-lightbox" class="cf-lightbox"><span class="cf-lightbox-hint">click / Esc to close</span><div class="cf-lightbox-inner"></div></div>
|
|
134
|
+
<script>
|
|
135
|
+
mermaid.initialize({ startOnLoad:true, theme:'dark', securityLevel:'strict', flowchart:{ ${FLOWCHART_CFG} } });
|
|
136
|
+
// Click any diagram to enlarge it in a scrollable overlay; click anywhere / Esc to close.
|
|
137
|
+
(function(){
|
|
138
|
+
var box = document.getElementById('cf-lightbox');
|
|
139
|
+
var inner = box.querySelector('.cf-lightbox-inner');
|
|
140
|
+
function close(){ box.classList.remove('open'); inner.replaceChildren(); }
|
|
141
|
+
document.addEventListener('click', function(e){
|
|
142
|
+
var card = e.target.closest ? e.target.closest('.mermaid') : null;
|
|
143
|
+
if (card && !box.contains(card)) {
|
|
144
|
+
var svg = card.querySelector('svg');
|
|
145
|
+
if (!svg) return;
|
|
146
|
+
var clone = svg.cloneNode(true);
|
|
147
|
+
clone.removeAttribute('height'); clone.removeAttribute('style');
|
|
148
|
+
inner.replaceChildren(clone);
|
|
149
|
+
box.classList.add('open');
|
|
150
|
+
} else if (box.classList.contains('open')) {
|
|
151
|
+
close();
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
document.addEventListener('keydown', function(e){ if (e.key === 'Escape') close(); });
|
|
155
|
+
})();
|
|
156
|
+
</script>
|
|
157
|
+
</body></html>`;
|
|
158
|
+
}
|
|
159
|
+
/** Themed gallery document — light (ns-onboard review context) or explicit dark-neo. Parallel to
|
|
160
|
+
* the legacy dark document in {@link renderGalleryHtml}; kept separate so that path stays byte-identical. */
|
|
161
|
+
function themedGalleryHtml(domain, graphs, mermaidSrc, subtitle, theme, expand, accent) {
|
|
162
|
+
const light = theme === 'light';
|
|
163
|
+
const c = light
|
|
164
|
+
? { scheme: 'light', pageBg: '#fafafa', text: '#1e293b', sub: '#64748b', cardBg: '#ffffff', cardBorder: '#e2e8f0', meta: '#64748b', mermaidBg: '#f8fafc', notes: '#b45309', legendText: '#475569', legendBg: '#ffffff', lightboxBg: 'rgba(248,250,252,.96)', hint: '#64748b', shadow: 'box-shadow:0 1px 2px rgba(0,0,0,.04);', link: '#21618c' }
|
|
165
|
+
: { scheme: 'dark', pageBg: '#0f1115', text: '#e6e6e6', sub: '#8a94a6', cardBg: '#161a22', cardBorder: '#232a36', meta: '#7b8494', mermaidBg: '#0c0e12', notes: '#c9a24a', legendText: '#aab', legendBg: '#161a22', lightboxBg: 'rgba(6,8,12,.95)', hint: '#8a94a6', shadow: '', link: '#7db3e6' };
|
|
166
|
+
const initTheme = light ? 'base' : 'dark';
|
|
167
|
+
const brandAccent = accent || c.link;
|
|
168
|
+
const single = graphs.length === 1; // a one-flow gallery (e.g. the portal modal) needs no contents nav
|
|
169
|
+
const isOpen = (g) => single || (!!expand && expand.has(flowAnchorId(g)));
|
|
170
|
+
// Table of contents grouped by entity kind; ● marks the pre-expanded (notable) flows.
|
|
171
|
+
const KIND_LABEL = { did: '📞 DIDs', user: '👤 Users', queue: '📋 Queues', attendant: '🔀 Auto Attendants' };
|
|
172
|
+
const byKind = new Map();
|
|
173
|
+
for (const g of graphs) {
|
|
174
|
+
const k = g.entity.kind;
|
|
175
|
+
if (!byKind.has(k))
|
|
176
|
+
byKind.set(k, []);
|
|
177
|
+
byKind.get(k).push(g);
|
|
178
|
+
}
|
|
179
|
+
const toc = [...byKind.entries()]
|
|
180
|
+
.map(([kind, gs]) => `<div class="cf-toc-group"><div class="cf-toc-h">${KIND_LABEL[kind] ?? escapeHtml(cap(kind))}</div>
|
|
181
|
+
<ul>${gs
|
|
182
|
+
.map((g) => `<li>${isOpen(g) ? '<b>●</b> ' : ''}<a href="#${flowAnchorId(g)}">${escapeHtml(g.entity.label)}</a></li>`)
|
|
183
|
+
.join('')}</ul></div>`)
|
|
184
|
+
.join('\n');
|
|
185
|
+
// A single-flow gallery (portal modal) renders as a fixed, non-collapsible card; multi-flow keeps the
|
|
186
|
+
// collapsible <details> for navigation.
|
|
187
|
+
const cards = graphs.map((g) => renderFlowCard(g, { theme, collapsible: !single, open: isOpen(g), backToTop: !single })).join('\n');
|
|
188
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8">
|
|
189
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
190
|
+
<title>Call Flows — ${escapeHtml(domain)}</title>
|
|
191
|
+
${mermaidScriptTag(mermaidSrc)}
|
|
192
|
+
<style>
|
|
193
|
+
:root { color-scheme: ${c.scheme}; scroll-behavior:smooth; }
|
|
194
|
+
body { background:${c.pageBg}; color:${c.text}; font:15px/1.5 system-ui,sans-serif; margin:0; padding:24px; }
|
|
195
|
+
a { color:${brandAccent}; }
|
|
196
|
+
h1 { font-size:14px; font-weight:600; color:${c.sub}; margin:0 0 2px; }
|
|
197
|
+
.cf-sub { color:${c.text}; margin:0 0 18px; font-size:18px; font-weight:700; }
|
|
198
|
+
.cf-legend { display:flex; align-items:center; flex-wrap:wrap; gap:10px; margin:0 0 18px; font-size:12px; color:${c.legendText}; }
|
|
199
|
+
.cf-legend span { background:${c.legendBg}; border:1px solid ${c.cardBorder}; border-radius:6px; padding:3px 8px; }
|
|
200
|
+
.cf-legend-lead { font-weight:700; color:${brandAccent}; }
|
|
201
|
+
.cf-toc { background:${c.cardBg}; border:1px solid ${c.cardBorder}; border-radius:12px; padding:14px 18px; margin:0 0 22px; ${c.shadow} }
|
|
202
|
+
.cf-toc-top { font-weight:700; font-size:13px; margin:0 0 8px; }
|
|
203
|
+
.cf-toc-cols { display:flex; flex-wrap:wrap; gap:8px 28px; }
|
|
204
|
+
.cf-toc-group { min-width:160px; }
|
|
205
|
+
.cf-toc-h { font-size:12px; font-weight:700; color:${c.sub}; margin:4px 0 2px; }
|
|
206
|
+
.cf-toc ul { margin:0 0 6px; padding-left:16px; font-size:13px; }
|
|
207
|
+
.cf-toc li { margin:1px 0; }
|
|
208
|
+
.cf-card { background:${c.cardBg}; border:1px solid ${c.cardBorder}; border-radius:12px; padding:0; margin:0 0 14px; ${c.shadow} scroll-margin-top:12px; }
|
|
209
|
+
section.cf-card { padding:12px 18px; border-top:3px solid ${brandAccent}; }
|
|
210
|
+
section.cf-card > h2 { font-size:16px; margin:0 0 2px; }
|
|
211
|
+
details.cf-card > summary { cursor:pointer; padding:12px 18px; font-size:15px; font-weight:700; list-style:none; user-select:none; }
|
|
212
|
+
details.cf-card > summary::-webkit-details-marker { display:none; }
|
|
213
|
+
details.cf-card > summary::before { content:'▸ '; color:${c.sub}; font-weight:400; }
|
|
214
|
+
details.cf-card[open] > summary::before { content:'▾ '; }
|
|
215
|
+
details.cf-card[open] > summary { border-bottom:1px solid ${c.cardBorder}; }
|
|
216
|
+
.cf-title { }
|
|
217
|
+
.cf-meta { color:${c.meta}; font-size:12px; font-weight:400; }
|
|
218
|
+
.cf-top { float:right; font-size:11px; font-weight:400; }
|
|
219
|
+
details.cf-card > .mermaid, details.cf-card > .cf-notes { margin:0 18px; }
|
|
220
|
+
.mermaid { background:${c.mermaidBg}; border-radius:8px; padding:14px; margin:14px 0; overflow:auto; text-align:center; cursor:zoom-in; }
|
|
221
|
+
.mermaid.cf-pz { overflow:hidden; cursor:grab; position:relative; text-align:left; padding:0; height:70vh; touch-action:none; }
|
|
222
|
+
.mermaid.cf-pz svg { position:absolute; top:0; left:0; max-width:none !important; height:auto; }
|
|
223
|
+
.cf-pz-ctl { position:absolute; top:10px; right:10px; display:flex; flex-direction:column; gap:5px; z-index:5; }
|
|
224
|
+
.cf-pz-ctl button { width:30px; height:30px; border:1px solid ${c.cardBorder}; background:${c.cardBg}; color:${c.text}; border-radius:6px; cursor:pointer; font:16px/1 system-ui,sans-serif; ${c.shadow} }
|
|
225
|
+
.cf-pz-ctl button:hover { border-color:${brandAccent}; color:${brandAccent}; }
|
|
226
|
+
${FLOW_LABEL_CSS}
|
|
227
|
+
.cf-notes { padding:0 0 14px 34px; color:${c.notes}; font-size:12.5px; }
|
|
228
|
+
.cf-notes li { margin:2px 0; }
|
|
229
|
+
.cf-lightbox { position:fixed; inset:0; background:${c.lightboxBg}; display:none; z-index:9999; padding:20px; cursor:zoom-out; }
|
|
230
|
+
.cf-lightbox.open { display:block; }
|
|
231
|
+
.cf-lightbox-inner { width:100%; height:100%; overflow:auto; display:flex; align-items:flex-start; justify-content:center; }
|
|
232
|
+
.cf-lightbox-inner svg { max-width:none !important; height:auto; }
|
|
233
|
+
.cf-lightbox-hint { position:fixed; top:10px; right:16px; color:${c.hint}; font-size:12px; pointer-events:none; }
|
|
234
|
+
</style></head>
|
|
235
|
+
<body>
|
|
236
|
+
<span id="cf-top"></span>
|
|
237
|
+
<h1>Call Flow Diagram — ${escapeHtml(domain)}</h1>
|
|
238
|
+
<p class="cf-sub">${escapeHtml(subtitle)}</p>
|
|
239
|
+
<div class="cf-legend">
|
|
240
|
+
<b class="cf-legend-lead">Legend:</b><span>📞 DID</span><span>🕒 time-of-day</span><span>👤 user</span><span>📱 ring devices</span>
|
|
241
|
+
<span>📋 queue</span><span>👥 agents</span><span>🔀 auto attendant</span><span>🔊 prompt</span>
|
|
242
|
+
<span>📭 voicemail</span><span>☎️ external</span>
|
|
243
|
+
</div>
|
|
244
|
+
${single ? '' : `<nav class="cf-toc">
|
|
245
|
+
<div class="cf-toc-top">Contents <span class="cf-meta">(● = notable / pre-expanded)</span></div>
|
|
246
|
+
<div class="cf-toc-cols">${toc}</div>
|
|
247
|
+
</nav>`}
|
|
248
|
+
${cards}
|
|
249
|
+
<div id="cf-lightbox" class="cf-lightbox"><span class="cf-lightbox-hint">click / Esc to close</span><div class="cf-lightbox-inner"></div></div>
|
|
250
|
+
${mermaidBootstrapInline(initTheme)}
|
|
251
|
+
</body></html>`;
|
|
252
|
+
}
|
|
253
|
+
/** The gallery's own Mermaid init + lightbox script (self-contained; not the host-page bootstrap). */
|
|
254
|
+
function mermaidBootstrapInline(initTheme) {
|
|
255
|
+
return `<script>
|
|
256
|
+
mermaid.initialize({ startOnLoad:false, theme:'${initTheme}', securityLevel:'strict', flowchart:{ ${FLOWCHART_CFG} } });
|
|
257
|
+
document.querySelectorAll('.mermaid').forEach(function(el){ if(!el.getAttribute('data-src')){ el.setAttribute('data-src', el.textContent); } });
|
|
258
|
+
mermaid.run({ querySelector:'.mermaid' }).then(function(){
|
|
259
|
+
var els = document.querySelectorAll('.mermaid');
|
|
260
|
+
// Single-flow (the portal modal): scroll/drag pan, Shift+scroll or +/- buttons zoom, flip layout.
|
|
261
|
+
if (els.length === 1) { panZoom(els[0]); } else { lightbox(); }
|
|
262
|
+
});
|
|
263
|
+
function panZoom(el){
|
|
264
|
+
var svg = el.querySelector('svg'); if(!svg) return;
|
|
265
|
+
el.classList.add('cf-pz');
|
|
266
|
+
// Pin the SVG to its natural viewBox pixel size so our transform (not mermaid's width:100% +
|
|
267
|
+
// viewBox auto-scaling) fully controls the displayed size — otherwise fit measurements are wrong.
|
|
268
|
+
var vb=(svg.viewBox&&svg.viewBox.baseVal)||{};
|
|
269
|
+
var natW=vb.width||svg.getBoundingClientRect().width, natH=vb.height||svg.getBoundingClientRect().height;
|
|
270
|
+
svg.setAttribute('width', natW); svg.setAttribute('height', natH); svg.style.maxWidth='none'; svg.style.transformOrigin='0 0';
|
|
271
|
+
var st = el._pz || (el._pz = {}); st.svg=svg; st.natW=natW; st.natH=natH; st.k=1; st.tx=0; st.ty=0; st.drag=false;
|
|
272
|
+
st.apply = function(){ svg.style.transform='translate('+st.tx+'px,'+st.ty+'px) scale('+st.k+')'; };
|
|
273
|
+
st.fit = function(){
|
|
274
|
+
var r=el.getBoundingClientRect(), w=st.natW||r.width, h=st.natH||r.height;
|
|
275
|
+
var pad=16, lr=/flowchart\\s+(LR|RL)/.test(el.getAttribute('data-src')||'');
|
|
276
|
+
// Fill the primary axis (TD → width, LR → height); never upscale past natural (1x) so small
|
|
277
|
+
// few-node diagrams don't balloon. Center the cross-axis; start-align the overflowing one.
|
|
278
|
+
st.k=Math.min(lr?(r.height-pad)/h:(r.width-pad)/w, 1); if(!isFinite(st.k)||st.k<=0){ st.k=1; }
|
|
279
|
+
if(lr){ st.ty=(r.height-h*st.k)/2; st.tx=(w*st.k>r.width)?pad:(r.width-w*st.k)/2; }
|
|
280
|
+
else { st.tx=(r.width-w*st.k)/2; st.ty=(h*st.k>r.height)?pad:(r.height-h*st.k)/2; }
|
|
281
|
+
st.apply();
|
|
282
|
+
};
|
|
283
|
+
st.zoomAt = function(f, cx, cy){ var nk=Math.max(0.2,Math.min(6,st.k*f)), r=nk/st.k; st.tx=cx-(cx-st.tx)*r; st.ty=cy-(cy-st.ty)*r; st.k=nk; st.apply(); };
|
|
284
|
+
function flip(){
|
|
285
|
+
var src=el.getAttribute('data-src')||'';
|
|
286
|
+
var cur=(src.match(/flowchart\\s+(TB|TD|LR|RL|BT)/)||[])[1]||'TD';
|
|
287
|
+
var next=(cur==='LR'||cur==='RL')?'TD':'LR';
|
|
288
|
+
el.setAttribute('data-src', src.replace(/flowchart\\s+(TB|TD|LR|RL|BT)/, 'flowchart '+next));
|
|
289
|
+
var oc=el.querySelector('.cf-pz-ctl'); if(oc){ oc.remove(); }
|
|
290
|
+
el.classList.remove('cf-pz'); el.removeAttribute('data-processed'); el.textContent=el.getAttribute('data-src');
|
|
291
|
+
mermaid.run({ nodes:[el] }).then(function(){ panZoom(el); });
|
|
292
|
+
}
|
|
293
|
+
var oldc=el.querySelector('.cf-pz-ctl'); if(oldc){ oldc.remove(); }
|
|
294
|
+
var ctl=document.createElement('div'); ctl.className='cf-pz-ctl';
|
|
295
|
+
function mk(t,tip,f){ var b=document.createElement('button'); b.type='button'; b.textContent=t; b.title=tip; b.addEventListener('click', function(e){ e.stopPropagation(); f(); }); ctl.appendChild(b); }
|
|
296
|
+
mk('+','Zoom in', function(){ st.zoomAt(1.2, el.clientWidth/2, 0); });
|
|
297
|
+
mk('−','Zoom out', function(){ st.zoomAt(1/1.2, el.clientWidth/2, 0); });
|
|
298
|
+
mk('↺','Reset view', function(){ st.fit(); });
|
|
299
|
+
mk('⇄','Flip layout (horizontal / vertical)', flip);
|
|
300
|
+
el.appendChild(ctl);
|
|
301
|
+
if(!el._pzBound){
|
|
302
|
+
el._pzBound=true;
|
|
303
|
+
el.addEventListener('wheel', function(e){
|
|
304
|
+
e.preventDefault();
|
|
305
|
+
if(e.shiftKey){ var d=e.deltaY||e.deltaX, r=el.getBoundingClientRect(); st.zoomAt(d<0?1.1:1/1.1, e.clientX-r.left, e.clientY-r.top); }
|
|
306
|
+
else { st.tx-=e.deltaX; st.ty-=e.deltaY; st.apply(); }
|
|
307
|
+
}, {passive:false});
|
|
308
|
+
el.addEventListener('pointerdown', function(e){ if(e.target.closest && e.target.closest('.cf-pz-ctl')){ return; } st.drag=true; st.px=e.clientX; st.py=e.clientY; try{ el.setPointerCapture(e.pointerId); }catch(_){} el.style.cursor='grabbing'; });
|
|
309
|
+
el.addEventListener('pointermove', function(e){ if(!st.drag){ return; } st.tx+=e.clientX-st.px; st.ty+=e.clientY-st.py; st.px=e.clientX; st.py=e.clientY; st.apply(); });
|
|
310
|
+
var end=function(){ st.drag=false; el.style.cursor='grab'; };
|
|
311
|
+
el.addEventListener('pointerup', end); el.addEventListener('pointercancel', end);
|
|
312
|
+
window.addEventListener('resize', function(){ if(st.fit){ st.fit(); } });
|
|
313
|
+
}
|
|
314
|
+
requestAnimationFrame(function(){ st.fit(); }); setTimeout(function(){ st.fit(); }, 80);
|
|
315
|
+
}
|
|
316
|
+
function lightbox(){
|
|
317
|
+
var box = document.getElementById('cf-lightbox'); if(!box){ return; }
|
|
318
|
+
var inner = box.querySelector('.cf-lightbox-inner');
|
|
319
|
+
function close(){ box.classList.remove('open'); inner.replaceChildren(); }
|
|
320
|
+
document.addEventListener('click', function(e){
|
|
321
|
+
var card = e.target.closest ? e.target.closest('.mermaid') : null;
|
|
322
|
+
if (card && !box.contains(card)) {
|
|
323
|
+
var svg = card.querySelector('svg'); if (!svg) return;
|
|
324
|
+
var clone = svg.cloneNode(true); clone.removeAttribute('height'); clone.removeAttribute('style');
|
|
325
|
+
inner.replaceChildren(clone); box.classList.add('open');
|
|
326
|
+
} else if (box.classList.contains('open')) { close(); }
|
|
327
|
+
});
|
|
328
|
+
document.addEventListener('keydown', function(e){ if (e.key === 'Escape') close(); });
|
|
329
|
+
}
|
|
330
|
+
</script>`;
|
|
331
|
+
}
|
|
332
|
+
//# sourceMappingURL=html.js.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public API of the portable call-flow library — the surface any host imports (Cloudflare
|
|
3
|
+
* Worker, ns-onboard CLI / review page / build-preview, the portal viewer). Everything
|
|
4
|
+
* re-exported here is Node-free and runtime-portable. The Node-only CLI (`cli.ts`) is NOT
|
|
5
|
+
* part of this surface.
|
|
6
|
+
*
|
|
7
|
+
* Typical use in another project:
|
|
8
|
+
* import { resolveFlow, toMermaid, renderGalleryHtml, verify } from '@dszp/netsapiens-lib';
|
|
9
|
+
* const graph = resolveFlow(snapshot, { kind: 'did', ref: '13175550100' });
|
|
10
|
+
* const html = renderGalleryHtml(snapshot.meta.domain, [graph]);
|
|
11
|
+
*/
|
|
12
|
+
export type { FlowGraph, FlowNode, FlowEdge, NodeKind, EdgeKind, Snapshot, Rec } from './model.js';
|
|
13
|
+
export { resolveFlow, listEntities, type EntityRef } from './resolver.js';
|
|
14
|
+
export { toMermaid, type FlowTheme, type MermaidOptions } from './mermaid.js';
|
|
15
|
+
export { THEMES, DEFAULT_LIGHT_THEME, DEFAULT_DARK_THEME, NODE_LIGHT, NODE_DARK, NODE_SLATE, NODE_A11Y, type ThemeDef, type ThemeChrome, type ThemeMode, type NodePalette, } from './themes.js';
|
|
16
|
+
export { renderGalleryHtml, renderFlowCards, renderFlowCard, mermaidBootstrap, flowAnchorId, type GalleryOptions, type CardOptions, } from './html.js';
|
|
17
|
+
export { resolveSvgSize, rasterizerScript } from './raster.js';
|
|
18
|
+
export { NsClient, NsApiError, fetchDomainSnapshot, listDomains, asArray, type NsClientConfig, type FetchSnapshotOptions } from './nsClient.js';
|
|
19
|
+
export { verify, validateJwtFormat, extractContext, assertClaims, verifyHs256Signature, normalizeToken, tokenKey, MemoryVerdictCache, type JwtVerdict, type JwtContext, type ClaimExpectations, type VerdictCache, type VerifyOptions, type FormatResult, } from './jwt.js';
|
|
20
|
+
export { type CallSensitivity, needsFreshAuth, SENSITIVITY_NOTE } from './sensitivity.js';
|
|
21
|
+
export { toPrincipal, parseOperator, isResellerScope, isAdminScope, type Principal, type Operator, type Scope, } from './principal.js';
|
|
22
|
+
export { ruleMatches, isAllowed, can, type PolicyRule, type Policy, type FeaturePolicies, } from './policy.js';
|
|
23
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public API of the portable call-flow library — the surface any host imports (Cloudflare
|
|
3
|
+
* Worker, ns-onboard CLI / review page / build-preview, the portal viewer). Everything
|
|
4
|
+
* re-exported here is Node-free and runtime-portable. The Node-only CLI (`cli.ts`) is NOT
|
|
5
|
+
* part of this surface.
|
|
6
|
+
*
|
|
7
|
+
* Typical use in another project:
|
|
8
|
+
* import { resolveFlow, toMermaid, renderGalleryHtml, verify } from '@dszp/netsapiens-lib';
|
|
9
|
+
* const graph = resolveFlow(snapshot, { kind: 'did', ref: '13175550100' });
|
|
10
|
+
* const html = renderGalleryHtml(snapshot.meta.domain, [graph]);
|
|
11
|
+
*/
|
|
12
|
+
export { resolveFlow, listEntities } from './resolver.js';
|
|
13
|
+
export { toMermaid } from './mermaid.js';
|
|
14
|
+
export { THEMES, DEFAULT_LIGHT_THEME, DEFAULT_DARK_THEME, NODE_LIGHT, NODE_DARK, NODE_SLATE, NODE_A11Y, } from './themes.js';
|
|
15
|
+
export { renderGalleryHtml, renderFlowCards, renderFlowCard, mermaidBootstrap, flowAnchorId, } from './html.js';
|
|
16
|
+
export { resolveSvgSize, rasterizerScript } from './raster.js';
|
|
17
|
+
export { NsClient, NsApiError, fetchDomainSnapshot, listDomains, asArray } from './nsClient.js';
|
|
18
|
+
export { verify, validateJwtFormat, extractContext, assertClaims, verifyHs256Signature, normalizeToken, tokenKey, MemoryVerdictCache, } from './jwt.js';
|
|
19
|
+
export { needsFreshAuth, SENSITIVITY_NOTE } from './sensitivity.js';
|
|
20
|
+
export { toPrincipal, parseOperator, isResellerScope, isAdminScope, } from './principal.js';
|
|
21
|
+
export { ruleMatches, isAllowed, can, } from './policy.js';
|
|
22
|
+
//# sourceMappingURL=index.js.map
|