@airalogy/aira-core 0.2.0 → 0.3.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/README.md +27 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +318 -181
- package/package.json +1 -1
- package/src/index.ts +266 -0
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@ Core TypeScript parser and validator for Airalogy `.aira` archives.
|
|
|
4
4
|
|
|
5
5
|
It opens `.aira` files in browser-compatible JavaScript, reads `_airalogy_archive/manifest.json`, lists archive members, loads JSON/text payloads, and validates manifest references, Record payload structure, Record hashes, Protocol file hashes, and offline blob hashes.
|
|
6
6
|
|
|
7
|
+
It can also create single-Protocol `.aira` archives in the browser. This is useful for editors that let users attach protocol-local assets such as `files/workflow-diagram.svg` and reference them from AIMD `fig` blocks.
|
|
8
|
+
|
|
7
9
|
Supported archive kinds are `protocol`, `protocols`, and `records`.
|
|
8
10
|
|
|
9
11
|
Example archives covering these kinds are available in `examples/aira/`.
|
|
@@ -19,4 +21,29 @@ const validation = await archive.validate()
|
|
|
19
21
|
const manifest = archive.manifest
|
|
20
22
|
```
|
|
21
23
|
|
|
24
|
+
Create a Protocol archive with protocol-local figure files:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { createProtocolAiraArchive } from '@airalogy/aira-core'
|
|
28
|
+
|
|
29
|
+
const bytes = await createProtocolAiraArchive({
|
|
30
|
+
aimd: [
|
|
31
|
+
'# Figure Protocol',
|
|
32
|
+
'',
|
|
33
|
+
'```fig',
|
|
34
|
+
'id: workflow_diagram',
|
|
35
|
+
'src: files/workflow-diagram.svg',
|
|
36
|
+
'title: Workflow Diagram',
|
|
37
|
+
'```',
|
|
38
|
+
'',
|
|
39
|
+
].join('\n'),
|
|
40
|
+
files: [
|
|
41
|
+
{
|
|
42
|
+
path: 'files/workflow-diagram.svg',
|
|
43
|
+
data: svgFile,
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
})
|
|
47
|
+
```
|
|
48
|
+
|
|
22
49
|
`.aira` archives are standard ZIP containers. The core package keeps reading independent from the full Airalogy platform, database, or execution engine.
|
package/dist/index.d.ts
CHANGED
|
@@ -91,6 +91,18 @@ export interface AiraValidationResult {
|
|
|
91
91
|
ok: boolean;
|
|
92
92
|
issues: string[];
|
|
93
93
|
}
|
|
94
|
+
export type AiraArchiveEntryData = string | Blob | ArrayBuffer | ArrayBufferView;
|
|
95
|
+
export interface CreateProtocolAiraArchiveFile {
|
|
96
|
+
path: string;
|
|
97
|
+
data: AiraArchiveEntryData;
|
|
98
|
+
}
|
|
99
|
+
export interface CreateProtocolAiraArchiveOptions {
|
|
100
|
+
aimd: string;
|
|
101
|
+
protocol?: Pick<AiraProtocolManifest, 'protocol_id' | 'protocol_version' | 'protocol_name' | 'entrypoint'>;
|
|
102
|
+
files?: CreateProtocolAiraArchiveFile[];
|
|
103
|
+
createdAt?: string;
|
|
104
|
+
}
|
|
105
|
+
export declare function createProtocolAiraArchive(options: CreateProtocolAiraArchiveOptions): Promise<Uint8Array>;
|
|
94
106
|
export declare class AiraArchive {
|
|
95
107
|
readonly bytes: Uint8Array;
|
|
96
108
|
readonly entries: AiraEntry[];
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB,oCAAoC,CAAA;AACnE,eAAO,MAAM,mBAAmB,qBAAqB,CAAA;AACrD,eAAO,MAAM,sBAAsB,oBAAoB,CAAA;AACvD,eAAO,MAAM,8BAA8B,IAAI,CAAA;AAE/C,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,WAAW,GAAG,SAAS,CAAA;AAElE,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,cAAc,EAAE,MAAM,CAAA;IACtB,gBAAgB,EAAE,MAAM,CAAA;IACxB,iBAAiB,EAAE,MAAM,CAAA;IACzB,iBAAiB,EAAE,MAAM,CAAA;CAC1B;AAED,MAAM,WAAW,oBAAoB;IACnC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACrC;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,cAAc,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IACvC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,sBAAsB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACvC;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAClC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IACzC,IAAI,CAAC,EAAE;QACL,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QAC7B,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QAC9B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QAC/B,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QAC9B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KACvB,CAAA;IACD,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;IACtC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAA;IACf,YAAY,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC3B;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,eAAe,CAAA;IACrB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,oBAAoB,CAAA;IAC/B,OAAO,CAAC,EAAE,kBAAkB,EAAE,CAAA;IAC9B,SAAS,CAAC,EAAE,oBAAoB,EAAE,CAAA;IAClC,KAAK,CAAC,EAAE,gBAAgB,EAAE,CAAA;IAC1B,KAAK,CAAC,EAAE,gBAAgB,EAAE,CAAA;IAC1B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,eAAe,CAAA;IACrB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,aAAa,EAAE,MAAM,CAAA;IACrB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,OAAO,CAAA;IACX,MAAM,EAAE,MAAM,EAAE,CAAA;CACjB;AAED,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,IAAI,GAAG,WAAW,GAAG,eAAe,CAAA;AAEhF,MAAM,WAAW,6BAA6B;IAC5C,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,oBAAoB,CAAA;CAC3B;AAED,MAAM,WAAW,gCAAgC;IAC/C,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,IAAI,CAAC,oBAAoB,EAAE,aAAa,GAAG,kBAAkB,GAAG,eAAe,GAAG,YAAY,CAAC,CAAA;IAC1G,KAAK,CAAC,EAAE,6BAA6B,EAAE,CAAA;IACvC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AA+YD,wBAAsB,yBAAyB,CAAC,OAAO,EAAE,gCAAgC,GAAG,OAAO,CAAC,UAAU,CAAC,CA0C9G;AAED,qBAAa,WAAW;IACtB,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAA;IAC1B,QAAQ,CAAC,OAAO,EAAE,SAAS,EAAE,CAAA;IAC7B,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAA;IAE/B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA+B;IAExD,OAAO;WAOM,IAAI,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,GAAG,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC;IA0B/E,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI1B,OAAO,IAAI,WAAW;IAoBhB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAkB5C,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAIvC,QAAQ,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAI/C,QAAQ,IAAI,OAAO,CAAC,oBAAoB,CAAC;YAmCjC,gBAAgB;YAmChB,oBAAoB;YAgCpB,eAAe;YAkDf,aAAa;IA2E3B,OAAO,CAAC,sBAAsB;CA6B/B;AAED,wBAAsB,eAAe,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,GAAG,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAElG;AAED,wBAAsB,sBAAsB,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,GAAG,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAEzG;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEtD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAEpD"}
|
package/dist/index.js
CHANGED
|
@@ -1,112 +1,248 @@
|
|
|
1
|
-
const
|
|
2
|
-
function
|
|
3
|
-
return
|
|
1
|
+
const m = "_airalogy_archive/manifest.json", A = "airalogy.archive", B = "airalogy.record", Q = 1, E = new TextDecoder("utf-8"), w = new TextEncoder(), M = /^[0-9a-f]{64}$/, O = 0, L = 2048, j = 4294967295;
|
|
2
|
+
function d(e, t) {
|
|
3
|
+
return e.getUint16(t, !0);
|
|
4
4
|
}
|
|
5
|
-
function
|
|
6
|
-
return
|
|
5
|
+
function p(e, t) {
|
|
6
|
+
return e.getUint32(t, !0);
|
|
7
7
|
}
|
|
8
|
-
function
|
|
9
|
-
return
|
|
8
|
+
function H(e) {
|
|
9
|
+
return E.decode(e);
|
|
10
10
|
}
|
|
11
|
-
function v(
|
|
12
|
-
return !
|
|
11
|
+
function v(e) {
|
|
12
|
+
return !e || e.startsWith("/") ? `Archive member '${e}' uses an absolute or empty path.` : e.split("/").some((t) => t === "..") ? `Archive member '${e}' escapes the archive root.` : null;
|
|
13
13
|
}
|
|
14
|
-
function
|
|
15
|
-
|
|
14
|
+
function P(e, t) {
|
|
15
|
+
const n = e.replace(/\\/g, "/").split("/").filter((r) => r && r !== ".").join("/"), i = v(n);
|
|
16
|
+
if (i || e.startsWith("/") || e.replace(/\\/g, "/").split("/").some((r) => r === ".."))
|
|
17
|
+
throw new Error(i ?? `${t} '${e}' is not a safe relative archive path.`);
|
|
18
|
+
if (n === m || n.startsWith("_airalogy_archive/"))
|
|
19
|
+
throw new Error(`${t} '${e}' conflicts with Airalogy archive metadata.`);
|
|
20
|
+
return n;
|
|
16
21
|
}
|
|
17
|
-
function
|
|
18
|
-
|
|
19
|
-
|
|
22
|
+
function y(e) {
|
|
23
|
+
return !!e && typeof e == "object" && !Array.isArray(e);
|
|
24
|
+
}
|
|
25
|
+
function N(e, t) {
|
|
26
|
+
const n = [];
|
|
27
|
+
if (!y(e))
|
|
20
28
|
return [`${t} must be a JSON object.`];
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
if (
|
|
24
|
-
return
|
|
25
|
-
|
|
26
|
-
for (const
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
+
e.format !== void 0 && e.format !== B && n.push(`${t} format must be '${B}' when present.`), e.schema_version !== void 0 && e.schema_version !== 1 && n.push(`${t} schema_version must be 1 when present.`), e.record_id !== void 0 && e.record_id !== null && (typeof e.record_id != "string" || e.record_id.length === 0) && n.push(`${t} record_id must be a non-empty string when present.`), e.airalogy_record_id !== void 0 && e.airalogy_record_id !== null && (typeof e.airalogy_record_id != "string" || e.airalogy_record_id.length === 0) && n.push(`${t} airalogy_record_id must be a non-empty string when present.`);
|
|
30
|
+
const i = e.record_version;
|
|
31
|
+
if (i != null && (typeof i != "number" || !Number.isInteger(i) || i < 1) && n.push(`${t} record_version must be a positive integer when present.`), e.metadata !== void 0 && e.metadata !== null && !y(e.metadata) && n.push(`${t} metadata must be an object when present.`), !y(e.data))
|
|
32
|
+
return n.push(`${t} data must be an object.`), n;
|
|
33
|
+
y(e.data.var) || n.push(`${t} data.var must be an object.`);
|
|
34
|
+
for (const r of ["step", "check", "quiz"]) {
|
|
35
|
+
const s = e.data[r];
|
|
36
|
+
s != null && !y(s) && n.push(`${t} data.${r} must be an object when present.`);
|
|
29
37
|
}
|
|
30
|
-
if (
|
|
31
|
-
if (!Array.isArray(
|
|
32
|
-
|
|
38
|
+
if (e.files !== void 0)
|
|
39
|
+
if (!Array.isArray(e.files))
|
|
40
|
+
n.push(`${t} files must be a list when present.`);
|
|
33
41
|
else
|
|
34
|
-
for (const [
|
|
35
|
-
if (!
|
|
36
|
-
|
|
42
|
+
for (const [r, s] of e.files.entries()) {
|
|
43
|
+
if (!y(s)) {
|
|
44
|
+
n.push(`${t} files[${r + 1}] must be an object.`);
|
|
37
45
|
continue;
|
|
38
46
|
}
|
|
39
|
-
["file_id", "source_uri", "blob_id"].some((
|
|
47
|
+
["file_id", "source_uri", "blob_id"].some((o) => typeof s[o] == "string" && s[o]) || n.push(`${t} files[${r + 1}] must include file_id, source_uri, or blob_id.`);
|
|
40
48
|
}
|
|
41
|
-
return
|
|
49
|
+
return n;
|
|
42
50
|
}
|
|
43
|
-
function
|
|
44
|
-
const t = Math.max(0,
|
|
45
|
-
for (let
|
|
46
|
-
if (
|
|
47
|
-
return
|
|
51
|
+
function T(e) {
|
|
52
|
+
const t = Math.max(0, e.length - 65535 - 22);
|
|
53
|
+
for (let n = e.length - 22; n >= t; n -= 1)
|
|
54
|
+
if (e[n] === 80 && e[n + 1] === 75 && e[n + 2] === 5 && e[n + 3] === 6)
|
|
55
|
+
return n;
|
|
48
56
|
throw new Error("Archive is not a valid zip file: EOCD not found.");
|
|
49
57
|
}
|
|
50
|
-
function
|
|
51
|
-
const t = new DataView(
|
|
52
|
-
let
|
|
53
|
-
for (let a = 0; a <
|
|
54
|
-
if (
|
|
55
|
-
throw new Error(`Archive central directory is invalid at offset ${
|
|
56
|
-
const c =
|
|
57
|
-
if (
|
|
58
|
-
throw new Error(`Archive local header is invalid for '${
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
name:
|
|
58
|
+
function C(e) {
|
|
59
|
+
const t = new DataView(e.buffer, e.byteOffset, e.byteLength), n = T(e), i = d(t, n + 10), r = p(t, n + 16), s = [];
|
|
60
|
+
let o = r;
|
|
61
|
+
for (let a = 0; a < i; a += 1) {
|
|
62
|
+
if (p(t, o) !== 33639248)
|
|
63
|
+
throw new Error(`Archive central directory is invalid at offset ${o}.`);
|
|
64
|
+
const c = d(t, o + 10), f = p(t, o + 20), h = p(t, o + 24), l = d(t, o + 28), u = d(t, o + 30), $ = d(t, o + 32), g = p(t, o + 42), S = o + 46, R = H(e.slice(S, S + l));
|
|
65
|
+
if (p(t, g) !== 67324752)
|
|
66
|
+
throw new Error(`Archive local header is invalid for '${R}'.`);
|
|
67
|
+
const D = d(t, g + 26), x = d(t, g + 28), I = g + 30 + D + x;
|
|
68
|
+
s.push({
|
|
69
|
+
name: R,
|
|
62
70
|
compressionMethod: c,
|
|
63
71
|
compressedSize: f,
|
|
64
|
-
uncompressedSize:
|
|
65
|
-
localHeaderOffset:
|
|
66
|
-
compressedDataStart:
|
|
67
|
-
}),
|
|
72
|
+
uncompressedSize: h,
|
|
73
|
+
localHeaderOffset: g,
|
|
74
|
+
compressedDataStart: I
|
|
75
|
+
}), o = S + l + u + $;
|
|
68
76
|
}
|
|
69
|
-
return
|
|
77
|
+
return s.filter((a) => !a.name.endsWith("/"));
|
|
70
78
|
}
|
|
71
|
-
async function
|
|
79
|
+
async function k(e) {
|
|
72
80
|
const t = globalThis.DecompressionStream;
|
|
73
81
|
if (!t)
|
|
74
82
|
throw new Error("This browser does not support DecompressionStream.");
|
|
75
|
-
const
|
|
76
|
-
return new Uint8Array(
|
|
83
|
+
const n = new Blob([e]).stream().pipeThrough(new t("deflate-raw")), i = await new Response(n).arrayBuffer();
|
|
84
|
+
return new Uint8Array(i);
|
|
85
|
+
}
|
|
86
|
+
async function U(e) {
|
|
87
|
+
const t = await crypto.subtle.digest("SHA-256", e);
|
|
88
|
+
return Array.from(new Uint8Array(t)).map((n) => n.toString(16).padStart(2, "0")).join("");
|
|
89
|
+
}
|
|
90
|
+
function z() {
|
|
91
|
+
const e = new Uint32Array(256);
|
|
92
|
+
for (let t = 0; t < 256; t += 1) {
|
|
93
|
+
let n = t;
|
|
94
|
+
for (let i = 0; i < 8; i += 1)
|
|
95
|
+
n = n & 1 ? 3988292384 ^ n >>> 1 : n >>> 1;
|
|
96
|
+
e[t] = n >>> 0;
|
|
97
|
+
}
|
|
98
|
+
return e;
|
|
99
|
+
}
|
|
100
|
+
const F = z();
|
|
101
|
+
function V(e) {
|
|
102
|
+
let t = 4294967295;
|
|
103
|
+
for (const n of e)
|
|
104
|
+
t = F[(t ^ n) & 255] ^ t >>> 8;
|
|
105
|
+
return (t ^ 4294967295) >>> 0;
|
|
106
|
+
}
|
|
107
|
+
function Z(e) {
|
|
108
|
+
const t = e.reduce((r, s) => r + s.byteLength, 0), n = new Uint8Array(t);
|
|
109
|
+
let i = 0;
|
|
110
|
+
for (const r of e)
|
|
111
|
+
n.set(r, i), i += r.byteLength;
|
|
112
|
+
return n;
|
|
113
|
+
}
|
|
114
|
+
function J(e) {
|
|
115
|
+
const t = Math.max(1980, Math.min(2107, e.getFullYear())), n = e.getMonth() + 1, i = e.getDate(), r = e.getHours(), s = e.getMinutes(), o = Math.floor(e.getSeconds() / 2);
|
|
116
|
+
return {
|
|
117
|
+
time: r << 11 | s << 5 | o,
|
|
118
|
+
date: t - 1980 << 9 | n << 5 | i
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function b(e, t) {
|
|
122
|
+
if (!Number.isInteger(e) || e < 0 || e > j)
|
|
123
|
+
throw new Error(`${t} exceeds the ZIP32 size limit.`);
|
|
124
|
+
}
|
|
125
|
+
function W(e, t) {
|
|
126
|
+
const n = w.encode(e.name);
|
|
127
|
+
b(e.bytes.byteLength, `Archive member '${e.name}'`);
|
|
128
|
+
const i = new Uint8Array(30 + n.byteLength), r = new DataView(i.buffer, i.byteOffset, i.byteLength);
|
|
129
|
+
return r.setUint32(0, 67324752, !0), r.setUint16(4, 20, !0), r.setUint16(6, L, !0), r.setUint16(8, O, !0), r.setUint16(10, t.time, !0), r.setUint16(12, t.date, !0), r.setUint32(14, e.crc32, !0), r.setUint32(18, e.bytes.byteLength, !0), r.setUint32(22, e.bytes.byteLength, !0), r.setUint16(26, n.byteLength, !0), r.setUint16(28, 0, !0), i.set(n, 30), i;
|
|
130
|
+
}
|
|
131
|
+
function G(e, t) {
|
|
132
|
+
const n = w.encode(e.name), i = new Uint8Array(46 + n.byteLength), r = new DataView(i.buffer, i.byteOffset, i.byteLength);
|
|
133
|
+
return r.setUint32(0, 33639248, !0), r.setUint16(4, 20, !0), r.setUint16(6, 20, !0), r.setUint16(8, L, !0), r.setUint16(10, O, !0), r.setUint16(12, t.time, !0), r.setUint16(14, t.date, !0), r.setUint32(16, e.crc32, !0), r.setUint32(20, e.bytes.byteLength, !0), r.setUint32(24, e.bytes.byteLength, !0), r.setUint16(28, n.byteLength, !0), r.setUint16(30, 0, !0), r.setUint16(32, 0, !0), r.setUint16(34, 0, !0), r.setUint16(36, 0, !0), r.setUint32(38, 0, !0), r.setUint32(42, e.localHeaderOffset, !0), i.set(n, 46), i;
|
|
77
134
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
135
|
+
function Y(e, t, n) {
|
|
136
|
+
if (b(e, "Archive entry count"), b(t, "Central directory"), b(n, "Central directory offset"), e > 65535)
|
|
137
|
+
throw new Error("Archive entry count exceeds the ZIP32 entry limit.");
|
|
138
|
+
const i = new Uint8Array(22), r = new DataView(i.buffer);
|
|
139
|
+
return r.setUint32(0, 101010256, !0), r.setUint16(4, 0, !0), r.setUint16(6, 0, !0), r.setUint16(8, e, !0), r.setUint16(10, e, !0), r.setUint32(12, t, !0), r.setUint32(16, n, !0), r.setUint16(20, 0, !0), i;
|
|
81
140
|
}
|
|
82
|
-
|
|
141
|
+
function q(e, t = /* @__PURE__ */ new Date()) {
|
|
142
|
+
const n = J(t), i = [], r = [], s = [];
|
|
143
|
+
let o = 0;
|
|
144
|
+
for (const f of e) {
|
|
145
|
+
const h = {
|
|
146
|
+
...f,
|
|
147
|
+
crc32: V(f.bytes),
|
|
148
|
+
localHeaderOffset: o
|
|
149
|
+
}, l = W(h, n);
|
|
150
|
+
r.push(l, h.bytes), o += l.byteLength + h.bytes.byteLength, b(o, "Archive size"), i.push(h);
|
|
151
|
+
}
|
|
152
|
+
const a = o;
|
|
153
|
+
for (const f of i) {
|
|
154
|
+
const h = G(f, n);
|
|
155
|
+
s.push(h), o += h.byteLength, b(o, "Archive size");
|
|
156
|
+
}
|
|
157
|
+
const c = o - a;
|
|
158
|
+
return Z([
|
|
159
|
+
...r,
|
|
160
|
+
...s,
|
|
161
|
+
Y(i.length, c, a)
|
|
162
|
+
]);
|
|
163
|
+
}
|
|
164
|
+
async function X(e) {
|
|
165
|
+
if (typeof e == "string")
|
|
166
|
+
return w.encode(e);
|
|
167
|
+
if (typeof Blob < "u" && e instanceof Blob)
|
|
168
|
+
return new Uint8Array(await e.arrayBuffer());
|
|
169
|
+
if (e instanceof ArrayBuffer)
|
|
170
|
+
return new Uint8Array(e);
|
|
171
|
+
if (ArrayBuffer.isView(e))
|
|
172
|
+
return new Uint8Array(e.buffer, e.byteOffset, e.byteLength);
|
|
173
|
+
throw new Error("Archive entry data must be a string, Blob, ArrayBuffer, or ArrayBufferView.");
|
|
174
|
+
}
|
|
175
|
+
function K(e) {
|
|
176
|
+
for (const t of e.split(/\r?\n/)) {
|
|
177
|
+
const n = t.trim();
|
|
178
|
+
if (n.startsWith("# "))
|
|
179
|
+
return n.slice(2).trim() || null;
|
|
180
|
+
}
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
async function tt(e) {
|
|
184
|
+
const t = P(e.protocol?.entrypoint ?? "protocol.aimd", "Protocol entrypoint"), n = /* @__PURE__ */ new Map();
|
|
185
|
+
n.set(t, w.encode(e.aimd));
|
|
186
|
+
for (const a of e.files ?? []) {
|
|
187
|
+
const c = P(a.path, "Protocol file");
|
|
188
|
+
if (c === t || n.has(c))
|
|
189
|
+
throw new Error(`Protocol archive contains duplicate file path '${c}'.`);
|
|
190
|
+
n.set(c, await X(a.data));
|
|
191
|
+
}
|
|
192
|
+
const i = Array.from(n.keys()), r = {};
|
|
193
|
+
for (const [a, c] of n)
|
|
194
|
+
r[a] = await U(c);
|
|
195
|
+
const s = {
|
|
196
|
+
format: A,
|
|
197
|
+
version: 1,
|
|
198
|
+
kind: "protocol",
|
|
199
|
+
created_at: e.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
200
|
+
protocol: {
|
|
201
|
+
protocol_id: e.protocol?.protocol_id ?? null,
|
|
202
|
+
protocol_version: e.protocol?.protocol_version ?? null,
|
|
203
|
+
protocol_name: e.protocol?.protocol_name ?? K(e.aimd) ?? null,
|
|
204
|
+
entrypoint: t,
|
|
205
|
+
files: i,
|
|
206
|
+
file_hashes: r
|
|
207
|
+
}
|
|
208
|
+
}, o = [
|
|
209
|
+
{
|
|
210
|
+
name: m,
|
|
211
|
+
bytes: w.encode(`${JSON.stringify(s, null, 2)}
|
|
212
|
+
`)
|
|
213
|
+
},
|
|
214
|
+
...i.map((a) => ({ name: a, bytes: n.get(a) }))
|
|
215
|
+
];
|
|
216
|
+
return q(o);
|
|
217
|
+
}
|
|
218
|
+
class _ {
|
|
83
219
|
bytes;
|
|
84
220
|
entries;
|
|
85
221
|
manifest;
|
|
86
222
|
entryMap;
|
|
87
|
-
constructor(t,
|
|
88
|
-
this.bytes = t, this.entries =
|
|
223
|
+
constructor(t, n, i) {
|
|
224
|
+
this.bytes = t, this.entries = n.map(({ compressedDataStart: r, ...s }) => s), this.entryMap = new Map(n.map((r) => [r.name, r])), this.manifest = i;
|
|
89
225
|
}
|
|
90
226
|
static async open(t) {
|
|
91
|
-
const
|
|
92
|
-
if (!
|
|
93
|
-
throw new Error(`Archive does not contain '${
|
|
94
|
-
const
|
|
95
|
-
format:
|
|
227
|
+
const n = t instanceof Uint8Array ? t : t instanceof Blob ? new Uint8Array(await t.arrayBuffer()) : new Uint8Array(t), i = C(n);
|
|
228
|
+
if (!i.find((a) => a.name === m))
|
|
229
|
+
throw new Error(`Archive does not contain '${m}'.`);
|
|
230
|
+
const o = await new _(n, i, {
|
|
231
|
+
format: A,
|
|
96
232
|
version: 0,
|
|
97
233
|
kind: "records"
|
|
98
|
-
}).readJson(
|
|
99
|
-
if (
|
|
100
|
-
throw new Error(`Unsupported archive format '${String(
|
|
101
|
-
if (
|
|
102
|
-
throw new Error(`Unsupported archive kind '${String(
|
|
103
|
-
return new
|
|
234
|
+
}).readJson(m);
|
|
235
|
+
if (o.format !== A)
|
|
236
|
+
throw new Error(`Unsupported archive format '${String(o.format)}'.`);
|
|
237
|
+
if (o.kind !== "protocol" && o.kind !== "protocols" && o.kind !== "records")
|
|
238
|
+
throw new Error(`Unsupported archive kind '${String(o.kind)}'.`);
|
|
239
|
+
return new _(n, i, o);
|
|
104
240
|
}
|
|
105
241
|
has(t) {
|
|
106
242
|
return this.entryMap.has(t);
|
|
107
243
|
}
|
|
108
244
|
summary() {
|
|
109
|
-
const t = Array.isArray(this.manifest.records) ? this.manifest.records.length : 0,
|
|
245
|
+
const t = Array.isArray(this.manifest.records) ? this.manifest.records.length : 0, n = this.manifest.kind === "protocol" ? 1 : Array.isArray(this.manifest.protocols) ? this.manifest.protocols.length : 0, i = Array.isArray(this.manifest.blobs) ? this.manifest.blobs.length : 0, r = Array.isArray(this.manifest.files) ? this.manifest.files.length : 0;
|
|
110
246
|
return {
|
|
111
247
|
format: this.manifest.format,
|
|
112
248
|
version: this.manifest.version,
|
|
@@ -114,96 +250,96 @@ class $ {
|
|
|
114
250
|
createdAt: this.manifest.created_at,
|
|
115
251
|
memberCount: this.entries.length,
|
|
116
252
|
recordCount: t,
|
|
117
|
-
protocolCount:
|
|
118
|
-
blobCount:
|
|
119
|
-
fileCount:
|
|
253
|
+
protocolCount: n,
|
|
254
|
+
blobCount: i,
|
|
255
|
+
fileCount: r
|
|
120
256
|
};
|
|
121
257
|
}
|
|
122
258
|
async readBytes(t) {
|
|
123
|
-
const
|
|
124
|
-
if (!
|
|
259
|
+
const n = this.entryMap.get(t);
|
|
260
|
+
if (!n)
|
|
125
261
|
throw new Error(`Archive member '${t}' not found.`);
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
262
|
+
const i = this.bytes.slice(
|
|
263
|
+
n.compressedDataStart,
|
|
264
|
+
n.compressedDataStart + n.compressedSize
|
|
129
265
|
);
|
|
130
|
-
if (
|
|
131
|
-
return
|
|
132
|
-
if (
|
|
133
|
-
return
|
|
134
|
-
throw new Error(`Archive member '${t}' uses unsupported compression method ${
|
|
266
|
+
if (n.compressionMethod === 0)
|
|
267
|
+
return i;
|
|
268
|
+
if (n.compressionMethod === 8)
|
|
269
|
+
return k(i);
|
|
270
|
+
throw new Error(`Archive member '${t}' uses unsupported compression method ${n.compressionMethod}.`);
|
|
135
271
|
}
|
|
136
272
|
async readText(t) {
|
|
137
|
-
return
|
|
273
|
+
return E.decode(await this.readBytes(t));
|
|
138
274
|
}
|
|
139
275
|
async readJson(t) {
|
|
140
276
|
return JSON.parse(await this.readText(t));
|
|
141
277
|
}
|
|
142
278
|
async validate() {
|
|
143
|
-
const t = [],
|
|
144
|
-
for (const
|
|
145
|
-
const
|
|
146
|
-
|
|
279
|
+
const t = [], n = new Set(this.entries.map((i) => i.name));
|
|
280
|
+
for (const i of this.entries) {
|
|
281
|
+
const r = v(i.name);
|
|
282
|
+
r && t.push(r);
|
|
147
283
|
}
|
|
148
|
-
return
|
|
284
|
+
return n.has(m) || t.push(`Archive is missing '${m}'.`), this.manifest.format !== A && t.push(`Unsupported archive format '${String(this.manifest.format)}'.`), this.manifest.version !== 1 && t.push(`Unsupported archive version '${String(this.manifest.version)}'.`), this.manifest.kind === "protocol" ? await this.validateProtocol(this.manifest.protocol, "", t) : this.manifest.kind === "protocols" ? await this.validateProtocolList(this.manifest.protocols, t, !0) : this.manifest.kind === "records" ? await this.validateRecords(t) : t.push(`Unsupported archive kind '${String(this.manifest.kind)}'.`), { ok: t.length === 0, issues: t };
|
|
149
285
|
}
|
|
150
|
-
async validateProtocol(t,
|
|
286
|
+
async validateProtocol(t, n, i) {
|
|
151
287
|
if (!t || typeof t != "object") {
|
|
152
|
-
|
|
288
|
+
i.push("Protocol manifest entry must be an object.");
|
|
153
289
|
return;
|
|
154
290
|
}
|
|
155
|
-
const
|
|
156
|
-
this.has(
|
|
157
|
-
const
|
|
158
|
-
for (const c of
|
|
159
|
-
const f = `${
|
|
160
|
-
if (
|
|
161
|
-
|
|
291
|
+
const r = t.entrypoint || "protocol.aimd", s = `${n}${r}`;
|
|
292
|
+
this.has(s) || i.push(`Protocol entrypoint '${s}' is missing.`);
|
|
293
|
+
const o = Array.isArray(t.files) ? t.files : [], a = t.file_hashes && typeof t.file_hashes == "object" ? t.file_hashes : {};
|
|
294
|
+
for (const c of o) {
|
|
295
|
+
const f = `${n}${c}`, h = v(f);
|
|
296
|
+
if (h) {
|
|
297
|
+
i.push(h);
|
|
162
298
|
continue;
|
|
163
299
|
}
|
|
164
300
|
if (!this.has(f)) {
|
|
165
|
-
|
|
301
|
+
i.push(`Protocol file '${f}' is missing.`);
|
|
166
302
|
continue;
|
|
167
303
|
}
|
|
168
|
-
const
|
|
169
|
-
if (
|
|
170
|
-
const
|
|
171
|
-
|
|
304
|
+
const l = a[c];
|
|
305
|
+
if (l) {
|
|
306
|
+
const u = await U(await this.readBytes(f));
|
|
307
|
+
u !== l && i.push(`Protocol file '${f}' sha256 mismatch: expected ${l}, got ${u}.`);
|
|
172
308
|
}
|
|
173
309
|
}
|
|
174
310
|
}
|
|
175
|
-
async validateProtocolList(t,
|
|
176
|
-
const
|
|
311
|
+
async validateProtocolList(t, n, i = !1) {
|
|
312
|
+
const r = /* @__PURE__ */ new Set();
|
|
177
313
|
if (!Array.isArray(t))
|
|
178
|
-
return
|
|
179
|
-
|
|
180
|
-
for (const [
|
|
181
|
-
if (!
|
|
182
|
-
|
|
314
|
+
return n.push("Protocols manifest field must be a list."), r;
|
|
315
|
+
i && t.length === 0 && n.push("Protocols manifest field must include at least one protocol.");
|
|
316
|
+
for (const [s, o] of t.entries()) {
|
|
317
|
+
if (!o || typeof o != "object") {
|
|
318
|
+
n.push(`Protocol manifest entry #${s + 1} must be an object.`);
|
|
183
319
|
continue;
|
|
184
320
|
}
|
|
185
|
-
if (!
|
|
186
|
-
|
|
321
|
+
if (!o.archive_root) {
|
|
322
|
+
n.push(`Protocol manifest entry #${s + 1} is missing archive_root.`);
|
|
187
323
|
continue;
|
|
188
324
|
}
|
|
189
|
-
if (
|
|
190
|
-
|
|
325
|
+
if (r.has(o.archive_root)) {
|
|
326
|
+
n.push(`Protocol manifest entry #${s + 1} uses duplicate archive_root '${o.archive_root}'.`);
|
|
191
327
|
continue;
|
|
192
328
|
}
|
|
193
|
-
|
|
329
|
+
r.add(o.archive_root), await this.validateProtocol(o, `${o.archive_root.replace(/\/+$/, "")}/`, n);
|
|
194
330
|
}
|
|
195
|
-
return
|
|
331
|
+
return r;
|
|
196
332
|
}
|
|
197
333
|
async validateRecords(t) {
|
|
198
|
-
const
|
|
199
|
-
for (const [
|
|
334
|
+
const n = Array.isArray(this.manifest.records) ? this.manifest.records : [], i = await this.validateProtocolList(this.manifest.protocols, t), r = /* @__PURE__ */ new Set();
|
|
335
|
+
for (const [o, a] of n.entries()) {
|
|
200
336
|
if (!a || typeof a != "object") {
|
|
201
|
-
t.push(`Record manifest entry #${
|
|
337
|
+
t.push(`Record manifest entry #${o + 1} must be an object.`);
|
|
202
338
|
continue;
|
|
203
339
|
}
|
|
204
340
|
const c = a.path;
|
|
205
341
|
if (!c) {
|
|
206
|
-
t.push(`Record manifest entry #${
|
|
342
|
+
t.push(`Record manifest entry #${o + 1} is missing a path.`);
|
|
207
343
|
continue;
|
|
208
344
|
}
|
|
209
345
|
const f = v(c);
|
|
@@ -215,108 +351,109 @@ class $ {
|
|
|
215
351
|
t.push(`Record file '${c}' is missing.`);
|
|
216
352
|
continue;
|
|
217
353
|
}
|
|
218
|
-
|
|
219
|
-
let
|
|
354
|
+
r.add(c);
|
|
355
|
+
let h, l;
|
|
220
356
|
try {
|
|
221
|
-
|
|
357
|
+
h = await this.readBytes(c), l = JSON.parse(E.decode(h));
|
|
222
358
|
} catch {
|
|
223
359
|
t.push(`Record file '${c}' is not valid UTF-8 JSON.`);
|
|
224
360
|
continue;
|
|
225
361
|
}
|
|
226
|
-
if (t.push(...
|
|
227
|
-
const
|
|
228
|
-
|
|
362
|
+
if (t.push(...N(l, `Record file '${c}'`)), a.sha256) {
|
|
363
|
+
const u = await U(h);
|
|
364
|
+
u !== a.sha256 && t.push(`Record file '${c}' sha256 mismatch: expected ${a.sha256}, got ${u}.`);
|
|
229
365
|
}
|
|
230
|
-
a.embedded_protocol_root && !
|
|
366
|
+
a.embedded_protocol_root && !i.has(a.embedded_protocol_root) && t.push(`Record file '${c}' references missing embedded protocol root '${a.embedded_protocol_root}'.`);
|
|
231
367
|
}
|
|
232
|
-
const
|
|
233
|
-
this.validateFileReferences(t,
|
|
368
|
+
const s = await this.validateBlobs(t);
|
|
369
|
+
this.validateFileReferences(t, s, r);
|
|
234
370
|
}
|
|
235
371
|
async validateBlobs(t) {
|
|
236
|
-
const
|
|
237
|
-
if (
|
|
238
|
-
return
|
|
239
|
-
if (!Array.isArray(
|
|
240
|
-
return t.push("Blobs manifest field must be a list."),
|
|
241
|
-
const
|
|
242
|
-
for (const [
|
|
243
|
-
if (!
|
|
244
|
-
t.push(`Blob manifest entry #${
|
|
372
|
+
const n = /* @__PURE__ */ new Set(), i = this.manifest.blobs;
|
|
373
|
+
if (i === void 0)
|
|
374
|
+
return n;
|
|
375
|
+
if (!Array.isArray(i))
|
|
376
|
+
return t.push("Blobs manifest field must be a list."), n;
|
|
377
|
+
const r = /* @__PURE__ */ new Set();
|
|
378
|
+
for (const [s, o] of i.entries()) {
|
|
379
|
+
if (!o || typeof o != "object") {
|
|
380
|
+
t.push(`Blob manifest entry #${s + 1} must be an object.`);
|
|
245
381
|
continue;
|
|
246
382
|
}
|
|
247
|
-
const a =
|
|
383
|
+
const a = o.blob_id, c = o.archive_path, f = o.sha256;
|
|
248
384
|
if (!a) {
|
|
249
|
-
t.push(`Blob manifest entry #${
|
|
385
|
+
t.push(`Blob manifest entry #${s + 1} is missing blob_id.`);
|
|
250
386
|
continue;
|
|
251
387
|
}
|
|
252
|
-
if (
|
|
253
|
-
t.push(`Blob manifest entry #${
|
|
388
|
+
if (n.has(a)) {
|
|
389
|
+
t.push(`Blob manifest entry #${s + 1} uses duplicate blob_id '${a}'.`);
|
|
254
390
|
continue;
|
|
255
391
|
}
|
|
256
|
-
if (
|
|
392
|
+
if (n.add(a), !f || !M.test(f)) {
|
|
257
393
|
t.push(`Blob '${a}' must include a valid sha256 hash.`);
|
|
258
394
|
continue;
|
|
259
395
|
}
|
|
260
|
-
const
|
|
261
|
-
if (a !==
|
|
396
|
+
const h = `sha256:${f}`;
|
|
397
|
+
if (a !== h && t.push(`Blob '${a}' does not match sha256-derived id '${h}'.`), !c) {
|
|
262
398
|
t.push(`Blob '${a}' is missing archive_path.`);
|
|
263
399
|
continue;
|
|
264
400
|
}
|
|
265
|
-
const
|
|
266
|
-
if (
|
|
267
|
-
t.push(
|
|
401
|
+
const l = v(c);
|
|
402
|
+
if (l) {
|
|
403
|
+
t.push(l);
|
|
268
404
|
continue;
|
|
269
405
|
}
|
|
270
|
-
if (c.startsWith("blobs/sha256/") || t.push(`Blob '${a}' archive_path must be under 'blobs/sha256/'.`),
|
|
406
|
+
if (c.startsWith("blobs/sha256/") || t.push(`Blob '${a}' archive_path must be under 'blobs/sha256/'.`), r.has(c)) {
|
|
271
407
|
t.push(`Blob '${a}' uses duplicate archive_path '${c}'.`);
|
|
272
408
|
continue;
|
|
273
409
|
}
|
|
274
|
-
if (
|
|
410
|
+
if (r.add(c), !this.has(c)) {
|
|
275
411
|
t.push(`Blob file '${c}' is missing.`);
|
|
276
412
|
continue;
|
|
277
413
|
}
|
|
278
|
-
const
|
|
279
|
-
|
|
414
|
+
const u = await this.readBytes(c), $ = await U(u);
|
|
415
|
+
$ !== f && t.push(`Blob file '${c}' sha256 mismatch: expected ${f}, got ${$}.`), typeof o.size == "number" && o.size !== u.byteLength ? t.push(`Blob file '${c}' size mismatch: expected ${o.size}, got ${u.byteLength}.`) : o.size !== void 0 && typeof o.size != "number" && t.push(`Blob '${a}' size must be a number when present.`);
|
|
280
416
|
}
|
|
281
|
-
return
|
|
417
|
+
return n;
|
|
282
418
|
}
|
|
283
|
-
validateFileReferences(t,
|
|
284
|
-
const
|
|
285
|
-
if (
|
|
286
|
-
if (!Array.isArray(
|
|
419
|
+
validateFileReferences(t, n, i) {
|
|
420
|
+
const r = this.manifest.files;
|
|
421
|
+
if (r !== void 0) {
|
|
422
|
+
if (!Array.isArray(r)) {
|
|
287
423
|
t.push("Files manifest field must be a list.");
|
|
288
424
|
return;
|
|
289
425
|
}
|
|
290
|
-
for (const [
|
|
291
|
-
if (!
|
|
292
|
-
t.push(`File manifest entry #${
|
|
426
|
+
for (const [s, o] of r.entries()) {
|
|
427
|
+
if (!o || typeof o != "object") {
|
|
428
|
+
t.push(`File manifest entry #${s + 1} must be an object.`);
|
|
293
429
|
continue;
|
|
294
430
|
}
|
|
295
|
-
!
|
|
431
|
+
!o.file_id && !o.source_uri && !o.blob_id && t.push(`File manifest entry #${s + 1} must include file_id, source_uri, or blob_id.`), o.blob_id && !n.has(o.blob_id) && t.push(`File manifest entry #${s + 1} references missing blob_id '${o.blob_id}'.`), o.record_path && !i.has(o.record_path) && t.push(`File manifest entry #${s + 1} references missing record_path '${o.record_path}'.`), o.field_path !== void 0 && typeof o.field_path != "string" && t.push(`File manifest entry #${s + 1} field_path must be a string.`);
|
|
296
432
|
}
|
|
297
433
|
}
|
|
298
434
|
}
|
|
299
435
|
}
|
|
300
|
-
async function
|
|
301
|
-
return
|
|
436
|
+
async function et(e) {
|
|
437
|
+
return _.open(e);
|
|
302
438
|
}
|
|
303
|
-
async function
|
|
304
|
-
return (await
|
|
439
|
+
async function nt(e) {
|
|
440
|
+
return (await _.open(e)).summary();
|
|
305
441
|
}
|
|
306
|
-
function
|
|
307
|
-
return JSON.stringify(
|
|
442
|
+
function rt(e) {
|
|
443
|
+
return JSON.stringify(e, null, 2);
|
|
308
444
|
}
|
|
309
|
-
function
|
|
310
|
-
return
|
|
445
|
+
function it(e) {
|
|
446
|
+
return w.encode(e);
|
|
311
447
|
}
|
|
312
448
|
export {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
449
|
+
B as AIRALOGY_RECORD_FORMAT,
|
|
450
|
+
Q as AIRALOGY_RECORD_SCHEMA_VERSION,
|
|
451
|
+
A as AIRA_ARCHIVE_FORMAT,
|
|
452
|
+
m as AIRA_MANIFEST_PATH,
|
|
453
|
+
_ as AiraArchive,
|
|
454
|
+
tt as createProtocolAiraArchive,
|
|
455
|
+
it as encodeUtf8,
|
|
456
|
+
et as openAiraArchive,
|
|
457
|
+
rt as prettyPrintJson,
|
|
458
|
+
nt as readAiraArchiveSummary
|
|
322
459
|
};
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -102,13 +102,40 @@ export interface AiraValidationResult {
|
|
|
102
102
|
issues: string[]
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
export type AiraArchiveEntryData = string | Blob | ArrayBuffer | ArrayBufferView
|
|
106
|
+
|
|
107
|
+
export interface CreateProtocolAiraArchiveFile {
|
|
108
|
+
path: string
|
|
109
|
+
data: AiraArchiveEntryData
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface CreateProtocolAiraArchiveOptions {
|
|
113
|
+
aimd: string
|
|
114
|
+
protocol?: Pick<AiraProtocolManifest, 'protocol_id' | 'protocol_version' | 'protocol_name' | 'entrypoint'>
|
|
115
|
+
files?: CreateProtocolAiraArchiveFile[]
|
|
116
|
+
createdAt?: string
|
|
117
|
+
}
|
|
118
|
+
|
|
105
119
|
type ZipEntryInternal = AiraEntry & {
|
|
106
120
|
compressedDataStart: number
|
|
107
121
|
}
|
|
108
122
|
|
|
123
|
+
type ZipEntryPayload = {
|
|
124
|
+
name: string
|
|
125
|
+
bytes: Uint8Array
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
type ZipEntryPrepared = ZipEntryPayload & {
|
|
129
|
+
crc32: number
|
|
130
|
+
localHeaderOffset: number
|
|
131
|
+
}
|
|
132
|
+
|
|
109
133
|
const textDecoder = new TextDecoder('utf-8')
|
|
110
134
|
const textEncoder = new TextEncoder()
|
|
111
135
|
const sha256Pattern = /^[0-9a-f]{64}$/
|
|
136
|
+
const ZIP_STORED_METHOD = 0
|
|
137
|
+
const ZIP_UTF8_FLAG = 0x0800
|
|
138
|
+
const ZIP_UINT32_MAX = 0xffffffff
|
|
112
139
|
|
|
113
140
|
function getUint16(view: DataView, offset: number): number {
|
|
114
141
|
return view.getUint16(offset, true)
|
|
@@ -132,6 +159,22 @@ function validateMemberPath(name: string): string | null {
|
|
|
132
159
|
return null
|
|
133
160
|
}
|
|
134
161
|
|
|
162
|
+
function normalizeArchiveMemberPath(path: string, label: string): string {
|
|
163
|
+
const normalized = path
|
|
164
|
+
.replace(/\\/g, '/')
|
|
165
|
+
.split('/')
|
|
166
|
+
.filter(part => part && part !== '.')
|
|
167
|
+
.join('/')
|
|
168
|
+
const issue = validateMemberPath(normalized)
|
|
169
|
+
if (issue || path.startsWith('/') || path.replace(/\\/g, '/').split('/').some(part => part === '..')) {
|
|
170
|
+
throw new Error(issue ?? `${label} '${path}' is not a safe relative archive path.`)
|
|
171
|
+
}
|
|
172
|
+
if (normalized === AIRA_MANIFEST_PATH || normalized.startsWith('_airalogy_archive/')) {
|
|
173
|
+
throw new Error(`${label} '${path}' conflicts with Airalogy archive metadata.`)
|
|
174
|
+
}
|
|
175
|
+
return normalized
|
|
176
|
+
}
|
|
177
|
+
|
|
135
178
|
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
136
179
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
|
|
137
180
|
}
|
|
@@ -291,6 +334,229 @@ async function sha256Hex(bytes: Uint8Array): Promise<string> {
|
|
|
291
334
|
.join('')
|
|
292
335
|
}
|
|
293
336
|
|
|
337
|
+
function makeCrc32Table(): Uint32Array {
|
|
338
|
+
const table = new Uint32Array(256)
|
|
339
|
+
for (let index = 0; index < 256; index += 1) {
|
|
340
|
+
let value = index
|
|
341
|
+
for (let bit = 0; bit < 8; bit += 1) {
|
|
342
|
+
value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1
|
|
343
|
+
}
|
|
344
|
+
table[index] = value >>> 0
|
|
345
|
+
}
|
|
346
|
+
return table
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const crc32Table = makeCrc32Table()
|
|
350
|
+
|
|
351
|
+
function crc32(bytes: Uint8Array): number {
|
|
352
|
+
let crc = 0xffffffff
|
|
353
|
+
for (const byte of bytes) {
|
|
354
|
+
crc = crc32Table[(crc ^ byte) & 0xff] ^ (crc >>> 8)
|
|
355
|
+
}
|
|
356
|
+
return (crc ^ 0xffffffff) >>> 0
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function concatBytes(parts: Uint8Array[]): Uint8Array {
|
|
360
|
+
const totalLength = parts.reduce((sum, part) => sum + part.byteLength, 0)
|
|
361
|
+
const output = new Uint8Array(totalLength)
|
|
362
|
+
let offset = 0
|
|
363
|
+
for (const part of parts) {
|
|
364
|
+
output.set(part, offset)
|
|
365
|
+
offset += part.byteLength
|
|
366
|
+
}
|
|
367
|
+
return output
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function dateToDosTimeAndDate(date: Date): { time: number; date: number } {
|
|
371
|
+
const year = Math.max(1980, Math.min(2107, date.getFullYear()))
|
|
372
|
+
const month = date.getMonth() + 1
|
|
373
|
+
const day = date.getDate()
|
|
374
|
+
const hours = date.getHours()
|
|
375
|
+
const minutes = date.getMinutes()
|
|
376
|
+
const seconds = Math.floor(date.getSeconds() / 2)
|
|
377
|
+
return {
|
|
378
|
+
time: (hours << 11) | (minutes << 5) | seconds,
|
|
379
|
+
date: ((year - 1980) << 9) | (month << 5) | day,
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function assertZipUint32(value: number, label: string): void {
|
|
384
|
+
if (!Number.isInteger(value) || value < 0 || value > ZIP_UINT32_MAX) {
|
|
385
|
+
throw new Error(`${label} exceeds the ZIP32 size limit.`)
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function writeZipLocalHeader(entry: ZipEntryPrepared, dos: { time: number; date: number }): Uint8Array {
|
|
390
|
+
const nameBytes = textEncoder.encode(entry.name)
|
|
391
|
+
assertZipUint32(entry.bytes.byteLength, `Archive member '${entry.name}'`)
|
|
392
|
+
const header = new Uint8Array(30 + nameBytes.byteLength)
|
|
393
|
+
const view = new DataView(header.buffer, header.byteOffset, header.byteLength)
|
|
394
|
+
view.setUint32(0, 0x04034b50, true)
|
|
395
|
+
view.setUint16(4, 20, true)
|
|
396
|
+
view.setUint16(6, ZIP_UTF8_FLAG, true)
|
|
397
|
+
view.setUint16(8, ZIP_STORED_METHOD, true)
|
|
398
|
+
view.setUint16(10, dos.time, true)
|
|
399
|
+
view.setUint16(12, dos.date, true)
|
|
400
|
+
view.setUint32(14, entry.crc32, true)
|
|
401
|
+
view.setUint32(18, entry.bytes.byteLength, true)
|
|
402
|
+
view.setUint32(22, entry.bytes.byteLength, true)
|
|
403
|
+
view.setUint16(26, nameBytes.byteLength, true)
|
|
404
|
+
view.setUint16(28, 0, true)
|
|
405
|
+
header.set(nameBytes, 30)
|
|
406
|
+
return header
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function writeZipCentralDirectoryEntry(entry: ZipEntryPrepared, dos: { time: number; date: number }): Uint8Array {
|
|
410
|
+
const nameBytes = textEncoder.encode(entry.name)
|
|
411
|
+
const header = new Uint8Array(46 + nameBytes.byteLength)
|
|
412
|
+
const view = new DataView(header.buffer, header.byteOffset, header.byteLength)
|
|
413
|
+
view.setUint32(0, 0x02014b50, true)
|
|
414
|
+
view.setUint16(4, 20, true)
|
|
415
|
+
view.setUint16(6, 20, true)
|
|
416
|
+
view.setUint16(8, ZIP_UTF8_FLAG, true)
|
|
417
|
+
view.setUint16(10, ZIP_STORED_METHOD, true)
|
|
418
|
+
view.setUint16(12, dos.time, true)
|
|
419
|
+
view.setUint16(14, dos.date, true)
|
|
420
|
+
view.setUint32(16, entry.crc32, true)
|
|
421
|
+
view.setUint32(20, entry.bytes.byteLength, true)
|
|
422
|
+
view.setUint32(24, entry.bytes.byteLength, true)
|
|
423
|
+
view.setUint16(28, nameBytes.byteLength, true)
|
|
424
|
+
view.setUint16(30, 0, true)
|
|
425
|
+
view.setUint16(32, 0, true)
|
|
426
|
+
view.setUint16(34, 0, true)
|
|
427
|
+
view.setUint16(36, 0, true)
|
|
428
|
+
view.setUint32(38, 0, true)
|
|
429
|
+
view.setUint32(42, entry.localHeaderOffset, true)
|
|
430
|
+
header.set(nameBytes, 46)
|
|
431
|
+
return header
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function writeZipEndOfCentralDirectory(entryCount: number, centralDirectorySize: number, centralDirectoryOffset: number): Uint8Array {
|
|
435
|
+
assertZipUint32(entryCount, 'Archive entry count')
|
|
436
|
+
assertZipUint32(centralDirectorySize, 'Central directory')
|
|
437
|
+
assertZipUint32(centralDirectoryOffset, 'Central directory offset')
|
|
438
|
+
if (entryCount > 0xffff) {
|
|
439
|
+
throw new Error('Archive entry count exceeds the ZIP32 entry limit.')
|
|
440
|
+
}
|
|
441
|
+
const end = new Uint8Array(22)
|
|
442
|
+
const view = new DataView(end.buffer)
|
|
443
|
+
view.setUint32(0, 0x06054b50, true)
|
|
444
|
+
view.setUint16(4, 0, true)
|
|
445
|
+
view.setUint16(6, 0, true)
|
|
446
|
+
view.setUint16(8, entryCount, true)
|
|
447
|
+
view.setUint16(10, entryCount, true)
|
|
448
|
+
view.setUint32(12, centralDirectorySize, true)
|
|
449
|
+
view.setUint32(16, centralDirectoryOffset, true)
|
|
450
|
+
view.setUint16(20, 0, true)
|
|
451
|
+
return end
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function createStoredZip(entries: ZipEntryPayload[], date = new Date()): Uint8Array {
|
|
455
|
+
const dos = dateToDosTimeAndDate(date)
|
|
456
|
+
const preparedEntries: ZipEntryPrepared[] = []
|
|
457
|
+
const localParts: Uint8Array[] = []
|
|
458
|
+
const centralParts: Uint8Array[] = []
|
|
459
|
+
let offset = 0
|
|
460
|
+
|
|
461
|
+
for (const entry of entries) {
|
|
462
|
+
const prepared: ZipEntryPrepared = {
|
|
463
|
+
...entry,
|
|
464
|
+
crc32: crc32(entry.bytes),
|
|
465
|
+
localHeaderOffset: offset,
|
|
466
|
+
}
|
|
467
|
+
const localHeader = writeZipLocalHeader(prepared, dos)
|
|
468
|
+
localParts.push(localHeader, prepared.bytes)
|
|
469
|
+
offset += localHeader.byteLength + prepared.bytes.byteLength
|
|
470
|
+
assertZipUint32(offset, 'Archive size')
|
|
471
|
+
preparedEntries.push(prepared)
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const centralDirectoryOffset = offset
|
|
475
|
+
for (const entry of preparedEntries) {
|
|
476
|
+
const central = writeZipCentralDirectoryEntry(entry, dos)
|
|
477
|
+
centralParts.push(central)
|
|
478
|
+
offset += central.byteLength
|
|
479
|
+
assertZipUint32(offset, 'Archive size')
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const centralDirectorySize = offset - centralDirectoryOffset
|
|
483
|
+
return concatBytes([
|
|
484
|
+
...localParts,
|
|
485
|
+
...centralParts,
|
|
486
|
+
writeZipEndOfCentralDirectory(preparedEntries.length, centralDirectorySize, centralDirectoryOffset),
|
|
487
|
+
])
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
async function toArchiveBytes(data: AiraArchiveEntryData): Promise<Uint8Array> {
|
|
491
|
+
if (typeof data === 'string') {
|
|
492
|
+
return textEncoder.encode(data)
|
|
493
|
+
}
|
|
494
|
+
if (typeof Blob !== 'undefined' && data instanceof Blob) {
|
|
495
|
+
return new Uint8Array(await data.arrayBuffer())
|
|
496
|
+
}
|
|
497
|
+
if (data instanceof ArrayBuffer) {
|
|
498
|
+
return new Uint8Array(data)
|
|
499
|
+
}
|
|
500
|
+
if (ArrayBuffer.isView(data)) {
|
|
501
|
+
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength)
|
|
502
|
+
}
|
|
503
|
+
throw new Error('Archive entry data must be a string, Blob, ArrayBuffer, or ArrayBufferView.')
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function inferProtocolNameFromAimd(aimd: string): string | null {
|
|
507
|
+
for (const line of aimd.split(/\r?\n/)) {
|
|
508
|
+
const trimmed = line.trim()
|
|
509
|
+
if (trimmed.startsWith('# ')) {
|
|
510
|
+
return trimmed.slice(2).trim() || null
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
return null
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
export async function createProtocolAiraArchive(options: CreateProtocolAiraArchiveOptions): Promise<Uint8Array> {
|
|
517
|
+
const entrypoint = normalizeArchiveMemberPath(options.protocol?.entrypoint ?? 'protocol.aimd', 'Protocol entrypoint')
|
|
518
|
+
const entries = new Map<string, Uint8Array>()
|
|
519
|
+
entries.set(entrypoint, textEncoder.encode(options.aimd))
|
|
520
|
+
|
|
521
|
+
for (const file of options.files ?? []) {
|
|
522
|
+
const path = normalizeArchiveMemberPath(file.path, 'Protocol file')
|
|
523
|
+
if (path === entrypoint || entries.has(path)) {
|
|
524
|
+
throw new Error(`Protocol archive contains duplicate file path '${path}'.`)
|
|
525
|
+
}
|
|
526
|
+
entries.set(path, await toArchiveBytes(file.data))
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const fileNames = Array.from(entries.keys())
|
|
530
|
+
const fileHashes: Record<string, string> = {}
|
|
531
|
+
for (const [path, bytes] of entries) {
|
|
532
|
+
fileHashes[path] = await sha256Hex(bytes)
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const manifest: AiraManifest = {
|
|
536
|
+
format: AIRA_ARCHIVE_FORMAT,
|
|
537
|
+
version: 1,
|
|
538
|
+
kind: 'protocol',
|
|
539
|
+
created_at: options.createdAt ?? new Date().toISOString(),
|
|
540
|
+
protocol: {
|
|
541
|
+
protocol_id: options.protocol?.protocol_id ?? null,
|
|
542
|
+
protocol_version: options.protocol?.protocol_version ?? null,
|
|
543
|
+
protocol_name: options.protocol?.protocol_name ?? inferProtocolNameFromAimd(options.aimd) ?? null,
|
|
544
|
+
entrypoint,
|
|
545
|
+
files: fileNames,
|
|
546
|
+
file_hashes: fileHashes,
|
|
547
|
+
},
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const archiveEntries: ZipEntryPayload[] = [
|
|
551
|
+
{
|
|
552
|
+
name: AIRA_MANIFEST_PATH,
|
|
553
|
+
bytes: textEncoder.encode(`${JSON.stringify(manifest, null, 2)}\n`),
|
|
554
|
+
},
|
|
555
|
+
...fileNames.map(name => ({ name, bytes: entries.get(name)! })),
|
|
556
|
+
]
|
|
557
|
+
return createStoredZip(archiveEntries)
|
|
558
|
+
}
|
|
559
|
+
|
|
294
560
|
export class AiraArchive {
|
|
295
561
|
readonly bytes: Uint8Array
|
|
296
562
|
readonly entries: AiraEntry[]
|