@playcademy/sdk 0.3.7-beta.1 → 0.3.7-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/server.js CHANGED
@@ -1,3 +1,204 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, {
5
+ get: all[name],
6
+ enumerable: true,
7
+ configurable: true,
8
+ set: (newValue) => all[name] = () => newValue
9
+ });
10
+ };
11
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
12
+ // ../utils/src/file-loader.ts
13
+ import { existsSync, readdirSync, statSync } from "fs";
14
+ import { readFile } from "fs/promises";
15
+ import { dirname, parse, resolve } from "path";
16
+ function findFilePath(filename, startDir, maxLevels = 3) {
17
+ const filenames = Array.isArray(filename) ? filename : [filename];
18
+ let currentDir = resolve(startDir);
19
+ let levelsSearched = 0;
20
+ while (levelsSearched <= maxLevels) {
21
+ for (const fname of filenames) {
22
+ const filePath = resolve(currentDir, fname);
23
+ if (existsSync(filePath)) {
24
+ return {
25
+ path: filePath,
26
+ dir: currentDir,
27
+ filename: fname
28
+ };
29
+ }
30
+ }
31
+ if (levelsSearched >= maxLevels) {
32
+ break;
33
+ }
34
+ const parentDir = dirname(currentDir);
35
+ if (parentDir === currentDir) {
36
+ break;
37
+ }
38
+ const parsed = parse(currentDir);
39
+ if (parsed.root === currentDir) {
40
+ break;
41
+ }
42
+ currentDir = parentDir;
43
+ levelsSearched++;
44
+ }
45
+ return null;
46
+ }
47
+ async function loadFile(filename, options = {}) {
48
+ const {
49
+ cwd = process.cwd(),
50
+ required = false,
51
+ searchUp = false,
52
+ maxLevels = 3,
53
+ parseJson = false,
54
+ stripComments = false
55
+ } = options;
56
+ let fileResult;
57
+ if (searchUp) {
58
+ fileResult = findFilePath(filename, cwd, maxLevels);
59
+ } else {
60
+ const filenames = Array.isArray(filename) ? filename : [filename];
61
+ fileResult = null;
62
+ for (const fname of filenames) {
63
+ const filePath = resolve(cwd, fname);
64
+ if (existsSync(filePath)) {
65
+ fileResult = {
66
+ path: filePath,
67
+ dir: cwd,
68
+ filename: fname
69
+ };
70
+ break;
71
+ }
72
+ }
73
+ }
74
+ if (!fileResult) {
75
+ if (required) {
76
+ const fileList = Array.isArray(filename) ? filename.join(" or ") : filename;
77
+ const message = searchUp ? `${fileList} not found in ${cwd} or up to ${maxLevels} parent directories` : `${fileList} not found at ${cwd}`;
78
+ throw new Error(message);
79
+ }
80
+ return null;
81
+ }
82
+ try {
83
+ let content = await readFile(fileResult.path, "utf8");
84
+ if (parseJson) {
85
+ if (stripComments) {
86
+ content = stripJsonComments(content);
87
+ }
88
+ return JSON.parse(content);
89
+ }
90
+ return content;
91
+ } catch (error) {
92
+ throw new Error(`Failed to load ${fileResult.filename} from ${fileResult.path}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
93
+ }
94
+ }
95
+ async function findFile(filename, options = {}) {
96
+ const { cwd = process.cwd(), searchUp = false, maxLevels = 3 } = options;
97
+ if (searchUp) {
98
+ return findFilePath(filename, cwd, maxLevels);
99
+ }
100
+ const filenames = Array.isArray(filename) ? filename : [filename];
101
+ for (const fname of filenames) {
102
+ const filePath = resolve(cwd, fname);
103
+ if (existsSync(filePath)) {
104
+ return {
105
+ path: filePath,
106
+ dir: cwd,
107
+ filename: fname
108
+ };
109
+ }
110
+ }
111
+ return null;
112
+ }
113
+ function stripJsonComments(jsonc) {
114
+ let result = jsonc.replace(/\/\*[\s\S]*?\*\//g, "");
115
+ result = result.replace(/\/\/.*/g, "");
116
+ return result;
117
+ }
118
+ var init_file_loader = () => {};
119
+
120
+ // src/server/utils/config-loader.ts
121
+ var exports_config_loader = {};
122
+ __export(exports_config_loader, {
123
+ loadConfig: () => loadConfig,
124
+ findConfigPath: () => findConfigPath
125
+ });
126
+ import { resolve as resolve2 } from "path";
127
+ async function findConfigPath(configPath) {
128
+ if (configPath) {
129
+ return resolve2(configPath);
130
+ }
131
+ const result = await findFile(["playcademy.config.js", "playcademy.config.mjs", "playcademy.config.json"], {
132
+ searchUp: true,
133
+ maxLevels: 3
134
+ });
135
+ if (!result) {
136
+ throw new Error("playcademy.config.js not found. Please create a playcademy.config.js file or specify the config path.");
137
+ }
138
+ return result.path;
139
+ }
140
+ async function loadConfig(configPath) {
141
+ try {
142
+ let config;
143
+ let actualPath;
144
+ if (configPath) {
145
+ actualPath = resolve2(configPath);
146
+ if (actualPath.endsWith(".json")) {
147
+ config = await loadFile(actualPath, { required: true, parseJson: true });
148
+ } else {
149
+ const module = await import(actualPath);
150
+ config = module.default || module;
151
+ }
152
+ } else {
153
+ actualPath = await findConfigPath();
154
+ if (actualPath.endsWith(".json")) {
155
+ config = await loadFile(actualPath, { required: true, parseJson: true });
156
+ } else {
157
+ const module = await import(actualPath);
158
+ config = module.default || module;
159
+ }
160
+ }
161
+ if (!config || typeof config !== "object") {
162
+ throw new Error("Config file must export/contain an object");
163
+ }
164
+ validateConfig(config);
165
+ return config;
166
+ } catch (error) {
167
+ throw new Error(`Failed to load config file: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
168
+ }
169
+ }
170
+ function validateConfig(config) {
171
+ if (!config || typeof config !== "object") {
172
+ throw new Error("Configuration must be an object");
173
+ }
174
+ const cfg = config;
175
+ if (!cfg.name || typeof cfg.name !== "string") {
176
+ throw new Error("Missing required field: name");
177
+ }
178
+ if (cfg.timeback) {
179
+ if (typeof cfg.timeback !== "object") {
180
+ throw new Error("timeback must be an object if provided");
181
+ }
182
+ const tb = cfg.timeback;
183
+ if (!tb.course) {
184
+ throw new Error("timeback.course is required for TimeBack integration");
185
+ }
186
+ if (typeof tb.course !== "object") {
187
+ throw new Error("timeback.course must be an object");
188
+ }
189
+ const course = tb.course;
190
+ if (!course.subjects || !Array.isArray(course.subjects) || course.subjects.length === 0) {
191
+ throw new Error("timeback.course.subjects is required (array of subjects)");
192
+ }
193
+ if (!course.grades || !Array.isArray(course.grades) || course.grades.length === 0) {
194
+ throw new Error("timeback.course.grades is required (array of grade levels)");
195
+ }
196
+ }
197
+ }
198
+ var init_config_loader = __esm(() => {
199
+ init_file_loader();
200
+ });
201
+
1
202
  // src/core/guards.ts
2
203
  var VALID_GRADES = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
3
204
  var VALID_SUBJECTS = [
@@ -211,207 +412,27 @@ async function makeApiRequest(baseUrl, apiToken, endpoint, method = "GET", body)
211
412
  return await response.json();
212
413
  }
213
414
 
214
- // src/server/utils/config-loader.ts
215
- import { resolve as resolve2 } from "path";
216
-
217
- // ../utils/src/file-loader.ts
218
- import { existsSync, readdirSync, statSync } from "fs";
219
- import { readFile } from "fs/promises";
220
- import { dirname, parse, resolve } from "path";
221
- function findFilePath(filename, startDir, maxLevels = 3) {
222
- const filenames = Array.isArray(filename) ? filename : [filename];
223
- let currentDir = resolve(startDir);
224
- let levelsSearched = 0;
225
- while (levelsSearched <= maxLevels) {
226
- for (const fname of filenames) {
227
- const filePath = resolve(currentDir, fname);
228
- if (existsSync(filePath)) {
229
- return {
230
- path: filePath,
231
- dir: currentDir,
232
- filename: fname
233
- };
234
- }
235
- }
236
- if (levelsSearched >= maxLevels) {
237
- break;
238
- }
239
- const parentDir = dirname(currentDir);
240
- if (parentDir === currentDir) {
241
- break;
242
- }
243
- const parsed = parse(currentDir);
244
- if (parsed.root === currentDir) {
245
- break;
246
- }
247
- currentDir = parentDir;
248
- levelsSearched++;
249
- }
250
- return null;
251
- }
252
- async function loadFile(filename, options = {}) {
253
- const {
254
- cwd = process.cwd(),
255
- required = false,
256
- searchUp = false,
257
- maxLevels = 3,
258
- parseJson = false,
259
- stripComments = false
260
- } = options;
261
- let fileResult;
262
- if (searchUp) {
263
- fileResult = findFilePath(filename, cwd, maxLevels);
264
- } else {
265
- const filenames = Array.isArray(filename) ? filename : [filename];
266
- fileResult = null;
267
- for (const fname of filenames) {
268
- const filePath = resolve(cwd, fname);
269
- if (existsSync(filePath)) {
270
- fileResult = {
271
- path: filePath,
272
- dir: cwd,
273
- filename: fname
274
- };
275
- break;
276
- }
277
- }
278
- }
279
- if (!fileResult) {
280
- if (required) {
281
- const fileList = Array.isArray(filename) ? filename.join(" or ") : filename;
282
- const message = searchUp ? `${fileList} not found in ${cwd} or up to ${maxLevels} parent directories` : `${fileList} not found at ${cwd}`;
283
- throw new Error(message);
284
- }
285
- return null;
286
- }
287
- try {
288
- let content = await readFile(fileResult.path, "utf8");
289
- if (parseJson) {
290
- if (stripComments) {
291
- content = stripJsonComments(content);
292
- }
293
- return JSON.parse(content);
294
- }
295
- return content;
296
- } catch (error) {
297
- throw new Error(`Failed to load ${fileResult.filename} from ${fileResult.path}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
298
- }
299
- }
300
- async function findFile(filename, options = {}) {
301
- const { cwd = process.cwd(), searchUp = false, maxLevels = 3 } = options;
302
- if (searchUp) {
303
- return findFilePath(filename, cwd, maxLevels);
304
- }
305
- const filenames = Array.isArray(filename) ? filename : [filename];
306
- for (const fname of filenames) {
307
- const filePath = resolve(cwd, fname);
308
- if (existsSync(filePath)) {
309
- return {
310
- path: filePath,
311
- dir: cwd,
312
- filename: fname
313
- };
314
- }
315
- }
316
- return null;
317
- }
318
- function stripJsonComments(jsonc) {
319
- let result = jsonc.replace(/\/\*[\s\S]*?\*\//g, "");
320
- result = result.replace(/\/\/.*/g, "");
321
- return result;
322
- }
323
-
324
- // src/server/utils/config-loader.ts
325
- async function findConfigPath(configPath) {
326
- if (configPath) {
327
- return resolve2(configPath);
328
- }
329
- const result = await findFile(["playcademy.config.js", "playcademy.config.mjs", "playcademy.config.json"], {
330
- searchUp: true,
331
- maxLevels: 3
332
- });
333
- if (!result) {
334
- throw new Error("playcademy.config.js not found. Please create a playcademy.config.js file or specify the config path.");
335
- }
336
- return result.path;
337
- }
338
- async function loadConfig(configPath) {
339
- try {
340
- let config;
341
- let actualPath;
342
- if (configPath) {
343
- actualPath = resolve2(configPath);
344
- if (actualPath.endsWith(".json")) {
345
- config = await loadFile(actualPath, { required: true, parseJson: true });
346
- } else {
347
- const module = await import(actualPath);
348
- config = module.default || module;
349
- }
350
- } else {
351
- actualPath = await findConfigPath();
352
- if (actualPath.endsWith(".json")) {
353
- config = await loadFile(actualPath, { required: true, parseJson: true });
354
- } else {
355
- const module = await import(actualPath);
356
- config = module.default || module;
357
- }
358
- }
359
- if (!config || typeof config !== "object") {
360
- throw new Error("Config file must export/contain an object");
361
- }
362
- validateConfig(config);
363
- return config;
364
- } catch (error) {
365
- throw new Error(`Failed to load config file: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
366
- }
367
- }
368
- function validateConfig(config) {
369
- if (!config || typeof config !== "object") {
370
- throw new Error("Configuration must be an object");
371
- }
372
- const cfg = config;
373
- if (!cfg.name || typeof cfg.name !== "string") {
374
- throw new Error("Missing required field: name");
375
- }
376
- if (cfg.timeback) {
377
- if (typeof cfg.timeback !== "object") {
378
- throw new Error("timeback must be an object if provided");
379
- }
380
- const tb = cfg.timeback;
381
- if (!tb.course) {
382
- throw new Error("timeback.course is required for TimeBack integration");
383
- }
384
- if (typeof tb.course !== "object") {
385
- throw new Error("timeback.course must be an object");
386
- }
387
- const course = tb.course;
388
- if (!course.subjects || !Array.isArray(course.subjects) || course.subjects.length === 0) {
389
- throw new Error("timeback.course.subjects is required (array of subjects)");
390
- }
391
- if (!course.grades || !Array.isArray(course.grades) || course.grades.length === 0) {
392
- throw new Error("timeback.course.grades is required (array of grade levels)");
393
- }
394
- }
395
- }
396
-
397
- // src/server/client.ts
415
+ // src/server/client-base.ts
398
416
  class PlaycademyClient {
399
417
  state;
400
418
  constructor(state) {
401
419
  this.state = state;
402
420
  }
403
421
  static async init(config) {
404
- const { apiKey, configPath, baseUrl, gameId } = config;
422
+ const { apiKey, baseUrl, gameId } = config;
405
423
  if (!apiKey || typeof apiKey !== "string") {
406
424
  throw new Error("[Playcademy SDK] apiKey is required");
407
425
  }
408
- const finalBaseUrl = baseUrl || process.env.PLAYCADEMY_BASE_URL || "https://hub.playcademy.net";
409
- const loadedConfig = config.config || await loadConfig(configPath);
426
+ if (!config.config) {
427
+ throw new Error("[Playcademy SDK] config is required in edge environments. " + "Pass config directly, or use PlaycademyClient from @playcademy/sdk/server " + "for filesystem-based config loading.");
428
+ }
429
+ const envBaseUrl = typeof process !== "undefined" ? process.env?.PLAYCADEMY_BASE_URL : undefined;
430
+ const finalBaseUrl = baseUrl || envBaseUrl || "https://hub.playcademy.net";
410
431
  const state = {
411
432
  apiKey,
412
433
  baseUrl: finalBaseUrl,
413
434
  gameId: gameId || "",
414
- config: loadedConfig
435
+ config: config.config
415
436
  };
416
437
  const client = new PlaycademyClient(state);
417
438
  if (!gameId) {
@@ -433,6 +454,18 @@ class PlaycademyClient {
433
454
  }
434
455
  timeback = createTimebackNamespace(this);
435
456
  }
457
+
458
+ // src/server/client.ts
459
+ class PlaycademyClient2 extends PlaycademyClient {
460
+ static async init(config) {
461
+ if (config.config) {
462
+ return super.init(config);
463
+ }
464
+ const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config_loader(), exports_config_loader));
465
+ const loadedConfig = await loadConfig2(config.configPath);
466
+ return super.init({ ...config, config: loadedConfig });
467
+ }
468
+ }
436
469
  // src/server/utils/verify-game-token.ts
437
470
  async function verifyGameToken(gameToken, options) {
438
471
  if (!gameToken || typeof gameToken !== "string") {
@@ -487,5 +520,5 @@ Please set the PLAYCADEMY_BASE_URL environment variable`);
487
520
  }
488
521
  export {
489
522
  verifyGameToken,
490
- PlaycademyClient
523
+ PlaycademyClient2 as PlaycademyClient
491
524
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcademy/sdk",
3
- "version": "0.3.7-beta.1",
3
+ "version": "0.3.7-beta.2",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -23,6 +23,11 @@
23
23
  "import": "./dist/internal.js",
24
24
  "require": "./dist/internal.js"
25
25
  },
26
+ "./server/edge": {
27
+ "types": "./dist/server/edge.d.ts",
28
+ "import": "./dist/server/edge.js",
29
+ "require": "./dist/server/edge.js"
30
+ },
26
31
  "./test": {
27
32
  "import": "./src/test.ts",
28
33
  "types": "./src/test.ts"