@kubohiroya/turbowarp-extension-loader 0.5.1 → 0.6.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.
package/README.ja.md CHANGED
@@ -223,6 +223,12 @@ Manifest は `uri` を持てます。その作品をダウンロードできる
223
223
  するたびに変わるので、ブラウザーのキャッシュも開く人の選択の記録も、触るたびに同じ作品を別名で
224
224
  綴じてしまいます。住所は中身が変わっても変わりません。
225
225
 
226
+ `prepareCatalogExtensionChanges` と `prepareExtensionAddition` は `uri` を受け取ります。作品が
227
+ 最初の住所を得るのも、作者が選んだ住所へ移るのも、この経路です。何も言わなければ作品が持って
228
+ いる住所がそのまま残り、`readProjectAuthoringState` がその住所を報告するので、ホストは初回保存
229
+ かどうかを決める前に見分けられます。割り当てるかどうかはホストの判断です。作者が住所を選んだ
230
+ のか、既定が必要な初回保存なのかを知っているのはホストだけだからです。
231
+
226
232
  `./project-authoring` の `createProjectUUID` と `defaultProjectURI` が、作者が住所を選んで
227
233
  いないときに与える住所を組みます。
228
234
 
package/README.md CHANGED
@@ -226,6 +226,13 @@ fingerprint changes on every save, so a browser cache and a record of what the o
226
226
  file one work under a new name each time it was touched. An address does not change when the bytes
227
227
  do.
228
228
 
229
+ `prepareCatalogExtensionChanges` and `prepareExtensionAddition` take a `uri` option, which is how
230
+ a project gets its first address and how it moves to one the author chose. Saying nothing keeps
231
+ the address the project already has, and `readProjectAuthoringState` reports that address, so a
232
+ host can tell a first save from a later one before deciding. Whether to assign one is the host's
233
+ call: only the host knows whether the author chose an address or whether this is a first save
234
+ that needs a default.
235
+
229
236
  `createProjectUUID` and `defaultProjectURI` in `./project-authoring` build the address a project
230
237
  is given when its author has not chosen one:
231
238
 
@@ -23,6 +23,16 @@ export interface ExtensionAdditionOptions {
23
23
  signal?: AbortSignal;
24
24
  loaderOptions?: LoaderOptions;
25
25
  archiveOptions?: ArchiveLimits;
26
+ /**
27
+ * The address to give this project, or nothing to keep the one it has.
28
+ *
29
+ * Whether to assign one is the host's call, not this function's: only the host knows whether
30
+ * the author chose an address or whether this is a first save that needs a default. So this
31
+ * sets what it is given and leaves the existing address alone when it is given nothing —
32
+ * `readProjectAuthoringState` reports what a project already carries, so the host can tell
33
+ * the two apart before deciding.
34
+ */
35
+ uri?: string;
26
36
  }
27
37
  export interface PreparedExtensionAddition {
28
38
  readonly projectBytes: Uint8Array;
@@ -21,7 +21,7 @@ function snapshotProject(input) {
21
21
  * Add a file-backed Embedded or URL-backed Remote extension without evaluating it.
22
22
  * A host approval is required before the candidate is returned; the original is untouched.
23
23
  */
24
- export async function prepareExtensionAddition(input, request, ports, { zip: Zip, signal: externalSignal, loaderOptions = {}, archiveOptions } = {}) {
24
+ export async function prepareExtensionAddition(input, request, ports, { zip: Zip, signal: externalSignal, loaderOptions = {}, archiveOptions, uri } = {}) {
25
25
  if (typeof Zip !== 'function' || typeof Zip.loadAsync !== 'function')
26
26
  throw new TypeError('A compatible JSZip constructor is required');
27
27
  if (!plainObject(request) || Object.keys(request).some(key => !['id', 'source', 'bytes', 'execution'].includes(key))) {
@@ -95,8 +95,10 @@ export async function prepareExtensionAddition(input, request, ports, { zip: Zip
95
95
  fail('INVALID_PROJECT', 'Project extensionURLs must be an object');
96
96
  const previous = prepared.manifest?.extensions || [];
97
97
  // Carried through every rebuild below: an address that changed when an extension was
98
- // added would not be an address.
99
- const carried = prepared.manifest?.uri === undefined ? {} : { uri: prepared.manifest.uri };
98
+ // added would not be an address. The host may set one, which is how a project gets its
99
+ // first; saying nothing keeps what is there.
100
+ const address = uri ?? prepared.manifest?.uri;
101
+ const carried = address === undefined ? {} : { uri: address };
100
102
  if (previous.some(spec => spec.id === draft.id) || (project.extensions || []).includes(draft.id) ||
101
103
  Object.hasOwn(project.extensionURLs || {}, draft.id))
102
104
  fail('DUPLICATE_EXTENSION', 'This extension ID already belongs to the project', draft.id);
@@ -23,6 +23,16 @@ export interface CatalogOptions {
23
23
  signal?: AbortSignal;
24
24
  loaderOptions?: LoaderOptions;
25
25
  archiveOptions?: ArchiveLimits;
26
+ /**
27
+ * The address to give this project, or nothing to keep the one it has.
28
+ *
29
+ * Whether to assign one is the host's call, not this function's: only the host knows whether
30
+ * the author chose an address or whether this is a first save that needs a default. So this
31
+ * sets what it is given and leaves the existing address alone when it is given nothing —
32
+ * `readProjectAuthoringState` reports what a project already carries, so the host can tell
33
+ * the two apart before deciding.
34
+ */
35
+ uri?: string;
26
36
  }
27
37
  export type CatalogPorts = Pick<LoaderPorts, 'fetcher' | 'trustProvider' | 'cache' | 'diagnostics'>;
28
38
  /** Read project-owned distribution requests; these records never grant execution permission. */
@@ -19,7 +19,7 @@ export async function readProjectAuthoringState(input, { zip, signal, archiveOpt
19
19
  const prepared = await io.prepare(input, { signal });
20
20
  return prepared.manifest;
21
21
  }
22
- export async function prepareCatalogExtensionChanges(input, requests, ports, { zip: Zip, signal: externalSignal, loaderOptions = {}, archiveOptions } = {}) {
22
+ export async function prepareCatalogExtensionChanges(input, requests, ports, { zip: Zip, signal: externalSignal, loaderOptions = {}, archiveOptions, uri } = {}) {
23
23
  const limits = { ...DEFAULT_LOADER_LIMITS, ...loaderOptions };
24
24
  for (const key of ['maxExtensions', 'maxBytes', 'maxTotalBytes', 'timeoutMs']) {
25
25
  if (!Number.isSafeInteger(limits[key]) || limits[key] < 1)
@@ -176,8 +176,10 @@ export async function prepareCatalogExtensionChanges(input, requests, ports, { z
176
176
  // The project's own address survives a change to its extensions. Rebuilding the manifest
177
177
  // without it would file the same work under a new name the next time it was saved, which
178
178
  // is the one thing an address is for.
179
- const manifest = validateManifest({ schemaVersion: 1, ...(prepared.manifest?.uri === undefined ?
180
- {} : { uri: prepared.manifest.uri }), extensions: [...extensions.values()] }, loaderOptions);
179
+ const address = uri ?? prepared.manifest?.uri;
180
+ const manifest = validateManifest({ schemaVersion: 1,
181
+ ...(address === undefined ? {} : { uri: address }),
182
+ extensions: [...extensions.values()] }, loaderOptions);
181
183
  for (const spec of manifest.extensions)
182
184
  if (spec.source.type === 'embedded-file')
183
185
  replacedPaths.delete(spec.source.path);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubohiroya/turbowarp-extension-loader",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "Host-independent verified TurboWarp Plus extension loading core",
5
5
  "keywords": [
6
6
  "turbowarp",