@pie-players/pie-players-shared 1.0.0-beta.1 → 1.0.0-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.
@@ -1,3 +1,4 @@
1
+ import { settleAll } from "../../promise/index.js";
1
2
  export class CompositeInstrumentationProvider {
2
3
  providerId = "composite";
3
4
  providerName = "Composite Provider";
@@ -7,7 +8,7 @@ export class CompositeInstrumentationProvider {
7
8
  this.providers = providers.filter(Boolean);
8
9
  }
9
10
  async initialize(config) {
10
- const results = await Promise.allSettled(this.providers.map((provider) => provider.initialize(config)));
11
+ const results = await settleAll(this.providers.map((provider) => provider.initialize(config)));
11
12
  this.initialized = true;
12
13
  if (config?.debug) {
13
14
  for (const [index, result] of results.entries()) {
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Shape check for the package specs an item config names in `elements`.
3
+ *
4
+ * Loaders that build CDN URLs from a spec run it through
5
+ * `isScopedPackageSpec` first: item configs are authored content, and a
6
+ * version such as `1.0.0/../../../gh/owner/repo@main` would otherwise
7
+ * resolve to a path outside the package.
8
+ */
9
+ /**
10
+ * A scoped npm package name with an optional semver version or dist-tag, as
11
+ * item configs store it: `@pie-element/rubric`, `@pie-element/rubric@latest`,
12
+ * `@pie-element/rubric@8.0.0-hotfix.2`.
13
+ */
14
+ export declare function isScopedPackageSpec(spec: unknown): spec is string;
15
+ /** Why a loader refused `spec`, for its failure reason. */
16
+ export declare function invalidPackageSpecMessage(spec: unknown): string;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Shape check for the package specs an item config names in `elements`.
3
+ *
4
+ * Loaders that build CDN URLs from a spec run it through
5
+ * `isScopedPackageSpec` first: item configs are authored content, and a
6
+ * version such as `1.0.0/../../../gh/owner/repo@main` would otherwise
7
+ * resolve to a path outside the package.
8
+ */
9
+ const SEMVER_VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
10
+ const DIST_TAG = /^[A-Za-z][A-Za-z0-9_-]*$/;
11
+ const SCOPED_PACKAGE_NAME = /^@[a-z0-9~-][a-z0-9._~-]*\/[a-z0-9~-][a-z0-9._~-]*$/;
12
+ /**
13
+ * A scoped npm package name with an optional semver version or dist-tag, as
14
+ * item configs store it: `@pie-element/rubric`, `@pie-element/rubric@latest`,
15
+ * `@pie-element/rubric@8.0.0-hotfix.2`.
16
+ */
17
+ export function isScopedPackageSpec(spec) {
18
+ if (typeof spec !== "string")
19
+ return false;
20
+ const versionAt = spec.indexOf("@", 1);
21
+ if (versionAt === -1)
22
+ return SCOPED_PACKAGE_NAME.test(spec);
23
+ const version = spec.slice(versionAt + 1);
24
+ return (SCOPED_PACKAGE_NAME.test(spec.slice(0, versionAt)) &&
25
+ (SEMVER_VERSION.test(version) || DIST_TAG.test(version)));
26
+ }
27
+ /** Why a loader refused `spec`, for its failure reason. */
28
+ export function invalidPackageSpecMessage(spec) {
29
+ return `Invalid element package spec ${JSON.stringify(String(spec))}: expected a scoped npm package name with an optional semver version or dist-tag`;
30
+ }
@@ -67,7 +67,8 @@ export declare function createEsmBackend(config: EsmBackendConfig): EsmBackend;
67
67
  export declare function mapEsmViewElements(elements: ElementMap, view?: string, viewConfig?: ViewConfig): ElementMap;
68
68
  /**
69
69
  * URL of a package's `./runtime-support` module on the ESM CDN the players load
70
- * elements from, or `undefined` when a custom provider names no package root.
70
+ * elements from, or `undefined` when a custom provider names no package root or
71
+ * `packageVersion` is not a scoped package spec.
71
72
  */
72
73
  export declare function resolveEsmRuntimeSupportUrl(packageVersion: string, options: {
73
74
  cdnBaseUrl: string;
@@ -21,6 +21,7 @@ import { BundleType, isCustomElementConstructor, Status, } from "../pie/types.js
21
21
  import { parsePackageName } from "../pie/utils.js";
22
22
  import { AdapterFailure, } from "./element-loader-types.js";
23
23
  import { isExactSemver } from "./element-package-policy.js";
24
+ import { invalidPackageSpecMessage, isScopedPackageSpec, } from "./element-package-spec.js";
24
25
  import { forwardMathjaxEvents } from "./mathjax-events.js";
25
26
  import { isPlainRecord } from "../object/index.js";
26
27
  export const BUILT_IN_VIEWS = {
@@ -80,12 +81,30 @@ export function createEsmBackend(config) {
80
81
  async function load(elements, context) {
81
82
  if (!elements || Object.keys(elements).length === 0)
82
83
  return;
84
+ // Refused before any URL is built from the spec; the other elements load.
85
+ const reasons = new Map();
86
+ const loadable = {};
87
+ for (const [tag, pkg] of Object.entries(elements)) {
88
+ if (isScopedPackageSpec(pkg)) {
89
+ loadable[tag] = pkg;
90
+ }
91
+ else {
92
+ reasons.set(tag, {
93
+ kind: "backend-rejected",
94
+ tag,
95
+ cause: invalidPackageSpecMessage(pkg),
96
+ });
97
+ }
98
+ }
99
+ if (Object.keys(loadable).length === 0) {
100
+ throw new AdapterFailure(reasons);
101
+ }
83
102
  if (moduleResolution === "import-map") {
84
103
  assertImportMapSupported();
85
104
  }
86
105
  forwardMathjaxEventsOf(context.doc);
87
106
  const newEntries = {};
88
- for (const [tag, pkg] of Object.entries(elements)) {
107
+ for (const [tag, pkg] of Object.entries(loadable)) {
89
108
  const packageName = parsePackageName(pkg).name;
90
109
  if (moduleResolution === "import-map") {
91
110
  const existingVersion = importMappedPackageVersions.get(packageName);
@@ -147,8 +166,7 @@ export function createEsmBackend(config) {
147
166
  await applyImportMap(imports, injectedMap, context.doc);
148
167
  }
149
168
  }
150
- const reasons = new Map();
151
- await Promise.all(Object.entries(elements).map(async ([tag, packageVersion]) => {
169
+ await Promise.all(Object.entries(loadable).map(async ([tag, packageVersion]) => {
152
170
  const packageName = parsePackageName(packageVersion).name;
153
171
  let actualTag;
154
172
  try {
@@ -609,9 +627,12 @@ async function fetchPackageMetadata(packageJsonUrl) {
609
627
  const RUNTIME_SUPPORT_PATH = "dist/runtime-support.js";
610
628
  /**
611
629
  * URL of a package's `./runtime-support` module on the ESM CDN the players load
612
- * elements from, or `undefined` when a custom provider names no package root.
630
+ * elements from, or `undefined` when a custom provider names no package root or
631
+ * `packageVersion` is not a scoped package spec.
613
632
  */
614
633
  export function resolveEsmRuntimeSupportUrl(packageVersion, options) {
634
+ if (!isScopedPackageSpec(packageVersion))
635
+ return undefined;
615
636
  const cdnBaseUrl = options.cdnBaseUrl.replace(/\/+$/, "");
616
637
  const provider = resolveCdnProvider(options.cdnProvider, cdnBaseUrl);
617
638
  if (typeof provider.runtimeSupportUrl === "function") {
@@ -34,6 +34,7 @@ export type { ElementMap } from "./ElementLoader.js";
34
34
  export { aggregateElements } from "./ElementLoader.js";
35
35
  export type { ElementPackagePolicy } from "./element-package-policy.js";
36
36
  export { assertElementPackagesAllowed, ElementPackagePolicyError, } from "./element-package-policy.js";
37
+ export { isScopedPackageSpec } from "./element-package-spec.js";
37
38
  export type { BackendContext, BackendOption, ElementLoaderBackend, ElementTag, EnsureRegisteredOptions, EsmBackendConfig, IifeBackendConfig, RegistrationFailureReason, } from "./element-loader.js";
38
39
  export { AdapterFailure, ElementAssertionError, ElementLoaderError, assertRegistered, describeRegistrationFailures, ensureRegistered, } from "./element-loader.js";
39
40
  export type { BuiltInEsmCdnProviderName, EsmBackend, EsmCdnProvider, EsmCdnProviderName, EsmCdnProviderOption, ViewConfig, } from "./esm-adapter.js";
@@ -32,6 +32,7 @@
32
32
  */
33
33
  export { aggregateElements } from "./ElementLoader.js";
34
34
  export { assertElementPackagesAllowed, ElementPackagePolicyError, } from "./element-package-policy.js";
35
+ export { isScopedPackageSpec } from "./element-package-spec.js";
35
36
  export { AdapterFailure, ElementAssertionError, ElementLoaderError, assertRegistered, describeRegistrationFailures, ensureRegistered, } from "./element-loader.js";
36
37
  export { BUILT_IN_VIEWS, createEsmBackend, mapEsmViewElements, resolveEsmAssetRoot, resolveEsmRuntimeSupportUrl, } from "./esm-adapter.js";
37
38
  export { forwardMathjaxEvents, MATHJAX_NO_ASSET_ROOT_EVENT, MATHJAX_VERSION_CONFLICT_EVENT, } from "./mathjax-events.js";
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Waits for every value to settle and resolves with each outcome in input
3
+ * order, as the `allSettled` static does. Built on `Promise.all` and `then` alone:
4
+ * a host page can replace the global `Promise` with an implementation that
5
+ * predates `allSettled` (zone.js 0.9.1 does), so published code calls no
6
+ * `Promise` static beyond `all`, `race`, `resolve` and `reject`.
7
+ * `bun run check:bundle-safety` enforces that on every built bundle.
8
+ * @param values The promises or plain values to settle.
9
+ */
10
+ export declare const settleAll: <T>(values: Iterable<T | PromiseLike<T>>) => Promise<PromiseSettledResult<Awaited<T>>[]>;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Waits for every value to settle and resolves with each outcome in input
3
+ * order, as the `allSettled` static does. Built on `Promise.all` and `then` alone:
4
+ * a host page can replace the global `Promise` with an implementation that
5
+ * predates `allSettled` (zone.js 0.9.1 does), so published code calls no
6
+ * `Promise` static beyond `all`, `race`, `resolve` and `reject`.
7
+ * `bun run check:bundle-safety` enforces that on every built bundle.
8
+ * @param values The promises or plain values to settle.
9
+ */
10
+ export const settleAll = (values) => Promise.all(Array.from(values, (value) => Promise.resolve(value).then((result) => ({
11
+ status: "fulfilled",
12
+ value: result,
13
+ }), (reason) => ({
14
+ status: "rejected",
15
+ reason,
16
+ }))));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pie-players/pie-players-shared",
3
- "version": "1.0.0-beta.1",
3
+ "version": "1.0.0-beta.2",
4
4
  "type": "module",
5
5
  "description": "Shared runtime + UI utilities for PIE players",
6
6
  "license": "MIT",
@@ -33,6 +33,10 @@
33
33
  "types": "./dist/object/index.d.ts",
34
34
  "import": "./dist/object/index.js"
35
35
  },
36
+ "./promise": {
37
+ "types": "./dist/promise/index.d.ts",
38
+ "import": "./dist/promise/index.js"
39
+ },
36
40
  "./types": {
37
41
  "types": "./dist/types/index.d.ts",
38
42
  "import": "./dist/types/index.js"
@@ -121,12 +125,12 @@
121
125
  "@biomejs/biome": "^2.5.15",
122
126
  "@happy-dom/global-registrator": "^20.14.5",
123
127
  "@pie-element/shared-math-rendering-mathjax": "0.1.4",
124
- "@playwright/test": "^1.63.0",
128
+ "@playwright/test": "^1.64.0",
125
129
  "@types/semver": "^7.8.0",
126
130
  "es-module-shims": "^2.8.4",
127
131
  "esbuild": "^0.28.2",
128
132
  "glob": "^13.0.0",
129
- "svelte": "^5.57.1",
133
+ "svelte": "^5.57.2",
130
134
  "svelte-check": "^4.7.6",
131
135
  "typescript": "^5.9.3"
132
136
  },