@pylonsync/functions 0.5.0 → 0.5.3

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pylonsync/functions",
3
- "version": "0.5.0",
3
+ "version": "0.5.3",
4
4
  "description": "TypeScript function runtime for pylon — defines server-side queries, mutations, and actions.",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -0,0 +1,44 @@
1
+ // Regression: the browser client namespaces its storage by the app's manifest
2
+ // name, but the name only lives server-side. The SSR runtime surfaces it as
3
+ // PYLON_APP_NAME and buildHydrationTail forwards it into __PYLON_DATA__ as `app`
4
+ // so the client can `configureClient({ appName })` at hydrate — before any token
5
+ // read. These pin that the field appears exactly when the env is set.
6
+
7
+ import { expect, test } from "bun:test";
8
+
9
+ import { buildHydrationTail } from "./ssr-runtime";
10
+
11
+ const base = {
12
+ component: "app/page.tsx",
13
+ layouts: [] as string[],
14
+ props: {},
15
+ ssrData: {},
16
+ manifestRoute: null,
17
+ publicPrefix: "/_pylon/build/",
18
+ manifestErr: null,
19
+ dataOnly: true, // return just the __PYLON_DATA__ script
20
+ };
21
+
22
+ function withEnv(value: string | undefined, fn: () => void) {
23
+ const prev = process.env.PYLON_APP_NAME;
24
+ if (value === undefined) delete process.env.PYLON_APP_NAME;
25
+ else process.env.PYLON_APP_NAME = value;
26
+ try {
27
+ fn();
28
+ } finally {
29
+ if (prev === undefined) delete process.env.PYLON_APP_NAME;
30
+ else process.env.PYLON_APP_NAME = prev;
31
+ }
32
+ }
33
+
34
+ test("injects app from PYLON_APP_NAME", () => {
35
+ withEnv("revtrail", () => {
36
+ expect(buildHydrationTail(base)).toContain('"app":"revtrail"');
37
+ });
38
+ });
39
+
40
+ test("omits app when PYLON_APP_NAME is unset", () => {
41
+ withEnv(undefined, () => {
42
+ expect(buildHydrationTail(base)).not.toContain('"app"');
43
+ });
44
+ });
@@ -330,6 +330,7 @@ const CLIENT_RUNTIME_SOURCE = `// Generated by Pylon SSR (Phase 2 client runtime
330
330
 
331
331
  import { createElement } from "react";
332
332
  import { hydrateRoot } from "react-dom/client";
333
+ import { configureClient } from "@pylonsync/react";
333
334
  import { createPylonBoundary, nearestBoundaryComponent } from "./client-boundary";
334
335
  import { LOADING_MODULES } from "./loading-registry";
335
336
  import { createNavPayloadCache } from "./nav-cache";
@@ -672,6 +673,13 @@ export function hydrate(component, Page, Layouts) {
672
673
  // of all prefetching. In flight from here, it resolves during hydration.
673
674
  void loadManifest();
674
675
  const data = readPylonData();
676
+ // Namespace client storage by the app's manifest name (injected into
677
+ // __PYLON_DATA__ as \`app\`) BEFORE any db hook / auth helper reads a token.
678
+ // Without this every app on a shared origin (all localhost:4321 in dev)
679
+ // shares the default \`pylon_token\` and clobbers each other's sessions.
680
+ // configureClient runs once here; it also migrates a legacy default-keyspace
681
+ // token so an existing session survives the switch.
682
+ if (data && data.app) configureClient({ appName: data.app });
675
683
  // First hydrate: the entry's component MATCHES the SSR'd page.
676
684
  // Establish the root + install the click + popstate handlers
677
685
  // exactly once.
@@ -2030,6 +2030,14 @@ export function buildHydrationTail(args: {
2030
2030
  props: serializableProps,
2031
2031
  ssrData: args.ssrData,
2032
2032
  };
2033
+ // App name (from the manifest, surfaced by the runtime as PYLON_APP_NAME) so
2034
+ // the client namespaces its localStorage/IndexedDB per-app at hydrate. Without
2035
+ // it every app on a shared origin (all localhost:4321 in dev) collides on the
2036
+ // default `pylon_token`. App-global + identity-free, so it stays byte-identical
2037
+ // across users — safe in the PPR-bucketed shared tail too.
2038
+ const appName =
2039
+ typeof process !== "undefined" ? process.env.PYLON_APP_NAME : undefined;
2040
+ if (appName) hydrationPayload.app = appName;
2033
2041
  if (args.kind) hydrationPayload.kind = args.kind;
2034
2042
  const json = escapeScriptJson(JSON.stringify(hydrationPayload));
2035
2043
  let tail = `<script id="__PYLON_DATA__" type="application/json">${json}</script>`;