@kubohiroya/turbowarp-asset-cache 0.3.2 → 0.4.0

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.
@@ -1,3 +1,20 @@
1
+ //#region src/config.ts
2
+ var extensionConfig = {
3
+ id: "kubohiroyaassetcache",
4
+ slug: "asset-cache",
5
+ name: "Asset Cache",
6
+ description: "Register, cache, display, and play image, audio, and runtime text assets in TurboWarp.",
7
+ descriptions: { ja: "TurboWarpで画像・音声・実行時テキストの素材を登録・保持し、表示・再生します。" },
8
+ author: "Hiroya Kubo",
9
+ license: "MPL-2.0",
10
+ unsandboxed: "required",
11
+ docsURI: "https://kubohiroya.github.io/turbowarp-asset-cache/",
12
+ color1: "#5b7cfa",
13
+ color2: "#425ed8",
14
+ color3: "#2f46aa",
15
+ blockIconURI: `data:image/svg+xml,${encodeURIComponent("<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 48 48\"><rect x=\"4\" y=\"8\" width=\"40\" height=\"32\" rx=\"6\" fill=\"#59C059\"/><path d=\"M14 20h12l3 3h7v12a2 2 0 0 1-2 2H14a2 2 0 0 1-2-2V22a2 2 0 0 1 2-2z\" fill=\"#fff\"/><path d=\"M24 17v8M21 22l3 3 3-3\" fill=\"none\" stroke=\"#59C059\" stroke-width=\"2.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>")}`
16
+ };
17
+ //#endregion
1
18
  //#region src/audio-output.ts
2
19
  /** Media volume handles attenuation and fades; this shared graph adds amplification. */
3
20
  var shared;
@@ -693,7 +710,7 @@ var block_definitions_default = {
693
710
  ]
694
711
  };
695
712
  //#endregion
696
- //#region node_modules/.pnpm/@kubohiroya+turbowarp-extension-data-editor@0.2.1_react@18.3.1/node_modules/@kubohiroya/turbowarp-extension-data-editor/dist/fixed-input-schema.js
713
+ //#region node_modules/.pnpm/@kubohiroya+turbowarp-extension-data-editor@0.3.4_react@18.3.1/node_modules/@kubohiroya/turbowarp-extension-data-editor/dist/fixed-input-schema.js
697
714
  var numberToken = /^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?$/u;
698
715
  var invalidNumber = Object.freeze({
699
716
  kind: "invalid",
@@ -1090,7 +1107,7 @@ function fixedInputRegistration(scratch) {
1090
1107
  };
1091
1108
  }
1092
1109
  //#endregion
1093
- //#region node_modules/.pnpm/@kubohiroya+turbowarp-named-data@0.3.1/node_modules/@kubohiroya/turbowarp-named-data/dist/composition.js
1110
+ //#region node_modules/.pnpm/@kubohiroya+turbowarp-named-data@0.5.0/node_modules/@kubohiroya/turbowarp-named-data/dist/composition.js
1094
1111
  var NAMED_DATA_REGISTRY_SYMBOL_KEY = "@kubohiroya/turbowarp-named-data/registry";
1095
1112
  var NAMED_DATA_REGISTRY_SYMBOL = Symbol.for(NAMED_DATA_REGISTRY_SYMBOL_KEY);
1096
1113
  var NAMED_DATA_NAMESPACE_PATTERN = /^[a-z][a-z0-9.-]{0,63}$/u;
@@ -1181,7 +1198,7 @@ var NamedDataRegistry = class {
1181
1198
  const provider = this.resolveProvider(reference, representation, context);
1182
1199
  throwIfAborted(context.signal);
1183
1200
  try {
1184
- const metadata = await provider.stat(reference, representation, context);
1201
+ const metadata = snapshotMetadata(await provider.stat(reference, representation, context));
1185
1202
  throwIfAborted(context.signal);
1186
1203
  validateMetadata(metadata, reference, representation);
1187
1204
  return metadata;
@@ -1193,9 +1210,11 @@ var NamedDataRegistry = class {
1193
1210
  const provider = this.resolveProvider(reference, representation, context);
1194
1211
  throwIfAborted(context.signal);
1195
1212
  let opened;
1213
+ let metadata;
1196
1214
  try {
1197
1215
  opened = await provider.openBody(reference, representation, context);
1198
- validateMetadata(opened, reference, representation);
1216
+ metadata = snapshotMetadata(opened);
1217
+ validateMetadata(metadata, reference, representation);
1199
1218
  if (!(opened.body instanceof Uint8Array) && !(opened.body instanceof ReadableStream)) throw new NamedDataError("NAMED_DATA_INVALID_METADATA", "Provider returned an invalid body.");
1200
1219
  } catch (error) {
1201
1220
  throw normalizeProviderError(error);
@@ -1229,14 +1248,7 @@ var NamedDataRegistry = class {
1229
1248
  }
1230
1249
  }
1231
1250
  return Object.freeze({
1232
- reference: Object.freeze({ ...opened.reference }),
1233
- nativeRepresentation: opened.nativeRepresentation,
1234
- representation: opened.representation,
1235
- mediaType: opened.mediaType,
1236
- ...opened.byteLength === void 0 ? {} : { byteLength: opened.byteLength },
1237
- ...opened.digest === void 0 ? {} : { digest: opened.digest },
1238
- revision: opened.revision,
1239
- replayable: opened.replayable,
1251
+ ...metadata,
1240
1252
  body: opened.body,
1241
1253
  release: tracked.release
1242
1254
  });
@@ -1347,15 +1359,61 @@ function validateReferenceShape(reference, representation) {
1347
1359
  if (reference.schemaRef !== void 0) validateSchemaRef(reference.kind, reference.schemaRef);
1348
1360
  }
1349
1361
  function validateMetadata(metadata, reference, representation) {
1350
- if (metadata.representation !== representation || !isNativeRepresentation(metadata.reference.kind, metadata.nativeRepresentation) || metadata.reference.namespace !== reference.namespace || metadata.reference.name !== reference.name || metadata.reference.kind !== reference.kind || metadata.reference.scope !== reference.scope || typeof metadata.mediaType !== "string" || metadata.mediaType.length === 0 || typeof metadata.revision !== "string" || metadata.revision.length === 0 || typeof metadata.replayable !== "boolean" || metadata.byteLength !== void 0 && (!Number.isSafeInteger(metadata.byteLength) || metadata.byteLength < 0) || metadata.digest !== void 0 && !/^sha256-[A-Za-z0-9_-]+$/u.test(metadata.digest)) throw new NamedDataError("NAMED_DATA_INVALID_METADATA", "Provider returned invalid metadata.");
1362
+ if (metadata.representation !== representation || !isNativeRepresentation(metadata.reference.kind, metadata.nativeRepresentation) || metadata.reference.namespace !== reference.namespace || metadata.reference.name !== reference.name || metadata.reference.kind !== reference.kind || metadata.reference.scope !== reference.scope || typeof metadata.mediaType !== "string" || metadata.mediaType.length === 0 || typeof metadata.revision !== "string" || metadata.revision.length === 0 || typeof metadata.replayable !== "boolean" || metadata.byteLength !== void 0 && (!Number.isSafeInteger(metadata.byteLength) || metadata.byteLength < 0) || metadata.digest !== void 0 && !/^sha256-[A-Za-z0-9_-]+$/u.test(metadata.digest)) throw invalidMetadata();
1351
1363
  const expectedSchema = reference.schemaRef;
1352
1364
  const actualSchema = metadata.schemaRef;
1353
1365
  if (actualSchema !== void 0) validateSchemaRef(metadata.reference.kind, actualSchema, "NAMED_DATA_INVALID_METADATA");
1354
1366
  if (metadata.reference.schemaRef !== void 0) validateSchemaRef(metadata.reference.kind, metadata.reference.schemaRef, "NAMED_DATA_INVALID_METADATA");
1355
1367
  if (!sameSchemaRef(expectedSchema, actualSchema) || !sameSchemaRef(expectedSchema, metadata.reference.schemaRef)) throw new NamedDataError("NAMED_DATA_SCHEMA_VERSION_MISMATCH", "Provider schemaRef does not match the requested reference.");
1356
1368
  }
1369
+ /**
1370
+ * Copies provider metadata once so validation and consumers observe the same frozen identity.
1371
+ * Fields are read exactly once; payload bodies are never part of the snapshot.
1372
+ */
1373
+ function snapshotMetadata(metadata) {
1374
+ if (typeof metadata !== "object" || metadata === null) throw invalidMetadata();
1375
+ const source = metadata.reference;
1376
+ if (typeof source !== "object" || source === null) throw invalidMetadata();
1377
+ const { namespace, name, kind, scope, schemaRef: referenceSchemaRef } = source;
1378
+ const { nativeRepresentation, representation, mediaType, byteLength, digest, revision, replayable, schemaRef } = metadata;
1379
+ const reference = snapshotSchemaRef(referenceSchemaRef);
1380
+ const resolved = snapshotSchemaRef(schemaRef);
1381
+ return Object.freeze({
1382
+ reference: Object.freeze({
1383
+ namespace,
1384
+ name,
1385
+ kind,
1386
+ scope,
1387
+ ...reference === void 0 ? {} : { schemaRef: reference }
1388
+ }),
1389
+ nativeRepresentation,
1390
+ representation,
1391
+ mediaType,
1392
+ ...byteLength === void 0 ? {} : { byteLength },
1393
+ ...digest === void 0 ? {} : { digest },
1394
+ revision,
1395
+ replayable,
1396
+ ...resolved === void 0 ? {} : { schemaRef: resolved }
1397
+ });
1398
+ }
1399
+ function snapshotSchemaRef(schemaRef) {
1400
+ if (schemaRef === void 0) return void 0;
1401
+ if (typeof schemaRef !== "object" || schemaRef === null || Object.keys(schemaRef).some((key) => key !== "id" && key !== "version")) throw invalidSchemaRef("NAMED_DATA_INVALID_METADATA");
1402
+ const { id, version } = schemaRef;
1403
+ if (typeof id !== "string" || typeof version !== "string") throw invalidSchemaRef("NAMED_DATA_INVALID_METADATA");
1404
+ return Object.freeze({
1405
+ id,
1406
+ version
1407
+ });
1408
+ }
1357
1409
  function validateSchemaRef(kind, schemaRef, errorCode = "NAMED_DATA_INVALID_REF") {
1358
- if (kind !== "structured" || typeof schemaRef !== "object" || schemaRef === null || !NAMED_DATA_SCHEMA_ID_PATTERN.test(String(schemaRef.id ?? "")) || !NAMED_DATA_SCHEMA_VERSION_PATTERN.test(String(schemaRef.version ?? "")) || Object.keys(schemaRef).some((key) => key !== "id" && key !== "version")) throw new NamedDataError(errorCode, "Invalid schemaRef; schema references require a structured value, stable id, and full SemVer version.");
1410
+ if (kind !== "structured" || typeof schemaRef !== "object" || schemaRef === null || !NAMED_DATA_SCHEMA_ID_PATTERN.test(String(schemaRef.id ?? "")) || !NAMED_DATA_SCHEMA_VERSION_PATTERN.test(String(schemaRef.version ?? "")) || Object.keys(schemaRef).some((key) => key !== "id" && key !== "version")) throw invalidSchemaRef(errorCode);
1411
+ }
1412
+ function invalidSchemaRef(errorCode) {
1413
+ return new NamedDataError(errorCode, "Invalid schemaRef; schema references require a structured value, stable id, and full SemVer version.");
1414
+ }
1415
+ function invalidMetadata() {
1416
+ return new NamedDataError("NAMED_DATA_INVALID_METADATA", "Provider returned invalid metadata.");
1359
1417
  }
1360
1418
  function sameSchemaRef(left, right) {
1361
1419
  return left === void 0 ? right === void 0 : right !== void 0 && left.id === right.id && left.version === right.version;
@@ -1781,10 +1839,10 @@ function throwIfNamedBodyAborted(signal) {
1781
1839
  }
1782
1840
  //#endregion
1783
1841
  //#region src/extension.ts
1784
- var EXTENSION_ID = "kubohiroyaassetcache";
1842
+ var EXTENSION_ID = extensionConfig.id;
1785
1843
  var EXTENSION_VERSION = "0.14.0";
1786
- var EXTENSION_DOCS_URI = "https://kubohiroya.github.io/turbowarp-asset-cache/";
1787
- var BLOCK_ICON_URI = `data:image/svg+xml,${encodeURIComponent("<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 64 64\"><path fill=\"#fff\" d=\"M19 47 29 17h7l10 30h-7l-2-7H27l-2 7h-6Zm10-13h6l-3-10-3 10Z\"/></svg>")}`;
1844
+ var EXTENSION_DOCS_URI = extensionConfig.docsURI;
1845
+ var BLOCK_ICON_URI = extensionConfig.blockIconURI;
1788
1846
  var DB_NAME = "tw-asset-cache";
1789
1847
  var DB_VERSION = 1;
1790
1848
  var STORE_NAME = "assets";
@@ -2359,9 +2417,9 @@ var AssetManagerExtension = class {
2359
2417
  name: Scratch.translate(block_definitions_default.extensionName),
2360
2418
  docsURI: EXTENSION_DOCS_URI,
2361
2419
  blockIconURI: BLOCK_ICON_URI,
2362
- color1: "#5b7cfa",
2363
- color2: "#425ed8",
2364
- color3: "#2f46aa",
2420
+ color1: extensionConfig.color1,
2421
+ color2: extensionConfig.color2,
2422
+ color3: extensionConfig.color3,
2365
2423
  blocks
2366
2424
  };
2367
2425
  }
@@ -4039,7 +4097,7 @@ function createOpfsBinaryObjectStore(options = {}) {
4039
4097
  async function committedObjectMatches(location, descriptor, signal) {
4040
4098
  assertActive(released, signal);
4041
4099
  try {
4042
- await verify(await readFileBytes(await (await location.directory.getFileHandle(location.name)).getFile(), signal), descriptor, subtleCrypto);
4100
+ await verify(await readCommittedBytes(location, signal, false), descriptor, subtleCrypto);
4043
4101
  assertActive(released, signal);
4044
4102
  return true;
4045
4103
  } catch (error) {
@@ -4049,6 +4107,18 @@ function createOpfsBinaryObjectStore(options = {}) {
4049
4107
  throw error;
4050
4108
  }
4051
4109
  }
4110
+ async function readCommittedBytes(location, signal, retryMissing = true) {
4111
+ for (let attempt = 0;; attempt += 1) {
4112
+ assertActive(released, signal);
4113
+ try {
4114
+ return await readFileBytes(await (await location.directory.getFileHandle(location.name)).getFile(), signal);
4115
+ } catch (error) {
4116
+ assertActive(released, signal);
4117
+ if (!(error instanceof DOMException && (error.name === "NotReadableError" || retryMissing && error.name === "NotFoundError")) || attempt >= 3) throw error;
4118
+ await new Promise((resolve) => setTimeout(resolve, 20 * (attempt + 1)));
4119
+ }
4120
+ }
4121
+ }
4052
4122
  function writableConflict(error) {
4053
4123
  return error instanceof DOMException && (error.name === "InvalidStateError" || error.name === "NoModificationAllowedError");
4054
4124
  }
@@ -4077,13 +4147,12 @@ function createOpfsBinaryObjectStore(options = {}) {
4077
4147
  assertActive(released, operationOptions.signal);
4078
4148
  let retryDelayMs = 20;
4079
4149
  while (!await committedObjectMatches(location, descriptor, operationOptions.signal)) try {
4080
- const finalHandle = await location.directory.getFileHandle(location.name, { create: true });
4081
- writable = await finalHandle.createWritable();
4150
+ writable = await (await location.directory.getFileHandle(location.name, { create: true })).createWritable();
4082
4151
  activeWritables.add(writable);
4083
4152
  await copyFileToWritable(stagedFile, writable, operationOptions.signal);
4084
4153
  activeWritables.delete(writable);
4085
4154
  writable = null;
4086
- await verify(await readFileBytes(await finalHandle.getFile(), operationOptions.signal), descriptor, subtleCrypto);
4155
+ await verify(await readCommittedBytes(location, operationOptions.signal), descriptor, subtleCrypto);
4087
4156
  break;
4088
4157
  } catch (error) {
4089
4158
  if (writable) {
@@ -4111,8 +4180,7 @@ function createOpfsBinaryObjectStore(options = {}) {
4111
4180
  const descriptor = validateDescriptor(descriptorValue);
4112
4181
  assertActive(released, operationOptions.signal);
4113
4182
  try {
4114
- const location = await objectLocation(descriptor.key);
4115
- const bytes = await readFileBytes(await (await location.directory.getFileHandle(location.name)).getFile(), operationOptions.signal);
4183
+ const bytes = await readCommittedBytes(await objectLocation(descriptor.key), operationOptions.signal);
4116
4184
  assertActive(released, operationOptions.signal);
4117
4185
  await verify(bytes, descriptor, subtleCrypto);
4118
4186
  assertActive(released, operationOptions.signal);
@@ -4145,9 +4213,13 @@ function createOpfsBinaryObjectStore(options = {}) {
4145
4213
  for await (const entry of staging.values()) {
4146
4214
  assertActive(released, operationOptions.signal);
4147
4215
  if (entry.kind !== "file") continue;
4148
- if ((await entry.getFile()).lastModified > createdBefore) continue;
4149
- await staging.removeEntry(entry.name);
4150
- removed += 1;
4216
+ try {
4217
+ if ((await entry.getFile()).lastModified > createdBefore) continue;
4218
+ await staging.removeEntry(entry.name);
4219
+ removed += 1;
4220
+ } catch (error) {
4221
+ if (!(error instanceof DOMException && error.name === "NotFoundError")) throw error;
4222
+ }
4151
4223
  if (removed >= limit) break;
4152
4224
  }
4153
4225
  return removed;
@@ -0,0 +1,17 @@
1
+ export declare const extensionConfig: {
2
+ readonly id: "kubohiroyaassetcache";
3
+ readonly slug: "asset-cache";
4
+ readonly name: "Asset Cache";
5
+ readonly description: "Register, cache, display, and play image, audio, and runtime text assets in TurboWarp.";
6
+ readonly descriptions: {
7
+ readonly ja: "TurboWarpで画像・音声・実行時テキストの素材を登録・保持し、表示・再生します。";
8
+ };
9
+ readonly author: "Hiroya Kubo";
10
+ readonly license: "MPL-2.0";
11
+ readonly unsandboxed: "required";
12
+ readonly docsURI: "https://kubohiroya.github.io/turbowarp-asset-cache/";
13
+ readonly color1: "#5b7cfa";
14
+ readonly color2: "#425ed8";
15
+ readonly color3: "#2f46aa";
16
+ readonly blockIconURI: `data:image/svg+xml,${string}`;
17
+ };
@@ -4,10 +4,10 @@ import { type AssetManagerFeatureFlags } from './feature-flags.js';
4
4
  import { type DOMImageResource } from './dom-image-resource.js';
5
5
  import { type AssetManagerAudioVoice, type AssetManagerAudioVoiceOptions } from './audio-voice.js';
6
6
  import { type NamedAssetBodyProvider } from './named-body-provider.js';
7
- export declare const EXTENSION_ID = "kubohiroyaassetcache";
7
+ export declare const EXTENSION_ID: "kubohiroyaassetcache";
8
8
  export declare const EXTENSION_VERSION = "0.14.0";
9
- export declare const EXTENSION_DOCS_URI = "https://kubohiroya.github.io/turbowarp-asset-cache/";
10
- export declare const BLOCK_ICON_URI: string;
9
+ export declare const EXTENSION_DOCS_URI: "https://kubohiroya.github.io/turbowarp-asset-cache/";
10
+ export declare const BLOCK_ICON_URI: `data:image/svg+xml,${string}`;
11
11
  type BlockArgs = Record<string, unknown>;
12
12
  export type AssetKind = 'external' | 'costume' | 'backdrop' | 'sound' | 'text';
13
13
  export interface EmbeddedAssetBytesInput {
@@ -132,13 +132,13 @@ export declare class AssetManagerExtension {
132
132
  loadingBackdrop(): string;
133
133
  loadingCostumeAt(args: BlockArgs): string;
134
134
  getInfo(): {
135
- id: string;
135
+ id: "kubohiroyaassetcache";
136
136
  name: string;
137
- docsURI: string;
138
- blockIconURI: string;
139
- color1: string;
140
- color2: string;
141
- color3: string;
137
+ docsURI: "https://kubohiroya.github.io/turbowarp-asset-cache/";
138
+ blockIconURI: `data:image/svg+xml,${string}`;
139
+ color1: "#5b7cfa";
140
+ color2: "#425ed8";
141
+ color3: "#2f46aa";
142
142
  blocks: Record<string, unknown>[];
143
143
  };
144
144
  /** Release only this optional editor registration; runtime data remains unchanged. */
@@ -1,6 +1,6 @@
1
1
  # 音声instanceの共通controller
2
2
 
3
- 追跡: [Issue #6](https://github.com/kubohiroya/turbowarp-asset-cache/issues/6)。開発中の追加APIで、既存公開版に同名APIがあることを示す文書ではありません。
3
+ 追跡: [Issue #6](https://github.com/kubohiroya/turbowarp-asset-cache/issues/6)。ここで説明するAPIは0.3.0以降の公開版に含まれます。
4
4
 
5
5
  `createAudioPlayback({asset: 'Music'}, {gain: 0.7, muted: false, playbackRate: 1, fadeIn: 0.5, fadeOut: 1.5})` は、停止状態の独立したinstanceを返します。`asset` と `url` のどちらか一つが必須です。直接URLは認証情報を含まないHTTP(S)に限定し、anonymous CORSで読み込みます。別originのサーバーにはAccess-Control-Allow-Originが必要です。これは後からsetGainで増幅する場合も必要となる[Web AudioのCORS仕様](https://webaudio.github.io/web-audio-api/#MediaElementAudioSourceNode-security)に従います。準備だけではnative `play()` を呼びません。有効化フラグは不要です。
6
6
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubohiroya/turbowarp-asset-cache",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "A browser-local image, audio, and runtime-text asset cache for TurboWarp projects.",
5
5
  "author": "Hiroya Kubo <hiroya@cuc.ac.jp>",
6
6
  "type": "module",
@@ -21,6 +21,7 @@
21
21
  "./package.json": "./package.json"
22
22
  },
23
23
  "files": [
24
+ "assets/",
24
25
  "dist/",
25
26
  "schemas/",
26
27
  "docs/fixed-input-editors.ja.md",
@@ -64,10 +65,10 @@
64
65
  },
65
66
  "devDependencies": {
66
67
  "@eslint/js": "^9.39.5",
67
- "@kubohiroya/turbowarp-extension-data-editor": "0.2.1",
68
- "@kubohiroya/turbowarp-extension-manifest": "0.3.2",
69
- "@kubohiroya/turbowarp-named-data": "0.3.1",
70
- "@kubohiroya/vite-plugin-turbowarp-extension": "0.7.0",
68
+ "@kubohiroya/turbowarp-extension-data-editor": "^0.3.4",
69
+ "@kubohiroya/turbowarp-extension-manifest": "^0.3.2",
70
+ "@kubohiroya/turbowarp-named-data": "^0.5.0",
71
+ "@kubohiroya/vite-plugin-turbowarp-extension": "^0.8.0",
71
72
  "@types/node": "^22.20.1",
72
73
  "@xmldom/xmldom": "0.9.11",
73
74
  "eslint": "^9.39.5",
@@ -78,7 +79,7 @@
78
79
  "vitest": "^4.1.10"
79
80
  },
80
81
  "peerDependencies": {
81
- "@kubohiroya/turbowarp-named-data": "0.3.1"
82
+ "@kubohiroya/turbowarp-named-data": "^0.5.0"
82
83
  },
83
84
  "keywords": [
84
85
  "turbowarp",