@assetlib/sdk-core 0.6.3-preview.1 → 0.7.0-preview.1

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.md CHANGED
@@ -100,7 +100,7 @@ A catalog placement can declare `"rendering": "template"`. Assetlib then deliver
100
100
  { "key": "tab.trips", "symbol": ["Tabs", "trips"], "width": 24, "height": 24, "rendering": "template" }
101
101
  ```
102
102
 
103
- Signed image descriptors (placements, state members, variant cells and catalog page images) may carry `rendering`. A present value that is not a string matching `^[a-z][a-z0-9-]{0,31}$` rejects the release; absent means `original`. Resolution compares the reference's rendering with the descriptor actually selected after arm, appearance and state selection. If they differ, or the descriptor names a value this client does not know, that release's descriptor is skipped as for an incompatible size: its cache entry is not read and nothing is downloaded. Resolution continues with the usual order, a compatible verified cached image from an older retained release and then the bundled fallback (`fallbackReason: 'missing'`). Other placements are unaffected. Catalog page images resolve as `original`. `ResolvedAsset.rendering` reports the delivered descriptor's rendering, `'original'` or `'template'`.
103
+ Signed image descriptors (placements, state members, variant cells and catalog page images) may carry `rendering`. A present value that is not a string matching `^[a-z][a-z0-9-]{0,31}$` rejects the release; absent means `original`. Resolution compares the reference's rendering with the descriptor actually selected after arm, appearance and state selection. If they differ, or the descriptor names a value this client does not know, that release's descriptor is skipped as for an incompatible size: its cache entry is not read and nothing is downloaded. When the current release publishes the key, resolution continues with the usual order, a compatible verified cached image from an older retained release and then the bundled fallback (`fallbackReason: 'missing'`). Other placements are unaffected. Catalog page images resolve as `original`. `ResolvedAsset.rendering` reports the delivered descriptor's rendering, `'original'` or `'template'`.
104
104
 
105
105
  The bundled fallback must also be a single-color shape, such as a PNG with alpha, because the app applies the same tint to remote and bundled artwork. Remote masks are rasters, so request physical pixels with `pixelWidth` and `pixelHeight` (logical size × display scale); the Expo adapter does this for template references. Template rendering needs this SDK and CLI or newer: older code generation drops the field, and older runtimes do not check it. Publishing template descriptors from the hosted console is planned; until then releases carry no `rendering` field.
106
106
 
@@ -200,11 +200,11 @@ The optional signed `animationSchemaVersion: 1` enables a per-slot `animation` d
200
200
 
201
201
  Limits: UTF-8 JSON up to 512 KiB, dimensions 1–2048, at most 4,194,304 pixels, 1–60 fps, duration up to 30 seconds / 1,800 frames, 32 layers, 2,048 shapes, 4,096 keyframes/vertices, 30,000 document values and nesting depth 32. An arbitrary hash-valid JSON file is never sufficient for playback.
202
202
 
203
- Verified cached animation works offline. Only the current descriptor may trigger a download; if it fails, retained historical releases may supply an already cached compatible animation. Historical bytes are reverified and never downloaded. If the current release removes animation or its placement, resolution returns `source: 'poster'` immediately instead of reviving an old animation. First-use offline and unsupported/failed animations also return that poster outcome.
203
+ Verified cached animation works offline. Only the current descriptor may trigger a download; if it fails, retained historical releases may supply an already cached compatible animation. Historical bytes are reverified and never downloaded. If the current release removes animation or its placement, resolution returns `source: 'poster'` immediately instead of reviving an old animation. When the placement itself is removed, `resolve()` of the same reference returns the bundled image, so the still poster is the app's bundled artwork. First-use offline and unsupported/failed animations also return that poster outcome.
204
204
 
205
205
  ## Short looping video
206
206
 
207
- A placement can carry a short, muted, looping MP4 clip beside its required still poster. `resolveVideo` is explicit; ordinary `resolve(ref)` never downloads a clip. On October 11, 2026 it was exercised through the Expo `AssetlibVideo` component on an iOS 27 Simulator and an Android 16 emulator (0.6.0-preview.1) and in headless Chrome 154 (0.6.2-preview.1); it has not been tested on a physical device.
207
+ A placement can carry a short, muted, looping MP4 clip beside its required still poster. `resolveVideo` is explicit; ordinary `resolve(ref)` never downloads a clip. On October 11, 2026 it was exercised through the Expo `AssetlibVideo` component on an iOS 27 Simulator and an Android 16 emulator (0.6.0-preview.1) in headless Chrome 154 (0.6.2-preview.1), and in Playwright WebKit, desktop Safari and Mobile Safari on an iOS 27 Simulator (0.6.4-preview.1); it has not been tested on a physical device.
208
208
 
209
209
  ```ts
210
210
  const clip = await client.resolveVideo(AppAssets.Cards.gold, { signal });
@@ -232,7 +232,7 @@ Success returns `{ source: 'remote' | 'cache', sequence, message, sha256, assetI
232
232
  - Delivery is credentialless. Redirects are rejected. A streaming Fetch implementation is required so chunked responses can be bounded. The Expo adapter uses `expo/fetch` on native.
233
233
  - Default deadline: 8 seconds per request; configurable from 20 milliseconds to 30 seconds. Clips use `videoTimeoutMs` instead (default 30 seconds, same range). Maximum manifest response: 256 KiB. Maximum image: 8 MiB. Maximum clip: 4 MiB. Maximum placements: 100.
234
234
  - Persistent state keeps the highest accepted sequence and eight signed releases, up to 3 MiB. A lower sequence or different payload reusing a sequence is rejected. Legitimate server rollback must publish a new, higher sequence.
235
- - A failed current image resolves to a compatible cached image from the retained release history, then to the app's bundled fallback. Older releases never cause a new download. Eviction or more than eight accepted releases can remove a previous fallback.
235
+ - If the current release does not publish the placement's key, the result is the bundled fallback; older releases are not used. If it publishes the key but its image cannot be used (incompatible size, rendering or state set, or a failed download or check), a compatible cached image from the retained release history is used, then the bundled fallback. Older releases never cause a new download. Eviction or more than eight accepted releases can remove a previous fallback. (Since 0.7.0-preview.1. 0.6.4-preview.1 and earlier also use a cached image from an older retained release when the current release does not publish the key.)
236
236
  - Cache bytes are rehashed before use. The supplied adapters bound image storage to 50 MiB and 100 entries per configuration. Persistence is required before activating a new release. Corrupt replay state fails closed; it is not automatically discarded.
237
237
 
238
238
  Clearing app data, uninstalling the app, or browser storage eviction can reset local replay protection across restarts. Changing pinned keys or the production manifest route does not reset it. An active client fails closed if accepted durable state disappears, moves backwards, conflicts, or cannot be verified. This preview does not claim protection against a compromised device or browser. Storage adapters must enforce atomic, nondecreasing state replacement. No background manifest refresh, resumable download, HTTP content negotiation, experiment assignment, or automatic signing-key distribution is implemented. Opt-in observation counts do not establish experiment exposure. Rendering support is supplied by the platform; a valid hash does not prove an image is decodable.
@@ -245,13 +245,13 @@ npm test
245
245
  npm pack
246
246
  ```
247
247
 
248
- Tests include the shared cross-platform contract corpus (`test/fixtures/shared`, byte-identical with the Swift and Kotlin SDKs' copies: 142 signed manifest cases, 43 public config cases exercised as strings and objects, 18 appearance/arm resolution cases, 10 tintable icon rendering cases, 2 video resolution and 2 video byte-failure cases, staging configuration, stateful replay, byte-failure and rendition selection cases), separate JavaScript key-set fixtures for a second trusted signer and an untrusted signer, catalog subset requests, Lottie profile rejection/metadata verification, animation cache/offline/rollback/removal behavior, clip header checks, current-only clip downloads, cache-only clip history and the clip deadline, selection, PNG/SVG bytes and offline fallback, plus real Node Ed25519 signatures and bounded mock delivery streams. They cover signature and byte tampering, independent pins, replay across client restarts, corrupt storage, offline cache fallback, contract mismatch, URL boundaries, deadlines, and offline code generation. The npm pack contains built JavaScript and declarations; it does not require TypeScript compilation when installed.
248
+ Tests include the shared cross-platform contract corpus (`test/fixtures/shared`, byte-identical with the Swift and Kotlin SDKs' copies: 149 signed manifest cases, 43 public config cases exercised as strings and objects, 18 appearance/arm resolution cases, 10 tintable icon rendering cases, 2 video resolution and 2 video byte-failure cases, 10 placement membership (history) cases, staging configuration, stateful replay, byte-failure and rendition selection cases), separate JavaScript key-set fixtures for a second trusted signer and an untrusted signer, catalog subset requests, Lottie profile rejection/metadata verification, animation cache/offline/rollback/removal behavior, clip header checks, current-only clip downloads, cache-only clip history and the clip deadline, selection, PNG/SVG bytes and offline fallback, plus real Node Ed25519 signatures and bounded mock delivery streams. They cover signature and byte tampering, independent pins, replay across client restarts, corrupt storage, offline cache fallback, contract mismatch, URL boundaries, deadlines, and offline code generation. The npm pack contains built JavaScript and declarations; it does not require TypeScript compilation when installed.
249
249
 
250
250
  ## State sets, catalog pages, and cache policies
251
251
 
252
252
  Release 0.4.0-preview.1 adds `StateSetRef` / `resolveStateSet`, signed `loadAssetPage` / `resolveAsset` runtime handles, and `cachePolicy: 'disk' | 'memory' | 'none'`. The default remains `disk`; ephemeral image policies preserve durable signed release state. Image requests are cancellable and bounded to four concurrent transfers by default. `clearMemoryCache()` releases retained session bytes.
253
253
 
254
- A state set declares 2–16 unique states in the checked-in catalog (`placements[].states`); code generation preserves those literal names. Resolve the complete family once and select its returned state. If any required member is unavailable, the whole family uses an eligible older cached release or the bundled family. Do not substitute one progress stage for another.
254
+ A state set declares 2–16 unique states in the checked-in catalog (`placements[].states`); code generation preserves those literal names. Resolve the complete family once and select its returned state. If any required member is unavailable, the whole family uses an eligible older cached release or the bundled family. If the current release does not publish the key, the result is the complete bundled family without consulting older releases. Do not substitute one progress stage for another.
255
255
 
256
256
  Catalog metadata pages are signed, scoped to the app/release/request cursor, and contain at most 50 image references. They fetch no image bodies. Preserve the first page's `sequence` on continuation requests. Runtime handles are bound to their issuing client and cannot be serialized or reconstructed as trusted references. Use the Expo virtualized-list example for visible-only image resolution.
257
257
 
package/dist/index.js CHANGED
@@ -536,6 +536,10 @@ export class AssetClient {
536
536
  const resolvedOptions = { ...options, arm: decision.arm };
537
537
  let message = this.storageFailure ?? 'No compatible published artwork is available.';
538
538
  let reason = this.storageFailure ? 'verification' : !history.length && this.lastError ? this.lastRefreshReason : 'missing';
539
+ // Membership is decided by the latest accepted release alone: a key it does not
540
+ // publish has no artwork, so neither the cache nor an older release is consulted.
541
+ if (!this.storageFailure && history.length && !history[0].payload.slots.some(item => item.key === ref.key))
542
+ return this.observed(ref, { source: 'bundle', sequence: null, arm: null, armSource: decision.armSource, fallbackReason: 'missing', message: decisionMessage('Using bundled artwork. The current release does not publish this placement.', decision), ...(ref.bundledAccessibility ? { accessibility: ref.bundledAccessibility } : {}) });
539
543
  for (const [index, { payload }] of history.entries()) {
540
544
  const slot = payload.slots.find(matches);
541
545
  if (!slot)
@@ -564,6 +568,8 @@ export class AssetClient {
564
568
  throwIfAborted(options.signal);
565
569
  const resolvedOptions = { ...options, arm: decision.arm };
566
570
  let reason = this.storageFailure ? 'verification' : !history.length && this.lastError ? this.lastRefreshReason : 'missing';
571
+ if (!this.storageFailure && history.length && !history[0].payload.slots.some(item => item.key === ref.key))
572
+ return this.observed(ref, { source: 'bundle', sequence: null, arm: null, armSource: decision.armSource, fallbackReason: 'missing', message: decisionMessage('Using the complete bundled state set. The current release does not publish this placement.', decision), states: Object.freeze({}) });
567
573
  for (const [index, { payload }] of history.entries()) {
568
574
  const placement = payload.slots.find(matches);
569
575
  const selected = placement && variantSlot(placement, decision.arm, options.appearance);
package/dist/telemetry.js CHANGED
@@ -28,7 +28,7 @@ export class ObservationReporter {
28
28
  this.fetcher = fetcher;
29
29
  this.interval = boundedInteger(options.flushIntervalMs, 60_000, 2_147_483_647);
30
30
  this.maxBatch = boundedInteger(options.maxBatch, 200, 200);
31
- const sdk = options.sdk ?? { name: 'sdk-core', version: '0.6.3-preview.1' };
31
+ const sdk = options.sdk ?? { name: 'sdk-core', version: '0.7.0-preview.1' };
32
32
  const build = options.build ?? { platform: 'web', appVersion: 'unknown', buildNumber: 'unknown' };
33
33
  if (!version(sdk.name) || !version(sdk.version) || !['ios', 'android', 'web', 'expo'].includes(build.platform) || !version(build.appVersion) || !version(build.buildNumber) || (options.installId !== undefined && !validId(options.installId)))
34
34
  throw new Error('Invalid observation configuration.');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assetlib/sdk-core",
3
- "version": "0.6.3-preview.1",
3
+ "version": "0.7.0-preview.1",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",