@playcademy/sandbox 0.7.1-beta.6 → 0.7.1-beta.7

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.
Files changed (3) hide show
  1. package/dist/cli.js +83 -25
  2. package/dist/server.js +83 -25
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1118,7 +1118,7 @@ var package_default;
1118
1118
  var init_package = __esm(() => {
1119
1119
  package_default = {
1120
1120
  name: "@playcademy/sandbox",
1121
- version: "0.7.1-beta.6",
1121
+ version: "0.7.1-beta.7",
1122
1122
  description: "Local development server for Playcademy game development",
1123
1123
  type: "module",
1124
1124
  exports: {
@@ -32690,6 +32690,10 @@ var init_assessment_runtime_lock_util = __esm(() => {
32690
32690
  });
32691
32691
 
32692
32692
  // ../api-core/src/services/bucket.service.ts
32693
+ function hasPagingOptions(options) {
32694
+ return options.cursor !== undefined || options.limit !== undefined || options.delimiter !== undefined;
32695
+ }
32696
+
32693
32697
  class BucketService {
32694
32698
  deps;
32695
32699
  constructor(deps) {
@@ -32710,16 +32714,18 @@ class BucketService {
32710
32714
  getBucketName(slug) {
32711
32715
  return getGameDeploymentId(slug, this.deps.sstStage);
32712
32716
  }
32713
- async listFiles(slug, user, prefix) {
32717
+ async listFiles(slug, user, options = {}) {
32714
32718
  const game2 = await this.deps.validateDeveloperAccessBySlug(user, slug);
32715
32719
  const bucketName = this.getBucketName(game2.slug);
32716
- const files = await this.deps.storage.listObjects(bucketName, prefix);
32720
+ const paged = hasPagingOptions(options);
32721
+ const page = paged ? await this.deps.storage.listObjectsPage(bucketName, options) : { files: await this.deps.storage.listObjects(bucketName, options.prefix) };
32717
32722
  setAttributes({
32718
32723
  "app.bucket.operation": "list",
32719
- "app.bucket.file_count": files.length,
32720
- "app.bucket.key_prefix": BucketService.getKeyPrefix(prefix)
32724
+ "app.bucket.file_count": page.files.length,
32725
+ "app.bucket.key_prefix": BucketService.getKeyPrefix(options.prefix),
32726
+ "app.bucket.paged": paged
32721
32727
  });
32722
- return files;
32728
+ return page;
32723
32729
  }
32724
32730
  async getFile(slug, key, user) {
32725
32731
  const game2 = await this.deps.validateDeveloperAccessBySlug(user, slug);
@@ -95256,6 +95262,13 @@ var init_cache_provider = __esm(() => {
95256
95262
  cache = new Map;
95257
95263
  });
95258
95264
 
95265
+ // ../utils/src/keys.ts
95266
+ function keyLevelEntry(key, prefix, delimiter) {
95267
+ const remainder = key.slice(prefix.length);
95268
+ const cut = remainder.indexOf(delimiter);
95269
+ return cut === -1 ? { name: key, rolled: false } : { name: prefix + remainder.slice(0, cut + delimiter.length), rolled: true };
95270
+ }
95271
+
95259
95272
  // src/infrastructure/api/providers/storage.provider.ts
95260
95273
  function getBucket(bucketName) {
95261
95274
  let bucket = storage.get(bucketName);
@@ -95269,18 +95282,57 @@ function createSandboxStorageProvider() {
95269
95282
  return {
95270
95283
  async listObjects(bucketName, prefix) {
95271
95284
  const bucket = getBucket(bucketName);
95285
+ const keys = [...bucket.keys()].filter((key) => !prefix || key.startsWith(prefix)).toSorted();
95286
+ return keys.map((key) => {
95287
+ const data = bucket.get(key);
95288
+ return {
95289
+ key,
95290
+ size: data.body.length,
95291
+ lastModified: new Date().toISOString(),
95292
+ contentType: data.contentType
95293
+ };
95294
+ });
95295
+ },
95296
+ async listObjectsPage(bucketName, options) {
95297
+ const { cursor: cursor2, delimiter } = options;
95298
+ const all = await this.listObjects(bucketName, options.prefix);
95299
+ let entries;
95300
+ if (delimiter) {
95301
+ const requestPrefix = options.prefix ?? "";
95302
+ const seen = new Set;
95303
+ entries = [];
95304
+ for (const file3 of all) {
95305
+ const entry = keyLevelEntry(file3.key, requestPrefix, delimiter);
95306
+ if (!entry.rolled) {
95307
+ entries.push({ name: entry.name, file: file3 });
95308
+ } else if (!seen.has(entry.name)) {
95309
+ seen.add(entry.name);
95310
+ entries.push({ name: entry.name });
95311
+ }
95312
+ }
95313
+ } else {
95314
+ entries = all.map((file3) => ({ name: file3.key, file: file3 }));
95315
+ }
95316
+ const remaining = cursor2 ? entries.filter((entry) => entry.name > cursor2) : entries;
95317
+ const limit = options.limit ?? 1000;
95318
+ const pageEntries = remaining.slice(0, limit);
95272
95319
  const files = [];
95273
- for (const [key, data] of bucket.entries()) {
95274
- if (!prefix || key.startsWith(prefix)) {
95275
- files.push({
95276
- key,
95277
- size: data.body.length,
95278
- lastModified: new Date().toISOString(),
95279
- contentType: data.contentType
95280
- });
95320
+ const prefixes = [];
95321
+ for (const entry of pageEntries) {
95322
+ if (entry.file) {
95323
+ files.push(entry.file);
95324
+ } else {
95325
+ prefixes.push(entry.name);
95281
95326
  }
95282
95327
  }
95283
- return files;
95328
+ const page = { files };
95329
+ if (prefixes.length > 0) {
95330
+ page.prefixes = prefixes;
95331
+ }
95332
+ if (remaining.length > limit) {
95333
+ page.cursor = pageEntries[pageEntries.length - 1].name;
95334
+ }
95335
+ return page;
95284
95336
  },
95285
95337
  async getObject(bucketName, key) {
95286
95338
  const bucket = getBucket(bucketName);
@@ -151126,6 +151178,17 @@ function requireUserId(userId) {
151126
151178
  }
151127
151179
  return userId;
151128
151180
  }
151181
+ function parseLimitParam(url4, max2) {
151182
+ const raw2 = url4.searchParams.get("limit");
151183
+ if (raw2 === null) {
151184
+ return;
151185
+ }
151186
+ const limit = Number(raw2);
151187
+ if (!Number.isInteger(limit) || limit < 1 || limit > max2) {
151188
+ throw ApiError.badRequest(`limit must be an integer between 1 and ${max2}`);
151189
+ }
151190
+ return limit;
151191
+ }
151129
151192
  var init_params_util = __esm(() => {
151130
151193
  init_src5();
151131
151194
  init_errors2();
@@ -151197,8 +151260,10 @@ var init_bucket_controller = __esm(() => {
151197
151260
  }
151198
151261
  const url4 = ctx.url;
151199
151262
  const prefix2 = url4.searchParams.get("prefix") || undefined;
151200
- const files = await ctx.services.bucket.listFiles(slug2, ctx.user, prefix2);
151201
- return { files };
151263
+ const cursor2 = url4.searchParams.get("cursor") || undefined;
151264
+ const delimiter = url4.searchParams.get("delimiter") || undefined;
151265
+ const limit = parseLimitParam(url4, 1000);
151266
+ return ctx.services.bucket.listFiles(slug2, ctx.user, { prefix: prefix2, cursor: cursor2, limit, delimiter });
151202
151267
  });
151203
151268
  getFile = requireDeveloper(async (ctx) => {
151204
151269
  const slug2 = ctx.params.slug;
@@ -151463,14 +151528,7 @@ var init_deployment_state_controller = __esm(() => {
151463
151528
  });
151464
151529
  history = requireDeveloper(async (ctx) => {
151465
151530
  const slug2 = requireSlug(ctx.params.slug);
151466
- const limitParam = ctx.url.searchParams.get("limit");
151467
- let limit;
151468
- if (limitParam !== null) {
151469
- limit = Number(limitParam);
151470
- if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
151471
- throw ApiError.badRequest("limit must be an integer between 1 and 100");
151472
- }
151473
- }
151531
+ const limit = parseLimitParam(ctx.url, 100);
151474
151532
  return ctx.services.deploymentState.history(slug2, ctx.user, { limit });
151475
151533
  });
151476
151534
  restorePoints = requireDeveloper(async (ctx) => {
package/dist/server.js CHANGED
@@ -1117,7 +1117,7 @@ var package_default;
1117
1117
  var init_package = __esm(() => {
1118
1118
  package_default = {
1119
1119
  name: "@playcademy/sandbox",
1120
- version: "0.7.1-beta.6",
1120
+ version: "0.7.1-beta.7",
1121
1121
  description: "Local development server for Playcademy game development",
1122
1122
  type: "module",
1123
1123
  exports: {
@@ -32689,6 +32689,10 @@ var init_assessment_runtime_lock_util = __esm(() => {
32689
32689
  });
32690
32690
 
32691
32691
  // ../api-core/src/services/bucket.service.ts
32692
+ function hasPagingOptions(options) {
32693
+ return options.cursor !== undefined || options.limit !== undefined || options.delimiter !== undefined;
32694
+ }
32695
+
32692
32696
  class BucketService {
32693
32697
  deps;
32694
32698
  constructor(deps) {
@@ -32709,16 +32713,18 @@ class BucketService {
32709
32713
  getBucketName(slug) {
32710
32714
  return getGameDeploymentId(slug, this.deps.sstStage);
32711
32715
  }
32712
- async listFiles(slug, user, prefix) {
32716
+ async listFiles(slug, user, options = {}) {
32713
32717
  const game2 = await this.deps.validateDeveloperAccessBySlug(user, slug);
32714
32718
  const bucketName = this.getBucketName(game2.slug);
32715
- const files = await this.deps.storage.listObjects(bucketName, prefix);
32719
+ const paged = hasPagingOptions(options);
32720
+ const page = paged ? await this.deps.storage.listObjectsPage(bucketName, options) : { files: await this.deps.storage.listObjects(bucketName, options.prefix) };
32716
32721
  setAttributes({
32717
32722
  "app.bucket.operation": "list",
32718
- "app.bucket.file_count": files.length,
32719
- "app.bucket.key_prefix": BucketService.getKeyPrefix(prefix)
32723
+ "app.bucket.file_count": page.files.length,
32724
+ "app.bucket.key_prefix": BucketService.getKeyPrefix(options.prefix),
32725
+ "app.bucket.paged": paged
32720
32726
  });
32721
- return files;
32727
+ return page;
32722
32728
  }
32723
32729
  async getFile(slug, key, user) {
32724
32730
  const game2 = await this.deps.validateDeveloperAccessBySlug(user, slug);
@@ -95255,6 +95261,13 @@ var init_cache_provider = __esm(() => {
95255
95261
  cache = new Map;
95256
95262
  });
95257
95263
 
95264
+ // ../utils/src/keys.ts
95265
+ function keyLevelEntry(key, prefix, delimiter) {
95266
+ const remainder = key.slice(prefix.length);
95267
+ const cut = remainder.indexOf(delimiter);
95268
+ return cut === -1 ? { name: key, rolled: false } : { name: prefix + remainder.slice(0, cut + delimiter.length), rolled: true };
95269
+ }
95270
+
95258
95271
  // src/infrastructure/api/providers/storage.provider.ts
95259
95272
  function getBucket(bucketName) {
95260
95273
  let bucket = storage.get(bucketName);
@@ -95268,18 +95281,57 @@ function createSandboxStorageProvider() {
95268
95281
  return {
95269
95282
  async listObjects(bucketName, prefix) {
95270
95283
  const bucket = getBucket(bucketName);
95284
+ const keys = [...bucket.keys()].filter((key) => !prefix || key.startsWith(prefix)).toSorted();
95285
+ return keys.map((key) => {
95286
+ const data = bucket.get(key);
95287
+ return {
95288
+ key,
95289
+ size: data.body.length,
95290
+ lastModified: new Date().toISOString(),
95291
+ contentType: data.contentType
95292
+ };
95293
+ });
95294
+ },
95295
+ async listObjectsPage(bucketName, options) {
95296
+ const { cursor: cursor2, delimiter } = options;
95297
+ const all = await this.listObjects(bucketName, options.prefix);
95298
+ let entries;
95299
+ if (delimiter) {
95300
+ const requestPrefix = options.prefix ?? "";
95301
+ const seen = new Set;
95302
+ entries = [];
95303
+ for (const file3 of all) {
95304
+ const entry = keyLevelEntry(file3.key, requestPrefix, delimiter);
95305
+ if (!entry.rolled) {
95306
+ entries.push({ name: entry.name, file: file3 });
95307
+ } else if (!seen.has(entry.name)) {
95308
+ seen.add(entry.name);
95309
+ entries.push({ name: entry.name });
95310
+ }
95311
+ }
95312
+ } else {
95313
+ entries = all.map((file3) => ({ name: file3.key, file: file3 }));
95314
+ }
95315
+ const remaining = cursor2 ? entries.filter((entry) => entry.name > cursor2) : entries;
95316
+ const limit = options.limit ?? 1000;
95317
+ const pageEntries = remaining.slice(0, limit);
95271
95318
  const files = [];
95272
- for (const [key, data] of bucket.entries()) {
95273
- if (!prefix || key.startsWith(prefix)) {
95274
- files.push({
95275
- key,
95276
- size: data.body.length,
95277
- lastModified: new Date().toISOString(),
95278
- contentType: data.contentType
95279
- });
95319
+ const prefixes = [];
95320
+ for (const entry of pageEntries) {
95321
+ if (entry.file) {
95322
+ files.push(entry.file);
95323
+ } else {
95324
+ prefixes.push(entry.name);
95280
95325
  }
95281
95326
  }
95282
- return files;
95327
+ const page = { files };
95328
+ if (prefixes.length > 0) {
95329
+ page.prefixes = prefixes;
95330
+ }
95331
+ if (remaining.length > limit) {
95332
+ page.cursor = pageEntries[pageEntries.length - 1].name;
95333
+ }
95334
+ return page;
95283
95335
  },
95284
95336
  async getObject(bucketName, key) {
95285
95337
  const bucket = getBucket(bucketName);
@@ -151125,6 +151177,17 @@ function requireUserId(userId) {
151125
151177
  }
151126
151178
  return userId;
151127
151179
  }
151180
+ function parseLimitParam(url4, max2) {
151181
+ const raw2 = url4.searchParams.get("limit");
151182
+ if (raw2 === null) {
151183
+ return;
151184
+ }
151185
+ const limit = Number(raw2);
151186
+ if (!Number.isInteger(limit) || limit < 1 || limit > max2) {
151187
+ throw ApiError.badRequest(`limit must be an integer between 1 and ${max2}`);
151188
+ }
151189
+ return limit;
151190
+ }
151128
151191
  var init_params_util = __esm(() => {
151129
151192
  init_src5();
151130
151193
  init_errors2();
@@ -151196,8 +151259,10 @@ var init_bucket_controller = __esm(() => {
151196
151259
  }
151197
151260
  const url4 = ctx.url;
151198
151261
  const prefix2 = url4.searchParams.get("prefix") || undefined;
151199
- const files = await ctx.services.bucket.listFiles(slug2, ctx.user, prefix2);
151200
- return { files };
151262
+ const cursor2 = url4.searchParams.get("cursor") || undefined;
151263
+ const delimiter = url4.searchParams.get("delimiter") || undefined;
151264
+ const limit = parseLimitParam(url4, 1000);
151265
+ return ctx.services.bucket.listFiles(slug2, ctx.user, { prefix: prefix2, cursor: cursor2, limit, delimiter });
151201
151266
  });
151202
151267
  getFile = requireDeveloper(async (ctx) => {
151203
151268
  const slug2 = ctx.params.slug;
@@ -151462,14 +151527,7 @@ var init_deployment_state_controller = __esm(() => {
151462
151527
  });
151463
151528
  history = requireDeveloper(async (ctx) => {
151464
151529
  const slug2 = requireSlug(ctx.params.slug);
151465
- const limitParam = ctx.url.searchParams.get("limit");
151466
- let limit;
151467
- if (limitParam !== null) {
151468
- limit = Number(limitParam);
151469
- if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
151470
- throw ApiError.badRequest("limit must be an integer between 1 and 100");
151471
- }
151472
- }
151530
+ const limit = parseLimitParam(ctx.url, 100);
151473
151531
  return ctx.services.deploymentState.history(slug2, ctx.user, { limit });
151474
151532
  });
151475
151533
  restorePoints = requireDeveloper(async (ctx) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcademy/sandbox",
3
- "version": "0.7.1-beta.6",
3
+ "version": "0.7.1-beta.7",
4
4
  "description": "Local development server for Playcademy game development",
5
5
  "type": "module",
6
6
  "exports": {