@aelionsdk/material-sdk 0.1.0-beta.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/LICENSE +21 -0
- package/README.md +7 -0
- package/dist/bundled-schemas.d.ts +5 -0
- package/dist/bundled-schemas.d.ts.map +1 -0
- package/dist/bundled-schemas.js +3 -0
- package/dist/canonical.d.ts +6 -0
- package/dist/canonical.d.ts.map +1 -0
- package/dist/canonical.js +33 -0
- package/dist/catalog.d.ts +24 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +47 -0
- package/dist/composition.d.ts +42 -0
- package/dist/composition.d.ts.map +1 -0
- package/dist/composition.js +169 -0
- package/dist/definition-builder.d.ts +48 -0
- package/dist/definition-builder.d.ts.map +1 -0
- package/dist/definition-builder.js +159 -0
- package/dist/graph-builder.d.ts +45 -0
- package/dist/graph-builder.d.ts.map +1 -0
- package/dist/graph-builder.js +125 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/lab.d.ts +48 -0
- package/dist/lab.d.ts.map +1 -0
- package/dist/lab.js +169 -0
- package/dist/migration.d.ts +22 -0
- package/dist/migration.d.ts.map +1 -0
- package/dist/migration.js +59 -0
- package/dist/package-limits.d.ts +30 -0
- package/dist/package-limits.d.ts.map +1 -0
- package/dist/package-limits.js +202 -0
- package/dist/package-shape.d.ts +6 -0
- package/dist/package-shape.d.ts.map +1 -0
- package/dist/package-shape.js +547 -0
- package/dist/package-snapshot.d.ts +5 -0
- package/dist/package-snapshot.d.ts.map +1 -0
- package/dist/package-snapshot.js +31 -0
- package/dist/package.d.ts +12 -0
- package/dist/package.d.ts.map +1 -0
- package/dist/package.js +244 -0
- package/dist/registry.d.ts +32 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +150 -0
- package/dist/schema-validation.d.ts +4 -0
- package/dist/schema-validation.d.ts.map +1 -0
- package/dist/schema-validation.js +36 -0
- package/dist/security.d.ts +70 -0
- package/dist/security.d.ts.map +1 -0
- package/dist/security.js +161 -0
- package/dist/types.d.ts +245 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/validation.d.ts +6 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +124 -0
- package/dist/zip.d.ts +4 -0
- package/dist/zip.d.ts.map +1 -0
- package/dist/zip.js +172 -0
- package/package.json +46 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
export const MATERIAL_PACKAGE_MAX_FILES = 256;
|
|
2
|
+
export const MATERIAL_PACKAGE_MAX_FILE_BYTES = 32 * 1024 * 1024;
|
|
3
|
+
export const MATERIAL_PACKAGE_MAX_MANIFEST_BYTES = 256 * 1024;
|
|
4
|
+
export const MATERIAL_PACKAGE_MAX_BYTES = 64 * 1024 * 1024;
|
|
5
|
+
export const MATERIAL_PACKAGE_MAX_ARCHIVE_BYTES = 65 * 1024 * 1024;
|
|
6
|
+
const ZIP_UINT16_MAX = 0xffff;
|
|
7
|
+
const ZIP_UINT32_MAX = 0xffffffff;
|
|
8
|
+
const typedArrayPrototype = Object.getPrototypeOf(Uint8Array.prototype);
|
|
9
|
+
/* eslint-disable @typescript-eslint/unbound-method -- captured built-in accessors are invoked with Reflect.apply below */
|
|
10
|
+
const typedArrayByteLengthGetter_ = Object.getOwnPropertyDescriptor(typedArrayPrototype, 'byteLength')?.get;
|
|
11
|
+
const typedArrayNameGetter_ = Object.getOwnPropertyDescriptor(typedArrayPrototype, Symbol.toStringTag)?.get;
|
|
12
|
+
const mapSizeGetter_ = Object.getOwnPropertyDescriptor(Map.prototype, 'size')?.get;
|
|
13
|
+
/* eslint-enable @typescript-eslint/unbound-method */
|
|
14
|
+
if (typedArrayByteLengthGetter_ === undefined ||
|
|
15
|
+
typedArrayNameGetter_ === undefined ||
|
|
16
|
+
mapSizeGetter_ === undefined) {
|
|
17
|
+
throw new Error('Required built-in collection accessors are unavailable');
|
|
18
|
+
}
|
|
19
|
+
const typedArrayByteLengthGetter = typedArrayByteLengthGetter_;
|
|
20
|
+
const typedArrayNameGetter = typedArrayNameGetter_;
|
|
21
|
+
const mapSizeGetter = mapSizeGetter_;
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method -- receiver supplied via Reflect.apply
|
|
23
|
+
const mapEntries = Map.prototype.entries;
|
|
24
|
+
const pathEncoder = new TextEncoder();
|
|
25
|
+
export const DEFAULT_MATERIAL_PACKAGE_BYTE_LIMITS = Object.freeze({
|
|
26
|
+
maxFiles: MATERIAL_PACKAGE_MAX_FILES,
|
|
27
|
+
maxFileBytes: MATERIAL_PACKAGE_MAX_FILE_BYTES,
|
|
28
|
+
maxManifestBytes: MATERIAL_PACKAGE_MAX_MANIFEST_BYTES,
|
|
29
|
+
maxPackageBytes: MATERIAL_PACKAGE_MAX_BYTES,
|
|
30
|
+
maxArchiveBytes: MATERIAL_PACKAGE_MAX_ARCHIVE_BYTES,
|
|
31
|
+
});
|
|
32
|
+
function invalid(message) {
|
|
33
|
+
throw new TypeError(`MATERIAL_PACKAGE_INVALID: ${message}`);
|
|
34
|
+
}
|
|
35
|
+
function transportField(record, key) {
|
|
36
|
+
if (record === null || typeof record !== 'object')
|
|
37
|
+
invalid('package must be an object');
|
|
38
|
+
let descriptor;
|
|
39
|
+
try {
|
|
40
|
+
descriptor = Object.getOwnPropertyDescriptor(record, key);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
invalid(`package field ${key} is inaccessible`);
|
|
44
|
+
}
|
|
45
|
+
if (descriptor === undefined || !('value' in descriptor)) {
|
|
46
|
+
invalid(`package field ${key} must be an own data property`);
|
|
47
|
+
}
|
|
48
|
+
return descriptor.value;
|
|
49
|
+
}
|
|
50
|
+
export function materialPackageBudgetExceeded(scope, actual, limit) {
|
|
51
|
+
throw new RangeError(`MATERIAL_PACKAGE_BUDGET_EXCEEDED: ${scope} is ${actual} bytes; limit is ${limit} bytes`);
|
|
52
|
+
}
|
|
53
|
+
function limit(name, value, maximum) {
|
|
54
|
+
if (!Number.isSafeInteger(value) || value < 0 || value > maximum) {
|
|
55
|
+
throw new RangeError(`MATERIAL_PACKAGE_LIMIT_INVALID: ${name} must be a non-negative safe integer no greater than ${maximum}`);
|
|
56
|
+
}
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
export function resolveMaterialPackageByteLimits(options = {}) {
|
|
60
|
+
return Object.freeze({
|
|
61
|
+
maxFiles: limit('maxFiles', options.maxFiles ?? DEFAULT_MATERIAL_PACKAGE_BYTE_LIMITS.maxFiles, ZIP_UINT16_MAX),
|
|
62
|
+
maxFileBytes: limit('maxFileBytes', options.maxFileBytes ?? DEFAULT_MATERIAL_PACKAGE_BYTE_LIMITS.maxFileBytes, ZIP_UINT32_MAX),
|
|
63
|
+
maxManifestBytes: limit('maxManifestBytes', options.maxManifestBytes ?? DEFAULT_MATERIAL_PACKAGE_BYTE_LIMITS.maxManifestBytes, ZIP_UINT32_MAX),
|
|
64
|
+
maxPackageBytes: limit('maxPackageBytes', options.maxPackageBytes ?? DEFAULT_MATERIAL_PACKAGE_BYTE_LIMITS.maxPackageBytes, ZIP_UINT32_MAX),
|
|
65
|
+
maxArchiveBytes: limit('maxArchiveBytes', options.maxArchiveBytes ?? DEFAULT_MATERIAL_PACKAGE_BYTE_LIMITS.maxArchiveBytes, ZIP_UINT32_MAX),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
export function addMaterialPackageBytes(total, value, scope) {
|
|
69
|
+
if (!Number.isSafeInteger(total) || total < 0 || !Number.isSafeInteger(value) || value < 0) {
|
|
70
|
+
invalid(`${scope} byte length is not a non-negative safe integer`);
|
|
71
|
+
}
|
|
72
|
+
const next = total + value;
|
|
73
|
+
if (!Number.isSafeInteger(next))
|
|
74
|
+
invalid(`${scope} byte total exceeds safe integer range`);
|
|
75
|
+
return next;
|
|
76
|
+
}
|
|
77
|
+
export function materialUint8ArrayByteLength(value, scope) {
|
|
78
|
+
let name;
|
|
79
|
+
let byteLength;
|
|
80
|
+
try {
|
|
81
|
+
name = Reflect.apply(typedArrayNameGetter, value, []);
|
|
82
|
+
byteLength = Reflect.apply(typedArrayByteLengthGetter, value, []);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
invalid(`${scope} must be a genuine Uint8Array`);
|
|
86
|
+
}
|
|
87
|
+
if (name !== 'Uint8Array' || !Number.isSafeInteger(byteLength) || Number(byteLength) < 0) {
|
|
88
|
+
invalid(`${scope} must be a genuine Uint8Array with a safe byte length`);
|
|
89
|
+
}
|
|
90
|
+
return Number(byteLength);
|
|
91
|
+
}
|
|
92
|
+
export function copyMaterialBytes(value, expectedByteLength, scope) {
|
|
93
|
+
const actualByteLength = materialUint8ArrayByteLength(value, scope);
|
|
94
|
+
if (actualByteLength !== expectedByteLength)
|
|
95
|
+
invalid(`${scope} changed during snapshot`);
|
|
96
|
+
const output = new Uint8Array(actualByteLength);
|
|
97
|
+
try {
|
|
98
|
+
Uint8Array.prototype.set.call(output, value);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
invalid(`${scope} could not be snapshotted`);
|
|
102
|
+
}
|
|
103
|
+
return output;
|
|
104
|
+
}
|
|
105
|
+
function materialMapSize(value) {
|
|
106
|
+
let size;
|
|
107
|
+
try {
|
|
108
|
+
size = Reflect.apply(mapSizeGetter, value, []);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
invalid('files must be a genuine Map');
|
|
112
|
+
}
|
|
113
|
+
if (!Number.isSafeInteger(size) || Number(size) < 0)
|
|
114
|
+
invalid('files has an invalid size');
|
|
115
|
+
return Number(size);
|
|
116
|
+
}
|
|
117
|
+
function materialMapEntries(value) {
|
|
118
|
+
try {
|
|
119
|
+
return Reflect.apply(mapEntries, value, []);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
invalid('files must be a genuine Map');
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function isWellFormedUnicode(value) {
|
|
126
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
127
|
+
const codeUnit = value.charCodeAt(index);
|
|
128
|
+
if (codeUnit >= 0xd800 && codeUnit <= 0xdbff) {
|
|
129
|
+
const trailing = value.charCodeAt(index + 1);
|
|
130
|
+
if (!(trailing >= 0xdc00 && trailing <= 0xdfff))
|
|
131
|
+
return false;
|
|
132
|
+
index += 1;
|
|
133
|
+
}
|
|
134
|
+
else if (codeUnit >= 0xdc00 && codeUnit <= 0xdfff) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
export function validMaterialPackagePath(path) {
|
|
141
|
+
if (typeof path !== 'string' || !isWellFormedUnicode(path))
|
|
142
|
+
return false;
|
|
143
|
+
const segments = path.split('/');
|
|
144
|
+
return (path.length > 0 &&
|
|
145
|
+
pathEncoder.encode(path).byteLength <= 512 &&
|
|
146
|
+
!path.startsWith('/') &&
|
|
147
|
+
!path.includes('\\') &&
|
|
148
|
+
!path.includes('\0') &&
|
|
149
|
+
segments.every(segment => segment.length > 0 && segment !== '.' && segment !== '..'));
|
|
150
|
+
}
|
|
151
|
+
export function inspectPackedMaterialPackage(packed, options = {}) {
|
|
152
|
+
const integrity = transportField(packed, 'integrity');
|
|
153
|
+
if (typeof integrity !== 'string' || !/^sha256:[0-9a-f]{64}$/u.test(integrity)) {
|
|
154
|
+
invalid('integrity must be a sha256 digest');
|
|
155
|
+
}
|
|
156
|
+
const limits = resolveMaterialPackageByteLimits(options);
|
|
157
|
+
const manifestBytes = transportField(packed, 'manifestBytes');
|
|
158
|
+
const manifestByteLength = materialUint8ArrayByteLength(manifestBytes, 'manifestBytes');
|
|
159
|
+
if (manifestByteLength > limits.maxManifestBytes) {
|
|
160
|
+
materialPackageBudgetExceeded('manifestBytes', manifestByteLength, limits.maxManifestBytes);
|
|
161
|
+
}
|
|
162
|
+
const archiveBytes = transportField(packed, 'archiveBytes');
|
|
163
|
+
const archiveByteLength = materialUint8ArrayByteLength(archiveBytes, 'archiveBytes');
|
|
164
|
+
if (archiveByteLength > limits.maxArchiveBytes) {
|
|
165
|
+
materialPackageBudgetExceeded('archiveBytes', archiveByteLength, limits.maxArchiveBytes);
|
|
166
|
+
}
|
|
167
|
+
const packedFiles = transportField(packed, 'files');
|
|
168
|
+
const size = materialMapSize(packedFiles);
|
|
169
|
+
if (size > limits.maxFiles) {
|
|
170
|
+
throw new RangeError(`MATERIAL_PACKAGE_BUDGET_EXCEEDED: package has ${size} files; limit is ${limits.maxFiles}`);
|
|
171
|
+
}
|
|
172
|
+
const files = [];
|
|
173
|
+
let packageBytes = 0;
|
|
174
|
+
for (const [path, data] of materialMapEntries(packedFiles)) {
|
|
175
|
+
if (typeof path !== 'string' || !validMaterialPackagePath(path)) {
|
|
176
|
+
invalid('unsafe file path');
|
|
177
|
+
}
|
|
178
|
+
if (path === 'signature.json')
|
|
179
|
+
invalid('signature.json is a reserved payload path');
|
|
180
|
+
const byteLength = materialUint8ArrayByteLength(data, `file ${path}`);
|
|
181
|
+
const fileLimit = path === 'manifest.json' ? limits.maxManifestBytes : limits.maxFileBytes;
|
|
182
|
+
if (byteLength > fileLimit)
|
|
183
|
+
materialPackageBudgetExceeded(`file ${path}`, byteLength, fileLimit);
|
|
184
|
+
packageBytes = addMaterialPackageBytes(packageBytes, byteLength, `file ${path}`);
|
|
185
|
+
if (packageBytes > limits.maxPackageBytes) {
|
|
186
|
+
materialPackageBudgetExceeded('package files', packageBytes, limits.maxPackageBytes);
|
|
187
|
+
}
|
|
188
|
+
files.push({ path, data: data, byteLength });
|
|
189
|
+
}
|
|
190
|
+
if (files.length !== size)
|
|
191
|
+
invalid('files changed during budget inspection');
|
|
192
|
+
return {
|
|
193
|
+
limits,
|
|
194
|
+
integrity: integrity,
|
|
195
|
+
manifestBytes,
|
|
196
|
+
manifestByteLength,
|
|
197
|
+
archiveBytes,
|
|
198
|
+
archiveByteLength,
|
|
199
|
+
files,
|
|
200
|
+
packageBytes,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { MaterialGraph } from '@aelionsdk/material-compiler';
|
|
2
|
+
import { type MaterialDefinition, type MaterialPackageManifest } from './types.js';
|
|
3
|
+
export declare function assertMaterialPackageManifestShape(value: unknown): asserts value is MaterialPackageManifest;
|
|
4
|
+
export declare function assertMaterialDefinitionShape(value: unknown): asserts value is MaterialDefinition;
|
|
5
|
+
export declare function assertMaterialGraphShape(value: unknown): asserts value is MaterialGraph;
|
|
6
|
+
//# sourceMappingURL=package-shape.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package-shape.d.ts","sourceRoot":"","sources":["../src/package-shape.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAElE,OAAO,EAML,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAC7B,MAAM,YAAY,CAAC;AAkapB,wBAAgB,kCAAkC,CAChD,KAAK,EAAE,OAAO,GACb,OAAO,CAAC,KAAK,IAAI,uBAAuB,CAsE1C;AAED,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,kBAAkB,CAuMjG;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,aAAa,CAyCvF"}
|