@loopwise/sdk 0.1.0-beta.0 → 0.1.0-beta.1

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/README.md CHANGED
@@ -1,37 +1,78 @@
1
1
  # @loopwise/sdk
2
2
 
3
- Storefront GraphQL client for Loopwise / Teachify. The SDK has no concept of
4
- "tenant" in its public surface — calls hit the same-origin BFF at
5
- `/api/graphql`, which derives the tenant from the request host and binds it
6
- to the response on the trusted server side.
3
+ Storefront GraphQL client for Loopwise / Teachify. Pick your path:
7
4
 
8
- ## Quick start
5
+ | You are producing… | Install | Module spec |
6
+ |---|---|---|
7
+ | A single self-contained HTML file (no build step — for `deploy_static_page`, a Claude Design artifact, or a CodePen) | none — load from a CDN | `https://esm.sh/@loopwise/sdk@beta` |
8
+ | A project with a bundler (Vite / Astro / Next.js / Remix) | `npm install @loopwise/sdk@beta` | `@loopwise/sdk` |
9
+ | **React** UI in either of the above | (see [`@loopwise/sdk-react`](#react--coming-soon)) | — |
10
+
11
+ Same package powers all three; the difference is the loader.
12
+
13
+ ## Quick start — bundler
9
14
 
10
15
  ```ts
11
16
  import { createClient } from '@loopwise/sdk';
12
17
 
18
+ // Browser, same-origin (the page is served from the tenant origin):
13
19
  const loopwise = createClient({});
14
20
  const { items } = await loopwise.courses.list({ limit: 20 });
15
21
  ```
16
22
 
17
- That's the entire happy path. Whether the page is on `acme.kaik.io`,
18
- `acme.myteachify.com`, or `nii.school`, the BFF on each origin already knows
19
- which tenant it's serving — the SDK just talks to its own origin.
23
+ That's the entire happy path **when the page is served from the tenant
24
+ origin** (`acme.kaik.io`, `acme.myteachify.com`, `nii.school`, etc.). The BFF
25
+ on that origin derives the tenant from the request host and binds it to the
26
+ response on the trusted server side.
27
+
28
+ ## Quick start — single-file HTML (no build)
29
+
30
+ ```html
31
+ <!doctype html>
32
+ <script type="module">
33
+ import { createClient } from 'https://esm.sh/@loopwise/sdk@beta';
34
+
35
+ // Same-origin variant — works when the HTML is served from the tenant origin
36
+ // (e.g. acme.kaik.io). Empty config; BFF derives the tenant from host.
37
+ const loopwise = createClient({});
38
+ const { items } = await loopwise.courses.list({ limit: 20 });
39
+ document.body.textContent = items.map(c => c.title).join(', ');
40
+ </script>
41
+ ```
20
42
 
21
- ### Cross-origin / SSR (advanced)
43
+ ## Cross-origin (Loopwise Pages preview, SSR, build pipelines)
22
44
 
23
- When the SDK runs outside a tenant origin (build pipeline, third-party site,
24
- SSR worker), pass `tenant` explicitly so the SDK knows where to send the
25
- request. An `apiKey` is required outside the browser to bypass cookie auth.
45
+ When the SDK runs **outside** a tenant origin, pass `tenant` explicitly so
46
+ the SDK can pick the right Rails origin to call.
26
47
 
27
48
  ```ts
28
49
  const loopwise = createClient({
29
- tenant: 'acme',
30
- apiKey: process.env.LOOPWISE_API_KEY, // SSR / build only
50
+ tenant: 'acme', // required cross-origin
51
+ apiKey: process.env.LOOPWISE_API_KEY, // required in SSR / build (browser ignores)
31
52
  });
32
53
  ```
33
54
 
34
- ## Cache option (`cache`)
55
+ ### Where this matters most: `deploy_static_page`
56
+
57
+ Pages deployed via `deploy_static_page` live at `https://<script>.loopwise.page/`
58
+ — that is **not** a tenant origin, so:
59
+
60
+ - The BFF same-origin trick does not apply. You **must** pass `tenant`.
61
+ - Browser cross-origin requests carry no first-party cookies, so anything
62
+ that requires a logged-in viewer (`cart`, `viewer.profile`,
63
+ authenticated mutations) **will not work** at preview time.
64
+ - Public, anonymous reads (`courses.list`, `posts.list`, etc.) are fine.
65
+
66
+ ```ts
67
+ // Inside an HTML bundle deployed to https://lw-acme-home.loopwise.page/
68
+ const loopwise = createClient({ tenant: 'acme' });
69
+ const { items } = await loopwise.courses.list({ limit: 20 }, { cache: 60 });
70
+ ```
71
+
72
+ For preview UX that needs authenticated state, see
73
+ [Preview modes](#preview-modes) below.
74
+
75
+ ## `cache` option
35
76
 
36
77
  Read-only resource calls accept an opt-in `cache` option that flips the
37
78
  request to a GraphQL-over-HTTP `GET` and asks the BFF to apply a
@@ -79,12 +120,46 @@ is the explicit, type-system-visible declaration of "this must never be cached".
79
120
  per Vercel POP, depending on traffic. Use shorter TTLs for time-sensitive
80
121
  content; use `cache: 'no-store'` for admin previews.
81
122
 
123
+ ## Preview modes
124
+
125
+ Two preview surfaces exist, each with different capability:
126
+
127
+ | Surface | Origin | Same-origin BFF? | Authenticated viewer? | Use for |
128
+ |---|---|---|---|---|
129
+ | **Loopwise Pages preview** | `<scriptName>.loopwise.page` | ❌ cross-origin | ❌ no first-party cookies | Anonymous storefront, marketing pages, public listings |
130
+ | **Tenant-origin preview** | `<tenant-origin>/_preview/<scriptName>/` | ✅ same-origin | ✅ admin can preview as a logged-in viewer | Cart, checkout, member-gated content, full storefront |
131
+
132
+ The tenant origin can be `<sub>.kaik.io`, `<sub>.myteachify.com`, or a
133
+ custom FQDN like `nii.school`. `deploy_static_page` returns both
134
+ surfaces (`preview_url` + `tenant_preview_url_template` with a
135
+ `<TENANT_ORIGIN>` placeholder).
136
+
137
+ Pick the surface that matches what you're testing — anonymous storefront
138
+ reads on Surface A (faster, edge-cacheable), authenticated flows on
139
+ Surface B (logged-in only, never edge-cached). Surface B requires
140
+ relative asset paths in your bundle (the proxy injects `<base>` so
141
+ relative paths resolve under `/_preview/<scriptName>/`; root-absolute
142
+ paths like `/style.css` still go to the tenant root and 404).
143
+
144
+ ## React — coming soon
145
+
146
+ A `@loopwise/sdk-react` companion package is planned, providing:
147
+
148
+ - `<LoopwiseProvider client={loopwise}>` context provider
149
+ - `useCourses({ limit })`, `useCourse({ slug })` hooks with Suspense support
150
+ - `useBatch(...)` for composed queries
151
+ - Cache and refetch control matching the underlying SDK's `cache` option
152
+
153
+ Until it ships, the vanilla SDK works inside React — you'll just wire up
154
+ your own state (`useEffect` + `useState`, TanStack Query, etc.).
155
+
82
156
  ## Public API
83
157
 
84
- - `createClient(config)` — storefront client. `config.tenant` is optional
85
- in the browser (BFF derives it from host) and required in SSR / build.
158
+ - `createClient(config)` — storefront client. `config.tenant` is optional in
159
+ the browser when the page is on a tenant origin (BFF derives it from
160
+ host); required cross-origin and in SSR / build.
86
161
  - `loopwise.courses.list(args, options?)` / `.get(args, options?)`.
87
162
  - `loopwise.batch(builders, options?)` — composes N queries into one HTTP request.
88
- - `loopwise.query<T>(document, variables?, operationName?, options?)` — raw GraphQL.
163
+ - `loopwise.query<T>(document, variables?, operationName?, options?)` — raw GraphQL escape hatch.
89
164
 
90
165
  See `apps/sdk-runtime-poc/` for a runnable browser validation page.
package/dist/index.mjs CHANGED
@@ -40,10 +40,11 @@ var LoopwiseClient = class {
40
40
  this.endpoint = config.endpoint ?? this.defaultEndpoint();
41
41
  }
42
42
  defaultEndpoint() {
43
- if (detectRuntime() === "browser") return "/api/graphql";
44
43
  const tenant = this.config.tenant;
45
- if (tenant.includes(".")) return `https://${tenant}/api/graphql`;
46
- return `https://${tenant}.kaik.io/api/graphql`;
44
+ if (detectRuntime() === "browser" && !tenant) return "/api/graphql";
45
+ const t = tenant;
46
+ if (t.includes(".")) return `https://${t}/api/graphql`;
47
+ return `https://${t}.kaik.io/api/graphql`;
47
48
  }
48
49
  async execute(descriptor, options = {}) {
49
50
  const document = renderSingleDocument(descriptor);
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@loopwise/sdk",
3
- "version": "0.1.0-beta.0",
3
+ "version": "0.1.0-beta.1",
4
4
  "description": "Loopwise platform SDK — agent-friendly headless API client",
5
5
  "repository": {
6
6
  "type": "git",
7
- "url": "https://github.com/kaikhq/manekineko.git",
7
+ "url": "git+https://github.com/kaikhq/manekineko.git",
8
8
  "directory": "packages/sdk"
9
9
  },
10
- "homepage": "https://github.com/kaikhq/manekineko/tree/develop/packages/sdk#readme",
10
+ "homepage": "https://www.npmjs.com/package/@loopwise/sdk",
11
11
  "bugs": {
12
12
  "url": "https://github.com/kaikhq/manekineko/issues"
13
13
  },