@syncbridge/common 0.13.8 → 0.14.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/constants.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare const version = "0.13.8";
1
+ export declare const version = "0.14.0";
2
2
  export declare const CLASS_METADATA: unique symbol;
3
3
  export declare const CLASS_TYPE: unique symbol;
4
4
  export declare const VARIABLE_CONTAINER: unique symbol;
package/constants.js CHANGED
@@ -1,4 +1,4 @@
1
- export const version = '0.13.8';
1
+ export const version = '0.14.0';
2
2
  export const CLASS_METADATA = Symbol.for('CLASS_METADATA');
3
3
  export const CLASS_TYPE = Symbol.for('CLASS_TYPE');
4
4
  export const VARIABLE_CONTAINER = Symbol.for('VARIABLE_CONTAINER');
@@ -33,7 +33,9 @@ export function Component(options) {
33
33
  throw new TypeError('Class name is required');
34
34
  /** Determine uri */
35
35
  Reflect.defineMetadata(CLASS_TYPE, 'component', target);
36
- const packageName = pkgjson.name;
36
+ /* As in @Processor: a package published under another name (the npm
37
+ * mirror's @sbext/vendor__name) keeps its registry name here */
38
+ const packageName = pkgjson.syncbridge?.name || pkgjson.name;
37
39
  const uri = packageName + ':' + className;
38
40
  const metadata = defineOwnMetadata(target, CLASS_METADATA, {
39
41
  classType: 'component',
package/index.d.ts CHANGED
@@ -17,8 +17,10 @@ export * from './models-document.js';
17
17
  export * from './processor-factory.js';
18
18
  export * from './registry/extension-package.js';
19
19
  export * from './registry/extension-registry.js';
20
+ export * from './registry/package-manifest.js';
20
21
  export * from './utils/decode-profile.js';
21
22
  export * from './utils/materialize-metadata.js';
22
23
  export * from './utils/normalize-metadata.js';
24
+ export * from './utils/registry-names.js';
23
25
  export * from './utils/type-guards.js';
24
26
  export * from './utils/uri-to-id.js';
package/index.js CHANGED
@@ -17,8 +17,10 @@ export * from './models-document.js';
17
17
  export * from './processor-factory.js';
18
18
  export * from './registry/extension-package.js';
19
19
  export * from './registry/extension-registry.js';
20
+ export * from './registry/package-manifest.js';
20
21
  export * from './utils/decode-profile.js';
21
22
  export * from './utils/materialize-metadata.js';
22
23
  export * from './utils/normalize-metadata.js';
24
+ export * from './utils/registry-names.js';
23
25
  export * from './utils/type-guards.js';
24
26
  export * from './utils/uri-to-id.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syncbridge/common",
3
- "version": "0.13.8",
3
+ "version": "0.14.0",
4
4
  "description": "SyncBridge Common utilities",
5
5
  "author": "Panates Inc",
6
6
  "license": "MIT",
@@ -4,6 +4,7 @@ import { BaseElement } from '../classes/base-element.js';
4
4
  import { ComponentBase } from '../classes/component-base.js';
5
5
  import { ProcessorBase } from '../classes/processor-base.js';
6
6
  import { ComponentMetadata, ElementMetadata, ProcessorMetadata } from '../models/index.js';
7
+ import { type ExtensionPackageJson } from './package-manifest.js';
7
8
  /**
8
9
  *
9
10
  */
@@ -13,9 +14,19 @@ export declare class ExtensionPackage {
13
14
  description: string;
14
15
  path: string;
15
16
  entryPoint: string;
17
+ /** See `getManifestVersion`; `undefined` for a class registered directly */
18
+ manifestVersion?: number;
19
+ /** Registry packages this one depends on: registry name -> semver range */
20
+ registryDependencies: Record<string, string>;
21
+ /** What the package expects the host to provide */
22
+ peerDependencies: Record<string, string>;
16
23
  components: ComponentExtension[];
17
24
  processors: ProcessorExtension[];
18
25
  errors: Error[];
26
+ /**
27
+ * Takes the package's identity and dependencies from its `package.json`.
28
+ */
29
+ assignManifest(pkgJson: ExtensionPackageJson): void;
19
30
  }
20
31
  /**
21
32
  *
@@ -5,13 +5,29 @@ import { ProcessorBase } from '../classes/processor-base.js';
5
5
  import { SbError } from '../classes/sb-error.js';
6
6
  import { ComponentMetadata, ElementMetadata, ProcessorMetadata, } from '../models/index.js';
7
7
  import { isComponentBase, isProcessorBase } from '../utils/type-guards.js';
8
+ import { getManifestVersion, } from './package-manifest.js';
8
9
  /**
9
10
  *
10
11
  */
11
12
  export class ExtensionPackage {
13
+ /** Registry packages this one depends on: registry name -> semver range */
14
+ registryDependencies = {};
15
+ /** What the package expects the host to provide */
16
+ peerDependencies = {};
12
17
  components = [];
13
18
  processors = [];
14
19
  errors = [];
20
+ /**
21
+ * Takes the package's identity and dependencies from its `package.json`.
22
+ */
23
+ assignManifest(pkgJson) {
24
+ this.name = pkgJson.syncbridge?.name || pkgJson.name;
25
+ this.version = pkgJson.version;
26
+ this.description = pkgJson.description;
27
+ this.manifestVersion = getManifestVersion(pkgJson);
28
+ this.registryDependencies = { ...pkgJson.syncbridge?.dependencies };
29
+ this.peerDependencies = { ...pkgJson.peerDependencies };
30
+ }
15
31
  }
16
32
  /**
17
33
  *
@@ -138,9 +138,7 @@ export class ExtensionRegistry extends AsyncEventEmitter {
138
138
  entryPoint = pathToFileURL(entryPoint).href;
139
139
  }
140
140
  const pkg = new ExtensionPackage();
141
- pkg.name = pkgJson.syncbridge?.name || pkgJson.name;
142
- pkg.version = pkgJson.version;
143
- pkg.description = pkgJson.description;
141
+ pkg.assignManifest(pkgJson);
144
142
  pkg.path = pkgDir;
145
143
  pkg.entryPoint = entryPoint;
146
144
  const module = await import(entryPoint);
@@ -0,0 +1,47 @@
1
+ /**
2
+ * The manifest version a package built today carries. Version 1 is the
3
+ * bundled layout, version 2 the unbundled one with registry dependencies.
4
+ */
5
+ export declare const MANIFEST_VERSION = 2;
6
+ /**
7
+ * The `syncbridge` field of an extension package's `package.json`.
8
+ */
9
+ export interface SyncBridgeManifest {
10
+ /** The name the package registers under, when it differs from `name` */
11
+ name?: string;
12
+ /** The version of the package contract - see `getManifestVersion` */
13
+ manifestVersion?: number;
14
+ /**
15
+ * @deprecated Written by syncbuild before `manifestVersion` existed and
16
+ * derived from syncbuild's own major version, so it describes the builder
17
+ * rather than the package. Read only as "manifest version 1".
18
+ */
19
+ version?: string;
20
+ /** Registry packages this one depends on: registry name -> semver range */
21
+ dependencies?: Record<string, string>;
22
+ components?: string[];
23
+ processors?: string[];
24
+ }
25
+ /**
26
+ * The parts of an extension package's `package.json` SyncBridge reads.
27
+ */
28
+ export interface ExtensionPackageJson {
29
+ name: string;
30
+ version: string;
31
+ description?: string;
32
+ main?: string;
33
+ module?: string;
34
+ dependencies?: Record<string, string>;
35
+ peerDependencies?: Record<string, string>;
36
+ syncbridge?: SyncBridgeManifest;
37
+ }
38
+ /**
39
+ * Returns the manifest version of an extension package, or `undefined` when
40
+ * the `package.json` has no `syncbridge` field and is not one.
41
+ *
42
+ * A manifest without `manifestVersion` is version 1, whatever its legacy
43
+ * `version` says: that field followed syncbuild's major version, so reading
44
+ * it as a contract version would make a new syncbuild major look like a new
45
+ * package layout.
46
+ */
47
+ export declare function getManifestVersion(pkgJson: Pick<ExtensionPackageJson, 'syncbridge'> | undefined): number | undefined;
@@ -0,0 +1,27 @@
1
+ import { SbError } from '../classes/sb-error.js';
2
+ /**
3
+ * The manifest version a package built today carries. Version 1 is the
4
+ * bundled layout, version 2 the unbundled one with registry dependencies.
5
+ */
6
+ export const MANIFEST_VERSION = 2;
7
+ /**
8
+ * Returns the manifest version of an extension package, or `undefined` when
9
+ * the `package.json` has no `syncbridge` field and is not one.
10
+ *
11
+ * A manifest without `manifestVersion` is version 1, whatever its legacy
12
+ * `version` says: that field followed syncbuild's major version, so reading
13
+ * it as a contract version would make a new syncbuild major look like a new
14
+ * package layout.
15
+ */
16
+ export function getManifestVersion(pkgJson) {
17
+ const manifest = pkgJson?.syncbridge;
18
+ if (!manifest || typeof manifest !== 'object')
19
+ return;
20
+ const v = manifest.manifestVersion;
21
+ if (v === undefined)
22
+ return 1;
23
+ if (!Number.isInteger(v) || v < 1) {
24
+ throw new SbError(`Invalid syncbridge.manifestVersion (${JSON.stringify(v)}), it must be a positive integer`);
25
+ }
26
+ return v;
27
+ }
@@ -29,7 +29,7 @@ export function materializeMetadata(profile, options) {
29
29
  */
30
30
  export function materializeMetadataSilent(profile, options) {
31
31
  const stackExecutor = new StackExecutor();
32
- const processorRec = ExtensionRegistry.getProcessor(profile.processorUri);
32
+ const processorRec = ExtensionRegistry.getProcessor(profile.processorUri, profile.processorVersion);
33
33
  const metadata = omitUndefined(clone({
34
34
  classType: processorRec.metadata.classType,
35
35
  className: processorRec.metadata.className,
@@ -0,0 +1,48 @@
1
+ /** Prefix that marks a registry package in an import specifier */
2
+ export declare const SB_SPECIFIER_PREFIX = "sb:";
3
+ /**
4
+ * The npm scope registry packages are published under for development and
5
+ * tests: `@abisena/iomt` becomes `@sbext/abisena__iomt`. Reserved, so no
6
+ * registry organization can take it.
7
+ */
8
+ export declare const SBEXT_SCOPE = "sbext";
9
+ /**
10
+ * Organization names the registry does not let anyone create. A policy for
11
+ * creating organizations, not a naming rule: `syncbridge` is reserved for its
12
+ * owner, while `@syncbridge/sqb` is still a valid registry name.
13
+ */
14
+ export declare const RESERVED_SCOPES: readonly string[];
15
+ /**
16
+ * Checks a registry organization name - the scope, with or without its `@`.
17
+ * Returns the problems found; an empty array means it is valid.
18
+ *
19
+ * `__` is refused because `toSbextName` joins the scope and the name with
20
+ * it: a scope containing one could not be split back apart.
21
+ */
22
+ export declare function validateScopeName(scope: string): string[];
23
+ /**
24
+ * Checks a registry package name. Registry names are scoped (`@vendor/name`)
25
+ * and follow npm's naming rules, and neither part may contain `__`.
26
+ * Returns the problems found; an empty array means it is valid.
27
+ */
28
+ export declare function validateRegistryName(name: string): string[];
29
+ /**
30
+ * Maps a registry name to the npm name it is published under:
31
+ * `@abisena/iomt` -> `@sbext/abisena__iomt`.
32
+ */
33
+ export declare function toSbextName(name: string): string;
34
+ /**
35
+ * Maps an npm name under `@sbext` back to its registry name:
36
+ * `@sbext/abisena__iomt` -> `@abisena/iomt`.
37
+ */
38
+ export declare function fromSbextName(npmName: string): string;
39
+ /**
40
+ * Parses an import specifier that names a registry package:
41
+ * `sb:@abisena/iomt/protocol` -> `{ name: '@abisena/iomt', subpath: './protocol' }`.
42
+ * Returns `undefined` for a specifier without the `sb:` prefix, and throws for
43
+ * one that has it but does not name a valid registry package.
44
+ */
45
+ export declare function parseSbSpecifier(specifier: string): {
46
+ name: string;
47
+ subpath: string;
48
+ } | undefined;
@@ -0,0 +1,108 @@
1
+ import { SbError } from '../classes/sb-error.js';
2
+ /** Prefix that marks a registry package in an import specifier */
3
+ export const SB_SPECIFIER_PREFIX = 'sb:';
4
+ /**
5
+ * The npm scope registry packages are published under for development and
6
+ * tests: `@abisena/iomt` becomes `@sbext/abisena__iomt`. Reserved, so no
7
+ * registry organization can take it.
8
+ */
9
+ export const SBEXT_SCOPE = 'sbext';
10
+ /**
11
+ * Organization names the registry does not let anyone create. A policy for
12
+ * creating organizations, not a naming rule: `syncbridge` is reserved for its
13
+ * owner, while `@syncbridge/sqb` is still a valid registry name.
14
+ */
15
+ export const RESERVED_SCOPES = [SBEXT_SCOPE, 'syncbridge'];
16
+ /** npm's limit on the length of a whole package name, scope included */
17
+ const MAX_NAME_LENGTH = 214;
18
+ /** Lower-case, URL-safe, and not starting with `.` or `_` */
19
+ const NAME_PART_PATTERN = /^[a-z0-9-][a-z0-9._-]*$/;
20
+ /**
21
+ * Checks a registry organization name - the scope, with or without its `@`.
22
+ * Returns the problems found; an empty array means it is valid.
23
+ *
24
+ * `__` is refused because `toSbextName` joins the scope and the name with
25
+ * it: a scope containing one could not be split back apart.
26
+ */
27
+ export function validateScopeName(scope) {
28
+ const s = scope.startsWith('@') ? scope.slice(1) : scope;
29
+ if (!s)
30
+ return ['Scope must not be empty'];
31
+ const problems = [];
32
+ if (!NAME_PART_PATTERN.test(s))
33
+ problems.push(`Scope "${s}" may only contain lower-case letters, digits, "-", "." and "_", and must not start with "." or "_"`);
34
+ if (s.includes('__'))
35
+ problems.push(`Scope "${s}" must not contain "__"`);
36
+ /* Unlike the other reserved scopes, this one can never be a vendor's */
37
+ if (s === SBEXT_SCOPE)
38
+ problems.push(`Scope "${s}" is reserved for the npm mirror`);
39
+ return problems;
40
+ }
41
+ /**
42
+ * Checks a registry package name. Registry names are scoped (`@vendor/name`)
43
+ * and follow npm's naming rules, and neither part may contain `__`.
44
+ * Returns the problems found; an empty array means it is valid.
45
+ */
46
+ export function validateRegistryName(name) {
47
+ const m = /^@([^/]*)\/([^/]*)$/.exec(name);
48
+ if (!m)
49
+ return [`"${name}" must be a scoped name, "@vendor/name"`];
50
+ const [, scope, pkg] = m;
51
+ const problems = validateScopeName(scope);
52
+ if (!pkg)
53
+ problems.push(`"${name}" has no name after the scope`);
54
+ else {
55
+ if (!NAME_PART_PATTERN.test(pkg))
56
+ problems.push(`Name "${pkg}" may only contain lower-case letters, digits, "-", "." and "_", and must not start with "." or "_"`);
57
+ if (pkg.includes('__'))
58
+ problems.push(`Name "${pkg}" must not contain "__"`);
59
+ }
60
+ if (name.length > MAX_NAME_LENGTH)
61
+ problems.push(`"${name}" is longer than ${MAX_NAME_LENGTH} characters`);
62
+ return problems;
63
+ }
64
+ function assertRegistryName(name) {
65
+ const problems = validateRegistryName(name);
66
+ if (problems.length)
67
+ throw new SbError(`Invalid registry name. ${problems.join('. ')}`);
68
+ }
69
+ /**
70
+ * Maps a registry name to the npm name it is published under:
71
+ * `@abisena/iomt` -> `@sbext/abisena__iomt`.
72
+ */
73
+ export function toSbextName(name) {
74
+ assertRegistryName(name);
75
+ const [scope, pkg] = name.slice(1).split('/');
76
+ return `@${SBEXT_SCOPE}/${scope}__${pkg}`;
77
+ }
78
+ /**
79
+ * Maps an npm name under `@sbext` back to its registry name:
80
+ * `@sbext/abisena__iomt` -> `@abisena/iomt`.
81
+ */
82
+ export function fromSbextName(npmName) {
83
+ const prefix = `@${SBEXT_SCOPE}/`;
84
+ if (!npmName.startsWith(prefix))
85
+ throw new SbError(`"${npmName}" is not under the @${SBEXT_SCOPE} scope`);
86
+ const parts = npmName.slice(prefix.length).split('__');
87
+ if (parts.length !== 2)
88
+ throw new SbError(`"${npmName}" is not a "@${SBEXT_SCOPE}/<vendor>__<name>" name`);
89
+ const name = `@${parts[0]}/${parts[1]}`;
90
+ assertRegistryName(name);
91
+ return name;
92
+ }
93
+ /**
94
+ * Parses an import specifier that names a registry package:
95
+ * `sb:@abisena/iomt/protocol` -> `{ name: '@abisena/iomt', subpath: './protocol' }`.
96
+ * Returns `undefined` for a specifier without the `sb:` prefix, and throws for
97
+ * one that has it but does not name a valid registry package.
98
+ */
99
+ export function parseSbSpecifier(specifier) {
100
+ if (!specifier.startsWith(SB_SPECIFIER_PREFIX))
101
+ return;
102
+ const rest = specifier.slice(SB_SPECIFIER_PREFIX.length);
103
+ const m = /^(@[^/]+\/[^/]+)(\/.*)?$/.exec(rest);
104
+ if (!m)
105
+ throw new SbError(`"${specifier}" must name a registry package, "sb:@vendor/name"`);
106
+ assertRegistryName(m[1]);
107
+ return { name: m[1], subpath: m[2] ? '.' + m[2] : '.' };
108
+ }