experimentalsince 0.4.0-pre.12

share-link

The portal deep-link grammar — one canonical URL shape addressing vault / period / collection / record, with the grant token confined to the fragment.
Subpath@noy-db/hub/share-link
Factorynone — direct named imports
Kindreference — a frozen link grammar, not a service
LOC coststandalone: 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

The single-use grant token travels only in the URL fragment (#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 vaultHandle ULID comes from