share-link
| Subpath | @noy-db/hub/share-link |
| Factory | none — direct named imports |
| Kind | reference — a frozen link grammar, not a service |
| LOC cost | standalone: no crypto, no store, no hub floor |
What it does
One canonical URL shape for addressing a single record across every portal surface — the LIFF permalink, the installed PWA, and the vendor console all speak it:
/r/{vaultHandle}/{collection}/{recordId}?period=…&v=…#g={token}
buildShareLink(parts, base?) renders that string; parseShareLink(input) reads one back and
throws ShareLinkParseError on anything malformed. That is the whole surface.
It is deliberately standalone: pure string and URL code, with no crypto, no store, and no hub
floor behind it. You can import it from a LIFF shell, a PWA, or klum-db
without dragging the rest of the hub along. The export surface is a frozen contract, pinned
upstream by a golden test.
The grant token lives in the fragment, and that is the point
#g=…). Fragments are never sent
to the server, so the token cannot land in access logs, proxy logs, or a Referer header — the
same transport rule on-magic-link follows.Everything before the # is addressing information and is safe to log. Everything after it is
capability. If you build links by string concatenation instead of buildShareLink, that separation
is the thing you are most likely to break.
Usage
import { buildShareLink, parseShareLink, ShareLinkParseError } from '@noy-db/hub/share-link'
const url = buildShareLink({
vaultHandle: '01J1QDX9Y3Z4A5B6C7D8E9FGHJ',
collection: 'invoices',
recordId: 'inv-1',
period: '2026-Q1', // optional
version: 3, // optional
grantToken: 'nQ3f…', // optional — rendered into the fragment
}, 'https://portal.example')
try {
const link = parseShareLink(url)
// → { vaultHandle, collection, recordId, period?, version?, grantToken? }
} catch (err) {
if (err instanceof ShareLinkParseError) err.code // machine-readable discriminant
}
ShareLinkParseError.code is a discriminant rather than a message, so a portal can branch on the
failure instead of matching prose: MALFORMED_URL, UNKNOWN_PREFIX, MALFORMED_PATH,
MALFORMED_SEGMENT, INVALID_VAULT_HANDLE, MALFORMED_QUERY, DUPLICATE_QUERY_KEY,
UNKNOWN_QUERY_KEY, INVALID_VERSION, MALFORMED_FRAGMENT, INVALID_PARTS.
Note vaultHandle — the opaque ULID from the pod header, not the vault's
name. Links therefore leak no tenant name at the URL layer.
See also
- in-liff —
initLiffApp()ingests a share link from the current location - on-magic-link — the same fragment-only transport rule
- bundle — where the
vaultHandleULID comes from