@loopwise/sdk 0.1.0-beta.1 → 0.1.0-beta.2
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/dist/config.mjs +1 -2
- package/dist/index.d.mts +163 -10
- package/dist/index.mjs +269 -18
- package/package.json +5 -3
package/dist/config.mjs
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -119,6 +119,34 @@ interface Lecturer {
|
|
|
119
119
|
name: string;
|
|
120
120
|
avatar: string | null;
|
|
121
121
|
}
|
|
122
|
+
interface Post {
|
|
123
|
+
id: string;
|
|
124
|
+
slug: string;
|
|
125
|
+
title: string;
|
|
126
|
+
subtitle: string | null;
|
|
127
|
+
excerpt: string | null;
|
|
128
|
+
/** Full body HTML. Truncated server-side when access is gated and the
|
|
129
|
+
* viewer doesn't have permission (login_required without auth, or paid
|
|
130
|
+
* without an active subscription). */
|
|
131
|
+
body: string;
|
|
132
|
+
/** Maps to schema `Post.cover_photo`. */
|
|
133
|
+
coverImage: string | null;
|
|
134
|
+
/** Unix timestamp (seconds since epoch). */
|
|
135
|
+
publishedAt: number;
|
|
136
|
+
/** Unix timestamp of the last update — schema sources this from the
|
|
137
|
+
* post's `updated_at`, so for any persisted record this is populated
|
|
138
|
+
* (typically equal to or after `publishedAt`). The `null` is included
|
|
139
|
+
* for forward-compat with a hypothetical "never modified" sentinel
|
|
140
|
+
* but isn't observed in current production data. */
|
|
141
|
+
modifiedAt: number | null;
|
|
142
|
+
/** Known values: `login_required`, `paid`, `public_access`. The
|
|
143
|
+
* `string & {}` escape hatch keeps autocomplete on the known set
|
|
144
|
+
* while accepting forward-compat values the schema may add. */
|
|
145
|
+
accessType: 'login_required' | 'paid' | 'public_access' | (string & {});
|
|
146
|
+
tags: string[] | null;
|
|
147
|
+
noindex: boolean;
|
|
148
|
+
author: Lecturer | null;
|
|
149
|
+
}
|
|
122
150
|
interface PageInfo {
|
|
123
151
|
page: number;
|
|
124
152
|
totalPages: number;
|
|
@@ -130,18 +158,42 @@ interface ListResult<T> {
|
|
|
130
158
|
items: T[];
|
|
131
159
|
pageInfo: PageInfo;
|
|
132
160
|
}
|
|
161
|
+
/** Simplified order status. Mirrors the storefront `Order.status` enum. */
|
|
162
|
+
type OrderStatus = 'PAID' | 'PENDING' | 'FAILED' | 'REFUNDED';
|
|
163
|
+
interface OrderItem {
|
|
164
|
+
/**
|
|
165
|
+
* Meta-pixel-aligned identifier (Lineitem#tracking_id server-side).
|
|
166
|
+
* Use directly in `content_ids` / `contents[].id` for Purchase events.
|
|
167
|
+
*/
|
|
168
|
+
id: string;
|
|
169
|
+
name: string | null;
|
|
170
|
+
quantity: number;
|
|
171
|
+
/** Per-unit price in the order currency. Maps to FB Pixel `contents[].item_price`. */
|
|
172
|
+
amount: number;
|
|
173
|
+
}
|
|
174
|
+
interface Order {
|
|
175
|
+
/** Stable order identifier — currently equals `Payment#trade_no` server-side. */
|
|
176
|
+
id: string;
|
|
177
|
+
status: OrderStatus;
|
|
178
|
+
/** Total paid amount in the order currency. Maps to FB Pixel `value`. */
|
|
179
|
+
total: number;
|
|
180
|
+
currency: string;
|
|
181
|
+
items: OrderItem[];
|
|
182
|
+
/** Unix timestamp (seconds since epoch). */
|
|
183
|
+
createdAt: number;
|
|
184
|
+
}
|
|
133
185
|
//#endregion
|
|
134
186
|
//#region src/resources/courses.d.ts
|
|
135
|
-
declare const DEFAULT_LIST_FIELDS: Selection<Course>;
|
|
136
|
-
declare const DEFAULT_GET_FIELDS: Selection<Course>;
|
|
137
|
-
interface CourseListArgs<S extends Selection<Course> = typeof DEFAULT_LIST_FIELDS> {
|
|
187
|
+
declare const DEFAULT_LIST_FIELDS$1: Selection<Course>;
|
|
188
|
+
declare const DEFAULT_GET_FIELDS$2: Selection<Course>;
|
|
189
|
+
interface CourseListArgs<S extends Selection<Course> = typeof DEFAULT_LIST_FIELDS$1> {
|
|
138
190
|
limit?: number;
|
|
139
191
|
page?: number;
|
|
140
192
|
/** SDK `limit` maps to schema `perPage`. Schema also has its own `limit`
|
|
141
193
|
* which we expose as `topN` if needed (not in v0). */
|
|
142
194
|
select?: S;
|
|
143
195
|
}
|
|
144
|
-
interface CourseGetArgs<S extends Selection<Course> = typeof DEFAULT_GET_FIELDS> {
|
|
196
|
+
interface CourseGetArgs<S extends Selection<Course> = typeof DEFAULT_GET_FIELDS$2> {
|
|
145
197
|
slug: string;
|
|
146
198
|
select?: S;
|
|
147
199
|
}
|
|
@@ -156,19 +208,120 @@ declare class CoursesResource {
|
|
|
156
208
|
* // Opt into GET + Cache-Control hint (60s):
|
|
157
209
|
* await loopwise.courses.list({ limit: 20 }, { cache: 60 })
|
|
158
210
|
*/
|
|
159
|
-
list<S extends Selection<Course> = typeof DEFAULT_LIST_FIELDS>(args?: CourseListArgs<S>, options?: FetchOptions): QueryBuilder<ListResult<Selected<Course, S>>>;
|
|
211
|
+
list<S extends Selection<Course> = typeof DEFAULT_LIST_FIELDS$1>(args?: CourseListArgs<S>, options?: FetchOptions): QueryBuilder<ListResult<Selected<Course, S>>>;
|
|
212
|
+
/**
|
|
213
|
+
* Get a single course by slug.
|
|
214
|
+
*
|
|
215
|
+
* Throws `LoopwiseError("GRAPHQL")` when the slug is not found
|
|
216
|
+
* (Storefront `Queries::Course` raises `service_error("Course-003")`
|
|
217
|
+
* on a `null: false` field). Wrap with try/catch to render 404 routes:
|
|
218
|
+
*
|
|
219
|
+
* @example
|
|
220
|
+
* try {
|
|
221
|
+
* const course = await loopwise.courses.get({ slug: 'finance' })
|
|
222
|
+
* // ...
|
|
223
|
+
* } catch (e) {
|
|
224
|
+
* if (e instanceof LoopwiseError && e.code === 'GRAPHQL') {
|
|
225
|
+
* return Astro.redirect('/404')
|
|
226
|
+
* }
|
|
227
|
+
* throw e
|
|
228
|
+
* }
|
|
229
|
+
*/
|
|
230
|
+
get<S extends Selection<Course> = typeof DEFAULT_GET_FIELDS$2>(args: CourseGetArgs<S>, options?: FetchOptions): QueryBuilder<Selected<Course, S>>;
|
|
231
|
+
}
|
|
232
|
+
//#endregion
|
|
233
|
+
//#region src/resources/orders.d.ts
|
|
234
|
+
declare const DEFAULT_GET_FIELDS$1: Selection<Order>;
|
|
235
|
+
interface OrderGetArgs<S extends Selection<Order> = typeof DEFAULT_GET_FIELDS$1> {
|
|
236
|
+
/**
|
|
237
|
+
* Order identifier — the value passed via `?tradeNo=...` on the post-checkout
|
|
238
|
+
* redirect to the merchant's success URL. Internally equals
|
|
239
|
+
* `Payment#trade_no` server-side.
|
|
240
|
+
*/
|
|
241
|
+
id: string;
|
|
242
|
+
select?: S;
|
|
243
|
+
}
|
|
244
|
+
declare class OrdersResource {
|
|
245
|
+
private client;
|
|
246
|
+
constructor(client: LoopwiseClient);
|
|
247
|
+
/**
|
|
248
|
+
* Look up a PII-safe order projection by id (the value the storefront
|
|
249
|
+
* appends to a merchant's `checkoutSuccessUrl` as `?tradeNo=...`).
|
|
250
|
+
*
|
|
251
|
+
* Returns `null`-shaped result via the GraphQL `null` payload when the
|
|
252
|
+
* id doesn't resolve in the current tenant — does NOT throw. Use the
|
|
253
|
+
* returned `status` to gate Meta Pixel `Purchase` events:
|
|
254
|
+
*
|
|
255
|
+
* @example
|
|
256
|
+
* const id = new URLSearchParams(location.search).get('tradeNo');
|
|
257
|
+
* if (!id) return;
|
|
258
|
+
* const order = await loopwise.orders.get({ id });
|
|
259
|
+
* if (order?.status === 'PAID') {
|
|
260
|
+
* fbq('track', 'Purchase', {
|
|
261
|
+
* value: order.total,
|
|
262
|
+
* currency: order.currency,
|
|
263
|
+
* contents: order.items.map(i => ({
|
|
264
|
+
* id: i.id, name: i.name, quantity: i.quantity, item_price: i.amount,
|
|
265
|
+
* })),
|
|
266
|
+
* content_ids: order.items.map(i => i.id),
|
|
267
|
+
* content_type: 'product',
|
|
268
|
+
* num_items: order.items.reduce((s, i) => s + i.quantity, 0),
|
|
269
|
+
* }, { eventID: order.id });
|
|
270
|
+
* }
|
|
271
|
+
*/
|
|
272
|
+
get<S extends Selection<Order> = typeof DEFAULT_GET_FIELDS$1>(args: OrderGetArgs<S>, options?: FetchOptions): QueryBuilder<Selected<Order, S> | null>;
|
|
273
|
+
}
|
|
274
|
+
//#endregion
|
|
275
|
+
//#region src/resources/posts.d.ts
|
|
276
|
+
declare const DEFAULT_LIST_FIELDS: Selection<Post>;
|
|
277
|
+
declare const DEFAULT_GET_FIELDS: Selection<Post>;
|
|
278
|
+
interface PostListArgs<S extends Selection<Post> = typeof DEFAULT_LIST_FIELDS> {
|
|
279
|
+
limit?: number;
|
|
280
|
+
page?: number;
|
|
281
|
+
/** SDK `limit` maps to schema `perPage`. */
|
|
282
|
+
select?: S;
|
|
283
|
+
}
|
|
284
|
+
interface PostGetArgs<S extends Selection<Post> = typeof DEFAULT_GET_FIELDS> {
|
|
285
|
+
slug: string;
|
|
286
|
+
select?: S;
|
|
287
|
+
}
|
|
288
|
+
declare class PostsResource {
|
|
289
|
+
private client;
|
|
290
|
+
constructor(client: LoopwiseClient);
|
|
291
|
+
/**
|
|
292
|
+
* List published posts for the current tenant, ordered by publishedAt desc.
|
|
293
|
+
* @example
|
|
294
|
+
* const { items, pageInfo } = await loopwise.posts.list({ limit: 10 })
|
|
295
|
+
* @example
|
|
296
|
+
* await loopwise.posts.list({ limit: 10 }, { cache: 60 })
|
|
297
|
+
*/
|
|
298
|
+
list<S extends Selection<Post> = typeof DEFAULT_LIST_FIELDS>(args?: PostListArgs<S>, options?: FetchOptions): QueryBuilder<ListResult<Selected<Post, S>>>;
|
|
160
299
|
/**
|
|
161
|
-
* Get a single
|
|
300
|
+
* Get a single post by slug.
|
|
301
|
+
*
|
|
302
|
+
* Throws `LoopwiseError("GRAPHQL")` when the slug is not found
|
|
303
|
+
* (Storefront `Queries::Post` raises `service_error("Post-000")` on a
|
|
304
|
+
* `null: false` field). Wrap with try/catch to render 404 routes:
|
|
305
|
+
*
|
|
162
306
|
* @example
|
|
163
|
-
*
|
|
164
|
-
*
|
|
307
|
+
* try {
|
|
308
|
+
* const post = await loopwise.posts.get({ slug: 'hello-world' })
|
|
309
|
+
* // ...
|
|
310
|
+
* } catch (e) {
|
|
311
|
+
* if (e instanceof LoopwiseError && e.code === 'GRAPHQL') {
|
|
312
|
+
* return Astro.redirect('/404')
|
|
313
|
+
* }
|
|
314
|
+
* throw e
|
|
315
|
+
* }
|
|
165
316
|
*/
|
|
166
|
-
get<S extends Selection<
|
|
317
|
+
get<S extends Selection<Post> = typeof DEFAULT_GET_FIELDS>(args: PostGetArgs<S>, options?: FetchOptions): QueryBuilder<Selected<Post, S>>;
|
|
167
318
|
}
|
|
168
319
|
//#endregion
|
|
169
320
|
//#region src/index.d.ts
|
|
170
321
|
interface Loopwise {
|
|
171
322
|
readonly courses: CoursesResource;
|
|
323
|
+
readonly orders: OrdersResource;
|
|
324
|
+
readonly posts: PostsResource;
|
|
172
325
|
/**
|
|
173
326
|
* Compose multiple resource queries into a single GraphQL HTTP request.
|
|
174
327
|
* @example
|
|
@@ -190,4 +343,4 @@ interface Loopwise {
|
|
|
190
343
|
}
|
|
191
344
|
declare function createClient(config: LoopwiseConfig): Loopwise;
|
|
192
345
|
//#endregion
|
|
193
|
-
export { type Course, type FetchOptions, type Lecturer, type ListResult, Loopwise, type LoopwiseConfig, LoopwiseError, type PageInfo, QueryBuilder, type Selected, type Selection, createClient };
|
|
346
|
+
export { type Course, type FetchOptions, type Lecturer, type ListResult, Loopwise, type LoopwiseConfig, LoopwiseError, type Order, type OrderItem, type OrderStatus, type PageInfo, type Post, QueryBuilder, type Selected, type Selection, createClient };
|
package/dist/index.mjs
CHANGED
|
@@ -15,14 +15,12 @@ var LoopwiseError = class extends Error {
|
|
|
15
15
|
this.requestId = detail.requestId;
|
|
16
16
|
}
|
|
17
17
|
};
|
|
18
|
-
|
|
19
18
|
//#endregion
|
|
20
19
|
//#region src/runtime.ts
|
|
21
20
|
function detectRuntime() {
|
|
22
21
|
if (typeof window !== "undefined" && typeof window.document !== "undefined") return "browser";
|
|
23
22
|
return "ssr";
|
|
24
23
|
}
|
|
25
|
-
|
|
26
24
|
//#endregion
|
|
27
25
|
//#region src/client.ts
|
|
28
26
|
const GET_URL_MAX_LENGTH = 2e3;
|
|
@@ -234,7 +232,6 @@ function literal(value) {
|
|
|
234
232
|
if (typeof value === "object") return `{ ${Object.entries(value).filter(([, v]) => v !== void 0).map(([k, v]) => `${k}: ${literal(v)}`).join(", ")} }`;
|
|
235
233
|
throw new LoopwiseError("CONFIG", `Unsupported argument type for GraphQL literal: ${typeof value}`);
|
|
236
234
|
}
|
|
237
|
-
|
|
238
235
|
//#endregion
|
|
239
236
|
//#region src/query.ts
|
|
240
237
|
var QueryBuilder = class {
|
|
@@ -250,7 +247,6 @@ var QueryBuilder = class {
|
|
|
250
247
|
return this.fetcher.execute(this.descriptor, this.fetchOptions).then(onFulfilled, onRejected);
|
|
251
248
|
}
|
|
252
249
|
};
|
|
253
|
-
|
|
254
250
|
//#endregion
|
|
255
251
|
//#region src/schema/courses.ts
|
|
256
252
|
/** Map SDK Course selection -> schema field selection set. */
|
|
@@ -300,16 +296,15 @@ function mapCoursePage(raw) {
|
|
|
300
296
|
}
|
|
301
297
|
};
|
|
302
298
|
}
|
|
303
|
-
|
|
304
299
|
//#endregion
|
|
305
300
|
//#region src/resources/courses.ts
|
|
306
|
-
const DEFAULT_LIST_FIELDS = {
|
|
301
|
+
const DEFAULT_LIST_FIELDS$1 = {
|
|
307
302
|
id: true,
|
|
308
303
|
slug: true,
|
|
309
304
|
title: true,
|
|
310
305
|
coverImage: true
|
|
311
306
|
};
|
|
312
|
-
const DEFAULT_GET_FIELDS = {
|
|
307
|
+
const DEFAULT_GET_FIELDS$2 = {
|
|
313
308
|
id: true,
|
|
314
309
|
slug: true,
|
|
315
310
|
title: true,
|
|
@@ -321,13 +316,14 @@ const DEFAULT_GET_FIELDS = {
|
|
|
321
316
|
avatar: true
|
|
322
317
|
}
|
|
323
318
|
};
|
|
324
|
-
function tenantVars(client) {
|
|
319
|
+
function tenantVars$2(client) {
|
|
325
320
|
return { subdomain: {
|
|
326
321
|
type: "String!",
|
|
327
322
|
value: client.config.tenant ?? ""
|
|
328
323
|
} };
|
|
329
324
|
}
|
|
330
325
|
var CoursesResource = class {
|
|
326
|
+
client;
|
|
331
327
|
constructor(client) {
|
|
332
328
|
this.client = client;
|
|
333
329
|
}
|
|
@@ -340,7 +336,7 @@ var CoursesResource = class {
|
|
|
340
336
|
* await loopwise.courses.list({ limit: 20 }, { cache: 60 })
|
|
341
337
|
*/
|
|
342
338
|
list(args = {}, options = {}) {
|
|
343
|
-
const selectionSet = `nodes { ${buildCourseSelection(args.select ?? DEFAULT_LIST_FIELDS)} } currentPage totalPages nodesCount hasNextPage hasPreviousPage`;
|
|
339
|
+
const selectionSet = `nodes { ${buildCourseSelection(args.select ?? DEFAULT_LIST_FIELDS$1)} } currentPage totalPages nodesCount hasNextPage hasPreviousPage`;
|
|
344
340
|
return new QueryBuilder(this.client, {
|
|
345
341
|
operation: "CourseList",
|
|
346
342
|
field: "courses",
|
|
@@ -349,35 +345,291 @@ var CoursesResource = class {
|
|
|
349
345
|
page: args.page,
|
|
350
346
|
perPage: args.limit
|
|
351
347
|
},
|
|
352
|
-
vars: tenantVars(this.client),
|
|
348
|
+
vars: tenantVars$2(this.client),
|
|
353
349
|
transform: (raw) => mapCoursePage(raw)
|
|
354
350
|
}, options);
|
|
355
351
|
}
|
|
356
352
|
/**
|
|
357
|
-
* Get a single course by slug.
|
|
353
|
+
* Get a single course by slug.
|
|
354
|
+
*
|
|
355
|
+
* Throws `LoopwiseError("GRAPHQL")` when the slug is not found
|
|
356
|
+
* (Storefront `Queries::Course` raises `service_error("Course-003")`
|
|
357
|
+
* on a `null: false` field). Wrap with try/catch to render 404 routes:
|
|
358
|
+
*
|
|
358
359
|
* @example
|
|
359
|
-
*
|
|
360
|
-
*
|
|
360
|
+
* try {
|
|
361
|
+
* const course = await loopwise.courses.get({ slug: 'finance' })
|
|
362
|
+
* // ...
|
|
363
|
+
* } catch (e) {
|
|
364
|
+
* if (e instanceof LoopwiseError && e.code === 'GRAPHQL') {
|
|
365
|
+
* return Astro.redirect('/404')
|
|
366
|
+
* }
|
|
367
|
+
* throw e
|
|
368
|
+
* }
|
|
361
369
|
*/
|
|
362
370
|
get(args, options = {}) {
|
|
363
|
-
const sel = args.select ?? DEFAULT_GET_FIELDS;
|
|
371
|
+
const sel = args.select ?? DEFAULT_GET_FIELDS$2;
|
|
364
372
|
return new QueryBuilder(this.client, {
|
|
365
373
|
operation: "CourseGet",
|
|
366
374
|
field: "course",
|
|
367
375
|
selectionSet: buildCourseSelection(sel),
|
|
368
376
|
args: { slug: args.slug },
|
|
377
|
+
vars: tenantVars$2(this.client),
|
|
378
|
+
transform: (raw) => mapCourse(raw)
|
|
379
|
+
}, options);
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
//#endregion
|
|
383
|
+
//#region src/schema/orders.ts
|
|
384
|
+
/** Map SDK Order selection -> schema field selection set. */
|
|
385
|
+
function buildOrderSelection(select) {
|
|
386
|
+
const parts = ["id"];
|
|
387
|
+
const sel = select;
|
|
388
|
+
if (sel.status) parts.push("status");
|
|
389
|
+
if (sel.total) parts.push("total");
|
|
390
|
+
if (sel.currency) parts.push("currency");
|
|
391
|
+
if (sel.createdAt) parts.push("createdAt");
|
|
392
|
+
if (sel.items && typeof sel.items === "object") {
|
|
393
|
+
const itemSel = sel.items;
|
|
394
|
+
const itemParts = ["id"];
|
|
395
|
+
if (itemSel.name) itemParts.push("name");
|
|
396
|
+
if (itemSel.quantity) itemParts.push("quantity");
|
|
397
|
+
if (itemSel.amount) itemParts.push("amount");
|
|
398
|
+
parts.push(`items { ${itemParts.join(" ")} }`);
|
|
399
|
+
}
|
|
400
|
+
return parts.join(" ");
|
|
401
|
+
}
|
|
402
|
+
function mapOrderItem(raw) {
|
|
403
|
+
const out = { id: raw.id };
|
|
404
|
+
if (raw.name !== void 0) out.name = raw.name ?? null;
|
|
405
|
+
if (raw.quantity !== void 0) out.quantity = raw.quantity;
|
|
406
|
+
if (raw.amount !== void 0) out.amount = raw.amount;
|
|
407
|
+
return out;
|
|
408
|
+
}
|
|
409
|
+
function mapOrder(raw) {
|
|
410
|
+
const out = { id: raw.id };
|
|
411
|
+
if (raw.status !== void 0) out.status = raw.status;
|
|
412
|
+
if (raw.total !== void 0) out.total = raw.total;
|
|
413
|
+
if (raw.currency !== void 0) out.currency = raw.currency;
|
|
414
|
+
if (raw.createdAt !== void 0) out.createdAt = raw.createdAt;
|
|
415
|
+
if (raw.items !== void 0) out.items = raw.items.map(mapOrderItem);
|
|
416
|
+
return out;
|
|
417
|
+
}
|
|
418
|
+
//#endregion
|
|
419
|
+
//#region src/resources/orders.ts
|
|
420
|
+
const DEFAULT_GET_FIELDS$1 = {
|
|
421
|
+
id: true,
|
|
422
|
+
status: true,
|
|
423
|
+
total: true,
|
|
424
|
+
currency: true,
|
|
425
|
+
createdAt: true,
|
|
426
|
+
items: {
|
|
427
|
+
id: true,
|
|
428
|
+
name: true,
|
|
429
|
+
quantity: true,
|
|
430
|
+
amount: true
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
function tenantVars$1(client) {
|
|
434
|
+
return { subdomain: {
|
|
435
|
+
type: "String!",
|
|
436
|
+
value: client.config.tenant ?? ""
|
|
437
|
+
} };
|
|
438
|
+
}
|
|
439
|
+
var OrdersResource = class {
|
|
440
|
+
client;
|
|
441
|
+
constructor(client) {
|
|
442
|
+
this.client = client;
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Look up a PII-safe order projection by id (the value the storefront
|
|
446
|
+
* appends to a merchant's `checkoutSuccessUrl` as `?tradeNo=...`).
|
|
447
|
+
*
|
|
448
|
+
* Returns `null`-shaped result via the GraphQL `null` payload when the
|
|
449
|
+
* id doesn't resolve in the current tenant — does NOT throw. Use the
|
|
450
|
+
* returned `status` to gate Meta Pixel `Purchase` events:
|
|
451
|
+
*
|
|
452
|
+
* @example
|
|
453
|
+
* const id = new URLSearchParams(location.search).get('tradeNo');
|
|
454
|
+
* if (!id) return;
|
|
455
|
+
* const order = await loopwise.orders.get({ id });
|
|
456
|
+
* if (order?.status === 'PAID') {
|
|
457
|
+
* fbq('track', 'Purchase', {
|
|
458
|
+
* value: order.total,
|
|
459
|
+
* currency: order.currency,
|
|
460
|
+
* contents: order.items.map(i => ({
|
|
461
|
+
* id: i.id, name: i.name, quantity: i.quantity, item_price: i.amount,
|
|
462
|
+
* })),
|
|
463
|
+
* content_ids: order.items.map(i => i.id),
|
|
464
|
+
* content_type: 'product',
|
|
465
|
+
* num_items: order.items.reduce((s, i) => s + i.quantity, 0),
|
|
466
|
+
* }, { eventID: order.id });
|
|
467
|
+
* }
|
|
468
|
+
*/
|
|
469
|
+
get(args, options = {}) {
|
|
470
|
+
const sel = args.select ?? DEFAULT_GET_FIELDS$1;
|
|
471
|
+
return new QueryBuilder(this.client, {
|
|
472
|
+
operation: "OrderGet",
|
|
473
|
+
field: "order",
|
|
474
|
+
selectionSet: buildOrderSelection(sel),
|
|
475
|
+
args: { id: args.id },
|
|
476
|
+
vars: tenantVars$1(this.client),
|
|
477
|
+
transform: (raw) => raw == null ? null : mapOrder(raw)
|
|
478
|
+
}, options);
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
//#endregion
|
|
482
|
+
//#region src/schema/posts.ts
|
|
483
|
+
/** Map SDK Post selection -> schema field selection set. */
|
|
484
|
+
function buildPostSelection(select) {
|
|
485
|
+
const parts = ["id", "slug"];
|
|
486
|
+
const sel = select;
|
|
487
|
+
if (sel.title) parts.push("title");
|
|
488
|
+
if (sel.subtitle) parts.push("subtitle");
|
|
489
|
+
if (sel.excerpt) parts.push("excerpt");
|
|
490
|
+
if (sel.body) parts.push("body");
|
|
491
|
+
if (sel.coverImage) parts.push("coverPhoto");
|
|
492
|
+
if (sel.publishedAt) parts.push("publishedAt");
|
|
493
|
+
if (sel.modifiedAt) parts.push("modifiedAt");
|
|
494
|
+
if (sel.accessType) parts.push("accessType");
|
|
495
|
+
if (sel.tags) parts.push("tags");
|
|
496
|
+
if (sel.noindex) parts.push("noindex");
|
|
497
|
+
if (sel.author && typeof sel.author === "object") {
|
|
498
|
+
const auth = sel.author;
|
|
499
|
+
const authParts = ["id"];
|
|
500
|
+
if (auth.name) authParts.push("name");
|
|
501
|
+
if (auth.avatar) authParts.push("avatar");
|
|
502
|
+
parts.push(`author { ${authParts.join(" ")} }`);
|
|
503
|
+
}
|
|
504
|
+
return parts.join(" ");
|
|
505
|
+
}
|
|
506
|
+
function mapPost(raw) {
|
|
507
|
+
const out = {
|
|
508
|
+
id: raw.id,
|
|
509
|
+
slug: raw.slug
|
|
510
|
+
};
|
|
511
|
+
if (raw.title !== void 0) out.title = raw.title;
|
|
512
|
+
if (raw.subtitle !== void 0) out.subtitle = raw.subtitle ?? null;
|
|
513
|
+
if (raw.excerpt !== void 0) out.excerpt = raw.excerpt ?? null;
|
|
514
|
+
if (raw.body !== void 0) out.body = raw.body;
|
|
515
|
+
if (raw.coverPhoto !== void 0) out.coverImage = raw.coverPhoto ?? null;
|
|
516
|
+
if (raw.publishedAt !== void 0) out.publishedAt = raw.publishedAt;
|
|
517
|
+
if (raw.modifiedAt !== void 0) out.modifiedAt = raw.modifiedAt ?? null;
|
|
518
|
+
if (raw.accessType !== void 0) out.accessType = raw.accessType;
|
|
519
|
+
if (raw.tags !== void 0) out.tags = raw.tags ?? null;
|
|
520
|
+
if (raw.noindex !== void 0) out.noindex = raw.noindex;
|
|
521
|
+
if (raw.author !== void 0) out.author = raw.author ? mapLecturer(raw.author) : null;
|
|
522
|
+
return out;
|
|
523
|
+
}
|
|
524
|
+
function mapPostPage(raw) {
|
|
525
|
+
return {
|
|
526
|
+
items: raw.nodes.map(mapPost),
|
|
527
|
+
pageInfo: {
|
|
528
|
+
page: raw.currentPage,
|
|
529
|
+
totalPages: raw.totalPages,
|
|
530
|
+
totalCount: raw.nodesCount,
|
|
531
|
+
hasNext: raw.hasNextPage,
|
|
532
|
+
hasPrev: raw.hasPreviousPage
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
//#endregion
|
|
537
|
+
//#region src/resources/posts.ts
|
|
538
|
+
const DEFAULT_LIST_FIELDS = {
|
|
539
|
+
id: true,
|
|
540
|
+
slug: true,
|
|
541
|
+
title: true,
|
|
542
|
+
excerpt: true,
|
|
543
|
+
coverImage: true,
|
|
544
|
+
publishedAt: true
|
|
545
|
+
};
|
|
546
|
+
const DEFAULT_GET_FIELDS = {
|
|
547
|
+
id: true,
|
|
548
|
+
slug: true,
|
|
549
|
+
title: true,
|
|
550
|
+
subtitle: true,
|
|
551
|
+
excerpt: true,
|
|
552
|
+
body: true,
|
|
553
|
+
coverImage: true,
|
|
554
|
+
publishedAt: true,
|
|
555
|
+
modifiedAt: true,
|
|
556
|
+
accessType: true,
|
|
557
|
+
author: {
|
|
558
|
+
id: true,
|
|
559
|
+
name: true,
|
|
560
|
+
avatar: true
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
function tenantVars(client) {
|
|
564
|
+
return { subdomain: {
|
|
565
|
+
type: "String!",
|
|
566
|
+
value: client.config.tenant ?? ""
|
|
567
|
+
} };
|
|
568
|
+
}
|
|
569
|
+
var PostsResource = class {
|
|
570
|
+
client;
|
|
571
|
+
constructor(client) {
|
|
572
|
+
this.client = client;
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* List published posts for the current tenant, ordered by publishedAt desc.
|
|
576
|
+
* @example
|
|
577
|
+
* const { items, pageInfo } = await loopwise.posts.list({ limit: 10 })
|
|
578
|
+
* @example
|
|
579
|
+
* await loopwise.posts.list({ limit: 10 }, { cache: 60 })
|
|
580
|
+
*/
|
|
581
|
+
list(args = {}, options = {}) {
|
|
582
|
+
const selectionSet = `nodes { ${buildPostSelection(args.select ?? DEFAULT_LIST_FIELDS)} } currentPage totalPages nodesCount hasNextPage hasPreviousPage`;
|
|
583
|
+
return new QueryBuilder(this.client, {
|
|
584
|
+
operation: "PostList",
|
|
585
|
+
field: "posts",
|
|
586
|
+
selectionSet,
|
|
587
|
+
args: {
|
|
588
|
+
page: args.page,
|
|
589
|
+
perPage: args.limit
|
|
590
|
+
},
|
|
591
|
+
vars: tenantVars(this.client),
|
|
592
|
+
transform: (raw) => mapPostPage(raw)
|
|
593
|
+
}, options);
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Get a single post by slug.
|
|
597
|
+
*
|
|
598
|
+
* Throws `LoopwiseError("GRAPHQL")` when the slug is not found
|
|
599
|
+
* (Storefront `Queries::Post` raises `service_error("Post-000")` on a
|
|
600
|
+
* `null: false` field). Wrap with try/catch to render 404 routes:
|
|
601
|
+
*
|
|
602
|
+
* @example
|
|
603
|
+
* try {
|
|
604
|
+
* const post = await loopwise.posts.get({ slug: 'hello-world' })
|
|
605
|
+
* // ...
|
|
606
|
+
* } catch (e) {
|
|
607
|
+
* if (e instanceof LoopwiseError && e.code === 'GRAPHQL') {
|
|
608
|
+
* return Astro.redirect('/404')
|
|
609
|
+
* }
|
|
610
|
+
* throw e
|
|
611
|
+
* }
|
|
612
|
+
*/
|
|
613
|
+
get(args, options = {}) {
|
|
614
|
+
const sel = args.select ?? DEFAULT_GET_FIELDS;
|
|
615
|
+
return new QueryBuilder(this.client, {
|
|
616
|
+
operation: "PostGet",
|
|
617
|
+
field: "post",
|
|
618
|
+
selectionSet: buildPostSelection(sel),
|
|
619
|
+
args: { slug: args.slug },
|
|
369
620
|
vars: tenantVars(this.client),
|
|
370
|
-
transform: (raw) =>
|
|
621
|
+
transform: (raw) => mapPost(raw)
|
|
371
622
|
}, options);
|
|
372
623
|
}
|
|
373
624
|
};
|
|
374
|
-
|
|
375
625
|
//#endregion
|
|
376
626
|
//#region src/index.ts
|
|
377
627
|
function createClient(config) {
|
|
378
628
|
const client = new LoopwiseClient(config);
|
|
379
629
|
return {
|
|
380
630
|
courses: new CoursesResource(client),
|
|
631
|
+
orders: new OrdersResource(client),
|
|
632
|
+
posts: new PostsResource(client),
|
|
381
633
|
batch(builders, options) {
|
|
382
634
|
return client.batch(builders, options);
|
|
383
635
|
},
|
|
@@ -386,6 +638,5 @@ function createClient(config) {
|
|
|
386
638
|
}
|
|
387
639
|
};
|
|
388
640
|
}
|
|
389
|
-
|
|
390
641
|
//#endregion
|
|
391
|
-
export { LoopwiseError, QueryBuilder, createClient };
|
|
642
|
+
export { LoopwiseError, QueryBuilder, createClient };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loopwise/sdk",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.2",
|
|
4
4
|
"description": "Loopwise platform SDK — agent-friendly headless API client",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -43,11 +43,13 @@
|
|
|
43
43
|
"build": "tsdown",
|
|
44
44
|
"dev": "tsdown --watch",
|
|
45
45
|
"typecheck": "tsc --noEmit",
|
|
46
|
-
"test": "vitest run"
|
|
46
|
+
"test": "vitest run",
|
|
47
|
+
"regen:lock": "node scripts/regen-lock.mjs"
|
|
47
48
|
},
|
|
48
49
|
"devDependencies": {
|
|
50
|
+
"graphql": "16.13.1",
|
|
49
51
|
"tsconfig": "workspace:*",
|
|
50
|
-
"tsdown": "0.
|
|
52
|
+
"tsdown": "0.22.0",
|
|
51
53
|
"typescript": "5.9.3",
|
|
52
54
|
"vitest": "4.1.0"
|
|
53
55
|
},
|