@operato/data-grist 10.29.31 → 10.29.36

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.
@@ -17,6 +17,8 @@ export declare class DataProvider {
17
17
  private _attachPageHandler;
18
18
  private _fetchHandlerWrap;
19
19
  private _fetchOptions;
20
+ /** Which fetch is the latest - an answer to an earlier one is dropped (fetch). */
21
+ private _fetchSequence;
20
22
  constructor(consumer: DataConsumer);
21
23
  dispose(): void;
22
24
  onAttachPage(): void;
@@ -30,6 +30,8 @@ export class DataProvider {
30
30
  this._fetchParamsChangeHandler = this.onFetchParamsChange.bind(this);
31
31
  this._recordChangeHandler = this.onRecordChange.bind(this);
32
32
  this._attachPageHandler = this.onAttachPage.bind(this);
33
+ /** Which fetch is the latest - an answer to an earlier one is dropped (fetch). */
34
+ this._fetchSequence = 0;
33
35
  this.consumer = consumer;
34
36
  this.fetchHandler = consumer.fetchHandler;
35
37
  this.consumer.addEventListener('attach-page', this._attachPageHandler);
@@ -82,7 +84,8 @@ export class DataProvider {
82
84
  }
83
85
  get fetchHandler() {
84
86
  if (!this._fetchHandlerWrap) {
85
- this._fetchHandlerWrap = async (options) => {
87
+ /* `current` says whether this read is still the one the grid shows - a replaced read tells the grid nothing. */
88
+ this._fetchHandlerWrap = async (options, current = () => true) => {
86
89
  var _a, _b, _c, _d, _e, _f;
87
90
  if (!this._fetchHandler) {
88
91
  return;
@@ -90,7 +93,7 @@ export class DataProvider {
90
93
  try {
91
94
  (_a = this.consumer) === null || _a === void 0 ? void 0 : _a.showSpinner();
92
95
  const result = await (this._fetchHandler || EMPTY_FETCHHANDLER)(options);
93
- (_c = (_b = this.consumer) === null || _b === void 0 ? void 0 : _b.fetchSucceeded) === null || _c === void 0 ? void 0 : _c.call(_b);
96
+ current() && ((_c = (_b = this.consumer) === null || _b === void 0 ? void 0 : _b.fetchSucceeded) === null || _c === void 0 ? void 0 : _c.call(_b));
94
97
  return result;
95
98
  }
96
99
  catch (error) {
@@ -98,7 +101,7 @@ export class DataProvider {
98
101
  * A failed read is not an empty list. The grid is told, so it can say "could not load"
99
102
  * instead of "nothing to show" - and the error still reaches whoever awaited the fetch.
100
103
  */
101
- (_e = (_d = this.consumer) === null || _d === void 0 ? void 0 : _d.fetchFailed) === null || _e === void 0 ? void 0 : _e.call(_d, error);
104
+ current() && ((_e = (_d = this.consumer) === null || _d === void 0 ? void 0 : _d.fetchFailed) === null || _e === void 0 ? void 0 : _e.call(_d, error));
102
105
  throw error;
103
106
  }
104
107
  finally {
@@ -168,11 +171,20 @@ export class DataProvider {
168
171
  sorters = sorters || sortings || this.sorters;
169
172
  filters = filters || this.filters;
170
173
  this.sorters = sorters;
171
- return this._update({
172
- /* fetch에서 limit과 page를 제공하지 않는 경우를 대비함. */
173
- limit,
174
- page,
175
- ...(await this.fetchHandler.call(null, {
174
+ /*
175
+ * **The last fetch asked is the one the grid shows.**
176
+ *
177
+ * Two fetches can be out at once - the grid reading its address, then the screen laying its own conditions
178
+ * over it a moment later. `_update` keeps the first answer to arrive and ignores the rest, so whichever came
179
+ * back first stood: a ledger opened from the dashboard's 이번 달 순현금흐름 showed all 2,908 lines, the answer
180
+ * to the read with no conditions, under a headroom that said October (2026-10-16). An answer to a fetch that a
181
+ * later one has replaced - or its failure - is dropped, and the later answer is the one kept.
182
+ */
183
+ const sequence = ++this._fetchSequence;
184
+ const current = () => sequence === this._fetchSequence;
185
+ let answer;
186
+ try {
187
+ answer = await this.fetchHandler.call(null, {
176
188
  page,
177
189
  limit,
178
190
  sorters,
@@ -181,7 +193,20 @@ export class DataProvider {
181
193
  ...(this._search ? { search: this._search } : {}),
182
194
  filters,
183
195
  options: this.fetchOptions
184
- }))
196
+ }, current);
197
+ }
198
+ catch (error) {
199
+ if (!current())
200
+ return;
201
+ throw error;
202
+ }
203
+ if (!current())
204
+ return;
205
+ return this._update({
206
+ /* fetch에서 limit과 page를 제공하지 않는 경우를 대비함. */
207
+ limit,
208
+ page,
209
+ ...answer
185
210
  });
186
211
  }
187
212
  async _update({ page, limit, total, records }, reset) {
@@ -1 +1 @@
1
- {"version":3,"file":"data-provider.js","sourceRoot":"","sources":["../../src/data-provider.ts"],"names":[],"mappings":"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","sourcesContent":["import { ZERO_RECORDS } from './configure/zero-config.js'\nimport { DataConsumer } from './data-consumer.js'\nimport { FetchHandler, FetchOption, FilterValue, GristRecord, SorterConfig, SortersConfig } from './types.js'\n\nfunction EMPTY_FETCHHANDLER() {\n return {\n total: 0,\n records: []\n }\n}\n\nfunction _calculateTotalPage(limit: number, total: number) {\n /*\n * total page는 1이상이어야 한다.\n * 즉, 레코드가 하나도 없어도, 페이지 갯수는 1이 되어야 한다.\n */\n return Math.max(1, Math.ceil(total / limit))\n}\n\n/*\n * For fetches nobody awaits (page and limit changes, filter and option changes). This does not hide a\n * failure: fetchFailed has already drawn it on the grid before the rejection gets here. It only stops\n * the same failure from also escaping as an unhandled rejection. Do not copy it to a place where the\n * failure is not already shown.\n */\nconst shownByConsumer = () => {}\n\nexport class DataProvider {\n page: number = 1\n limit: number = 20\n total: number = 0\n records: GristRecord[] = []\n\n private consumer?: DataConsumer\n private _fetchHandler?: FetchHandler\n private _sorters?: SorterConfig[]\n private _filters?: FilterValue[]\n /*\n * 검색 낱말 — `search-mode=\"term\"` 에서만 찬다. 옛 길은 낱말을 칸별 필터로 바꿔 `_filters` 에\n * 넣으므로 이 자리는 비어 있고, 그래서 두 길이 한 화면에서 같이 서지 않는다.\n */\n private _search?: string\n\n private _pageChangeHandler: EventListener = this.onPageChange.bind(this)\n private _limitChangeHandler = this.onLimitChange.bind(this)\n private _fetchParamsChangeHandler = this.onFetchParamsChange.bind(this)\n private _recordChangeHandler = this.onRecordChange.bind(this)\n private _attachPageHandler: EventListener = this.onAttachPage.bind(this)\n\n private _fetchHandlerWrap: any\n private _fetchOptions: any\n\n constructor(consumer: DataConsumer) {\n this.consumer = consumer\n\n this.fetchHandler = consumer.fetchHandler\n\n this.consumer.addEventListener('attach-page', this._attachPageHandler)\n this.consumer.addEventListener('page-change', this._pageChangeHandler)\n this.consumer.addEventListener('limit-change', this._limitChangeHandler)\n this.consumer.addEventListener('fetch-params-change', this._fetchParamsChangeHandler)\n this.consumer.addEventListener('record-change', this._recordChangeHandler)\n }\n\n dispose() {\n this.consumer?.removeEventListener('attach-page', this._attachPageHandler)\n this.consumer?.removeEventListener('page-change', this._pageChangeHandler)\n this.consumer?.removeEventListener('limit-change', this._limitChangeHandler)\n this.consumer?.removeEventListener('fetch-params-change', this._fetchParamsChangeHandler)\n this.consumer?.removeEventListener('record-change', this._recordChangeHandler)\n }\n\n onAttachPage() {\n this.attach().catch(shownByConsumer)\n }\n\n onPageChange(e: Event) {\n var page = (e as CustomEvent).detail\n this.fetch({ page }).catch(shownByConsumer)\n }\n\n onLimitChange(e: Event) {\n var limit = (e as CustomEvent).detail\n this.fetch({ limit }).catch(shownByConsumer)\n }\n\n onFetchParamsChange(e: Event) {\n const { sorters, filters, search, from } = (e as CustomEvent).detail || {}\n\n sorters && (this.sorters = sorters)\n filters && (this.filters = filters)\n /* 빈 낱말도 값이다 — 「검색을 지웠다」를 지난 낱말로 덮으면 목록이 안 돌아온다. */\n search !== undefined && (this._search = search)\n\n if (this.consumer?.mode !== 'GRID') {\n this.page = 0\n }\n\n from !== 'config' && this.fetch().catch(shownByConsumer)\n }\n\n onRecordChange(e: Event) {\n this.consumer?.checkDirties()\n }\n\n get fetchOptions() {\n return this._fetchOptions\n }\n\n set fetchOptions(fetchOptions) {\n this._fetchOptions = fetchOptions\n\n this.fetch().catch(shownByConsumer)\n }\n\n get fetchHandler() {\n if (!this._fetchHandlerWrap) {\n this._fetchHandlerWrap = async (options: FetchOption) => {\n if (!this._fetchHandler) {\n return\n }\n\n try {\n this.consumer?.showSpinner()\n const result = await (this._fetchHandler || EMPTY_FETCHHANDLER)(options)\n this.consumer?.fetchSucceeded?.()\n return result\n } catch (error) {\n /*\n * A failed read is not an empty list. The grid is told, so it can say \"could not load\"\n * instead of \"nothing to show\" - and the error still reaches whoever awaited the fetch.\n */\n this.consumer?.fetchFailed?.(error)\n throw error\n } finally {\n this.consumer?.hideSpinner()\n }\n }\n }\n\n return this._fetchHandlerWrap\n }\n\n set fetchHandler(fetchHandler) {\n this._fetchHandler = fetchHandler\n delete this._fetchHandlerWrap\n }\n\n get sorters() {\n return this._sorters\n }\n\n set sorters(sorters) {\n this._sorters = sorters\n }\n\n /* alias for sorters */\n get sortings() {\n return this._sorters\n }\n\n set sortings(sorters) {\n this._sorters = sorters\n }\n\n get filters() {\n return this._filters\n }\n\n set filters(filters) {\n this._filters = filters\n }\n\n async attach(reset = false) {\n var { page = 0, limit = 20 } = this\n\n /*\n * page는 0 based index가 아님에 주의한다.\n * 즉, page 값이 1 은 첫페이지를 의미한다.\n */\n if (reset) {\n this.records = ZERO_RECORDS\n page = 1\n } else {\n /* attach의 경우는 grist data의 변경상태를 유지하기 위해서, grist._data 를 기반으로 한다. */\n this.records = this.consumer?._data ? this.consumer._data.records : ZERO_RECORDS\n page = page + 1\n }\n\n return this._update(\n {\n /* fetch에서 limit과 page를 제공하지 않는 경우를 대비함. */\n limit,\n page,\n ...(await this.fetchHandler.call(null, {\n page,\n limit,\n sorters: this.sorters,\n sortings: this.sorters,\n filters: this.filters,\n options: this.fetchOptions\n }))\n },\n reset\n )\n }\n\n async fetch({\n page = this.page,\n limit = this.limit,\n sorters,\n sortings,\n filters\n }: {\n page?: number\n limit?: number\n sorters?: SortersConfig\n sortings?: SortersConfig\n filters?: FilterValue[]\n } = {}) {\n /* sortings property is only for things-factory server-side list parameter convention */\n /* fetchHandler should reture { page, limit, total, records } */\n this.records = ZERO_RECORDS\n\n sorters = sorters || sortings || this.sorters\n filters = filters || this.filters\n\n this.sorters = sorters\n\n return this._update({\n /* fetch에서 limit과 page를 제공하지 않는 경우를 대비함. */\n limit,\n page,\n ...(await this.fetchHandler.call(null, {\n page,\n limit,\n sorters,\n sortings: sorters,\n /* 안 쓰는 화면에 빈 칸을 보내지 않는다 — 옛 길 소비자의 fetchHandler 가 그날 안 바뀐다. */\n ...(this._search ? { search: this._search } : {}),\n filters,\n options: this.fetchOptions\n }))\n })\n }\n\n async _update(\n {\n page,\n limit,\n total,\n records\n }: {\n page: number\n limit: number\n total: number\n records: GristRecord[]\n },\n reset?: boolean\n ): Promise<void> {\n // total을 감안해서 page가 최대값을 넘지 않도록 한다.\n var maxpage = _calculateTotalPage(limit, total)\n if (maxpage < page) {\n return await this.fetch({ page: maxpage, limit })\n }\n\n // page와 limit이 없는 경우 records 검사 전에 초기화\n this.page = this.page || page\n this.limit = this.limit || limit\n\n if (!records) {\n return\n }\n\n if (this.records === ZERO_RECORDS) {\n this.records = records\n } else if (this.page < page) {\n // attach인 경우에는 records를 append한다.\n this.records = [...this.records, ...records]\n } else {\n return\n }\n\n this.limit = limit\n this.total = total\n this.page = page\n\n this.consumer &&\n (this.consumer.data = {\n page: this.page,\n limit: this.limit,\n total: this.total,\n\n records: this.records\n })\n }\n}\n"]}
1
+ {"version":3,"file":"data-provider.js","sourceRoot":"","sources":["../../src/data-provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAIzD,SAAS,kBAAkB;IACzB,OAAO;QACL,KAAK,EAAE,CAAC;QACR,OAAO,EAAE,EAAE;KACZ,CAAA;AACH,CAAC;AAED,SAAS,mBAAmB,CAAC,KAAa,EAAE,KAAa;IACvD;;;OAGG;IACH,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAA;AAC9C,CAAC;AAED;;;;;GAKG;AACH,MAAM,eAAe,GAAG,GAAG,EAAE,GAAE,CAAC,CAAA;AAEhC,MAAM,OAAO,YAAY;IA2BvB,YAAY,QAAsB;QA1BlC,SAAI,GAAW,CAAC,CAAA;QAChB,UAAK,GAAW,EAAE,CAAA;QAClB,UAAK,GAAW,CAAC,CAAA;QACjB,YAAO,GAAkB,EAAE,CAAA;QAYnB,uBAAkB,GAAkB,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAChE,wBAAmB,GAAG,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACnD,8BAAyB,GAAG,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC/D,yBAAoB,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACrD,uBAAkB,GAAkB,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAIxE,kFAAkF;QAC1E,mBAAc,GAAG,CAAC,CAAA;QAGxB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAA;QAExB,IAAI,CAAC,YAAY,GAAG,QAAQ,CAAC,YAAY,CAAA;QAEzC,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,aAAa,EAAE,IAAI,CAAC,kBAAkB,CAAC,CAAA;QACtE,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,aAAa,EAAE,IAAI,CAAC,kBAAkB,CAAC,CAAA;QACtE,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,cAAc,EAAE,IAAI,CAAC,mBAAmB,CAAC,CAAA;QACxE,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,qBAAqB,EAAE,IAAI,CAAC,yBAAyB,CAAC,CAAA;QACrF,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,eAAe,EAAE,IAAI,CAAC,oBAAoB,CAAC,CAAA;IAC5E,CAAC;IAED,OAAO;;QACL,MAAA,IAAI,CAAC,QAAQ,0CAAE,mBAAmB,CAAC,aAAa,EAAE,IAAI,CAAC,kBAAkB,CAAC,CAAA;QAC1E,MAAA,IAAI,CAAC,QAAQ,0CAAE,mBAAmB,CAAC,aAAa,EAAE,IAAI,CAAC,kBAAkB,CAAC,CAAA;QAC1E,MAAA,IAAI,CAAC,QAAQ,0CAAE,mBAAmB,CAAC,cAAc,EAAE,IAAI,CAAC,mBAAmB,CAAC,CAAA;QAC5E,MAAA,IAAI,CAAC,QAAQ,0CAAE,mBAAmB,CAAC,qBAAqB,EAAE,IAAI,CAAC,yBAAyB,CAAC,CAAA;QACzF,MAAA,IAAI,CAAC,QAAQ,0CAAE,mBAAmB,CAAC,eAAe,EAAE,IAAI,CAAC,oBAAoB,CAAC,CAAA;IAChF,CAAC;IAED,YAAY;QACV,IAAI,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,eAAe,CAAC,CAAA;IACtC,CAAC;IAED,YAAY,CAAC,CAAQ;QACnB,IAAI,IAAI,GAAI,CAAiB,CAAC,MAAM,CAAA;QACpC,IAAI,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,eAAe,CAAC,CAAA;IAC7C,CAAC;IAED,aAAa,CAAC,CAAQ;QACpB,IAAI,KAAK,GAAI,CAAiB,CAAC,MAAM,CAAA;QACrC,IAAI,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,eAAe,CAAC,CAAA;IAC9C,CAAC;IAED,mBAAmB,CAAC,CAAQ;;QAC1B,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,GAAI,CAAiB,CAAC,MAAM,IAAI,EAAE,CAAA;QAE1E,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,CAAA;QACnC,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,CAAA;QACnC,mDAAmD;QACnD,MAAM,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,CAAA;QAE/C,IAAI,CAAA,MAAA,IAAI,CAAC,QAAQ,0CAAE,IAAI,MAAK,MAAM,EAAE,CAAC;YACnC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAA;QACf,CAAC;QAED,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,eAAe,CAAC,CAAA;IAC1D,CAAC;IAED,cAAc,CAAC,CAAQ;;QACrB,MAAA,IAAI,CAAC,QAAQ,0CAAE,YAAY,EAAE,CAAA;IAC/B,CAAC;IAED,IAAI,YAAY;QACd,OAAO,IAAI,CAAC,aAAa,CAAA;IAC3B,CAAC;IAED,IAAI,YAAY,CAAC,YAAY;QAC3B,IAAI,CAAC,aAAa,GAAG,YAAY,CAAA;QAEjC,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,eAAe,CAAC,CAAA;IACrC,CAAC;IAED,IAAI,YAAY;QACd,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC;YAC5B,gHAAgH;YAChH,IAAI,CAAC,iBAAiB,GAAG,KAAK,EAAE,OAAoB,EAAE,UAAyB,GAAG,EAAE,CAAC,IAAI,EAAE,EAAE;;gBAC3F,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;oBACxB,OAAM;gBACR,CAAC;gBAED,IAAI,CAAC;oBACH,MAAA,IAAI,CAAC,QAAQ,0CAAE,WAAW,EAAE,CAAA;oBAC5B,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,aAAa,IAAI,kBAAkB,CAAC,CAAC,OAAO,CAAC,CAAA;oBACxE,OAAO,EAAE,KAAI,MAAA,MAAA,IAAI,CAAC,QAAQ,0CAAE,cAAc,kDAAI,CAAA,CAAA;oBAC9C,OAAO,MAAM,CAAA;gBACf,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf;;;uBAGG;oBACH,OAAO,EAAE,KAAI,MAAA,MAAA,IAAI,CAAC,QAAQ,0CAAE,WAAW,mDAAG,KAAK,CAAC,CAAA,CAAA;oBAChD,MAAM,KAAK,CAAA;gBACb,CAAC;wBAAS,CAAC;oBACT,MAAA,IAAI,CAAC,QAAQ,0CAAE,WAAW,EAAE,CAAA;gBAC9B,CAAC;YACH,CAAC,CAAA;QACH,CAAC;QAED,OAAO,IAAI,CAAC,iBAAiB,CAAA;IAC/B,CAAC;IAED,IAAI,YAAY,CAAC,YAAY;QAC3B,IAAI,CAAC,aAAa,GAAG,YAAY,CAAA;QACjC,OAAO,IAAI,CAAC,iBAAiB,CAAA;IAC/B,CAAC;IAED,IAAI,OAAO;QACT,OAAO,IAAI,CAAC,QAAQ,CAAA;IACtB,CAAC;IAED,IAAI,OAAO,CAAC,OAAO;QACjB,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAA;IACzB,CAAC;IAED,uBAAuB;IACvB,IAAI,QAAQ;QACV,OAAO,IAAI,CAAC,QAAQ,CAAA;IACtB,CAAC;IAED,IAAI,QAAQ,CAAC,OAAO;QAClB,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAA;IACzB,CAAC;IAED,IAAI,OAAO;QACT,OAAO,IAAI,CAAC,QAAQ,CAAA;IACtB,CAAC;IAED,IAAI,OAAO,CAAC,OAAO;QACjB,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAA;IACzB,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,KAAK,GAAG,KAAK;;QACxB,IAAI,EAAE,IAAI,GAAG,CAAC,EAAE,KAAK,GAAG,EAAE,EAAE,GAAG,IAAI,CAAA;QAEnC;;;WAGG;QACH,IAAI,KAAK,EAAE,CAAC;YACV,IAAI,CAAC,OAAO,GAAG,YAAY,CAAA;YAC3B,IAAI,GAAG,CAAC,CAAA;QACV,CAAC;aAAM,CAAC;YACN,oEAAoE;YACpE,IAAI,CAAC,OAAO,GAAG,CAAA,MAAA,IAAI,CAAC,QAAQ,0CAAE,KAAK,EAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,CAAA;YAChF,IAAI,GAAG,IAAI,GAAG,CAAC,CAAA;QACjB,CAAC;QAED,OAAO,IAAI,CAAC,OAAO,CACjB;YACE,2CAA2C;YAC3C,KAAK;YACL,IAAI;YACJ,GAAG,CAAC,MAAM,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE;gBACrC,IAAI;gBACJ,KAAK;gBACL,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,QAAQ,EAAE,IAAI,CAAC,OAAO;gBACtB,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,OAAO,EAAE,IAAI,CAAC,YAAY;aAC3B,CAAC,CAAC;SACJ,EACD,KAAK,CACN,CAAA;IACH,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,EACV,IAAI,GAAG,IAAI,CAAC,IAAI,EAChB,KAAK,GAAG,IAAI,CAAC,KAAK,EAClB,OAAO,EACP,QAAQ,EACR,OAAO,KAOL,EAAE;QACJ,wFAAwF;QACxF,gEAAgE;QAChE,IAAI,CAAC,OAAO,GAAG,YAAY,CAAA;QAE3B,OAAO,GAAG,OAAO,IAAI,QAAQ,IAAI,IAAI,CAAC,OAAO,CAAA;QAC7C,OAAO,GAAG,OAAO,IAAI,IAAI,CAAC,OAAO,CAAA;QAEjC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;QAEtB;;;;;;;;WAQG;QACH,MAAM,QAAQ,GAAG,EAAE,IAAI,CAAC,cAAc,CAAA;QACtC,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,QAAQ,KAAK,IAAI,CAAC,cAAc,CAAA;QACtD,IAAI,MAAM,CAAA;QACV,IAAI,CAAC;YACH,MAAM,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE;gBAC1C,IAAI;gBACJ,KAAK;gBACL,OAAO;gBACP,QAAQ,EAAE,OAAO;gBACjB,+DAA+D;gBAC/D,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACjD,OAAO;gBACP,OAAO,EAAE,IAAI,CAAC,YAAY;aAC3B,EAAE,OAAO,CAAC,CAAA;QACb,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,OAAO,EAAE;gBAAE,OAAM;YACtB,MAAM,KAAK,CAAA;QACb,CAAC;QACD,IAAI,CAAC,OAAO,EAAE;YAAE,OAAM;QAEtB,OAAO,IAAI,CAAC,OAAO,CAAC;YAClB,2CAA2C;YAC3C,KAAK;YACL,IAAI;YACJ,GAAG,MAAM;SACV,CAAC,CAAA;IACJ,CAAC;IAED,KAAK,CAAC,OAAO,CACX,EACE,IAAI,EACJ,KAAK,EACL,KAAK,EACL,OAAO,EAMR,EACD,KAAe;QAEf,oCAAoC;QACpC,IAAI,OAAO,GAAG,mBAAmB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;QAC/C,IAAI,OAAO,GAAG,IAAI,EAAE,CAAC;YACnB,OAAO,MAAM,IAAI,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAA;QACnD,CAAC;QAED,uCAAuC;QACvC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,CAAA;QAC7B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,KAAK,CAAA;QAEhC,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAM;QACR,CAAC;QAED,IAAI,IAAI,CAAC,OAAO,KAAK,YAAY,EAAE,CAAC;YAClC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;QACxB,CAAC;aAAM,IAAI,IAAI,CAAC,IAAI,GAAG,IAAI,EAAE,CAAC;YAC5B,kCAAkC;YAClC,IAAI,CAAC,OAAO,GAAG,CAAC,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,OAAO,CAAC,CAAA;QAC9C,CAAC;aAAM,CAAC;YACN,OAAM;QACR,CAAC;QAED,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;QAClB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;QAClB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAEhB,IAAI,CAAC,QAAQ;YACX,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,GAAG;gBACpB,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,KAAK,EAAE,IAAI,CAAC,KAAK;gBAEjB,OAAO,EAAE,IAAI,CAAC,OAAO;aACtB,CAAC,CAAA;IACN,CAAC;CACF","sourcesContent":["import { ZERO_RECORDS } from './configure/zero-config.js'\nimport { DataConsumer } from './data-consumer.js'\nimport { FetchHandler, FetchOption, FilterValue, GristRecord, SorterConfig, SortersConfig } from './types.js'\n\nfunction EMPTY_FETCHHANDLER() {\n return {\n total: 0,\n records: []\n }\n}\n\nfunction _calculateTotalPage(limit: number, total: number) {\n /*\n * total page는 1이상이어야 한다.\n * 즉, 레코드가 하나도 없어도, 페이지 갯수는 1이 되어야 한다.\n */\n return Math.max(1, Math.ceil(total / limit))\n}\n\n/*\n * For fetches nobody awaits (page and limit changes, filter and option changes). This does not hide a\n * failure: fetchFailed has already drawn it on the grid before the rejection gets here. It only stops\n * the same failure from also escaping as an unhandled rejection. Do not copy it to a place where the\n * failure is not already shown.\n */\nconst shownByConsumer = () => {}\n\nexport class DataProvider {\n page: number = 1\n limit: number = 20\n total: number = 0\n records: GristRecord[] = []\n\n private consumer?: DataConsumer\n private _fetchHandler?: FetchHandler\n private _sorters?: SorterConfig[]\n private _filters?: FilterValue[]\n /*\n * 검색 낱말 — `search-mode=\"term\"` 에서만 찬다. 옛 길은 낱말을 칸별 필터로 바꿔 `_filters` 에\n * 넣으므로 이 자리는 비어 있고, 그래서 두 길이 한 화면에서 같이 서지 않는다.\n */\n private _search?: string\n\n private _pageChangeHandler: EventListener = this.onPageChange.bind(this)\n private _limitChangeHandler = this.onLimitChange.bind(this)\n private _fetchParamsChangeHandler = this.onFetchParamsChange.bind(this)\n private _recordChangeHandler = this.onRecordChange.bind(this)\n private _attachPageHandler: EventListener = this.onAttachPage.bind(this)\n\n private _fetchHandlerWrap: any\n private _fetchOptions: any\n /** Which fetch is the latest - an answer to an earlier one is dropped (fetch). */\n private _fetchSequence = 0\n\n constructor(consumer: DataConsumer) {\n this.consumer = consumer\n\n this.fetchHandler = consumer.fetchHandler\n\n this.consumer.addEventListener('attach-page', this._attachPageHandler)\n this.consumer.addEventListener('page-change', this._pageChangeHandler)\n this.consumer.addEventListener('limit-change', this._limitChangeHandler)\n this.consumer.addEventListener('fetch-params-change', this._fetchParamsChangeHandler)\n this.consumer.addEventListener('record-change', this._recordChangeHandler)\n }\n\n dispose() {\n this.consumer?.removeEventListener('attach-page', this._attachPageHandler)\n this.consumer?.removeEventListener('page-change', this._pageChangeHandler)\n this.consumer?.removeEventListener('limit-change', this._limitChangeHandler)\n this.consumer?.removeEventListener('fetch-params-change', this._fetchParamsChangeHandler)\n this.consumer?.removeEventListener('record-change', this._recordChangeHandler)\n }\n\n onAttachPage() {\n this.attach().catch(shownByConsumer)\n }\n\n onPageChange(e: Event) {\n var page = (e as CustomEvent).detail\n this.fetch({ page }).catch(shownByConsumer)\n }\n\n onLimitChange(e: Event) {\n var limit = (e as CustomEvent).detail\n this.fetch({ limit }).catch(shownByConsumer)\n }\n\n onFetchParamsChange(e: Event) {\n const { sorters, filters, search, from } = (e as CustomEvent).detail || {}\n\n sorters && (this.sorters = sorters)\n filters && (this.filters = filters)\n /* 빈 낱말도 값이다 — 「검색을 지웠다」를 지난 낱말로 덮으면 목록이 안 돌아온다. */\n search !== undefined && (this._search = search)\n\n if (this.consumer?.mode !== 'GRID') {\n this.page = 0\n }\n\n from !== 'config' && this.fetch().catch(shownByConsumer)\n }\n\n onRecordChange(e: Event) {\n this.consumer?.checkDirties()\n }\n\n get fetchOptions() {\n return this._fetchOptions\n }\n\n set fetchOptions(fetchOptions) {\n this._fetchOptions = fetchOptions\n\n this.fetch().catch(shownByConsumer)\n }\n\n get fetchHandler() {\n if (!this._fetchHandlerWrap) {\n /* `current` says whether this read is still the one the grid shows - a replaced read tells the grid nothing. */\n this._fetchHandlerWrap = async (options: FetchOption, current: () => boolean = () => true) => {\n if (!this._fetchHandler) {\n return\n }\n\n try {\n this.consumer?.showSpinner()\n const result = await (this._fetchHandler || EMPTY_FETCHHANDLER)(options)\n current() && this.consumer?.fetchSucceeded?.()\n return result\n } catch (error) {\n /*\n * A failed read is not an empty list. The grid is told, so it can say \"could not load\"\n * instead of \"nothing to show\" - and the error still reaches whoever awaited the fetch.\n */\n current() && this.consumer?.fetchFailed?.(error)\n throw error\n } finally {\n this.consumer?.hideSpinner()\n }\n }\n }\n\n return this._fetchHandlerWrap\n }\n\n set fetchHandler(fetchHandler) {\n this._fetchHandler = fetchHandler\n delete this._fetchHandlerWrap\n }\n\n get sorters() {\n return this._sorters\n }\n\n set sorters(sorters) {\n this._sorters = sorters\n }\n\n /* alias for sorters */\n get sortings() {\n return this._sorters\n }\n\n set sortings(sorters) {\n this._sorters = sorters\n }\n\n get filters() {\n return this._filters\n }\n\n set filters(filters) {\n this._filters = filters\n }\n\n async attach(reset = false) {\n var { page = 0, limit = 20 } = this\n\n /*\n * page는 0 based index가 아님에 주의한다.\n * 즉, page 값이 1 은 첫페이지를 의미한다.\n */\n if (reset) {\n this.records = ZERO_RECORDS\n page = 1\n } else {\n /* attach의 경우는 grist data의 변경상태를 유지하기 위해서, grist._data 를 기반으로 한다. */\n this.records = this.consumer?._data ? this.consumer._data.records : ZERO_RECORDS\n page = page + 1\n }\n\n return this._update(\n {\n /* fetch에서 limit과 page를 제공하지 않는 경우를 대비함. */\n limit,\n page,\n ...(await this.fetchHandler.call(null, {\n page,\n limit,\n sorters: this.sorters,\n sortings: this.sorters,\n filters: this.filters,\n options: this.fetchOptions\n }))\n },\n reset\n )\n }\n\n async fetch({\n page = this.page,\n limit = this.limit,\n sorters,\n sortings,\n filters\n }: {\n page?: number\n limit?: number\n sorters?: SortersConfig\n sortings?: SortersConfig\n filters?: FilterValue[]\n } = {}) {\n /* sortings property is only for things-factory server-side list parameter convention */\n /* fetchHandler should reture { page, limit, total, records } */\n this.records = ZERO_RECORDS\n\n sorters = sorters || sortings || this.sorters\n filters = filters || this.filters\n\n this.sorters = sorters\n\n /*\n * **The last fetch asked is the one the grid shows.**\n *\n * Two fetches can be out at once - the grid reading its address, then the screen laying its own conditions\n * over it a moment later. `_update` keeps the first answer to arrive and ignores the rest, so whichever came\n * back first stood: a ledger opened from the dashboard's 이번 달 순현금흐름 showed all 2,908 lines, the answer\n * to the read with no conditions, under a headroom that said October (2026-10-16). An answer to a fetch that a\n * later one has replaced - or its failure - is dropped, and the later answer is the one kept.\n */\n const sequence = ++this._fetchSequence\n const current = () => sequence === this._fetchSequence\n let answer\n try {\n answer = await this.fetchHandler.call(null, {\n page,\n limit,\n sorters,\n sortings: sorters,\n /* 안 쓰는 화면에 빈 칸을 보내지 않는다 — 옛 길 소비자의 fetchHandler 가 그날 안 바뀐다. */\n ...(this._search ? { search: this._search } : {}),\n filters,\n options: this.fetchOptions\n }, current)\n } catch (error) {\n if (!current()) return\n throw error\n }\n if (!current()) return\n\n return this._update({\n /* fetch에서 limit과 page를 제공하지 않는 경우를 대비함. */\n limit,\n page,\n ...answer\n })\n }\n\n async _update(\n {\n page,\n limit,\n total,\n records\n }: {\n page: number\n limit: number\n total: number\n records: GristRecord[]\n },\n reset?: boolean\n ): Promise<void> {\n // total을 감안해서 page가 최대값을 넘지 않도록 한다.\n var maxpage = _calculateTotalPage(limit, total)\n if (maxpage < page) {\n return await this.fetch({ page: maxpage, limit })\n }\n\n // page와 limit이 없는 경우 records 검사 전에 초기화\n this.page = this.page || page\n this.limit = this.limit || limit\n\n if (!records) {\n return\n }\n\n if (this.records === ZERO_RECORDS) {\n this.records = records\n } else if (this.page < page) {\n // attach인 경우에는 records를 append한다.\n this.records = [...this.records, ...records]\n } else {\n return\n }\n\n this.limit = limit\n this.total = total\n this.page = page\n\n this.consumer &&\n (this.consumer.data = {\n page: this.page,\n limit: this.limit,\n total: this.total,\n\n records: this.records\n })\n }\n}\n"]}
@@ -0,0 +1 @@
1
+ import '../src/data-grist.js';
@@ -0,0 +1,91 @@
1
+ import { expect, fixture } from '@open-wc/testing';
2
+ import { html } from 'lit';
3
+ import '../src/data-grist.js';
4
+ /**
5
+ * The last fetch asked is the one the grid shows.
6
+ *
7
+ * A ledger opened from a dashboard card asked twice in a row - once with no conditions as it read its address,
8
+ * then with the month the card named. The grid kept whichever answer arrived first - the read with no conditions -
9
+ * and showed every line under a headroom that said October (2026-10-16).
10
+ */
11
+ const config = {
12
+ columns: [
13
+ { type: 'string', name: 'id', hidden: true },
14
+ { type: 'string', name: 'name', header: 'name' }
15
+ ],
16
+ pagination: { pages: [20] }
17
+ };
18
+ const answer = (total) => ({
19
+ total,
20
+ records: Array.from({ length: Math.min(total, 20) }, (_, i) => ({ id: `${total}-${i}`, name: `row ${i}` }))
21
+ });
22
+ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
23
+ describe('ox-grist with two fetches out at once', () => {
24
+ /* Resizing inside a fixture trips Chrome's benign ResizeObserver loop error; it says nothing about fetching. */
25
+ const ignoreResizeLoop = (e) => {
26
+ if (String(e.message).includes('ResizeObserver loop')) {
27
+ e.stopImmediatePropagation();
28
+ e.preventDefault();
29
+ }
30
+ };
31
+ let previousOnError = null;
32
+ before(() => {
33
+ window.addEventListener('error', ignoreResizeLoop, true);
34
+ previousOnError = window.onerror;
35
+ window.onerror = (message, ...rest) => String(message).includes('ResizeObserver loop') ? true : previousOnError === null || previousOnError === void 0 ? void 0 : previousOnError.call(window, message, ...rest);
36
+ });
37
+ after(() => {
38
+ window.removeEventListener('error', ignoreResizeLoop, true);
39
+ window.onerror = previousOnError;
40
+ });
41
+ /*
42
+ * The answer depends on the conditions, as on the ledger: the read with none, asked first, comes back first; the
43
+ * month's comes back after it. The grid fetches once by itself when attached; the test starts after that settled.
44
+ */
45
+ const MONTH = [{ name: 'month', operator: 'eq', value: 10 }];
46
+ const byConditions = (failUnfiltered = false) => async ({ filters }) => {
47
+ if (!(filters === null || filters === void 0 ? void 0 : filters.length)) {
48
+ await wait(20);
49
+ if (failUnfiltered)
50
+ throw new Error('the unfiltered read failed');
51
+ return answer(2908);
52
+ }
53
+ await wait(300);
54
+ return answer(4);
55
+ };
56
+ async function attached(handler) {
57
+ const el = (await fixture(html `<ox-grist style="height: 300px" .config=${config} .fetchHandler=${handler}></ox-grist>`));
58
+ await wait(400);
59
+ return el;
60
+ }
61
+ it('shows the answer to the later fetch, though the earlier one came back first', async () => {
62
+ const el = await attached(byConditions());
63
+ el.filters = [];
64
+ const unfiltered = el.fetch();
65
+ el.filters = MONTH;
66
+ const month = el.fetch();
67
+ await Promise.all([unfiltered, month]);
68
+ await el.updateComplete;
69
+ expect(el.data.total).to.equal(4);
70
+ });
71
+ it('does not report the failure of a fetch a later one replaced', async () => {
72
+ const el = await attached(byConditions(true));
73
+ el.filters = [];
74
+ const unfiltered = el.fetch().then(() => 'kept quiet', () => 'reported');
75
+ el.filters = MONTH;
76
+ await el.fetch();
77
+ expect(await unfiltered).to.equal('kept quiet');
78
+ await el.updateComplete;
79
+ expect(el.data.total).to.equal(4);
80
+ /* The grid draws "could not load" from fetchFailed - a replaced read must not make it say so. */
81
+ const body = el.renderRoot.querySelector('#grist');
82
+ expect(!!(body === null || body === void 0 ? void 0 : body.renderRoot.querySelector('ox-empty-note'))).to.equal(false);
83
+ });
84
+ it('still shows a single fetch as before', async () => {
85
+ const el = (await fixture(html `<ox-grist style="height: 300px" .config=${config} .fetchHandler=${async () => answer(7)}></ox-grist>`));
86
+ await el.fetch();
87
+ await el.updateComplete;
88
+ expect(el.data.total).to.equal(7);
89
+ });
90
+ });
91
+ //# sourceMappingURL=latest-fetch-wins.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"latest-fetch-wins.test.js","sourceRoot":"","sources":["../../test/latest-fetch-wins.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect, fixture } from '@open-wc/testing'\nimport { html } from 'lit'\n\nimport '../src/data-grist.js'\n\n/**\n * The last fetch asked is the one the grid shows.\n *\n * A ledger opened from a dashboard card asked twice in a row - once with no conditions as it read its address,\n * then with the month the card named. The grid kept whichever answer arrived first - the read with no conditions -\n * and showed every line under a headroom that said October (2026-10-16).\n */\n\nconst config = {\n columns: [\n { type: 'string', name: 'id', hidden: true },\n { type: 'string', name: 'name', header: 'name' }\n ],\n pagination: { pages: [20] }\n}\n\nconst answer = (total: number) => ({\n total,\n records: Array.from({ length: Math.min(total, 20) }, (_, i) => ({ id: `${total}-${i}`, name: `row ${i}` }))\n})\n\nconst wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))\n\ntype Grist = HTMLElement & { fetch(): Promise<void>; data: { total: number }; updateComplete: Promise<unknown> }\n\ndescribe('ox-grist with two fetches out at once', () => {\n /* Resizing inside a fixture trips Chrome's benign ResizeObserver loop error; it says nothing about fetching. */\n const ignoreResizeLoop = (e: ErrorEvent) => {\n if (String(e.message).includes('ResizeObserver loop')) {\n e.stopImmediatePropagation()\n e.preventDefault()\n }\n }\n let previousOnError: OnErrorEventHandler = null\n before(() => {\n window.addEventListener('error', ignoreResizeLoop, true)\n previousOnError = window.onerror\n window.onerror = (message, ...rest) =>\n String(message).includes('ResizeObserver loop') ? true : previousOnError?.call(window, message, ...rest)\n })\n after(() => {\n window.removeEventListener('error', ignoreResizeLoop, true)\n window.onerror = previousOnError\n })\n\n /*\n * The answer depends on the conditions, as on the ledger: the read with none, asked first, comes back first; the\n * month's comes back after it. The grid fetches once by itself when attached; the test starts after that settled.\n */\n const MONTH = [{ name: 'month', operator: 'eq', value: 10 }]\n const byConditions = (failUnfiltered = false) => async ({ filters }: { filters?: unknown[] }) => {\n if (!filters?.length) {\n await wait(20)\n if (failUnfiltered) throw new Error('the unfiltered read failed')\n return answer(2908)\n }\n await wait(300)\n return answer(4)\n }\n\n async function attached(handler: any) {\n const el = (await fixture(html`<ox-grist style=\"height: 300px\" .config=${config} .fetchHandler=${handler}></ox-grist>`)) as Grist & {\n filters: unknown[]\n renderRoot: ShadowRoot\n }\n await wait(400)\n return el\n }\n\n it('shows the answer to the later fetch, though the earlier one came back first', async () => {\n const el = await attached(byConditions())\n\n el.filters = []\n const unfiltered = el.fetch()\n el.filters = MONTH\n const month = el.fetch()\n await Promise.all([unfiltered, month])\n await el.updateComplete\n\n expect(el.data.total).to.equal(4)\n })\n\n it('does not report the failure of a fetch a later one replaced', async () => {\n const el = await attached(byConditions(true))\n\n el.filters = []\n const unfiltered = el.fetch().then(\n () => 'kept quiet',\n () => 'reported'\n )\n el.filters = MONTH\n await el.fetch()\n\n expect(await unfiltered).to.equal('kept quiet')\n await el.updateComplete\n expect(el.data.total).to.equal(4)\n /* The grid draws \"could not load\" from fetchFailed - a replaced read must not make it say so. */\n const body = el.renderRoot.querySelector('#grist') as (HTMLElement & { renderRoot: ShadowRoot }) | null\n expect(!!body?.renderRoot.querySelector('ox-empty-note')).to.equal(false)\n })\n\n it('still shows a single fetch as before', async () => {\n const el = (await fixture(html`<ox-grist style=\"height: 300px\" .config=${config} .fetchHandler=${async () => answer(7)}></ox-grist>`)) as Grist\n await el.fetch()\n await el.updateComplete\n\n expect(el.data.total).to.equal(7)\n })\n})\n"]}