@aranova/tracking-next 0.20.1 → 0.21.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 (42) hide show
  1. package/README.md +80 -1
  2. package/dist/blog-server.js +6 -3
  3. package/dist/blog-server.js.map +1 -1
  4. package/dist/blog-server.mjs +6 -3
  5. package/dist/blog-server.mjs.map +1 -1
  6. package/dist/blog.js +4 -2
  7. package/dist/blog.js.map +1 -1
  8. package/dist/blog.mjs +4 -2
  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 +517 -0
  19. package/dist/calendar.js.map +1 -0
  20. package/dist/calendar.mjs +457 -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 +60 -3
  25. package/dist/index.d.ts +60 -3
  26. package/dist/index.js +511 -18
  27. package/dist/index.js.map +1 -1
  28. package/dist/index.mjs +474 -18
  29. package/dist/index.mjs.map +1 -1
  30. package/dist/request-Doq36Rt0.d.mts +19 -0
  31. package/dist/request-Doq36Rt0.d.ts +19 -0
  32. package/dist/{sales-DrH6dY5F.d.mts → sales-B3BnYncU.d.mts} +5 -44
  33. package/dist/{sales-DrH6dY5F.d.ts → sales-CDjXRdmd.d.ts} +5 -44
  34. package/dist/sales.d.mts +3 -1
  35. package/dist/sales.d.ts +3 -1
  36. package/dist/sales.js +22 -17
  37. package/dist/sales.js.map +1 -1
  38. package/dist/sales.mjs +22 -17
  39. package/dist/sales.mjs.map +1 -1
  40. package/dist/transport-Bku8zftU.d.mts +721 -0
  41. package/dist/transport-DsOY6kcW.d.ts +721 -0
  42. package/package.json +22 -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)
@@ -679,6 +679,7 @@ function buildBlogSitemapEntries(posts, opts) {
679
679
 
680
680
  // ../tracking-core/src/resources/blog/toc.ts
681
681
  var import_github_slugger = __toESM(require("github-slugger"));
682
+ var import_remark_gfm = __toESM(require("remark-gfm"));
682
683
  var import_remark_mdx = __toESM(require("remark-mdx"));
683
684
  var import_remark_parse = __toESM(require("remark-parse"));
684
685
  var import_unified = require("unified");
@@ -707,7 +708,7 @@ function headingText(node) {
707
708
  function extractBlogHeadings(mdx) {
708
709
  let tree;
709
710
  try {
710
- tree = (0, import_unified.unified)().use(import_remark_parse.default).use(import_remark_mdx.default).parse(mdx);
711
+ tree = (0, import_unified.unified)().use(import_remark_parse.default).use(import_remark_gfm.default).use(import_remark_mdx.default).parse(mdx);
711
712
  } catch {
712
713
  return [];
713
714
  }
@@ -740,6 +741,7 @@ function estimateReadingTime(mdx) {
740
741
  }
741
742
 
742
743
  // ../tracking-core/src/resources/blog/validate-mdx.ts
744
+ var import_remark_gfm2 = __toESM(require("remark-gfm"));
743
745
  var import_remark_mdx2 = __toESM(require("remark-mdx"));
744
746
  var import_remark_parse2 = __toESM(require("remark-parse"));
745
747
  var import_unified2 = require("unified");
@@ -932,7 +934,7 @@ function validateBlogMdx(mdx, options) {
932
934
  }
933
935
  let tree;
934
936
  try {
935
- tree = (0, import_unified2.unified)().use(import_remark_parse2.default).use(import_remark_mdx2.default).parse(mdx);
937
+ tree = (0, import_unified2.unified)().use(import_remark_parse2.default).use(import_remark_gfm2.default).use(import_remark_mdx2.default).parse(mdx);
936
938
  } catch (error) {
937
939
  const line = error.line;
938
940
  return {
@@ -1008,6 +1010,7 @@ function validateBlogMdx(mdx, options) {
1008
1010
  // ../tracking-core/src/resources/blog/view/blog-post-view.tsx
1009
1011
  var import_rsc = require("next-mdx-remote/rsc");
1010
1012
  var import_rehype_slug = __toESM(require("rehype-slug"));
1013
+ var import_remark_gfm3 = __toESM(require("remark-gfm"));
1011
1014
 
1012
1015
  // ../tracking-core/src/resources/blog/view/components.tsx
1013
1016
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -2019,7 +2022,7 @@ async function BlogPostView({
2019
2022
  const compiled = await (0, import_rsc.compileMDX)({
2020
2023
  source: mdx,
2021
2024
  components: merged,
2022
- options: { mdxOptions: { rehypePlugins: [import_rehype_slug.default] } }
2025
+ options: { mdxOptions: { remarkPlugins: [import_remark_gfm3.default], rehypePlugins: [import_rehype_slug.default] } }
2023
2026
  });
2024
2027
  content = compiled.content;
2025
2028
  } catch {