@astrale-os/sdk 0.5.0-beta.137 → 0.5.0-beta.139

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,6 +1,7 @@
1
- import { existsSync, readFileSync, readdirSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
2
- import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
1
+ import { existsSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { join, resolve } from 'node:path';
3
3
  import { admitPackageImports, resolvePackageImport } from './imports.js';
4
+ import { confined, resolveImportTarget, resolveLocal, runtimeSpecifier } from './paths.js';
4
5
  import { declarationReferences } from './references.js';
5
6
  import { declarationEdits, declarationFacade } from './transform.js';
6
7
  /** Normalize emitted declarations without re-emitting or re-typechecking their source program. */
@@ -87,42 +88,6 @@ export function reachableDeclarations(root, requested, imports = {}) {
87
88
  }
88
89
  return [...seen].sort();
89
90
  }
90
- function resolveImportTarget(root, target) {
91
- return resolveLocal(join(root, 'entry.d.ts'), `./${target.slice('./dist/'.length)}`);
92
- }
93
- function resolveLocal(containing, specifier) {
94
- const base = resolve(dirname(containing), specifier);
95
- const candidates = [
96
- base.endsWith('.mjs') ? base.replace(/\.mjs$/u, '.d.mts') : '',
97
- base.endsWith('.cjs') ? base.replace(/\.cjs$/u, '.d.cts') : '',
98
- base.endsWith('.js') ? base.replace(/\.js$/u, '.d.ts') : '',
99
- /\.d\.(?:ts|mts|cts)$/u.test(base) ? base : '',
100
- `${base}.d.ts`,
101
- join(base, 'index.d.ts'),
102
- ];
103
- const file = candidates.find((candidate) => candidate && existsSync(candidate));
104
- if (!file)
105
- throw new TypeError(`Cannot resolve declaration reference ${specifier} from ${containing}.`);
106
- return file;
107
- }
108
- /** Point a local declaration reference at the emitted ESM module that owns it. */
109
- function runtimeSpecifier(containing, declaration) {
110
- const runtime = declaration
111
- .replace(/\.d\.mts$/u, '.mjs')
112
- .replace(/\.d\.cts$/u, '.cjs')
113
- .replace(/\.d\.ts$/u, '.js');
114
- const path = relative(dirname(containing), runtime).replaceAll('\\', '/');
115
- return path.startsWith('./') || path.startsWith('../') ? path : `./${path}`;
116
- }
117
- function confined(root, file) {
118
- const path = relative(realpathSync(root), realpathSync(file));
119
- if (path === '..' ||
120
- path.startsWith(`..${process.platform === 'win32' ? '\\' : '/'}`) ||
121
- isAbsolute(path)) {
122
- throw new TypeError(`Declaration reference escapes package root: ${file}.`);
123
- }
124
- return file;
125
- }
126
91
  function apply(source, edits) {
127
92
  let result = source;
128
93
  for (const edit of [...edits].sort((left, right) => right.start - left.start)) {
@@ -0,0 +1,5 @@
1
+ export declare function resolveImportTarget(root: string, target: string): string;
2
+ export declare function resolveLocal(containing: string, specifier: string): string;
3
+ /** Point a local declaration reference at the emitted ESM module that owns it. */
4
+ export declare function runtimeSpecifier(containing: string, declaration: string): string;
5
+ export declare function confined(root: string, file: string): string;
@@ -0,0 +1,38 @@
1
+ import { existsSync, realpathSync } from 'node:fs';
2
+ import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
3
+ export function resolveImportTarget(root, target) {
4
+ return resolveLocal(join(root, 'entry.d.ts'), `./${target.slice('./dist/'.length)}`);
5
+ }
6
+ export function resolveLocal(containing, specifier) {
7
+ const base = resolve(dirname(containing), specifier);
8
+ const candidates = [
9
+ base.endsWith('.mjs') ? base.replace(/\.mjs$/u, '.d.mts') : '',
10
+ base.endsWith('.cjs') ? base.replace(/\.cjs$/u, '.d.cts') : '',
11
+ base.endsWith('.js') ? base.replace(/\.js$/u, '.d.ts') : '',
12
+ /\.d\.(?:ts|mts|cts)$/u.test(base) ? base : '',
13
+ `${base}.d.ts`,
14
+ join(base, 'index.d.ts'),
15
+ ];
16
+ const file = candidates.find((candidate) => candidate && existsSync(candidate));
17
+ if (!file)
18
+ throw new TypeError(`Cannot resolve declaration reference ${specifier} from ${containing}.`);
19
+ return file;
20
+ }
21
+ /** Point a local declaration reference at the emitted ESM module that owns it. */
22
+ export function runtimeSpecifier(containing, declaration) {
23
+ const runtime = declaration
24
+ .replace(/\.d\.mts$/u, '.mjs')
25
+ .replace(/\.d\.cts$/u, '.cjs')
26
+ .replace(/\.d\.ts$/u, '.js');
27
+ const path = relative(dirname(containing), runtime).replaceAll('\\', '/');
28
+ return path.startsWith('./') || path.startsWith('../') ? path : `./${path}`;
29
+ }
30
+ export function confined(root, file) {
31
+ const path = relative(realpathSync(root), realpathSync(file));
32
+ if (path === '..' ||
33
+ path.startsWith(`..${process.platform === 'win32' ? '\\' : '/'}`) ||
34
+ isAbsolute(path)) {
35
+ throw new TypeError(`Declaration reference escapes package root: ${file}.`);
36
+ }
37
+ return file;
38
+ }
@@ -0,0 +1,13 @@
1
+ import type { PackageImports } from './declarations/imports.js';
2
+ export interface FinalizeDomainPackageInput {
3
+ readonly projectDir: string;
4
+ /** Additional emitted type-closure mappings supplied by the SDK compiler. */
5
+ readonly imports?: PackageImports;
6
+ }
7
+ export interface FinalizeDomainPackageResult {
8
+ readonly declarations: number;
9
+ readonly replacements: number;
10
+ readonly packages: readonly string[];
11
+ }
12
+ /** Finalize already-compiled Schema, Runtime, and Application exports through one SDK boundary. */
13
+ export declare function finalizeDomainPackage(input: FinalizeDomainPackageInput): Promise<FinalizeDomainPackageResult>;
@@ -0,0 +1,62 @@
1
+ import { readFileSync, rmSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { normalizeDeclarations, verifyDeclarationPackage } from './declarations/index.js';
4
+ import { verifyPublishedJavaScript } from './exports.js';
5
+ import { normalizeJavaScript, verifyContractJavaScript } from './javascript.js';
6
+ import { admitSelfPackage } from './self.js';
7
+ /** Finalize already-compiled Schema, Runtime, and Application exports through one SDK boundary. */
8
+ export async function finalizeDomainPackage(input) {
9
+ const projectDir = resolve(input.projectDir);
10
+ const root = resolve(projectDir, 'dist');
11
+ try {
12
+ const manifest = JSON.parse(readFileSync(resolve(projectDir, 'package.json'), 'utf8'));
13
+ const published = manifest.publishConfig ?? manifest;
14
+ const exports = published.exports ?? manifest.exports ?? {};
15
+ const targets = new Set();
16
+ const collect = (value, types = false) => {
17
+ if (typeof value === 'string') {
18
+ if (types)
19
+ targets.add(value);
20
+ }
21
+ else if (value !== null && typeof value === 'object') {
22
+ for (const [condition, nested] of Object.entries(value))
23
+ collect(nested, types || condition === 'types');
24
+ }
25
+ };
26
+ collect(published.types, true);
27
+ collect(exports);
28
+ if (targets.size === 0)
29
+ throw new TypeError('Published Domain package declares no type export.');
30
+ const entrypoints = [...targets].sort().map((target) => {
31
+ if (!target.startsWith('./dist/') || !/\.d\.(?:ts|mts|cts)$/u.test(target)) {
32
+ throw new TypeError(`Published Domain type export must target a declaration under ./dist: ${target}.`);
33
+ }
34
+ return target.slice('./dist/'.length);
35
+ });
36
+ const imports = { ...manifest.publishConfig?.imports, ...input.imports };
37
+ const self = admitSelfPackage(manifest.name, Object.keys(exports).filter((key) => key !== './package.json'));
38
+ verifyPublishedJavaScript(projectDir, manifest);
39
+ const javascriptReplacements = normalizeJavaScript(root, imports);
40
+ const javascriptPackages = verifyContractJavaScript(root, self);
41
+ const normalized = await normalizeDeclarations({ root, entrypoints, imports });
42
+ const verified = await verifyDeclarationPackage({ root, packageRoot: projectDir, entrypoints });
43
+ const packages = [...new Set([...javascriptPackages, ...verified.packages])].sort();
44
+ const declared = new Set([
45
+ ...Object.keys(manifest.dependencies ?? {}),
46
+ ...Object.keys(manifest.optionalDependencies ?? {}),
47
+ ...Object.keys(manifest.peerDependencies ?? {}),
48
+ ]);
49
+ const missing = packages.filter((name) => !declared.has(name));
50
+ if (missing.length > 0)
51
+ throw new TypeError(`Published Domain contract references undeclared packages: ${missing.join(', ')}.`);
52
+ return Object.freeze({
53
+ declarations: verified.files.length,
54
+ replacements: javascriptReplacements + normalized.replacements,
55
+ packages: Object.freeze(packages),
56
+ });
57
+ }
58
+ catch (cause) {
59
+ rmSync(root, { recursive: true, force: true });
60
+ throw cause;
61
+ }
62
+ }
@@ -1,2 +1,4 @@
1
1
  export type { PackageDomainInput, PackageDomainResult } from './packaging.js';
2
2
  export { packageDomain } from './packaging.js';
3
+ export type { FinalizeDomainPackageInput, FinalizeDomainPackageResult } from './finalize.js';
4
+ export { finalizeDomainPackage } from './finalize.js';
@@ -1 +1,2 @@
1
1
  export { packageDomain } from './packaging.js';
2
+ export { finalizeDomainPackage } from './finalize.js';
@@ -1,3 +1,6 @@
1
+ import { type PackageImports } from './declarations/imports.js';
1
2
  import { type SelfPackage } from './self.js';
2
3
  /** Verify emitted Schema JavaScript and return its complete external package set. */
3
4
  export declare function verifyContractJavaScript(root: string, self?: SelfPackage): readonly string[];
5
+ /** Normalize local runtime imports with the same package mappings as declarations. */
6
+ export declare function normalizeJavaScript(root: string, imports: PackageImports): number;
@@ -1,6 +1,8 @@
1
- import { readFileSync, readdirSync } from 'node:fs';
2
- import { join } from 'node:path';
1
+ import { readFileSync, readdirSync, writeFileSync } from 'node:fs';
2
+ import { extname, join } from 'node:path';
3
3
  import ts from 'typescript';
4
+ import { admitPackageImports, resolvePackageImport, } from './declarations/imports.js';
5
+ import { confined, resolveImportTarget, resolveLocal, runtimeSpecifier, } from './declarations/paths.js';
4
6
  import { isSelfPackageReference, packageName } from './self.js';
5
7
  /** Verify emitted Schema JavaScript and return its complete external package set. */
6
8
  export function verifyContractJavaScript(root, self) {
@@ -69,3 +71,44 @@ function moduleSpecifier(node) {
69
71
  ? argument.text
70
72
  : undefined;
71
73
  }
74
+ /** Normalize local runtime imports with the same package mappings as declarations. */
75
+ export function normalizeJavaScript(root, imports) {
76
+ const mappings = admitPackageImports(imports);
77
+ const output = new Map();
78
+ let replacements = 0;
79
+ for (const file of javascriptFiles(root)) {
80
+ const source = readFileSync(file, 'utf8');
81
+ const document = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true);
82
+ const edits = [];
83
+ const visit = (node) => {
84
+ const specifier = moduleSpecifier(node);
85
+ if (specifier !== undefined &&
86
+ (specifier.startsWith('#') || (specifier.startsWith('.') && extname(specifier) === ''))) {
87
+ const target = specifier.startsWith('#')
88
+ ? resolveImportTarget(root, resolvePackageImport(specifier, mappings))
89
+ : resolveLocal(file, specifier);
90
+ confined(root, target);
91
+ const literal = ts.isImportDeclaration(node) || ts.isExportDeclaration(node)
92
+ ? node.moduleSpecifier
93
+ : node.arguments[0];
94
+ edits.push({
95
+ start: literal.getStart(document) + 1,
96
+ end: literal.getEnd() - 1,
97
+ replacement: runtimeSpecifier(file, target),
98
+ });
99
+ }
100
+ ts.forEachChild(node, visit);
101
+ };
102
+ visit(document);
103
+ let normalized = source;
104
+ for (const edit of edits.sort((left, right) => right.start - left.start)) {
105
+ normalized = `${normalized.slice(0, edit.start)}${edit.replacement}${normalized.slice(edit.end)}`;
106
+ }
107
+ if (edits.length > 0)
108
+ output.set(file, normalized);
109
+ replacements += edits.length;
110
+ }
111
+ for (const [file, source] of output)
112
+ writeFileSync(file, source);
113
+ return replacements;
114
+ }
@@ -1,10 +1,7 @@
1
+ import { type FinalizeDomainPackageResult } from './finalize.js';
1
2
  export interface PackageDomainInput {
2
3
  readonly projectDir: string;
3
4
  }
4
- export interface PackageDomainResult {
5
- readonly declarations: number;
6
- readonly replacements: number;
7
- readonly packages: readonly string[];
8
- }
5
+ export type PackageDomainResult = FinalizeDomainPackageResult;
9
6
  /** Compile, normalize, and verify every public Schema contract in one Domain package. */
10
7
  export declare function packageDomain(input: PackageDomainInput): Promise<PackageDomainResult>;
@@ -3,10 +3,7 @@ import { rm } from 'node:fs/promises';
3
3
  import { posix, resolve } from 'node:path';
4
4
  import { admitDomainPackage } from './admission.js';
5
5
  import { compileDomainPackage } from './compile.js';
6
- import { normalizeDeclarations, verifyDeclarationPackage } from './declarations/index.js';
7
- import { verifyPublishedJavaScript } from './exports.js';
8
- import { verifyContractJavaScript } from './javascript.js';
9
- import { admitSelfPackage } from './self.js';
6
+ import { finalizeDomainPackage } from './finalize.js';
10
7
  /** Compile, normalize, and verify every public Schema contract in one Domain package. */
11
8
  export async function packageDomain(input) {
12
9
  const projectDir = resolve(input.projectDir);
@@ -16,29 +13,13 @@ export async function packageDomain(input) {
16
13
  const manifest = JSON.parse(await readFile(resolve(projectDir, 'package.json'), 'utf8'));
17
14
  admitDomainPackage(projectDir, manifest);
18
15
  const contracts = admitContractManifest(manifest);
19
- const self = admitSelfPackage(manifest.name, contracts.map(({ subpath }) => subpath));
20
16
  const { typeClosure } = compileDomainPackage(projectDir, contracts.map(({ source }) => source));
21
- const entrypoints = contracts.map(({ declaration }) => declaration);
22
- const declarationEntrypoints = entrypoints;
23
- verifyPublishedJavaScript(projectDir, manifest);
24
- const javascriptPackages = verifyContractJavaScript(root, self);
25
- const normalized = await normalizeDeclarations({
26
- root,
17
+ const result = await finalizeDomainPackage({
18
+ projectDir,
27
19
  imports: declarationImports(manifest, typeClosure),
28
- entrypoints: declarationEntrypoints,
29
- });
30
- const verified = await verifyDeclarationPackage({
31
- root,
32
- packageRoot: projectDir,
33
- entrypoints: declarationEntrypoints,
34
- });
35
- const packages = Object.freeze([...new Set([...javascriptPackages, ...verified.packages])].sort());
36
- verifyDependencies(manifest, packages);
37
- return Object.freeze({
38
- declarations: verified.files.length,
39
- replacements: normalized.replacements,
40
- packages,
41
20
  });
21
+ verifyUsedDependencies(manifest, result.packages);
22
+ return result;
42
23
  }
43
24
  catch (cause) {
44
25
  await rm(root, { recursive: true, force: true });
@@ -98,7 +79,6 @@ function admitContractManifest(manifest) {
98
79
  return Object.freeze({
99
80
  subpath,
100
81
  source: source.types.slice(2),
101
- declaration: expected.types.slice('./dist/'.length),
102
82
  });
103
83
  });
104
84
  if (new Set(contracts.map(({ source }) => source)).size !== contracts.length) {
@@ -177,17 +157,13 @@ function publishedTargets(source) {
177
157
  import: `./dist/${stem}.js`,
178
158
  });
179
159
  }
180
- function verifyDependencies(manifest, packages) {
160
+ function verifyUsedDependencies(manifest, packages) {
181
161
  const declared = new Set([
182
162
  ...Object.keys(manifest.dependencies ?? {}),
183
163
  ...Object.keys(manifest.optionalDependencies ?? {}),
184
164
  ...Object.keys(manifest.peerDependencies ?? {}),
185
165
  ]);
186
166
  const used = new Set(packages);
187
- const missing = packages.filter((name) => !declared.has(name));
188
- if (missing.length > 0) {
189
- throw new TypeError(`Published Domain contract references undeclared packages: ${missing.join(', ')}.`);
190
- }
191
167
  const unused = [...declared].filter((name) => !used.has(name)).sort();
192
168
  if (unused.length > 0) {
193
169
  throw new TypeError(`Domain contract declares unused production packages: ${unused.join(', ')}.`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrale-os/sdk",
3
- "version": "0.5.0-beta.137",
3
+ "version": "0.5.0-beta.139",
4
4
  "description": "Schema-first SDK for defining, composing, and deploying Astrale domains",
5
5
  "keywords": [
6
6
  "astrale",