abra-flexi 0.5.7 → 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
@@ -186,6 +186,7 @@ var AFEntity = class AFEntity {
186
186
  }
187
187
  constructor(stitkyCache) {
188
188
  this._orig = {};
189
+ this._isNew = true;
189
190
  this._stitkyCache = stitkyCache;
190
191
  }
191
192
  getPropertyTypeAnnotation(key) {
@@ -201,7 +202,7 @@ var AFEntity = class AFEntity {
201
202
  return !this.hasChanged();
202
203
  }
203
204
  get isNew() {
204
- return !this.id && !this.kod;
205
+ return this._isNew;
205
206
  }
206
207
  getCotr() {
207
208
  return this.constructor;
@@ -66578,6 +66579,29 @@ function extractEvidence(inUrl) {
66578
66579
  return split[3];
66579
66580
  }
66580
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
+
66581
66605
  //#endregion
66582
66606
  //#region src/abra/AFStitkyCache.ts
66583
66607
  const DEBOUNCE_MS = 5 * 1e3;
@@ -66596,51 +66620,61 @@ var AFStitkyCache = class {
66596
66620
  if (this._strategy === StitkyCacheStrategy.None) return;
66597
66621
  const now = /* @__PURE__ */ new Date();
66598
66622
  if (this._lastUpdate && now.getTime() - this._lastUpdate.getTime() < DEBOUNCE_MS) return;
66599
- const skupOpts = {
66600
- limit: NO_LIMIT,
66601
- detail: AFQueryDetail.FULL,
66602
- noUpdateStitkyCache: true
66603
- };
66604
- if (this._lastUpdate) skupOpts.filter = Filter(`lastUpdate > ':date'`, { date: this._lastUpdate });
66605
- const skUpdate = await this._client.query(AFSkupinaStitku, skupOpts);
66606
- for (const sk of skUpdate) {
66607
- let found = this._stitekSkupiny.find((ss) => ss.id === sk.id);
66608
- if (!found) {
66609
- this._stitekSkupiny.push(sk);
66610
- continue;
66611
- }
66612
- Object.assign(found, sk);
66613
- }
66614
- const stitOpts = {
66615
- limit: NO_LIMIT,
66616
- detail: [
66617
- "id",
66618
- "kod",
66619
- "lastUpdate",
66620
- "nazev",
66621
- "nazevA",
66622
- "nazevB",
66623
- "nazevC",
66624
- "nazevD",
66625
- "poznam",
66626
- "popis",
66627
- "platiOd",
66628
- "platiDo",
66629
- "skupVybKlic"
66630
- ],
66631
- noUpdateStitkyCache: true
66632
- };
66633
- if (this._lastUpdate) skupOpts.filter = Filter(`lastUpdate > ':date'`, { date: this._lastUpdate });
66634
- const stUpdate = await this._client.query(AFStitek, stitOpts);
66635
- for (const st of stUpdate) {
66636
- st.skupVybKlic = this._stitekSkupiny.find((ss) => ss.kod === st.skupVybKlic?.kod);
66637
- const found = this._stitky.find((s) => s.id === st.id);
66638
- if (!found) {
66639
- this._stitky.push(st);
66640
- 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;
66641
66675
  }
66642
- Object.assign(found, st);
66643
- }
66676
+ })();
66677
+ return this._inflight;
66644
66678
  }
66645
66679
  stitkyWithString(keys, groupFilter) {
66646
66680
  if (!keys) return void 0;
@@ -66660,15 +66694,27 @@ var AFStitkyCache = class {
66660
66694
  return list;
66661
66695
  }
66662
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
+ }
66663
66701
 
66664
66702
  //#endregion
66665
66703
  //#region src/abra/AFApiClient.ts
66666
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
+ };
66667
66712
  var AFApiClient = class {
66668
66713
  constructor(config) {
66669
66714
  this._url = config.url;
66670
66715
  this._company = config.company;
66671
66716
  this._fetch = config.fetch || fetch;
66717
+ this._logger = makeFilteringLogger(config.logger ?? console, config.logLevel ?? (config.logger ? "debug" : "none"));
66672
66718
  this._stitkyCache = new AFStitkyCache(this, config.stitkyCacheStrategy);
66673
66719
  }
66674
66720
  get url() {
@@ -66680,14 +66726,14 @@ var AFApiClient = class {
66680
66726
  get stitkyCacheStrategy() {
66681
66727
  return this._stitkyCache.strategy;
66682
66728
  }
66683
- async queryRaw(entityPath, options) {
66684
- 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) {
66685
66730
  const detail = options.detail || AFQueryDetail.SUMMARY;
66686
66731
  let furl = options.filter?.toUrlComponent();
66687
66732
  if (furl && !furl.length) furl = void 0;
66688
- 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;
66689
66735
  url += furl ? "/" + furl : "";
66690
- url += "." + ABRA_API_FORMAT;
66736
+ url += "." + format;
66691
66737
  url = addParamToUrl(url, "detail", composeDetail(detail));
66692
66738
  url = addParamToUrl(url, "includes", composeIncludes(detail, entityPath));
66693
66739
  url = addParamToUrl(url, "relations", composeRelations(detail));
@@ -66706,20 +66752,61 @@ var AFApiClient = class {
66706
66752
  url = addParamToUrl(url, "pocetMesicu", options.pocetMesicu);
66707
66753
  url = addParamToUrl(url, "date", options.date);
66708
66754
  url = addParamToUrl(url, "currency", options.currency);
66709
- 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);
66710
66761
  try {
66711
66762
  const raw$1 = await this._fetch(url, { signal: options.abortController?.signal });
66712
- if (raw$1.status >= 400 && raw$1.status < 600) throw new AFError(AFErrorCode.ABRA_FLEXI_ERROR, `${raw$1.status} ${raw$1.statusText}`);
66713
- 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
+ };
66714
66798
  } catch (e) {
66715
66799
  if (!(e instanceof AFError)) {
66716
- console.log(e);
66800
+ this._logger.error(e);
66717
66801
  e = new AFError(AFErrorCode.UNKNOWN, e.toString());
66718
66802
  }
66719
66803
  throw e;
66720
66804
  }
66721
66805
  }
66722
- async query(entity, options) {
66806
+ async queryFile(entity, format, options = {}) {
66807
+ return this.queryFileRaw(entity.EntityPath, format, options);
66808
+ }
66809
+ async query(entity, options = {}) {
66723
66810
  const res = this.queryRaw(entity.EntityPath, options);
66724
66811
  try {
66725
66812
  const rawData = await res;
@@ -66728,7 +66815,7 @@ var AFApiClient = class {
66728
66815
  return data;
66729
66816
  } catch (e) {
66730
66817
  if (!(e instanceof AFError)) {
66731
- console.log(e);
66818
+ this._logger.error(e);
66732
66819
  e = new AFError(AFErrorCode.UNKNOWN, e.toString());
66733
66820
  }
66734
66821
  throw e;
@@ -66775,7 +66862,7 @@ var AFApiClient = class {
66775
66862
  out.push(res);
66776
66863
  } catch (e) {
66777
66864
  if (!(e instanceof AFError)) {
66778
- console.log(e);
66865
+ this._logger.error(e);
66779
66866
  e = new AFError(AFErrorCode.UNKNOWN, e.toString());
66780
66867
  }
66781
66868
  throw e;
@@ -66786,7 +66873,7 @@ var AFApiClient = class {
66786
66873
  if (!options.noUpdateStitkyCache) await this._stitkyCache.fetchTick();
66787
66874
  return out;
66788
66875
  }
66789
- async populate(entities, options) {
66876
+ async populate(entities, options = {}) {
66790
66877
  const fetchBy = [];
66791
66878
  for (const en of entities) {
66792
66879
  if (typeof en.id !== "undefined" && en.id !== null) {
@@ -66825,18 +66912,19 @@ var AFApiClient = class {
66825
66912
  if (!en) continue;
66826
66913
  const oKeys = Object.keys(enQ);
66827
66914
  for (const okey of oKeys) this._decodeProperty(en, okey, enQ);
66915
+ en._isNew = false;
66828
66916
  }
66829
66917
  if (!options.noUpdateStitkyCache) await this._stitkyCache.fetchTick();
66830
66918
  } catch (e) {
66831
66919
  if (!(e instanceof AFError)) {
66832
- console.log(e);
66920
+ this._logger.error(e);
66833
66921
  e = new AFError(AFErrorCode.UNKNOWN, e.toString());
66834
66922
  }
66835
66923
  throw e;
66836
66924
  }
66837
66925
  return entities;
66838
66926
  }
66839
- async populateOne(entity, options) {
66927
+ async populateOne(entity, options = {}) {
66840
66928
  const res = await this.populate([entity], options);
66841
66929
  if (!res || !res.length) throw new AFError(AFErrorCode.OBJECT_NOT_FOUND, `${entity} object not found. Kod / ID: ${entity.kod} / ${entity.id}`);
66842
66930
  return res[0];
@@ -66849,14 +66937,15 @@ var AFApiClient = class {
66849
66937
  const ent = new entity(this._stitkyCache);
66850
66938
  if (id.id) ent.id = id.id;
66851
66939
  if (id.kod) ent.kod = id.kod;
66940
+ ent._isNew = false;
66852
66941
  return ent;
66853
66942
  }
66854
66943
  async saveRaw(entityPath, data, options) {
66855
66944
  if (!this.company || !this.company.length) throw new AFError(AFErrorCode.MISSING_ABRA_COMPANY, `Can't query AFApiClient without providing company path component first.`);
66856
66945
  if (!options) options = {};
66857
66946
  let url = this._url + "/c/" + this.company + "/" + entityPath + ".json";
66858
- console.log(url);
66859
- console.log(data);
66947
+ this._logger.debug(url);
66948
+ this._logger.debug(data);
66860
66949
  if (options.removeStitky) data["stitky@removeAll"] = "true";
66861
66950
  try {
66862
66951
  const raw$1 = await this._fetch(url, {
@@ -66865,98 +66954,189 @@ var AFApiClient = class {
66865
66954
  headers: { "Content-Type": "application/json" },
66866
66955
  body: JSON.stringify({ winstrom: [{ [entityPath]: data }] })
66867
66956
  });
66957
+ const json = await raw$1.json().catch(() => null);
66958
+ this._logger.debug(json);
66868
66959
  if (raw$1.status >= 400 && raw$1.status < 600) {
66869
- console.log(JSON.stringify(await raw$1.json(), null, "\n"));
66870
- 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}` : ""}`);
66962
+ }
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}` : ""}`);
66871
66967
  }
66872
- const json = await raw$1.json();
66873
- console.log(JSON.stringify(json));
66874
- if (json.winstrom["success"] === "true") {}
66968
+ return Array.isArray(jres?.results) ? jres.results : [];
66875
66969
  } catch (e) {
66876
66970
  if (!(e instanceof AFError)) {
66877
- console.log(e);
66971
+ this._logger.error(e);
66878
66972
  e = new AFError(AFErrorCode.UNKNOWN, e.toString());
66879
66973
  }
66880
66974
  throw e;
66881
66975
  }
66882
66976
  }
66883
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
+ }
66884
66982
  const obj = this._encodeEntity(entity);
66885
66983
  const res = this.saveRaw(entity.constructor.EntityPath, obj, options);
66886
66984
  try {
66887
- await res;
66985
+ const results = await res;
66986
+ this._applySaveResultToEntity(entity, results);
66987
+ entity._isNew = false;
66888
66988
  return entity;
66889
66989
  } catch (e) {
66890
66990
  if (!(e instanceof AFError)) {
66891
- console.log(e);
66991
+ this._logger.error(e);
66892
66992
  e = new AFError(AFErrorCode.UNKNOWN, e.toString());
66893
66993
  }
66894
66994
  throw e;
66895
66995
  }
66896
66996
  }
66897
- async deleteRaw(entityPath, id, options) {
66997
+ async deleteRaw(entityPath, id, options = {}) {
66898
66998
  if (!this.company || !this.company.length) throw new AFError(AFErrorCode.MISSING_ABRA_COMPANY, `Can't query AFApiClient without providing company path component first.`);
66899
66999
  if (!id && typeof id !== "number" || id === "") throw new AFError(AFErrorCode.MISSING_ID, `Can't delete entity without knowing it's id.`);
66900
- let url = this._url + "/c/" + this.company + "/" + entityPath + "/" + id + ".json";
66901
- console.log(url);
66902
- try {
66903
- 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 = {
66904
67015
  signal: options.abortController?.signal,
66905
67016
  method: "DELETE"
66906
- });
66907
- if (raw$1.status >= 400 && raw$1.status < 600) throw new AFError(AFErrorCode.ABRA_FLEXI_ERROR, `${raw$1.status} ${raw$1.statusText}`);
66908
- const json = await raw$1.json();
66909
- console.log(json);
66910
- 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
+ }
66911
67033
  } catch (e) {
66912
67034
  if (!(e instanceof AFError)) {
66913
- console.log(e);
67035
+ this._logger.error(e);
66914
67036
  e = new AFError(AFErrorCode.UNKNOWN, e.toString());
66915
67037
  }
66916
67038
  throw e;
66917
67039
  }
66918
67040
  }
66919
- async delete(entity, options) {
67041
+ async delete(entity, options = {}) {
66920
67042
  if (entity.isNew) return true;
66921
- 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
+ });
66922
67048
  try {
66923
67049
  await res;
66924
67050
  return true;
66925
67051
  } catch (e) {
66926
67052
  if (!(e instanceof AFError)) {
66927
- console.log(e);
67053
+ this._logger.error(e);
66928
67054
  e = new AFError(AFErrorCode.UNKNOWN, e.toString());
66929
67055
  }
66930
67056
  throw e;
66931
67057
  }
66932
67058
  }
66933
- async deleteUserRelation(entity, options) {
67059
+ async callEntityActionRaw(entityPath, id, actionName, options = {}) {
66934
67060
  if (!this.company || !this.company.length) throw new AFError(AFErrorCode.MISSING_ABRA_COMPANY, `Can't query AFApiClient without providing company path component first.`);
66935
- const id = entity?.id;
66936
- if (!id && typeof id !== "number") throw new AFError(AFErrorCode.MISSING_ID, `Can't delete entity without knowing it's id.`);
66937
- const entityPath = entity.constructor.EntityPath;
66938
- 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);
66939
67065
  try {
66940
67066
  const raw$1 = await this._fetch(url, {
66941
67067
  signal: options.abortController?.signal,
66942
67068
  method: "PUT",
67069
+ headers: { "Content-Type": "application/json" },
66943
67070
  body: JSON.stringify({ winstrom: [{
66944
67071
  [entityPath]: { id },
66945
- [entityPath + "@action"]: "delete"
67072
+ [entityPath + "@action"]: actionName
66946
67073
  }] })
66947
67074
  });
66948
- if (raw$1.status >= 400 && raw$1.status < 600) throw new AFError(AFErrorCode.ABRA_FLEXI_ERROR, `${raw$1.status} ${raw$1.statusText}`);
66949
- const json = await raw$1.json();
66950
- console.log(json);
66951
- 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;
66952
67087
  } catch (e) {
66953
67088
  if (!(e instanceof AFError)) {
66954
- console.log(e);
67089
+ this._logger.error(e);
66955
67090
  e = new AFError(AFErrorCode.UNKNOWN, e.toString());
66956
67091
  }
66957
67092
  throw e;
66958
67093
  }
66959
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
+ }
66960
67140
  _decodeEntityObj(entity, obj) {
66961
67141
  if (!obj) return [];
66962
67142
  if (typeof obj === "string") {
@@ -66968,6 +67148,7 @@ var AFApiClient = class {
66968
67148
  const ent = new entity(this._stitkyCache);
66969
67149
  const oKeys = Object.keys(o);
66970
67150
  for (const okey of oKeys) this._decodeProperty(ent, okey, o);
67151
+ ent._isNew = false;
66971
67152
  res.push(ent);
66972
67153
  }
66973
67154
  return res;
@@ -67024,7 +67205,7 @@ var AFApiClient = class {
67024
67205
  return;
67025
67206
  }
67026
67207
  if (!(val instanceof AFEntity)) throw new AFError(AFErrorCode.UNKNOWN, `Key '${key}' on ${entity.constructor.EntityName}(id: ${entity.id}) referencing not AFEntity instance`);
67027
- 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.`);
67028
67209
  if (typeof val.id === "undefined") obj[key] = `code:${val.kod}`;
67029
67210
  else obj[key] = val.id;
67030
67211
  return;
@@ -67034,6 +67215,28 @@ var AFApiClient = class {
67034
67215
  if (entity instanceof AFPriloha && key === "content") obj["content@encoding"] = "base64";
67035
67216
  }
67036
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
+ }
67037
67240
 
67038
67241
  //#endregion
67039
67242
  //#region src/abra/AFApiSession.ts