Skip to content

Releases: oddbit/shrtnr

app-v0.36.1

Choose a tag to compare

@github-actions github-actions released this 30 Jun 10:20
16909c1

Maintenance release (PRs #27, #28). No new endpoints or schema changes.

  • Dependency refresh. Upgrades all app and tooling dependencies to their latest versions and pins the build to Node >=22.18.0.
  • Type-narrowed background handlers. Handlers that only schedule deferred work now accept a WaitUntilContext (Pick<ExecutionContext, "waitUntil">) instead of the full ExecutionContext, so Hono's c.executionCtx passes directly and the prior unsafe casts drop out.
  • UI fixes. Bundle overview cards clamp titles to two lines and reserve that height so cards stay aligned. The API keys page corrects scope-pill spacing.
  • OpenAPI paths and schemas are unchanged from 0.36.0; only info.version changes. The bump refreshes the recorded spec hash in all three SDKs, with no SDK code changes. Full suite: 72 files, 1042 tests.

pub-v2.1.0

Choose a tag to compare

@github-actions github-actions released this 24 Jun 06:29
039b3eb

Add links.breakdown and bundles.breakdown for paging through the countries, sources and domains analytics panels (offset/limit, returns items + total).

app-v0.36.0

Choose a tag to compare

@github-actions github-actions released this 24 Jun 06:28
039b3eb

Feature release. Rebuilds the admin dashboard on htmx widget islands, redesigns the API keys page, enriches bundle overview cards, and adds a paginated analytics breakdown endpoint. PRs #16, #18, #19, #21, #22, #24, #25, #26.

  • Widget-island dashboard. The dashboard renders as a shell of placeholders and loads each panel (KPIs, timeline, top links, top countries, top domains, recent links) as an independent widget over htmx (vendored 2.0.4). A generic admin widget route serves each fragment with per-widget Cache API caching keyed by range, language, and filters, invalidated by a KV version token. Admin writes bump that token best-effort so panels refresh after an edit. The widget context builds from a single settings fetch per request.
  • API keys page redesigned for onboarding. The keys page walks a developer from key creation to first call and lists the published SDKs through a shared SdkList component.
  • Bundle overview cards show average clicks per day and traffic coverage, aligned with the bundle detail page.
  • Paginated breakdown. links and bundles expose a breakdown endpoint for the countries, sources, and domains panels (offset/limit, returns items plus total) with a deterministic tie-break in the ordering, and the admin detail pages paginate those panels. The client method shipped earlier in the SDKs (npm/py 1.1.0, Dart 2.1.0).
  • Fixes. The dashboard counts distinct referrer hosts exactly and shows the primary slug in the most-clicked panel. Dashboard link deltas enrich in bulk to cut per-request CPU. The widget error fragment escapes interpolated values, cache-key parts are url-encoded, and disabled links stay resolvable for up to one second after disabling. Slug copy moved to a delegated data-* handler that skips empty targets.
  • OpenAPI paths and schemas are unchanged from 0.35.5 (the breakdown endpoint was already in the recorded spec); only info.version changes. The bump refreshes the recorded spec hash in all three SDKs, with no SDK code changes. Full suite: 72 files, 1042 tests.

py-v1.1.0

Choose a tag to compare

@github-actions github-actions released this 19 Jun 05:41
96fc42d
  • Add links.breakdown and bundles.breakdown (sync and async) for paging through the countries, sources and domains analytics panels (offset/limit, returns items + total).

npm-v1.1.0

Choose a tag to compare

@github-actions github-actions released this 19 Jun 05:41
96fc42d
  • Add links.breakdown and bundles.breakdown for paging through the countries, sources and domains analytics panels (offset/limit, returns { items, total }). Exports new types BreakdownDimension and BreakdownPage.

app-v0.35.5

Choose a tag to compare

@github-actions github-actions released this 17 Jun 14:14
14a99c3

Defect-review release (PR #15) fixing analytics "Sources" inflation. No new features.

  • Shortlinks no longer appear as referring sources for their own clicks. Self-referrer detection broadened from the bare-origin root to any same-host slug: a slug is never a page, it only 301s away, so a Referer pointing at one can only be a self-referral artifact (a self-looping or cross-slug crawler that stamps Referer with the URL it fetched). Such clicks now carry the is_self_referrer flag and drop out of the Sources breakdown and totals under the default filters.
  • The rule matches the actual slug route shape (a single path segment outside the reserved _ namespace), so real same-host endpoints like /.well-known/oauth-authorization-server and /cdn-cgi/access/* stay as meaningful referrers. The own host is read per request, so no domain is hardcoded and every deployment works.
  • OpenAPI surface unchanged. The version bump refreshes the recorded spec hash in all three SDKs; no SDK code changes. Full suite: 50 files, 953 tests.

app-v0.35.4

Choose a tag to compare

@github-actions github-actions released this 15 Jun 09:18
90ed29e

Defect-review release (PR #14) extending the 0.35.3 concurrency hardening to slug removal and disable. No new features.

  • Removing a slug no longer orphans analytics rows when a click lands mid-operation. The lifetime guard moved inside the delete transaction as a NOT EXISTS (SELECT 1 FROM clicks ...) condition, so a click recorded between the service's pre-read and the batch delete blocks the delete instead of stranding its click rows.
  • Removing or disabling a primary slug no longer leaves a link with two primaries under concurrency. The primary handover re-checks inside the batch that the slug still holds primary, so a concurrent setPrimary that moved primary to another slug can no longer promote the random slug alongside the new primary.
  • removeSlug reports the real outcome instead of a false success. When the guard blocks the delete, the API returns 400 (slug still present, has clicks) or 404 (slug left this link concurrently) rather than { removed: true }, and the cache entry is evicted only after a confirmed delete so a still-resolving slug is not dropped. The 404-vs-400 disambiguation compares link_id, since a freed slug value can be re-claimed by another link.
  • OpenAPI surface unchanged. The version bump refreshes the recorded spec hash in all three SDKs; no SDK code changes. Full suite: 50 files, 938 tests.

app-v0.35.3

Choose a tag to compare

@github-actions github-actions released this 12 Jun 00:49
cc316d3

Robustness release: two defect-review PRs (#12, #13) hardening concurrency, input validation, and redirect behavior. No new features.

  • Slug redirects now send Cache-Control: private, max-age=90 alongside the 301. A bare 301 is cached by browsers indefinitely, so returning visitors skipped the Worker forever: disabling, expiring, or retargeting a link never reached them and their repeat clicks went unrecorded. The short private max-age (the Bitly approach) keeps the SEO semantics while forcing revalidation within seconds.
  • expires_at = 0 now reads as a real epoch timestamp (expired) instead of "no expiry". The redirect handler and the admin pages (expired filter, expired badge, expiry control) use null-aware checks, matching the documented contract that null means no expiry.
  • Multi-statement mutations run in transactional D1 batches: link deletion, custom slug insertion with primary handover, slug disable/remove primary fallback. A mid-sequence failure can no longer orphan slug or click rows or strand a link without a primary slug. setPrimary collapsed to a single conditional UPDATE that no-ops when the slug does not belong to the link; previously a mismatched slug cleared every primary flag and set none.
  • The "never delete a link with clicks" guard moved inside the delete transaction as a NOT EXISTS condition. A click recorded between the service's check and the delete now blocks the delete instead of being silently removed with the link.
  • Concurrent requests racing on the same custom slug get a 409 from the UNIQUE constraint instead of an unhandled 500. disableSlug/enableSlug return 404 instead of crashing when the slug is concurrently deleted.
  • Link search escapes SQLite LIKE metacharacters. Searching for _ previously matched every link; % and \ are escaped the same way and every comparison carries ESCAPE '\'.
  • MCP get_link_qr encodes utm_medium=qr instead of a bare ?qr, so scans of MCP-issued QR codes register as QR traffic in analytics, matching the REST QR endpoint.
  • Settings validation: theme and lang are checked against their allowed sets on write and clamped to null on read (legacy rows and direct D1 edits no longer leak unknown values), API key titles cap at 120 characters, and a corrupted stored slug_default_length falls back to the default instead of returning NaN or blocking link creation.
  • The admin JSON path (no zod schema) now rejects malformed field types at the service layer: non-string labels and non-integer or negative expires_at values return 400 instead of being stored verbatim.
  • getBundle applies the viewer's bot and self-referrer filter preferences, so bundle detail and the MCP get_bundle tool report the same totals as the bundles list.
  • With Cloudflare Access configured, unauthenticated requests to /_/admin/api/* get 401 JSON instead of a 302 redirect to the landing page; page routes keep the redirect. Theme and lang cookie values clamp to known sets before rendering.
  • OpenAPI surface unchanged. The version bump refreshes the recorded spec hash in all three SDKs; no SDK code changes. Full suite: 50 files, 932 tests.

app-v0.35.2

Choose a tag to compare

@github-actions github-actions released this 30 May 09:14
6405491
  • Link deletion no longer reports success when the database guard blocks it. deleteLink now honors the boolean from LinkRepository.delete(): if a click lands between the service's click check and the row delete, the API returns the real outcome instead of { deleted: true }. A link that gained clicks returns 400, a link that was concurrently removed returns 404. The existence check on that path uses a cheap SELECT 1 rather than a full link load.
  • Deleting a link evicts the slug set captured at delete time, not the slugs read at the start of the call. A custom slug added in the race window is now removed from KV instead of lingering with no TTL, which had kept the deleted link resolving on redirects.
  • disableLink returns 404 when the link is concurrently deleted instead of throwing a 500. It now null-guards the repository result the same way enableLink already did.
  • Dependency refresh: wrangler 4.88 → 4.95, hono 4.12.18 → 4.12.23, agents 0.12.3 → 0.13.3, @cloudflare/workers-oauth-provider 0.5 → 0.7, @hono/zod-openapi 1.3 → 1.4, @cloudflare/vitest-pool-workers 0.16.0 → 0.16.10, @cloudflare/workers-types → 4.20260528.
  • CI reads the Node version from package.json (pinned to 22) instead of hardcoding 24, so the tested major matches the supported floor.
  • OpenAPI surface unchanged. The version bump refreshes the recorded spec hash in all three SDKs; no SDK code changes.

py-v1.0.2

Choose a tag to compare

@github-actions github-actions released this 07 May 06:21
8319c01
  • links.update() and bundles.update() can now distinguish "omit this field" from "set this field to null". Optional nullable params (label, expires_at, description, icon) default to a private UNSET sentinel; only keys explicitly provided by the caller are included in the request body. Pass None to clear a value, omit the param to leave the server-side value untouched. Sync and async resources both updated; regression tests cover both clients.