@ariestools/aries-datalake-client 0.1.19 → 0.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,252 @@
1
+ // src/CompositePayloadsClient.ts
2
+ var CompositePayloadsWriteError = class extends Error {
3
+ result;
4
+ constructor(result) {
5
+ super("Required datalake writes did not complete");
6
+ this.name = "CompositePayloadsWriteError";
7
+ this.result = result;
8
+ }
9
+ };
10
+ var CompositePayloadsReadError = class extends Error {
11
+ result;
12
+ constructor(result) {
13
+ super("Datalake read could not be verified");
14
+ this.name = "CompositePayloadsReadError";
15
+ this.result = result;
16
+ }
17
+ };
18
+ var CompositePayloadsClient = class {
19
+ identify;
20
+ targets;
21
+ constructor(options) {
22
+ if (typeof options.identify !== "function") throw new TypeError("Canonical payload identity is required");
23
+ if (options.targets.length === 0) throw new TypeError("At least one datalake target is required");
24
+ const names = /* @__PURE__ */ new Set();
25
+ this.targets = options.targets.map((target) => {
26
+ if (!/^[a-z0-9][a-z0-9._-]{0,63}$/i.test(target.name) || names.has(target.name)) {
27
+ throw new TypeError("Datalake target names must be unique non-secret identifiers");
28
+ }
29
+ names.add(target.name);
30
+ return { ...target, policy: snapshotPolicy(target.policy) };
31
+ });
32
+ this.identify = options.identify;
33
+ }
34
+ /** Missing hashes fall through in target order. Provider errors and corruption fail explicitly. */
35
+ async get(hashes) {
36
+ return (await this.getWithReceipts(hashes)).payloads;
37
+ }
38
+ async getMany(hashes) {
39
+ return await this.get(hashes);
40
+ }
41
+ async getWithReceipts(hashes) {
42
+ const requested = [...new Set(hashes)];
43
+ if (requested.some((hash) => typeof hash !== "string" || hash.length === 0)) throw new TypeError("Hashes must be non-empty strings");
44
+ const found = /* @__PURE__ */ new Map();
45
+ const result = {
46
+ payloads: [],
47
+ receipts: [],
48
+ sources: {}
49
+ };
50
+ for (const target of this.targets) {
51
+ const remaining = requested.filter((hash) => !found.has(hash));
52
+ if (remaining.length === 0) break;
53
+ const receipt = {
54
+ target: target.name,
55
+ requested: remaining,
56
+ found: []
57
+ };
58
+ result.receipts.push(receipt);
59
+ let response;
60
+ try {
61
+ response = await target.client.getMany([...remaining]);
62
+ } catch {
63
+ receipt.error = "provider_error";
64
+ throw new CompositePayloadsReadError(result);
65
+ }
66
+ try {
67
+ const verified = await this.verify(response, new Set(remaining));
68
+ for (const payload of verified.values()) {
69
+ found.set(payload._hash, payload);
70
+ result.sources[payload._hash] = target.name;
71
+ receipt.found.push(payload._hash);
72
+ }
73
+ result.payloads = requested.flatMap((hash) => found.has(hash) ? [found.get(hash)] : []);
74
+ } catch {
75
+ receipt.error = "invalid_response";
76
+ throw new CompositePayloadsReadError(result);
77
+ }
78
+ }
79
+ return result;
80
+ }
81
+ /** Payload-array acknowledgment suitable for consumers of insert/get method contracts. */
82
+ async insert(payloads) {
83
+ return (await this.insertWithReceipts(payloads)).acknowledged;
84
+ }
85
+ async insertWithReceipts(payloads) {
86
+ const snapshots = payloads.map(snapshotPayload);
87
+ const prepared = [];
88
+ const seen = /* @__PURE__ */ new Set();
89
+ const encoder = new TextEncoder();
90
+ for (const payload of snapshots) {
91
+ const { hash } = await this.identify(payload);
92
+ requireHash(hash);
93
+ if (!seen.has(hash)) {
94
+ prepared.push({
95
+ payload,
96
+ hash,
97
+ bytes: encoder.encode(JSON.stringify(payload)).byteLength
98
+ });
99
+ seen.add(hash);
100
+ }
101
+ }
102
+ const writes = this.targets.map((target) => prepareWrite(target, prepared));
103
+ const outcomes = await Promise.all(writes.map((write) => this.writeTarget(write)));
104
+ const acknowledged = /* @__PURE__ */ new Map();
105
+ for (const outcome of outcomes) {
106
+ for (const payload of outcome.acknowledged) acknowledged.set(payload._hash, payload);
107
+ }
108
+ const result = {
109
+ acknowledged: prepared.flatMap(({ hash }) => acknowledged.has(hash) ? [acknowledged.get(hash)] : []),
110
+ receipts: writes.map((write) => write.receipt)
111
+ };
112
+ if (result.receipts.some((receipt) => receipt.status === "failed")) throw new CompositePayloadsWriteError(result);
113
+ return result;
114
+ }
115
+ async verify(payloads, expected) {
116
+ if (!Array.isArray(payloads)) throw new TypeError("Expected payload array");
117
+ const snapshots = payloads.map((payload) => {
118
+ if (typeof payload?._sequence !== "string" || payload._sequence.length === 0) throw new TypeError("Missing storage sequence");
119
+ return {
120
+ ...snapshotPayload(payload),
121
+ _hash: payload._hash,
122
+ _dataHash: payload._dataHash,
123
+ _sequence: payload._sequence
124
+ };
125
+ });
126
+ const verified = /* @__PURE__ */ new Map();
127
+ for (const payload of snapshots) {
128
+ const identity = await this.identify(snapshotPayload(payload));
129
+ if (identity.hash !== payload._hash || identity.dataHash !== payload._dataHash || !expected.has(identity.hash)) {
130
+ throw new TypeError("Unexpected payload identity");
131
+ }
132
+ verified.set(identity.hash, payload);
133
+ }
134
+ return verified;
135
+ }
136
+ async writeTarget({
137
+ target,
138
+ payloads,
139
+ receipt
140
+ }) {
141
+ const result = { acknowledged: [], receipts: [receipt] };
142
+ if (payloads.length === 0) return result;
143
+ let response;
144
+ try {
145
+ response = await target.client.insert(payloads.map((item) => snapshotPayload(item.payload)));
146
+ } catch {
147
+ receipt.status = "failed";
148
+ receipt.error = "provider_error";
149
+ return result;
150
+ }
151
+ try {
152
+ await this.verify(response.inserted, new Set(receipt.eligible));
153
+ if (!Array.isArray(response.summary.rejected) || response.summary.rejected.some((hash) => !receipt.eligible.includes(hash))) {
154
+ throw new TypeError("Unexpected rejected hashes");
155
+ }
156
+ receipt.rejected = [...new Set(response.summary.rejected)];
157
+ } catch {
158
+ receipt.status = "failed";
159
+ receipt.error = "invalid_response";
160
+ return result;
161
+ }
162
+ let readback;
163
+ try {
164
+ readback = await target.client.getMany([...receipt.eligible]);
165
+ } catch {
166
+ receipt.status = "failed";
167
+ receipt.error = "provider_error";
168
+ return result;
169
+ }
170
+ try {
171
+ const verified = await this.verify(readback, new Set(receipt.eligible));
172
+ result.acknowledged = receipt.eligible.flatMap((hash) => verified.has(hash) ? [verified.get(hash)] : []);
173
+ receipt.acknowledged = result.acknowledged.map((payload) => payload._hash);
174
+ if (receipt.rejected.length > 0 || verified.size !== receipt.eligible.length) {
175
+ receipt.status = "failed";
176
+ receipt.error = receipt.rejected.length > 0 ? "policy_rejected" : "incomplete_readback";
177
+ }
178
+ } catch {
179
+ receipt.status = "failed";
180
+ receipt.error = "invalid_response";
181
+ }
182
+ return result;
183
+ }
184
+ };
185
+ function snapshotPolicy(policy) {
186
+ if (!policy || policy.mode !== "all" && policy.mode !== "selected") throw new TypeError("Explicit datalake policy is required");
187
+ if (policy.mode === "selected" && (!Array.isArray(policy.allowedSchemas) || policy.maxPayloadBytes === void 0)) {
188
+ throw new TypeError("Selected datalakes require an allowlist and byte limit");
189
+ }
190
+ for (const schemas of [policy.allowedSchemas, policy.disallowedSchemas]) {
191
+ if (schemas !== void 0 && (!Array.isArray(schemas) || schemas.some((schema) => typeof schema !== "string" || schema.length === 0))) {
192
+ throw new TypeError("Schema lists must contain non-empty identifiers");
193
+ }
194
+ }
195
+ for (const limit of [policy.maxPayloadBytes, ...Object.values(policy.schemaMaxPayloadBytes ?? {})]) {
196
+ if (limit !== void 0 && (!Number.isSafeInteger(limit) || limit <= 0)) throw new RangeError("Payload byte limits must be positive safe integers");
197
+ }
198
+ return {
199
+ ...policy,
200
+ allowedSchemas: policy.allowedSchemas === void 0 ? void 0 : [...policy.allowedSchemas],
201
+ disallowedSchemas: policy.disallowedSchemas === void 0 ? void 0 : [...policy.disallowedSchemas],
202
+ schemaMaxPayloadBytes: { ...policy.schemaMaxPayloadBytes }
203
+ };
204
+ }
205
+ function prepareWrite(target, payloads) {
206
+ const selected = [];
207
+ const excluded = [];
208
+ for (const item of payloads) {
209
+ const reason = exclusionReason(target.policy, item);
210
+ if (reason) excluded.push({ hash: item.hash, reason });
211
+ else selected.push(item);
212
+ }
213
+ return {
214
+ target,
215
+ payloads: selected,
216
+ receipt: {
217
+ target: target.name,
218
+ policyRevision: target.policy.revision,
219
+ eligible: selected.map((item) => item.hash),
220
+ excluded,
221
+ acknowledged: [],
222
+ rejected: [],
223
+ status: selected.length > 0 ? "complete" : "excluded"
224
+ }
225
+ };
226
+ }
227
+ function exclusionReason(policy, item) {
228
+ const { schema } = item.payload;
229
+ if (policy.allowedSchemas !== void 0 && !policy.allowedSchemas.includes(schema) || policy.disallowedSchemas?.includes(schema)) return "schema";
230
+ const schemaLimit = policy.schemaMaxPayloadBytes && Object.hasOwn(policy.schemaMaxPayloadBytes, schema) ? policy.schemaMaxPayloadBytes[schema] : void 0;
231
+ const limit = Math.min(policy.maxPayloadBytes ?? Infinity, schemaLimit ?? Infinity);
232
+ if (item.bytes > limit) return "size";
233
+ if (policy.isValid && !policy.isValid(snapshotPayload(item.payload))) return "validation";
234
+ return void 0;
235
+ }
236
+ function snapshotPayload(payload) {
237
+ if (!payload || typeof payload !== "object" || Array.isArray(payload) || typeof payload.schema !== "string" || payload.schema.length === 0) {
238
+ throw new TypeError("Expected flat schema-bearing payloads");
239
+ }
240
+ const stripped = Object.fromEntries(Object.entries(payload).filter(([key]) => !key.startsWith("_")));
241
+ const serialized = JSON.stringify(stripped);
242
+ const copy = JSON.parse(serialized);
243
+ if (copy.schema !== payload.schema) throw new TypeError("Payload serialization changed its schema");
244
+ return copy;
245
+ }
246
+ function requireHash(hash) {
247
+ if (typeof hash !== "string" || hash.length === 0) throw new TypeError("Canonical payload identity returned an invalid hash");
248
+ }
249
+
1
250
  // src/credentials.ts
2
251
  import {
3
252
  chmodSync,
@@ -110,7 +359,8 @@ var LocalDatalakeClient = class {
110
359
  if (store.datalakes.some((dl) => dl.name === request.name)) {
111
360
  throw new Error(`Datalake already exists: ${request.name}`);
112
361
  }
113
- const now = (/* @__PURE__ */ new Date()).toISOString();
362
+ const nowDate = /* @__PURE__ */ new Date();
363
+ const now = nowDate.toISOString();
114
364
  const tier = request.config.tier;
115
365
  const descriptor = {
116
366
  id: generateId(),
@@ -157,10 +407,11 @@ var LocalDatalakeClient = class {
157
407
  const store = readStore();
158
408
  const datalake = requireDatalake(store, request.datalakeId);
159
409
  const existing = datalake.acl.findIndex((entry2) => entry2.principal === request.principal);
410
+ const grantedAtDate = /* @__PURE__ */ new Date();
160
411
  const entry = {
161
412
  principal: request.principal,
162
413
  role: request.role,
163
- grantedAt: (/* @__PURE__ */ new Date()).toISOString(),
414
+ grantedAt: grantedAtDate.toISOString(),
164
415
  grantedBy: LOCAL_OWNER_ID
165
416
  };
166
417
  if (existing === -1) {
@@ -186,11 +437,12 @@ var LocalDatalakeClient = class {
186
437
  exp: Math.floor(Date.now() / 1e3) + ttl
187
438
  })
188
439
  ).toString("base64url");
440
+ const expiresAtDate = new Date(Date.now() + ttl * 1e3);
189
441
  return {
190
442
  token: `local.${issued}`,
191
443
  datalakeId: datalake.id,
192
444
  role: request.role,
193
- expiresAt: new Date(Date.now() + ttl * 1e3).toISOString(),
445
+ expiresAt: expiresAtDate.toISOString(),
194
446
  url: datalake.url ?? `http://localhost:8080/datalakes/${datalake.name}`
195
447
  };
196
448
  }
@@ -198,7 +450,8 @@ var LocalDatalakeClient = class {
198
450
  const store = readStore();
199
451
  const datalake = requireDatalake(store, request.datalakeId);
200
452
  datalake.acl = datalake.acl.filter((entry) => entry.principal !== request.principal);
201
- datalake.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
453
+ const updatedAtDate = /* @__PURE__ */ new Date();
454
+ datalake.updatedAt = updatedAtDate.toISOString();
202
455
  writeStore(store);
203
456
  return datalake;
204
457
  }
@@ -337,10 +590,11 @@ import {
337
590
  function setDefaultDatalake(name, id) {
338
591
  const home = getAriesHome();
339
592
  if (!existsSync3(home)) mkdirSync3(home, { recursive: true });
593
+ const setAtDate = /* @__PURE__ */ new Date();
340
594
  const payload = {
341
595
  name,
342
596
  id,
343
- setAt: (/* @__PURE__ */ new Date()).toISOString()
597
+ setAt: setAtDate.toISOString()
344
598
  };
345
599
  writeFileSync3(getDefaultDatalakePath(), JSON.stringify(payload, void 0, 2), "utf8");
346
600
  }
@@ -362,11 +616,9 @@ function clearDefaultDatalake() {
362
616
 
363
617
  // src/PayloadsClient.ts
364
618
  import {
365
- DATALAKE_API_VERSION,
366
619
  DATALAKE_HEADER_DUPLICATES,
367
620
  DATALAKE_HEADER_NEXT_CURSOR,
368
621
  DATALAKE_HEADER_REJECTED,
369
- DatalakeApiError as DatalakeApiError2,
370
622
  datalakePlaneClearPath,
371
623
  datalakePlaneDeletePath,
372
624
  datalakePlaneGetManyPath,
@@ -375,6 +627,9 @@ import {
375
627
  datalakePlaneNextPath,
376
628
  datalakePlaneUsagePath
377
629
  } from "@ariestools/aries-datalake-core/browser";
630
+
631
+ // src/payloadRequest.ts
632
+ import { DATALAKE_API_VERSION, DatalakeApiError as DatalakeApiError2 } from "@ariestools/aries-datalake-core/browser";
378
633
  function extractRequestBase(urlWithPossiblePath) {
379
634
  try {
380
635
  const parsed = new URL(urlWithPossiblePath);
@@ -386,6 +641,24 @@ function extractRequestBase(urlWithPossiblePath) {
386
641
  return urlWithPossiblePath.replace(/\/$/, "");
387
642
  }
388
643
  }
644
+ function parsePayloadResponse(response, text, method) {
645
+ const payload = text.length > 0 ? JSON.parse(text) : void 0;
646
+ if (!response.ok) {
647
+ const errorBody = isApiErrorBody2(payload) ? payload : {
648
+ code: "internal",
649
+ message: "Unexpected " + response.status + " response from " + method + " data plane"
650
+ };
651
+ throw new DatalakeApiError2(response.status, errorBody);
652
+ }
653
+ return payload;
654
+ }
655
+ function isApiErrorBody2(value) {
656
+ if (typeof value !== "object" || value === null) return false;
657
+ const maybe = value;
658
+ return typeof maybe.code === "string" && typeof maybe.message === "string";
659
+ }
660
+
661
+ // src/PayloadsClient.ts
389
662
  var RestPayloadsClient = class {
390
663
  authToken;
391
664
  datalakeId;
@@ -469,20 +742,12 @@ var RestPayloadsClient = class {
469
742
  const response = await this.fetchImpl(`${this.requestBase}${path4}`, init);
470
743
  if (response.status === 204) return { data: void 0, headers: response.headers };
471
744
  const text = await response.text();
472
- const payload = text.length > 0 ? JSON.parse(text) : void 0;
473
- if (!response.ok) {
474
- const errorBody = isApiErrorBody2(payload) ? payload : {
475
- code: "internal",
476
- message: `Unexpected ${response.status} response from ${method} data plane`
477
- };
478
- throw new DatalakeApiError2(response.status, errorBody);
479
- }
480
- return { data: payload, headers: response.headers };
745
+ return { data: parsePayloadResponse(response, text, method), headers: response.headers };
481
746
  }
482
747
  };
483
748
  function readInsertSummary(headers) {
484
749
  const duplicatesRaw = headers.get(DATALAKE_HEADER_DUPLICATES);
485
- const duplicates = duplicatesRaw === null ? 0 : Number.parseInt(duplicatesRaw, 10);
750
+ const duplicates = duplicatesRaw === null ? 0 : Math.trunc(Number(duplicatesRaw));
486
751
  const rejectedRaw = headers.get(DATALAKE_HEADER_REJECTED);
487
752
  const rejected = rejectedRaw === null || rejectedRaw.length === 0 ? [] : rejectedRaw.split(",").map((value) => value.trim()).filter((value) => value.length > 0);
488
753
  return {
@@ -490,15 +755,92 @@ function readInsertSummary(headers) {
490
755
  rejected
491
756
  };
492
757
  }
493
- function isApiErrorBody2(value) {
494
- if (typeof value !== "object" || value === null) return false;
495
- const maybe = value;
496
- return typeof maybe.code === "string" && typeof maybe.message === "string";
758
+
759
+ // src/RestPublicPayloadsReader.ts
760
+ import { datalakePlaneGetManyPath as datalakePlaneGetManyPath2, datalakePlaneGetPath as datalakePlaneGetPath2 } from "@ariestools/aries-datalake-core/browser";
761
+ import { readResponseText } from "@ariestools/sdk/fetch";
762
+ var RestPublicPayloadsReader = class {
763
+ datalakeId;
764
+ fetchImpl;
765
+ requestBase;
766
+ constructor(options) {
767
+ this.requestBase = publicRequestBase(options.baseUrl);
768
+ this.datalakeId = options.datalakeId;
769
+ this.fetchImpl = options.fetchImpl;
770
+ }
771
+ async get(hash, options = {}) {
772
+ const payload = await this.request(datalakePlaneGetPath2(this.datalakeId, hash), options);
773
+ if (!isStoredPayload(payload)) throw new TypeError("Data plane returned an invalid stored payload");
774
+ return payload;
775
+ }
776
+ async getMany(hashes, options = {}) {
777
+ const policy = pinReadOptions(options);
778
+ const requested = [...hashes];
779
+ if (requested.length === 0) return [];
780
+ const payloads = await this.request(datalakePlaneGetManyPath2(this.datalakeId), policy, requested);
781
+ if (!Array.isArray(payloads) || !payloads.every(isStoredPayload)) {
782
+ throw new TypeError("Data plane returned an invalid stored-payload array");
783
+ }
784
+ return payloads;
785
+ }
786
+ async request(path4, options, hashes) {
787
+ const { maxResponseBytes, signal } = pinReadOptions(options);
788
+ const method = hashes === void 0 ? "GET" : "POST";
789
+ const headers = { accept: "application/json" };
790
+ const init = {
791
+ credentials: "omit",
792
+ headers,
793
+ method,
794
+ redirect: "error",
795
+ signal
796
+ };
797
+ if (hashes !== void 0) {
798
+ headers["content-type"] = "application/json";
799
+ init.body = JSON.stringify(hashes);
800
+ }
801
+ const fetchImpl = this.fetchImpl ?? globalThis.fetch;
802
+ const response = await fetchImpl(this.requestBase + path4, init);
803
+ const text = await readResponseText(response, { maxResponseBytes, signal });
804
+ return parsePayloadResponse(response, text, method);
805
+ }
806
+ };
807
+ function pinReadOptions(options) {
808
+ const { maxResponseBytes, signal } = options;
809
+ if (maxResponseBytes !== void 0 && (!Number.isSafeInteger(maxResponseBytes) || maxResponseBytes <= 0)) {
810
+ throw new RangeError("maxResponseBytes must be a positive safe integer");
811
+ }
812
+ signal?.throwIfAborted();
813
+ return { maxResponseBytes, signal };
814
+ }
815
+ function publicRequestBase(baseUrl) {
816
+ let parsed;
817
+ try {
818
+ parsed = new URL(baseUrl);
819
+ } catch {
820
+ throw new TypeError("baseUrl must be an absolute HTTP(S) URL");
821
+ }
822
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
823
+ throw new TypeError("baseUrl must be an absolute HTTP(S) URL");
824
+ }
825
+ if (parsed.username !== "" || parsed.password !== "") {
826
+ throw new TypeError("Public data-plane URLs must not contain credentials");
827
+ }
828
+ if (parsed.href.includes("#")) throw new TypeError("Public data-plane URLs must not contain fragments");
829
+ return extractRequestBase(baseUrl);
830
+ }
831
+ function isStoredPayload(value) {
832
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
833
+ const payload = value;
834
+ return ["schema", "_hash", "_dataHash", "_sequence"].every((key) => Object.hasOwn(payload, key) && typeof payload[key] === "string");
497
835
  }
498
836
  export {
837
+ CompositePayloadsClient,
838
+ CompositePayloadsReadError,
839
+ CompositePayloadsWriteError,
499
840
  LocalDatalakeClient,
500
841
  RestDatalakeClient,
501
842
  RestPayloadsClient,
843
+ RestPublicPayloadsReader,
502
844
  clearCredentials,
503
845
  clearDefaultDatalake,
504
846
  createDatalakeClient,