@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 +93 -18
- package/dist/index.mjs +4 -3
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,37 +1,78 @@
|
|
|
1
1
|
# @loopwise/sdk
|
|
2
2
|
|
|
3
|
-
Storefront GraphQL client for Loopwise / Teachify.
|
|
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
|
-
|
|
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
|
|
18
|
-
`acme.myteachify.com`,
|
|
19
|
-
|
|
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
|
-
|
|
43
|
+
## Cross-origin (Loopwise Pages preview, SSR, build pipelines)
|
|
22
44
|
|
|
23
|
-
When the SDK runs outside a tenant origin
|
|
24
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
46
|
-
|
|
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.
|
|
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://
|
|
10
|
+
"homepage": "https://www.npmjs.com/package/@loopwise/sdk",
|
|
11
11
|
"bugs": {
|
|
12
12
|
"url": "https://github.com/kaikhq/manekineko/issues"
|
|
13
13
|
},
|