abra-flexi 0.5.6 → 0.6.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/dist/index.cjs CHANGED
@@ -96,7 +96,7 @@ function parsePropertyValue(propertyType, annot, obj) {
96
96
  return new Date(parseInt(s[0], parseInt(s[1]) - 1));
97
97
  case PropertyType.Numeric: return new big_js.Big(obj).round(annot?.decimals ?? 2);
98
98
  case PropertyType.Logic: return obj.toLowerCase() === "true";
99
- case PropertyType.Blob: return Buffer.from(obj, "base64");
99
+ case PropertyType.Blob: return base64ToBytes(obj);
100
100
  case PropertyType.Array:
101
101
  if (!annot?.itemType) throw new AFError(AFErrorCode.ITEM_TYPE_MISSING, "itemType is required for Array");
102
102
  const myAnnot = { ...annot };
@@ -121,7 +121,7 @@ function serializePropertyValue(propertyType, annot, val) {
121
121
  if (val instanceof big_js.Big) return val.toString();
122
122
  return val;
123
123
  case PropertyType.Logic: return !!val ? "true" : "false";
124
- case PropertyType.Blob: return val.toString("base64");
124
+ case PropertyType.Blob: return bytesToBase64(val);
125
125
  case PropertyType.Array:
126
126
  if (!annot?.itemType) throw new AFError(AFErrorCode.ITEM_TYPE_MISSING, "itemType is required for Array");
127
127
  const myAnnot = { ...annot };
@@ -146,6 +146,22 @@ function dateToLocalIso(date) {
146
146
  const offset = -date.getTimezoneOffset();
147
147
  return `${yyyy}-${mm}-${dd}T${hh}:${mi}:${ss}.${ms}${offset >= 0 ? "+" : "-"}${pad(Math.floor(Math.abs(offset) / 60))}:${pad(Math.abs(offset) % 60)}`;
148
148
  }
149
+ function base64ToBytes(base64) {
150
+ if (typeof Buffer !== "undefined") {
151
+ const buf = Buffer.from(base64, "base64");
152
+ return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
153
+ }
154
+ const binary = atob(base64);
155
+ const bytes = new Uint8Array(binary.length);
156
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
157
+ return bytes;
158
+ }
159
+ function bytesToBase64(bytes) {
160
+ if (typeof Buffer !== "undefined") return Buffer.from(bytes).toString("base64");
161
+ let binary = "";
162
+ for (let i = 0; i < bytes.byteLength; i++) binary += String.fromCharCode(bytes[i]);
163
+ return btoa(binary);
164
+ }
149
165
  function arraysEqual(a, b) {
150
166
  if (a === b) return true;
151
167
  if (a.length !== b.length) return false;
@@ -170,6 +186,7 @@ var AFEntity = class AFEntity {
170
186
  }
171
187
  constructor(stitkyCache) {
172
188
  this._orig = {};
189
+ this._isNew = true;
173
190
  this._stitkyCache = stitkyCache;
174
191
  }
175
192
  getPropertyTypeAnnotation(key) {
@@ -185,7 +202,7 @@ var AFEntity = class AFEntity {
185
202
  return !this.hasChanged();
186
203
  }
187
204
  get isNew() {
188
- return !this.id && !this.kod;
205
+ return this._isNew;
189
206
  }
190
207
  getCotr() {
191
208
  return this.constructor;
@@ -66562,6 +66579,29 @@ function extractEvidence(inUrl) {
66562
66579
  return split[3];
66563
66580
  }
66564
66581
 
66582
+ //#endregion
66583
+ //#region src/abra/AFNestedEntityResolver.ts
66584
+ function resolveNestedEntityPathPrefix(entityPath, options) {
66585
+ switch (entityPath) {
66586
+ case "individualni-cenik": {
66587
+ const sel = serializeParentSelector(options.adresarId, "adresarId", entityPath);
66588
+ if (sel === void 0) return "";
66589
+ return `adresar/${sel}/`;
66590
+ }
66591
+ default: return "";
66592
+ }
66593
+ }
66594
+ function serializeParentSelector(value, optionName, entityPath) {
66595
+ if (value === void 0 || value === null) return void 0;
66596
+ if (value instanceof AFFilter) {
66597
+ const piece = value.toUrlComponent();
66598
+ if (!piece.length) throw new AFError(AFErrorCode.MISSING_ID, `'${optionName}' for ${entityPath} resolved to empty URL component.`);
66599
+ return piece;
66600
+ }
66601
+ if (typeof value === "string" && !value.length) throw new AFError(AFErrorCode.MISSING_ID, `'${optionName}' for ${entityPath} must be a non-empty id.`);
66602
+ return String(value);
66603
+ }
66604
+
66565
66605
  //#endregion
66566
66606
  //#region src/abra/AFStitkyCache.ts
66567
66607
  const DEBOUNCE_MS = 5 * 1e3;
@@ -66580,51 +66620,61 @@ var AFStitkyCache = class {
66580
66620
  if (this._strategy === StitkyCacheStrategy.None) return;
66581
66621
  const now = /* @__PURE__ */ new Date();
66582
66622
  if (this._lastUpdate && now.getTime() - this._lastUpdate.getTime() < DEBOUNCE_MS) return;
66583
- const skupOpts = {
66584
- limit: NO_LIMIT,
66585
- detail: AFQueryDetail.FULL,
66586
- noUpdateStitkyCache: true
66587
- };
66588
- if (this._lastUpdate) skupOpts.filter = Filter(`lastUpdate > ':date'`, { date: this._lastUpdate });
66589
- const skUpdate = await this._client.query(AFSkupinaStitku, skupOpts);
66590
- for (const sk of skUpdate) {
66591
- let found = this._stitekSkupiny.find((ss) => ss.id === sk.id);
66592
- if (!found) {
66593
- this._stitekSkupiny.push(sk);
66594
- continue;
66595
- }
66596
- Object.assign(found, sk);
66597
- }
66598
- const stitOpts = {
66599
- limit: NO_LIMIT,
66600
- detail: [
66601
- "id",
66602
- "kod",
66603
- "lastUpdate",
66604
- "nazev",
66605
- "nazevA",
66606
- "nazevB",
66607
- "nazevC",
66608
- "nazevD",
66609
- "poznam",
66610
- "popis",
66611
- "platiOd",
66612
- "platiDo",
66613
- "skupVybKlic"
66614
- ],
66615
- noUpdateStitkyCache: true
66616
- };
66617
- if (this._lastUpdate) skupOpts.filter = Filter(`lastUpdate > ':date'`, { date: this._lastUpdate });
66618
- const stUpdate = await this._client.query(AFStitek, stitOpts);
66619
- for (const st of stUpdate) {
66620
- st.skupVybKlic = this._stitekSkupiny.find((ss) => ss.kod === st.skupVybKlic?.kod);
66621
- const found = this._stitky.find((s) => s.id === st.id);
66622
- if (!found) {
66623
- this._stitky.push(st);
66624
- continue;
66623
+ if (this._inflight) return this._inflight;
66624
+ this._inflight = (async () => {
66625
+ try {
66626
+ const sinceTs = this._lastUpdate ? formatAbraTimestamp(this._lastUpdate) : void 0;
66627
+ const skupOpts = {
66628
+ limit: NO_LIMIT,
66629
+ detail: AFQueryDetail.FULL,
66630
+ noUpdateStitkyCache: true
66631
+ };
66632
+ if (sinceTs) skupOpts.filter = Filter(`lastUpdate > :date`, { date: sinceTs });
66633
+ const skUpdate = await this._client.query(AFSkupinaStitku, skupOpts);
66634
+ for (const sk of skUpdate) {
66635
+ let found = this._stitekSkupiny.find((ss) => ss.id === sk.id);
66636
+ if (!found) {
66637
+ this._stitekSkupiny.push(sk);
66638
+ continue;
66639
+ }
66640
+ Object.assign(found, sk);
66641
+ }
66642
+ const stitOpts = {
66643
+ limit: NO_LIMIT,
66644
+ detail: [
66645
+ "id",
66646
+ "kod",
66647
+ "lastUpdate",
66648
+ "nazev",
66649
+ "nazevA",
66650
+ "nazevB",
66651
+ "nazevC",
66652
+ "nazevD",
66653
+ "poznam",
66654
+ "popis",
66655
+ "platiOd",
66656
+ "platiDo",
66657
+ "skupVybKlic"
66658
+ ],
66659
+ noUpdateStitkyCache: true
66660
+ };
66661
+ if (sinceTs) stitOpts.filter = Filter(`lastUpdate > :date`, { date: sinceTs });
66662
+ const stUpdate = await this._client.query(AFStitek, stitOpts);
66663
+ for (const st of stUpdate) {
66664
+ st.skupVybKlic = this._stitekSkupiny.find((ss) => ss.kod === st.skupVybKlic?.kod);
66665
+ const found = this._stitky.find((s) => s.id === st.id);
66666
+ if (!found) {
66667
+ this._stitky.push(st);
66668
+ continue;
66669
+ }
66670
+ Object.assign(found, st);
66671
+ }
66672
+ this._lastUpdate = now;
66673
+ } finally {
66674
+ this._inflight = void 0;
66625
66675
  }
66626
- Object.assign(found, st);
66627
- }
66676
+ })();
66677
+ return this._inflight;
66628
66678
  }
66629
66679
  stitkyWithString(keys, groupFilter) {
66630
66680
  if (!keys) return void 0;
@@ -66644,15 +66694,27 @@ var AFStitkyCache = class {
66644
66694
  return list;
66645
66695
  }
66646
66696
  };
66697
+ function formatAbraTimestamp(d) {
66698
+ const pad = (n) => String(n).padStart(2, "0");
66699
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
66700
+ }
66647
66701
 
66648
66702
  //#endregion
66649
66703
  //#region src/abra/AFApiClient.ts
66650
66704
  const ABRA_API_FORMAT = "json";
66705
+ const LOG_LEVEL_RANK = {
66706
+ none: 0,
66707
+ error: 1,
66708
+ warn: 2,
66709
+ info: 3,
66710
+ debug: 4
66711
+ };
66651
66712
  var AFApiClient = class {
66652
66713
  constructor(config) {
66653
66714
  this._url = config.url;
66654
66715
  this._company = config.company;
66655
66716
  this._fetch = config.fetch || fetch;
66717
+ this._logger = makeFilteringLogger(config.logger ?? console, config.logLevel ?? (config.logger ? "debug" : "none"));
66656
66718
  this._stitkyCache = new AFStitkyCache(this, config.stitkyCacheStrategy);
66657
66719
  }
66658
66720
  get url() {
@@ -66664,14 +66726,14 @@ var AFApiClient = class {
66664
66726
  get stitkyCacheStrategy() {
66665
66727
  return this._stitkyCache.strategy;
66666
66728
  }
66667
- async queryRaw(entityPath, options) {
66668
- if (!this.company || !this.company.length) throw new AFError(AFErrorCode.MISSING_ABRA_COMPANY, `Can't query AFApiClient without providing company path component first.`);
66729
+ _buildQueryUrl(entityPath, format, options) {
66669
66730
  const detail = options.detail || AFQueryDetail.SUMMARY;
66670
66731
  let furl = options.filter?.toUrlComponent();
66671
66732
  if (furl && !furl.length) furl = void 0;
66672
- let url = this._url + "/c/" + this.company + "/" + (options.entityPathPrefix ?? "") + entityPath;
66733
+ const pathPrefix = resolveNestedEntityPathPrefix(entityPath, options);
66734
+ let url = this._url + "/c/" + this.company + "/" + pathPrefix + entityPath;
66673
66735
  url += furl ? "/" + furl : "";
66674
- url += "." + ABRA_API_FORMAT;
66736
+ url += "." + format;
66675
66737
  url = addParamToUrl(url, "detail", composeDetail(detail));
66676
66738
  url = addParamToUrl(url, "includes", composeIncludes(detail, entityPath));
66677
66739
  url = addParamToUrl(url, "relations", composeRelations(detail));
@@ -66690,20 +66752,61 @@ var AFApiClient = class {
66690
66752
  url = addParamToUrl(url, "pocetMesicu", options.pocetMesicu);
66691
66753
  url = addParamToUrl(url, "date", options.date);
66692
66754
  url = addParamToUrl(url, "currency", options.currency);
66693
- console.log(url);
66755
+ return url;
66756
+ }
66757
+ async queryRaw(entityPath, options = {}) {
66758
+ if (!this.company || !this.company.length) throw new AFError(AFErrorCode.MISSING_ABRA_COMPANY, `Can't query AFApiClient without providing company path component first.`);
66759
+ const url = this._buildQueryUrl(entityPath, ABRA_API_FORMAT, options);
66760
+ this._logger.debug(url);
66694
66761
  try {
66695
66762
  const raw$1 = await this._fetch(url, { signal: options.abortController?.signal });
66696
- if (raw$1.status >= 400 && raw$1.status < 600) throw new AFError(AFErrorCode.ABRA_FLEXI_ERROR, `${raw$1.status} ${raw$1.statusText}`);
66697
- return (await raw$1.json()).winstrom[entityPath];
66763
+ const json = await raw$1.json().catch(() => null);
66764
+ if (raw$1.status >= 400 && raw$1.status < 600) {
66765
+ const details = this._extractAbraErrors(json);
66766
+ throw new AFError(AFErrorCode.ABRA_FLEXI_ERROR, `${raw$1.status} ${raw$1.statusText}${details ? ` — ${details}` : ""}`);
66767
+ }
66768
+ return json?.winstrom?.[entityPath];
66769
+ } catch (e) {
66770
+ if (!(e instanceof AFError)) {
66771
+ this._logger.error(e);
66772
+ e = new AFError(AFErrorCode.UNKNOWN, e.toString());
66773
+ }
66774
+ throw e;
66775
+ }
66776
+ }
66777
+ async queryFileRaw(entityPath, format, options = {}) {
66778
+ if (!this.company || !this.company.length) throw new AFError(AFErrorCode.MISSING_ABRA_COMPANY, `Can't query AFApiClient without providing company path component first.`);
66779
+ let url = this._buildQueryUrl(entityPath, format, options);
66780
+ url = addParamToUrl(url, "report-name", options.reportName);
66781
+ url = addParamToUrl(url, "report-lang", options.reportLang);
66782
+ this._logger.debug(url);
66783
+ try {
66784
+ const raw$1 = await this._fetch(url, { signal: options.abortController?.signal });
66785
+ if (raw$1.status >= 400 && raw$1.status < 600) {
66786
+ let details = "";
66787
+ if ((raw$1.headers.get("content-type") || "").includes("application/json")) {
66788
+ const json = await raw$1.json().catch(() => null);
66789
+ details = this._extractAbraErrors(json);
66790
+ }
66791
+ throw new AFError(AFErrorCode.ABRA_FLEXI_ERROR, `${raw$1.status} ${raw$1.statusText}${details ? ` — ${details}` : ""}`);
66792
+ }
66793
+ return {
66794
+ blob: await raw$1.blob(),
66795
+ contentType: raw$1.headers.get("content-type") || "application/octet-stream",
66796
+ filename: parseContentDispositionFilename(raw$1.headers.get("content-disposition"))
66797
+ };
66698
66798
  } catch (e) {
66699
66799
  if (!(e instanceof AFError)) {
66700
- console.log(e);
66800
+ this._logger.error(e);
66701
66801
  e = new AFError(AFErrorCode.UNKNOWN, e.toString());
66702
66802
  }
66703
66803
  throw e;
66704
66804
  }
66705
66805
  }
66706
- async query(entity, options) {
66806
+ async queryFile(entity, format, options = {}) {
66807
+ return this.queryFileRaw(entity.EntityPath, format, options);
66808
+ }
66809
+ async query(entity, options = {}) {
66707
66810
  const res = this.queryRaw(entity.EntityPath, options);
66708
66811
  try {
66709
66812
  const rawData = await res;
@@ -66712,7 +66815,7 @@ var AFApiClient = class {
66712
66815
  return data;
66713
66816
  } catch (e) {
66714
66817
  if (!(e instanceof AFError)) {
66715
- console.log(e);
66818
+ this._logger.error(e);
66716
66819
  e = new AFError(AFErrorCode.UNKNOWN, e.toString());
66717
66820
  }
66718
66821
  throw e;
@@ -66759,7 +66862,7 @@ var AFApiClient = class {
66759
66862
  out.push(res);
66760
66863
  } catch (e) {
66761
66864
  if (!(e instanceof AFError)) {
66762
- console.log(e);
66865
+ this._logger.error(e);
66763
66866
  e = new AFError(AFErrorCode.UNKNOWN, e.toString());
66764
66867
  }
66765
66868
  throw e;
@@ -66770,7 +66873,7 @@ var AFApiClient = class {
66770
66873
  if (!options.noUpdateStitkyCache) await this._stitkyCache.fetchTick();
66771
66874
  return out;
66772
66875
  }
66773
- async populate(entities, options) {
66876
+ async populate(entities, options = {}) {
66774
66877
  const fetchBy = [];
66775
66878
  for (const en of entities) {
66776
66879
  if (typeof en.id !== "undefined" && en.id !== null) {
@@ -66809,18 +66912,19 @@ var AFApiClient = class {
66809
66912
  if (!en) continue;
66810
66913
  const oKeys = Object.keys(enQ);
66811
66914
  for (const okey of oKeys) this._decodeProperty(en, okey, enQ);
66915
+ en._isNew = false;
66812
66916
  }
66813
66917
  if (!options.noUpdateStitkyCache) await this._stitkyCache.fetchTick();
66814
66918
  } catch (e) {
66815
66919
  if (!(e instanceof AFError)) {
66816
- console.log(e);
66920
+ this._logger.error(e);
66817
66921
  e = new AFError(AFErrorCode.UNKNOWN, e.toString());
66818
66922
  }
66819
66923
  throw e;
66820
66924
  }
66821
66925
  return entities;
66822
66926
  }
66823
- async populateOne(entity, options) {
66927
+ async populateOne(entity, options = {}) {
66824
66928
  const res = await this.populate([entity], options);
66825
66929
  if (!res || !res.length) throw new AFError(AFErrorCode.OBJECT_NOT_FOUND, `${entity} object not found. Kod / ID: ${entity.kod} / ${entity.id}`);
66826
66930
  return res[0];
@@ -66833,14 +66937,15 @@ var AFApiClient = class {
66833
66937
  const ent = new entity(this._stitkyCache);
66834
66938
  if (id.id) ent.id = id.id;
66835
66939
  if (id.kod) ent.kod = id.kod;
66940
+ ent._isNew = false;
66836
66941
  return ent;
66837
66942
  }
66838
66943
  async saveRaw(entityPath, data, options) {
66839
66944
  if (!this.company || !this.company.length) throw new AFError(AFErrorCode.MISSING_ABRA_COMPANY, `Can't query AFApiClient without providing company path component first.`);
66840
66945
  if (!options) options = {};
66841
66946
  let url = this._url + "/c/" + this.company + "/" + entityPath + ".json";
66842
- console.log(url);
66843
- console.log(data);
66947
+ this._logger.debug(url);
66948
+ this._logger.debug(data);
66844
66949
  if (options.removeStitky) data["stitky@removeAll"] = "true";
66845
66950
  try {
66846
66951
  const raw$1 = await this._fetch(url, {
@@ -66849,98 +66954,189 @@ var AFApiClient = class {
66849
66954
  headers: { "Content-Type": "application/json" },
66850
66955
  body: JSON.stringify({ winstrom: [{ [entityPath]: data }] })
66851
66956
  });
66957
+ const json = await raw$1.json().catch(() => null);
66958
+ this._logger.debug(json);
66852
66959
  if (raw$1.status >= 400 && raw$1.status < 600) {
66853
- console.log(JSON.stringify(await raw$1.json(), null, "\n"));
66854
- throw new AFError(AFErrorCode.ABRA_FLEXI_ERROR, `${raw$1.status} ${raw$1.statusText}`);
66960
+ const details = this._extractAbraErrors(json);
66961
+ throw new AFError(AFErrorCode.ABRA_FLEXI_ERROR, `${raw$1.status} ${raw$1.statusText}${details ? ` — ${details}` : ""}`);
66855
66962
  }
66856
- const json = await raw$1.json();
66857
- console.log(JSON.stringify(json));
66858
- if (json.winstrom["success"] === "true") {}
66963
+ const jres = json?.winstrom;
66964
+ if (jres && jres["success"] === "false") {
66965
+ const details = this._extractAbraErrors(json);
66966
+ throw new AFError(AFErrorCode.ABRA_FLEXI_ERROR, `Save of ${entityPath} failed${details ? ` — ${details}` : ""}`);
66967
+ }
66968
+ return Array.isArray(jres?.results) ? jres.results : [];
66859
66969
  } catch (e) {
66860
66970
  if (!(e instanceof AFError)) {
66861
- console.log(e);
66971
+ this._logger.error(e);
66862
66972
  e = new AFError(AFErrorCode.UNKNOWN, e.toString());
66863
66973
  }
66864
66974
  throw e;
66865
66975
  }
66866
66976
  }
66867
66977
  async save(entity, options) {
66978
+ if (entity.isNew) {
66979
+ const uvs = entity["uzivatelske-vazby"];
66980
+ if (Array.isArray(uvs) && uvs.length > 0) throw new AFError(AFErrorCode.FORBIDDEN_OPERATION, `Creating ${entity.constructor.EntityName} with 'uzivatelske-vazby' is not supported by the API. Workaround: save the entity first without user relations, then add them in a second update (save) request.`);
66981
+ }
66868
66982
  const obj = this._encodeEntity(entity);
66869
66983
  const res = this.saveRaw(entity.constructor.EntityPath, obj, options);
66870
66984
  try {
66871
- await res;
66985
+ const results = await res;
66986
+ this._applySaveResultToEntity(entity, results);
66987
+ entity._isNew = false;
66872
66988
  return entity;
66873
66989
  } catch (e) {
66874
66990
  if (!(e instanceof AFError)) {
66875
- console.log(e);
66991
+ this._logger.error(e);
66876
66992
  e = new AFError(AFErrorCode.UNKNOWN, e.toString());
66877
66993
  }
66878
66994
  throw e;
66879
66995
  }
66880
66996
  }
66881
- async deleteRaw(entityPath, id, options) {
66997
+ async deleteRaw(entityPath, id, options = {}) {
66882
66998
  if (!this.company || !this.company.length) throw new AFError(AFErrorCode.MISSING_ABRA_COMPANY, `Can't query AFApiClient without providing company path component first.`);
66883
66999
  if (!id && typeof id !== "number" || id === "") throw new AFError(AFErrorCode.MISSING_ID, `Can't delete entity without knowing it's id.`);
66884
- let url = this._url + "/c/" + this.company + "/" + entityPath + "/" + id + ".json";
66885
- console.log(url);
66886
- try {
66887
- const raw$1 = await this._fetch(url, {
67000
+ let url;
67001
+ let fetchOptions;
67002
+ if (options.asUserRelation) {
67003
+ url = this._url + "/c/" + this.company + "/" + entityPath + ".json";
67004
+ fetchOptions = {
67005
+ signal: options.abortController?.signal,
67006
+ method: "PUT",
67007
+ body: JSON.stringify({ winstrom: [{
67008
+ [entityPath]: { id },
67009
+ [entityPath + "@action"]: "delete"
67010
+ }] })
67011
+ };
67012
+ } else {
67013
+ url = this._url + "/c/" + this.company + "/" + entityPath + "/" + id + ".json";
67014
+ fetchOptions = {
66888
67015
  signal: options.abortController?.signal,
66889
67016
  method: "DELETE"
66890
- });
66891
- if (raw$1.status >= 400 && raw$1.status < 600) throw new AFError(AFErrorCode.ABRA_FLEXI_ERROR, `${raw$1.status} ${raw$1.statusText}`);
66892
- const json = await raw$1.json();
66893
- console.log(json);
66894
- if (json.winstrom["success"] === "true") {}
67017
+ };
67018
+ }
67019
+ this._logger.debug(url);
67020
+ try {
67021
+ const raw$1 = await this._fetch(url, fetchOptions);
67022
+ const json = await raw$1.json().catch(() => null);
67023
+ this._logger.debug(json);
67024
+ if (raw$1.status >= 400 && raw$1.status < 600) {
67025
+ const details = this._extractAbraErrors(json);
67026
+ throw new AFError(AFErrorCode.ABRA_FLEXI_ERROR, `${raw$1.status} ${raw$1.statusText}${details ? ` — ${details}` : ""}`);
67027
+ }
67028
+ const jres = json?.winstrom;
67029
+ if (jres && jres["success"] === "false") {
67030
+ const details = this._extractAbraErrors(json);
67031
+ throw new AFError(AFErrorCode.ABRA_FLEXI_ERROR, `Delete of ${entityPath} failed${details ? ` — ${details}` : ""}`);
67032
+ }
66895
67033
  } catch (e) {
66896
67034
  if (!(e instanceof AFError)) {
66897
- console.log(e);
67035
+ this._logger.error(e);
66898
67036
  e = new AFError(AFErrorCode.UNKNOWN, e.toString());
66899
67037
  }
66900
67038
  throw e;
66901
67039
  }
66902
67040
  }
66903
- async delete(entity, options) {
67041
+ async delete(entity, options = {}) {
66904
67042
  if (entity.isNew) return true;
66905
- const res = this.deleteRaw(entity.constructor.EntityPath, entity.id, options);
67043
+ const entityPath = entity.constructor.EntityPath;
67044
+ const res = this.deleteRaw(entityPath, entity.id, {
67045
+ ...options,
67046
+ asUserRelation: entity instanceof AFUzivatelskaVazba
67047
+ });
66906
67048
  try {
66907
67049
  await res;
66908
67050
  return true;
66909
67051
  } catch (e) {
66910
67052
  if (!(e instanceof AFError)) {
66911
- console.log(e);
67053
+ this._logger.error(e);
66912
67054
  e = new AFError(AFErrorCode.UNKNOWN, e.toString());
66913
67055
  }
66914
67056
  throw e;
66915
67057
  }
66916
67058
  }
66917
- async deleteUserRelation(entity, options) {
67059
+ async callEntityActionRaw(entityPath, id, actionName, options = {}) {
66918
67060
  if (!this.company || !this.company.length) throw new AFError(AFErrorCode.MISSING_ABRA_COMPANY, `Can't query AFApiClient without providing company path component first.`);
66919
- const id = entity?.id;
66920
- if (!id && typeof id !== "number") throw new AFError(AFErrorCode.MISSING_ID, `Can't delete entity without knowing it's id.`);
66921
- const entityPath = entity.constructor.EntityPath;
66922
- let url = this._url + "/c/" + this.company + "/" + entityPath + ".json";
67061
+ if (id === void 0 || id === null || id === "") throw new AFError(AFErrorCode.MISSING_ID, `Can't call action '${actionName}' on ${entityPath} without id.`);
67062
+ if (!actionName || !actionName.length) throw new AFError(AFErrorCode.UNKNOWN, `Action name must be a non-empty string.`);
67063
+ const url = this._url + "/c/" + this.company + "/" + entityPath + ".json";
67064
+ this._logger.debug(url);
66923
67065
  try {
66924
67066
  const raw$1 = await this._fetch(url, {
66925
67067
  signal: options.abortController?.signal,
66926
67068
  method: "PUT",
67069
+ headers: { "Content-Type": "application/json" },
66927
67070
  body: JSON.stringify({ winstrom: [{
66928
67071
  [entityPath]: { id },
66929
- [entityPath + "@action"]: "delete"
67072
+ [entityPath + "@action"]: actionName
66930
67073
  }] })
66931
67074
  });
66932
- if (raw$1.status >= 400 && raw$1.status < 600) throw new AFError(AFErrorCode.ABRA_FLEXI_ERROR, `${raw$1.status} ${raw$1.statusText}`);
66933
- const json = await raw$1.json();
66934
- console.log(json);
66935
- if (json.winstrom["success"] === "true") {}
67075
+ const json = await raw$1.json().catch(() => null);
67076
+ this._logger.debug(json);
67077
+ if (raw$1.status >= 400 && raw$1.status < 600) {
67078
+ const details = this._extractAbraErrors(json);
67079
+ throw new AFError(AFErrorCode.ABRA_FLEXI_ERROR, `${raw$1.status} ${raw$1.statusText}${details ? ` — ${details}` : ""}`);
67080
+ }
67081
+ const jres = json?.winstrom;
67082
+ if (jres && jres["success"] === "false") {
67083
+ const details = this._extractAbraErrors(json);
67084
+ throw new AFError(AFErrorCode.ABRA_FLEXI_ERROR, `Action '${actionName}' on ${entityPath} failed${details ? ` — ${details}` : ""}`);
67085
+ }
67086
+ return true;
66936
67087
  } catch (e) {
66937
67088
  if (!(e instanceof AFError)) {
66938
- console.log(e);
67089
+ this._logger.error(e);
66939
67090
  e = new AFError(AFErrorCode.UNKNOWN, e.toString());
66940
67091
  }
66941
67092
  throw e;
66942
67093
  }
66943
67094
  }
67095
+ async callEntityAction(entity, actionName, options = {}) {
67096
+ if (entity.isNew) throw new AFError(AFErrorCode.RELATED_INSTANCE_NOT_SAVED, `Can't call action '${actionName}' on an unsaved ${entity.constructor.EntityName}. Save it first.`);
67097
+ if (entity.id === void 0 || entity.id === null) throw new AFError(AFErrorCode.MISSING_ID, `Can't call action '${actionName}' on ${entity.constructor.EntityName} without id.`);
67098
+ const entityPath = entity.constructor.EntityPath;
67099
+ return this.callEntityActionRaw(entityPath, entity.id, actionName, options);
67100
+ }
67101
+ _applySaveResultToEntity(entity, results) {
67102
+ if (!Array.isArray(results) || !results.length) return;
67103
+ const entityPath = entity.constructor.EntityPath;
67104
+ let r = results.find((res) => res && typeof res.ref === "string" && res.ref.includes(`/${entityPath}/`));
67105
+ if (!r) {
67106
+ const refless = results.filter((res) => res && typeof res.ref !== "string");
67107
+ if (refless.length === 1 && results.length === 1) r = refless[0];
67108
+ }
67109
+ if (!r || r.id === void 0 || r.id === null) return;
67110
+ const newId = typeof r.id === "number" ? r.id : Number(r.id);
67111
+ if (Number.isFinite(newId)) {
67112
+ entity.id = newId;
67113
+ entity._orig.id = newId;
67114
+ }
67115
+ }
67116
+ _extractAbraErrors(json) {
67117
+ if (!json) return "";
67118
+ const winstrom = json.winstrom;
67119
+ if (!winstrom) return "";
67120
+ const messages = [];
67121
+ if (typeof winstrom.message === "string" && winstrom.message.length) messages.push(winstrom.message);
67122
+ const results = winstrom.results;
67123
+ if (Array.isArray(results)) for (const r of results) {
67124
+ if (!r || !Array.isArray(r.errors)) continue;
67125
+ for (const err of r.errors) {
67126
+ if (!err) continue;
67127
+ if (typeof err === "string") {
67128
+ messages.push(err);
67129
+ continue;
67130
+ }
67131
+ const parts = [];
67132
+ if (err.code) parts.push(`[${err.code}]`);
67133
+ if (err.for) parts.push(`(${err.for})`);
67134
+ if (err.message) parts.push(err.message);
67135
+ if (parts.length) messages.push(parts.join(" "));
67136
+ }
67137
+ }
67138
+ return messages.join("; ");
67139
+ }
66944
67140
  _decodeEntityObj(entity, obj) {
66945
67141
  if (!obj) return [];
66946
67142
  if (typeof obj === "string") {
@@ -66952,6 +67148,7 @@ var AFApiClient = class {
66952
67148
  const ent = new entity(this._stitkyCache);
66953
67149
  const oKeys = Object.keys(o);
66954
67150
  for (const okey of oKeys) this._decodeProperty(ent, okey, o);
67151
+ ent._isNew = false;
66955
67152
  res.push(ent);
66956
67153
  }
66957
67154
  return res;
@@ -67008,7 +67205,7 @@ var AFApiClient = class {
67008
67205
  return;
67009
67206
  }
67010
67207
  if (!(val instanceof AFEntity)) throw new AFError(AFErrorCode.UNKNOWN, `Key '${key}' on ${entity.constructor.EntityName}(id: ${entity.id}) referencing not AFEntity instance`);
67011
- if (val.isNew) throw new AFError(AFErrorCode.RELATED_INSTANCE_NOT_SAVED, `Key '${key}' on ${entity.constructor.EntityName}(id: ${entity.id}) referencing not saved (new) instance - missing 'id' in it`);
67208
+ if (val.isNew) throw new AFError(AFErrorCode.RELATED_INSTANCE_NOT_SAVED, `Key '${key}' on ${entity.constructor.EntityName}(id: ${entity.id}) references an unsaved instance. Save it first, or use createIdStub() to reference an existing entity by id/kod.`);
67012
67209
  if (typeof val.id === "undefined") obj[key] = `code:${val.kod}`;
67013
67210
  else obj[key] = val.id;
67014
67211
  return;
@@ -67018,6 +67215,28 @@ var AFApiClient = class {
67018
67215
  if (entity instanceof AFPriloha && key === "content") obj["content@encoding"] = "base64";
67019
67216
  }
67020
67217
  };
67218
+ function makeFilteringLogger(target, level) {
67219
+ const threshold = LOG_LEVEL_RANK[level];
67220
+ const make = (lvl) => {
67221
+ if (LOG_LEVEL_RANK[lvl] > threshold) return () => {};
67222
+ return (...args) => target[lvl](...args);
67223
+ };
67224
+ return {
67225
+ debug: make("debug"),
67226
+ info: make("info"),
67227
+ warn: make("warn"),
67228
+ error: make("error")
67229
+ };
67230
+ }
67231
+ function parseContentDispositionFilename(header) {
67232
+ if (!header) return void 0;
67233
+ const ext = /filename\*=(?:UTF-8'')?([^;]+)/i.exec(header);
67234
+ if (ext) try {
67235
+ return decodeURIComponent(ext[1].trim().replace(/^"|"$/g, ""));
67236
+ } catch {}
67237
+ const plain = /filename=("([^"]+)"|([^;]+))/i.exec(header);
67238
+ if (plain) return (plain[2] ?? plain[3] ?? "").trim();
67239
+ }
67021
67240
 
67022
67241
  //#endregion
67023
67242
  //#region src/abra/AFApiSession.ts