@lotics/app-sdk 0.125.6 → 0.125.8

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.js CHANGED
@@ -5779,6 +5779,46 @@ function endOfDay(date2, options) {
5779
5779
  return _date;
5780
5780
  }
5781
5781
 
5782
+ // ../../node_modules/date-fns/startOfMonth.js
5783
+ function startOfMonth(date2, options) {
5784
+ const _date = toDate(date2, options?.in);
5785
+ _date.setDate(1);
5786
+ _date.setHours(0, 0, 0, 0);
5787
+ return _date;
5788
+ }
5789
+
5790
+ // ../../node_modules/date-fns/endOfMonth.js
5791
+ function endOfMonth(date2, options) {
5792
+ const _date = toDate(date2, options?.in);
5793
+ const month = _date.getMonth();
5794
+ _date.setFullYear(_date.getFullYear(), month + 1, 0);
5795
+ _date.setHours(23, 59, 59, 999);
5796
+ return _date;
5797
+ }
5798
+
5799
+ // ../../node_modules/date-fns/addMonths.js
5800
+ function addMonths(date2, amount, options) {
5801
+ const _date = toDate(date2, options?.in);
5802
+ if (isNaN(amount)) return constructFrom(options?.in || date2, NaN);
5803
+ if (!amount) {
5804
+ return _date;
5805
+ }
5806
+ const dayOfMonth = _date.getDate();
5807
+ const endOfDesiredMonth = constructFrom(options?.in || date2, _date.getTime());
5808
+ endOfDesiredMonth.setMonth(_date.getMonth() + amount + 1, 0);
5809
+ const daysInMonth = endOfDesiredMonth.getDate();
5810
+ if (dayOfMonth >= daysInMonth) {
5811
+ return endOfDesiredMonth;
5812
+ } else {
5813
+ _date.setFullYear(
5814
+ endOfDesiredMonth.getFullYear(),
5815
+ endOfDesiredMonth.getMonth(),
5816
+ dayOfMonth
5817
+ );
5818
+ return _date;
5819
+ }
5820
+ }
5821
+
5782
5822
  // ../../node_modules/date-fns/isWithinInterval.js
5783
5823
  function isWithinInterval(date2, interval, options) {
5784
5824
  const time = +toDate(date2, options?.in);
@@ -6091,7 +6131,7 @@ function addDays2(dateStr, days) {
6091
6131
  date2.setDate(date2.getDate() + days);
6092
6132
  return formatDate(date2.getFullYear(), date2.getMonth() + 1, date2.getDate());
6093
6133
  }
6094
- function addMonths(dateStr, months) {
6134
+ function addMonths2(dateStr, months) {
6095
6135
  const parsed = parseDate2(dateStr);
6096
6136
  const target = new Date(Date.UTC(parsed.year, parsed.month - 1 + months, 1));
6097
6137
  const lastDay = new Date(Date.UTC(target.getUTCFullYear(), target.getUTCMonth() + 1, 0)).getUTCDate();
@@ -6363,7 +6403,7 @@ function resolveRelativePoint(offset, unit, fixedTime, timezone) {
6363
6403
  result = addYears(result, offset);
6364
6404
  break;
6365
6405
  case "months":
6366
- result = addMonths(result, offset);
6406
+ result = addMonths2(result, offset);
6367
6407
  break;
6368
6408
  case "weeks":
6369
6409
  result = addDays2(result, offset * 7);
@@ -6388,10 +6428,10 @@ function resolvePeriodPoint(period, boundary, offset, fixedTime, timezone) {
6388
6428
  referenceDate = addDays2(referenceDate, offset * 7);
6389
6429
  break;
6390
6430
  case "month":
6391
- referenceDate = addMonths(referenceDate, offset);
6431
+ referenceDate = addMonths2(referenceDate, offset);
6392
6432
  break;
6393
6433
  case "quarter":
6394
- referenceDate = addMonths(referenceDate, offset * 3);
6434
+ referenceDate = addMonths2(referenceDate, offset * 3);
6395
6435
  break;
6396
6436
  case "year":
6397
6437
  referenceDate = addYears(referenceDate, offset);
@@ -6886,6 +6926,44 @@ function getDateExpressionFunctions(defaultTimezone) {
6886
6926
  return endOfDay(normalized).toISOString();
6887
6927
  },
6888
6928
  // ========================================================================
6929
+ // MONTHS — payment terms ("30 days after month end") count from a month's edge
6930
+ // ========================================================================
6931
+ /**
6932
+ * Get the first moment of the date's month in the workspace timezone. Returns ISO string, or null if date is null.
6933
+ */
6934
+ startOfMonth: (date2, timezone) => {
6935
+ if (date2 == null || date2 === "") return null;
6936
+ validateDateInput(date2, "date", "startOfMonth");
6937
+ validateOptionalTimezone(timezone, "startOfMonth");
6938
+ const tz = getTimezone(timezone);
6939
+ const normalized = normalizeDateWithValidation(date2, tz, "startOfMonth");
6940
+ return startOfMonth(tz ? new TZDate(normalized, tz) : normalized).toISOString();
6941
+ },
6942
+ /**
6943
+ * Get the last moment of the date's month in the workspace timezone. Returns ISO string, or null if date is null.
6944
+ */
6945
+ endOfMonth: (date2, timezone) => {
6946
+ if (date2 == null || date2 === "") return null;
6947
+ validateDateInput(date2, "date", "endOfMonth");
6948
+ validateOptionalTimezone(timezone, "endOfMonth");
6949
+ const tz = getTimezone(timezone);
6950
+ const normalized = normalizeDateWithValidation(date2, tz, "endOfMonth");
6951
+ return endOfMonth(tz ? new TZDate(normalized, tz) : normalized).toISOString();
6952
+ },
6953
+ /**
6954
+ * Add months to a date (negative subtracts); a day past the target month's end lands on its last day
6955
+ * (31 Jan + 1 month is 28 or 29 Feb). Returns ISO string, or null if date is null.
6956
+ */
6957
+ addMonths: (date2, months, timezone) => {
6958
+ if (date2 == null || date2 === "") return null;
6959
+ validateDateInput(date2, "date", "addMonths");
6960
+ validateNumber(months, "months", "addMonths");
6961
+ validateOptionalTimezone(timezone, "addMonths");
6962
+ const tz = getTimezone(timezone);
6963
+ const normalized = normalizeDateWithValidation(date2, tz, "addMonths");
6964
+ return addMonths(tz ? new TZDate(normalized, tz) : normalized, months).toISOString();
6965
+ },
6966
+ // ========================================================================
6889
6967
  // DATE DIFFERENCES
6890
6968
  // ========================================================================
6891
6969
  /**
@@ -7386,6 +7464,9 @@ var HELPER_COST = {
7386
7464
  subMinutes: "builds",
7387
7465
  startOfDay: "builds",
7388
7466
  endOfDay: "builds",
7467
+ startOfMonth: "builds",
7468
+ endOfMonth: "builds",
7469
+ addMonths: "builds",
7389
7470
  differenceInCalendarDays: "reads",
7390
7471
  differenceInHours: "reads",
7391
7472
  differenceInMinutes: "reads",
@@ -246,9 +246,9 @@ Reads paginate two different ways, and the difference is load-bearing:
246
246
  opaque cursor instead of counting an offset, so a deep scroll stays O(page) and — the reason it
247
247
  matters — **never skips or duplicates a row as the set shifts** under concurrent inserts/deletes.
248
248
  The cursor is internal: the hook sends it and reads the next one back; you never see it. The
249
- server keysets a single sortable key (the runtime `sort`, else the template's own order on a
250
- column it projects, else the record id) and falls back to offset transparently for a multi-key
251
- sort, or a `first` key, it can't seek. So an infinite feed needs **no
249
+ server keysets the sort's column keys (the runtime `sort`, else the template's own order on the
250
+ columns it projects, else the record id) and falls back to offset transparently for a `first`
251
+ key it can't seek. So an infinite feed needs **no
252
252
  dedupe** — key rows by `__source_record_id` for stable React keys, not to guard against repeats.
253
253
  - **`page`, `limit`, `queryAll` and manual `rpc("query", { limit, offset })` use offset
254
254
  pagination** — count `offset` rows, skip them, return the next page. Two consequences a keyset
package/docs/queries.md CHANGED
@@ -878,7 +878,7 @@ template as derived nodes, in this order: `filter` (narrow) → `sort` (order)
878
878
  `offset` (`limit` clamped to the 10,000-row cap, §10); `useQuery` with `more` opts into **keyset
879
879
  (seek)** by sending `keyset: true` + the prior page's `cursor`, and the server returns the next
880
880
  `next_cursor` (null on the last page). Seek stays O(page) and never skips/duplicates a row as the
881
- set shifts, falling back to offset for a multi-key sort, or a `first` key, it can't seek.
881
+ set shifts, falling back to offset for a `first` key it can't seek.
882
882
  - **`count: true`** — returns `{ total }` only: a COUNT over the *filtered* set, ignoring
883
883
  sort/limit/offset. Drives "Page 1 of N". With **`count_by: "<column>"`** the same scan also
884
884
  returns `counts` — rows per value of that projected column (a multi-value row under each value,
package/docs/workflows.md CHANGED
@@ -498,7 +498,7 @@ full menu by category, so a miss is one informed retry.
498
498
  | **Number** | `sum`, `sumBy`, `mean`, `meanBy`, `min`, `max`, `minBy`, `maxBy`, `round`, `ceil`, `floor`, `abs`, `mod`, `pow`, `sqrt`, `clamp`, `percentage` |
499
499
  | **String** | `upper`, `lower`, `capitalize`, `trim`, `contains`, `startsWith`, `endsWith`, `replace`, `replaceAll`, `removeChars(text, chars)` (without every character that occurs in `chars`), `removeSpaces(text)` (without any whitespace), `substring`, `length`, `split`, `join`, `padStart`, `padEnd`, `uuidFrom(text)` (the same UUID for the same text), `formatNumber(value, decimals)` (fixed-decimal, ungrouped), `formatDecimal(value, decimals, locale)` (grouped for a reader — `formatDecimal(151000, 0, "vi-VN")` → `151.000`), `numberToWords(x, lang?)` (`"vi"` or `"en"`) |
500
500
  | **Object** | `keys`, `values`, `entries`, `get`, `pick`, `omit`, `merge`, `nonNullKeys` |
501
- | **Date** | `now`, `formatDate`, `parseDate`, `addDays`, `subDays`, `addHours`, `subHours`, `addMinutes`, `subMinutes`, `startOfDay`, `endOfDay`, `differenceInCalendarDays`, `differenceInHours`, `differenceInMinutes`, `isBefore`, `isAfter`, `isSameDay`, `isToday`, `isWithinRange`, `meetsBounds` |
501
+ | **Date** | `now`, `formatDate`, `parseDate`, `addDays`, `subDays`, `addHours`, `subHours`, `addMinutes`, `subMinutes`, `startOfDay`, `endOfDay`, `startOfMonth`, `endOfMonth`, `addMonths`, `differenceInCalendarDays`, `differenceInHours`, `differenceInMinutes`, `isBefore`, `isAfter`, `isSameDay`, `isToday`, `isWithinRange`, `meetsBounds` |
502
502
  | **Other** | `formatCurrency(amount, locale, currency)`, `randomNumber(len)`, `randomAlphaNumeric(len)`, `sample(items)`, `current_member_in_any_group(["grp_…"])`, `current_member_stands_in_for([memberId])`, `current_member_stands_in_for_any_group(["grp_…"])` |
503
503
 
504
504
  **Emptiness on a record cell.** `isNull` is the strict check — true only for `null`/`undefined`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/app-sdk",
3
- "version": "0.125.6",
3
+ "version": "0.125.8",
4
4
  "description": "The SDK a Lotics custom-code app reads and writes through — typed hooks over the host bridge, cell readers, mount() and AppRouter",
5
5
  "type": "module",
6
6
  "exports": {