@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 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[];
@@ -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":"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"}
package/dist/index.js CHANGED
@@ -1,112 +1,248 @@
1
- const y = "_airalogy_archive/manifest.json", g = "airalogy.archive", E = "airalogy.record", k = 1, R = new TextDecoder("utf-8"), x = new TextEncoder(), j = /^[0-9a-f]{64}$/;
2
- function m(i, t) {
3
- return i.getUint16(t, !0);
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 u(i, t) {
6
- return i.getUint32(t, !0);
5
+ function p(e, t) {
6
+ return e.getUint32(t, !0);
7
7
  }
8
- function I(i) {
9
- return R.decode(i);
8
+ function H(e) {
9
+ return E.decode(e);
10
10
  }
11
- function v(i) {
12
- return !i || i.startsWith("/") ? `Archive member '${i}' uses an absolute or empty path.` : i.split("/").some((t) => t === "..") ? `Archive member '${i}' escapes the archive root.` : null;
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 p(i) {
15
- return !!i && typeof i == "object" && !Array.isArray(i);
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 D(i, t) {
18
- const e = [];
19
- if (!p(i))
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
- i.format !== void 0 && i.format !== E && e.push(`${t} format must be '${E}' when present.`), i.schema_version !== void 0 && i.schema_version !== 1 && e.push(`${t} schema_version must be 1 when present.`), i.record_id !== void 0 && i.record_id !== null && (typeof i.record_id != "string" || i.record_id.length === 0) && e.push(`${t} record_id must be a non-empty string when present.`), i.airalogy_record_id !== void 0 && i.airalogy_record_id !== null && (typeof i.airalogy_record_id != "string" || i.airalogy_record_id.length === 0) && e.push(`${t} airalogy_record_id must be a non-empty string when present.`);
22
- const o = i.record_version;
23
- if (o != null && (typeof o != "number" || !Number.isInteger(o) || o < 1) && e.push(`${t} record_version must be a positive integer when present.`), i.metadata !== void 0 && i.metadata !== null && !p(i.metadata) && e.push(`${t} metadata must be an object when present.`), !p(i.data))
24
- return e.push(`${t} data must be an object.`), e;
25
- p(i.data.var) || e.push(`${t} data.var must be an object.`);
26
- for (const s of ["step", "check", "quiz"]) {
27
- const r = i.data[s];
28
- r != null && !p(r) && e.push(`${t} data.${s} must be an object when present.`);
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 (i.files !== void 0)
31
- if (!Array.isArray(i.files))
32
- e.push(`${t} files must be a list when present.`);
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 [s, r] of i.files.entries()) {
35
- if (!p(r)) {
36
- e.push(`${t} files[${s + 1}] must be an object.`);
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((n) => typeof r[n] == "string" && r[n]) || e.push(`${t} files[${s + 1}] must include file_id, source_uri, or blob_id.`);
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 e;
49
+ return n;
42
50
  }
43
- function C(i) {
44
- const t = Math.max(0, i.length - 65535 - 22);
45
- for (let e = i.length - 22; e >= t; e -= 1)
46
- if (i[e] === 80 && i[e + 1] === 75 && i[e + 2] === 5 && i[e + 3] === 6)
47
- return e;
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 M(i) {
51
- const t = new DataView(i.buffer, i.byteOffset, i.byteLength), e = C(i), o = m(t, e + 10), s = u(t, e + 16), r = [];
52
- let n = s;
53
- for (let a = 0; a < o; a += 1) {
54
- if (u(t, n) !== 33639248)
55
- throw new Error(`Archive central directory is invalid at offset ${n}.`);
56
- const c = m(t, n + 10), f = u(t, n + 20), d = u(t, n + 24), h = m(t, n + 28), l = m(t, n + 30), _ = m(t, n + 32), b = u(t, n + 42), w = n + 46, S = I(i.slice(w, w + h));
57
- if (u(t, b) !== 67324752)
58
- throw new Error(`Archive local header is invalid for '${S}'.`);
59
- const B = m(t, b + 26), O = m(t, b + 28), P = b + 30 + B + O;
60
- r.push({
61
- name: S,
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: d,
65
- localHeaderOffset: b,
66
- compressedDataStart: P
67
- }), n = w + h + l + _;
72
+ uncompressedSize: h,
73
+ localHeaderOffset: g,
74
+ compressedDataStart: I
75
+ }), o = S + l + u + $;
68
76
  }
69
- return r.filter((a) => !a.name.endsWith("/"));
77
+ return s.filter((a) => !a.name.endsWith("/"));
70
78
  }
71
- async function U(i) {
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 e = new Blob([i]).stream().pipeThrough(new t("deflate-raw")), o = await new Response(e).arrayBuffer();
76
- return new Uint8Array(o);
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
- async function A(i) {
79
- const t = await crypto.subtle.digest("SHA-256", i);
80
- return Array.from(new Uint8Array(t)).map((e) => e.toString(16).padStart(2, "0")).join("");
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
- class $ {
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, e, o) {
88
- this.bytes = t, this.entries = e.map(({ compressedDataStart: s, ...r }) => r), this.entryMap = new Map(e.map((s) => [s.name, s])), this.manifest = o;
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 e = t instanceof Uint8Array ? t : t instanceof Blob ? new Uint8Array(await t.arrayBuffer()) : new Uint8Array(t), o = M(e);
92
- if (!o.find((a) => a.name === y))
93
- throw new Error(`Archive does not contain '${y}'.`);
94
- const n = await new $(e, o, {
95
- format: g,
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(y);
99
- if (n.format !== g)
100
- throw new Error(`Unsupported archive format '${String(n.format)}'.`);
101
- if (n.kind !== "protocol" && n.kind !== "protocols" && n.kind !== "records")
102
- throw new Error(`Unsupported archive kind '${String(n.kind)}'.`);
103
- return new $(e, o, n);
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, e = this.manifest.kind === "protocol" ? 1 : Array.isArray(this.manifest.protocols) ? this.manifest.protocols.length : 0, o = Array.isArray(this.manifest.blobs) ? this.manifest.blobs.length : 0, s = Array.isArray(this.manifest.files) ? this.manifest.files.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: e,
118
- blobCount: o,
119
- fileCount: s
253
+ protocolCount: n,
254
+ blobCount: i,
255
+ fileCount: r
120
256
  };
121
257
  }
122
258
  async readBytes(t) {
123
- const e = this.entryMap.get(t);
124
- if (!e)
259
+ const n = this.entryMap.get(t);
260
+ if (!n)
125
261
  throw new Error(`Archive member '${t}' not found.`);
126
- const o = this.bytes.slice(
127
- e.compressedDataStart,
128
- e.compressedDataStart + e.compressedSize
262
+ const i = this.bytes.slice(
263
+ n.compressedDataStart,
264
+ n.compressedDataStart + n.compressedSize
129
265
  );
130
- if (e.compressionMethod === 0)
131
- return o;
132
- if (e.compressionMethod === 8)
133
- return U(o);
134
- throw new Error(`Archive member '${t}' uses unsupported compression method ${e.compressionMethod}.`);
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 R.decode(await this.readBytes(t));
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 = [], e = new Set(this.entries.map((o) => o.name));
144
- for (const o of this.entries) {
145
- const s = v(o.name);
146
- s && t.push(s);
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 e.has(y) || t.push(`Archive is missing '${y}'.`), this.manifest.format !== g && 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 };
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, e, o) {
286
+ async validateProtocol(t, n, i) {
151
287
  if (!t || typeof t != "object") {
152
- o.push("Protocol manifest entry must be an object.");
288
+ i.push("Protocol manifest entry must be an object.");
153
289
  return;
154
290
  }
155
- const s = t.entrypoint || "protocol.aimd", r = `${e}${s}`;
156
- this.has(r) || o.push(`Protocol entrypoint '${r}' is missing.`);
157
- const n = Array.isArray(t.files) ? t.files : [], a = t.file_hashes && typeof t.file_hashes == "object" ? t.file_hashes : {};
158
- for (const c of n) {
159
- const f = `${e}${c}`, d = v(f);
160
- if (d) {
161
- o.push(d);
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
- o.push(`Protocol file '${f}' is missing.`);
301
+ i.push(`Protocol file '${f}' is missing.`);
166
302
  continue;
167
303
  }
168
- const h = a[c];
169
- if (h) {
170
- const l = await A(await this.readBytes(f));
171
- l !== h && o.push(`Protocol file '${f}' sha256 mismatch: expected ${h}, got ${l}.`);
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, e, o = !1) {
176
- const s = /* @__PURE__ */ new Set();
311
+ async validateProtocolList(t, n, i = !1) {
312
+ const r = /* @__PURE__ */ new Set();
177
313
  if (!Array.isArray(t))
178
- return e.push("Protocols manifest field must be a list."), s;
179
- o && t.length === 0 && e.push("Protocols manifest field must include at least one protocol.");
180
- for (const [r, n] of t.entries()) {
181
- if (!n || typeof n != "object") {
182
- e.push(`Protocol manifest entry #${r + 1} must be an object.`);
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 (!n.archive_root) {
186
- e.push(`Protocol manifest entry #${r + 1} is missing archive_root.`);
321
+ if (!o.archive_root) {
322
+ n.push(`Protocol manifest entry #${s + 1} is missing archive_root.`);
187
323
  continue;
188
324
  }
189
- if (s.has(n.archive_root)) {
190
- e.push(`Protocol manifest entry #${r + 1} uses duplicate archive_root '${n.archive_root}'.`);
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
- s.add(n.archive_root), await this.validateProtocol(n, `${n.archive_root.replace(/\/+$/, "")}/`, e);
329
+ r.add(o.archive_root), await this.validateProtocol(o, `${o.archive_root.replace(/\/+$/, "")}/`, n);
194
330
  }
195
- return s;
331
+ return r;
196
332
  }
197
333
  async validateRecords(t) {
198
- const e = Array.isArray(this.manifest.records) ? this.manifest.records : [], o = await this.validateProtocolList(this.manifest.protocols, t), s = /* @__PURE__ */ new Set();
199
- for (const [n, a] of e.entries()) {
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 #${n + 1} must be an object.`);
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 #${n + 1} is missing a path.`);
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
- s.add(c);
219
- let d, h;
354
+ r.add(c);
355
+ let h, l;
220
356
  try {
221
- d = await this.readBytes(c), h = JSON.parse(R.decode(d));
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(...D(h, `Record file '${c}'`)), a.sha256) {
227
- const l = await A(d);
228
- l !== a.sha256 && t.push(`Record file '${c}' sha256 mismatch: expected ${a.sha256}, got ${l}.`);
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 && !o.has(a.embedded_protocol_root) && t.push(`Record file '${c}' references missing embedded protocol root '${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 r = await this.validateBlobs(t);
233
- this.validateFileReferences(t, r, s);
368
+ const s = await this.validateBlobs(t);
369
+ this.validateFileReferences(t, s, r);
234
370
  }
235
371
  async validateBlobs(t) {
236
- const e = /* @__PURE__ */ new Set(), o = this.manifest.blobs;
237
- if (o === void 0)
238
- return e;
239
- if (!Array.isArray(o))
240
- return t.push("Blobs manifest field must be a list."), e;
241
- const s = /* @__PURE__ */ new Set();
242
- for (const [r, n] of o.entries()) {
243
- if (!n || typeof n != "object") {
244
- t.push(`Blob manifest entry #${r + 1} must be an object.`);
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 = n.blob_id, c = n.archive_path, f = n.sha256;
383
+ const a = o.blob_id, c = o.archive_path, f = o.sha256;
248
384
  if (!a) {
249
- t.push(`Blob manifest entry #${r + 1} is missing blob_id.`);
385
+ t.push(`Blob manifest entry #${s + 1} is missing blob_id.`);
250
386
  continue;
251
387
  }
252
- if (e.has(a)) {
253
- t.push(`Blob manifest entry #${r + 1} uses duplicate blob_id '${a}'.`);
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 (e.add(a), !f || !j.test(f)) {
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 d = `sha256:${f}`;
261
- if (a !== d && t.push(`Blob '${a}' does not match sha256-derived id '${d}'.`), !c) {
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 h = v(c);
266
- if (h) {
267
- t.push(h);
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/'.`), s.has(c)) {
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 (s.add(c), !this.has(c)) {
410
+ if (r.add(c), !this.has(c)) {
275
411
  t.push(`Blob file '${c}' is missing.`);
276
412
  continue;
277
413
  }
278
- const l = await this.readBytes(c), _ = await A(l);
279
- _ !== f && t.push(`Blob file '${c}' sha256 mismatch: expected ${f}, got ${_}.`), typeof n.size == "number" && n.size !== l.byteLength ? t.push(`Blob file '${c}' size mismatch: expected ${n.size}, got ${l.byteLength}.`) : n.size !== void 0 && typeof n.size != "number" && t.push(`Blob '${a}' size must be a number when present.`);
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 e;
417
+ return n;
282
418
  }
283
- validateFileReferences(t, e, o) {
284
- const s = this.manifest.files;
285
- if (s !== void 0) {
286
- if (!Array.isArray(s)) {
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 [r, n] of s.entries()) {
291
- if (!n || typeof n != "object") {
292
- t.push(`File manifest entry #${r + 1} must be an object.`);
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
- !n.file_id && !n.source_uri && !n.blob_id && t.push(`File manifest entry #${r + 1} must include file_id, source_uri, or blob_id.`), n.blob_id && !e.has(n.blob_id) && t.push(`File manifest entry #${r + 1} references missing blob_id '${n.blob_id}'.`), n.record_path && !o.has(n.record_path) && t.push(`File manifest entry #${r + 1} references missing record_path '${n.record_path}'.`), n.field_path !== void 0 && typeof n.field_path != "string" && t.push(`File manifest entry #${r + 1} field_path must be a string.`);
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 L(i) {
301
- return $.open(i);
436
+ async function et(e) {
437
+ return _.open(e);
302
438
  }
303
- async function N(i) {
304
- return (await $.open(i)).summary();
439
+ async function nt(e) {
440
+ return (await _.open(e)).summary();
305
441
  }
306
- function H(i) {
307
- return JSON.stringify(i, null, 2);
442
+ function rt(e) {
443
+ return JSON.stringify(e, null, 2);
308
444
  }
309
- function z(i) {
310
- return x.encode(i);
445
+ function it(e) {
446
+ return w.encode(e);
311
447
  }
312
448
  export {
313
- E as AIRALOGY_RECORD_FORMAT,
314
- k as AIRALOGY_RECORD_SCHEMA_VERSION,
315
- g as AIRA_ARCHIVE_FORMAT,
316
- y as AIRA_MANIFEST_PATH,
317
- $ as AiraArchive,
318
- z as encodeUtf8,
319
- L as openAiraArchive,
320
- H as prettyPrintJson,
321
- N as readAiraArchiveSummary
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@airalogy/aira-core",
3
3
  "type": "module",
4
- "version": "0.2.0",
4
+ "version": "0.3.0",
5
5
  "description": "Core parser and validator for Airalogy .aira archives",
6
6
  "license": "Apache-2.0",
7
7
  "repository": {
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[]