@aranova/tracking-next 0.20.2 → 0.22.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.
Files changed (48) hide show
  1. package/README.md +80 -1
  2. package/dist/blog-server.js +12 -0
  3. package/dist/blog-server.js.map +1 -1
  4. package/dist/blog-server.mjs +12 -0
  5. package/dist/blog-server.mjs.map +1 -1
  6. package/dist/blog.js +7 -0
  7. package/dist/blog.js.map +1 -1
  8. package/dist/blog.mjs +7 -0
  9. package/dist/blog.mjs.map +1 -1
  10. package/dist/calendar-server.d.mts +53 -0
  11. package/dist/calendar-server.d.ts +53 -0
  12. package/dist/calendar-server.js +206 -0
  13. package/dist/calendar-server.js.map +1 -0
  14. package/dist/calendar-server.mjs +180 -0
  15. package/dist/calendar-server.mjs.map +1 -0
  16. package/dist/calendar.d.mts +112 -0
  17. package/dist/calendar.d.ts +112 -0
  18. package/dist/calendar.js +524 -0
  19. package/dist/calendar.js.map +1 -0
  20. package/dist/calendar.mjs +464 -0
  21. package/dist/calendar.mjs.map +1 -0
  22. package/dist/date-BHetnL7s.d.mts +26 -0
  23. package/dist/date-BHetnL7s.d.ts +26 -0
  24. package/dist/index.d.mts +62 -5
  25. package/dist/index.d.ts +62 -5
  26. package/dist/index.js +566 -21
  27. package/dist/index.js.map +1 -1
  28. package/dist/index.mjs +529 -21
  29. package/dist/index.mjs.map +1 -1
  30. package/dist/{phone-utils-B2vHJkNz.d.mts → phone-utils-BhWfNuPS.d.mts} +8 -0
  31. package/dist/{phone-utils-B2vHJkNz.d.ts → phone-utils-BhWfNuPS.d.ts} +8 -0
  32. package/dist/phone.d.mts +1 -1
  33. package/dist/phone.d.ts +1 -1
  34. package/dist/request-Doq36Rt0.d.mts +19 -0
  35. package/dist/request-Doq36Rt0.d.ts +19 -0
  36. package/dist/{sales-DrH6dY5F.d.mts → sales-C7tPnqbq.d.ts} +9 -48
  37. package/dist/{sales-DrH6dY5F.d.ts → sales-H_6bHrL-.d.mts} +9 -48
  38. package/dist/sales.d.mts +3 -1
  39. package/dist/sales.d.ts +3 -1
  40. package/dist/sales.js +29 -17
  41. package/dist/sales.js.map +1 -1
  42. package/dist/sales.mjs +29 -17
  43. package/dist/sales.mjs.map +1 -1
  44. package/dist/server.d.mts +1 -1
  45. package/dist/server.d.ts +1 -1
  46. package/dist/transport-C-_rFSR4.d.ts +721 -0
  47. package/dist/transport-yUF5Wize.d.mts +721 -0
  48. package/package.json +21 -1
package/README.md CHANGED
@@ -341,6 +341,81 @@ npx @aranova/tracking-cli gen # reads ARANOVA_TRACKING_SECRET_KEY from
341
341
  See the full guide: [sales-tracking.md](https://github.com/AranovaIO/aranova_internal/blob/master/docs/tracking-package/sales-tracking.md)
342
342
  and the CLI reference: [cli.md](https://github.com/AranovaIO/aranova_internal/blob/master/docs/tracking-package/cli.md).
343
343
 
344
+ ## Calendar Bookings
345
+
346
+ Take bookings on a client site against a connected Google Calendar. Unlike sales,
347
+ reads and writes are **separate entries**, and the split is structural rather than a
348
+ convention — the read entry exposes no write verb at all.
349
+
350
+ Read availability with your **public** key, straight from the browser:
351
+
352
+ ```ts
353
+ import { createCalendarReadClient, computeSlots } from "@aranova/tracking-next/calendar";
354
+ import type { AranovaCalendarKey } from "./aranova-services"; // generated, see below
355
+
356
+ const calendar = createCalendarReadClient<AranovaCalendarKey>({
357
+ apiKey: process.env.NEXT_PUBLIC_ARANOVA_TRACKING_API_KEY!,
358
+ endpoint,
359
+ });
360
+
361
+ // Busy intervals only — never the contents of anyone's events.
362
+ // `since`/`until` are ISO instants WITH an offset; a date-only value is a 400.
363
+ const { busy, time_zone } = await calendar.busy("consultations", { since, until });
364
+ ```
365
+
366
+ **Opening hours live in your app, not in the dashboard.** The API answers only "when is
367
+ this person busy"; `computeSlots()` is a pure, dependency-free helper that turns that plus
368
+ your own hours into offerable slots:
369
+
370
+ ```ts
371
+ const { slots, nextAvailableAt } = computeSlots({
372
+ busy,
373
+ timeZone: time_zone,
374
+ openingHours: { weekly: { 1: [{ start: "09:00", end: "17:00" }] } },
375
+ durationMinutes: 30,
376
+ minNoticeMinutes: 240,
377
+ bufferAfterMinutes: 15,
378
+ });
379
+ ```
380
+
381
+ Writes need your **secret** key, so they run only on your own server. Drop in one route
382
+ file — `./calendar-server` carries `import "server-only"`, so a client component importing
383
+ it fails the build instead of shipping the key to a browser:
384
+
385
+ ```ts
386
+ // app/api/aranova/calendar/[...route]/route.ts
387
+ import { createCalendarRoutes } from "@aranova/tracking-next/calendar-server";
388
+
389
+ export const runtime = "nodejs";
390
+ export const dynamic = "force-dynamic";
391
+
392
+ export const { POST, PATCH, DELETE } = createCalendarRoutes({
393
+ secretKey: process.env.ARANOVA_TRACKING_SECRET_KEY!, // never NEXT_PUBLIC_
394
+ endpoint: process.env.ARANOVA_TRACKING_ENDPOINT!,
395
+ });
396
+ ```
397
+
398
+ Why the split, when sales is one isomorphic client: a public key ships in a JS bundle and
399
+ its Origin allowlist is a browser-enforced header a script forges trivially. A junk sale is
400
+ a database row you can delete; a junk **booking** puts an event on a real person's calendar
401
+ and emails their contacts.
402
+
403
+ `bookings.create()` returns a one-time `manage_token`. Put it in your confirmation email and
404
+ send it as `X-Aranova-Manage-Token` to reschedule or cancel — it authorises exactly that one
405
+ booking, which is why a public key alone still manages nothing.
406
+
407
+ Headless hooks on the root entry (no booking UI ships, for the same reason no consent UI does):
408
+
409
+ ```tsx
410
+ "use client";
411
+ import { useCalendarBusy, useSlots, useBookingForm } from "@aranova/tracking-next";
412
+ // wrap in <CalendarClientProvider value={calendar}> — the hooks need a read client
413
+ ```
414
+
415
+ Generate the typed `AranovaCalendarKey` union with the CLI (`npx @aranova/tracking-cli gen`).
416
+
417
+ Full guide: [calendar.md](https://github.com/AranovaIO/aranova_internal/blob/master/docs/tracking-package/calendar.md)
418
+
344
419
  ## On-site conversion firing
345
420
 
346
421
  Run `tracking-cli gen` once to emit `ARANOVA_TRACKING_CONFIG`, then use that
@@ -495,8 +570,12 @@ Reads are best-effort: network/parse failures degrade to `null`/empty — a blog
495
570
  attribution hooks, event types; `createSalesClient()` (isomorphic — public key writes;
496
571
  secret key reads/CRUD, `summary`, `customers.*`, `business.config`) +
497
572
  `toMinor`/`fromMinor`/`formatMoney`/`formatDateInTz`; phone
498
- (`parsePhone`/`toE164`/`formatPhone`/`phoneField`, `usePhoneField`, `PhoneField`)
573
+ (`parsePhone`/`toE164`/`formatPhone`/`phoneField`, `usePhoneField`, `PhoneField`); calendar
574
+ read surface + headless hooks (`CalendarClientProvider`, `useCalendarBusy`, `useSlots`,
575
+ `useBookingForm`) — booking **writes** are not here, see `/calendar-server`
499
576
  - `@aranova/tracking-next/sales`: **React-free** server-safe SDK — `createSalesClient`, money/date helpers, and all sale/customer/config types. Use this in Server Components, route handlers, and Node servers; the root entry re-exports the same symbols for back-compat.
577
+ - `@aranova/tracking-next/calendar`: **React-free, read-only** — `createCalendarReadClient`, `computeSlots`, timezone helpers, and the booking types. Public key, browser-safe. Deliberately exposes no write verb.
578
+ - `@aranova/tracking-next/calendar-server`: **`server-only`** — `createCalendarWriteClient` and `createCalendarRoutes()` (drop-in booking route handler). Secret key; importing this from a client component fails the build.
500
579
  - `@aranova/tracking-next/middleware`: `createTrackingMiddleware()`
501
580
  - `@aranova/tracking-next/server`: `getTrackingParamsServer()`
502
581
  - `@aranova/tracking-next/phone`: isomorphic phone utils (no React)
@@ -84,6 +84,13 @@ __export(blog_server_exports, {
84
84
  module.exports = __toCommonJS(blog_server_exports);
85
85
  var import_server_only = require("server-only");
86
86
 
87
+ // ../tracking-core/src/capabilities.ts
88
+ var CAPABILITY_DOM_ATTRIBUTE = "data-aranova-capability";
89
+ var registered = /* @__PURE__ */ new Set();
90
+ function registerCapability(capability) {
91
+ registered.add(capability);
92
+ }
93
+
87
94
  // ../tracking-core/src/resources/blog/schema.ts
88
95
  var import_zod2 = require("zod");
89
96
 
@@ -422,6 +429,7 @@ function parseBlogPostContent(raw) {
422
429
 
423
430
  // ../tracking-core/src/resources/blog/client.ts
424
431
  function createBlogClient(config) {
432
+ registerCapability("blog_rendering");
425
433
  const base = config.cdnBaseUrl.replace(/\/+$/, "");
426
434
  const doFetch = (...args) => (config.fetchImpl ?? fetch)(...args);
427
435
  async function fetchJson(url, init) {
@@ -1984,6 +1992,7 @@ var BLOG_BASE_CSS = `/* Aranova blog post base stylesheet \u2014 authored source
1984
1992
 
1985
1993
  // ../tracking-core/src/resources/blog/view/blog-post-view.tsx
1986
1994
  var import_jsx_runtime2 = require("react/jsx-runtime");
1995
+ var CAPABILITY_MARKER = { [CAPABILITY_DOM_ATTRIBUTE]: "blog_rendering" };
1987
1996
  async function BlogPostView({
1988
1997
  mdx,
1989
1998
  theme = BASE_BLOG_THEME,
@@ -2006,6 +2015,7 @@ async function BlogPostView({
2006
2015
  className: joinClassNames("aranova-blog-post", classNames?.article),
2007
2016
  "data-error": "mdx-validation-failed",
2008
2017
  style,
2018
+ ...CAPABILITY_MARKER,
2009
2019
  children: [
2010
2020
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: BLOG_BASE_CSS }),
2011
2021
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { children: [
@@ -2032,6 +2042,7 @@ async function BlogPostView({
2032
2042
  className: joinClassNames("aranova-blog-post", classNames?.article),
2033
2043
  "data-error": "mdx-render-failed",
2034
2044
  style,
2045
+ ...CAPABILITY_MARKER,
2035
2046
  children: [
2036
2047
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: BLOG_BASE_CSS }),
2037
2048
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { children: "This post could not be displayed." })
@@ -2045,6 +2056,7 @@ async function BlogPostView({
2045
2056
  className: joinClassNames("aranova-blog-post", classNames?.article),
2046
2057
  "data-motion": motion,
2047
2058
  style,
2059
+ ...CAPABILITY_MARKER,
2048
2060
  children: [
2049
2061
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: BLOG_BASE_CSS }),
2050
2062
  header,