@ethisyscore/core-utils 1.94.1 → 1.95.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/chunk-2F5FENXB.js +84 -0
- package/dist/chunk-2F5FENXB.js.map +1 -0
- package/dist/chunk-3LIIUXBE.js +14 -0
- package/dist/chunk-3LIIUXBE.js.map +1 -0
- package/dist/chunk-G2XGZH7P.cjs +16 -0
- package/dist/chunk-G2XGZH7P.cjs.map +1 -0
- package/dist/chunk-GAZLFZEL.js +105 -0
- package/dist/chunk-GAZLFZEL.js.map +1 -0
- package/dist/chunk-LT7I4JVK.cjs +121 -0
- package/dist/chunk-LT7I4JVK.cjs.map +1 -0
- package/dist/chunk-OEDAX6WN.js +121 -0
- package/dist/chunk-OEDAX6WN.js.map +1 -0
- package/dist/chunk-P5FSVRZO.cjs +127 -0
- package/dist/chunk-P5FSVRZO.cjs.map +1 -0
- package/dist/chunk-RLPXYKSM.cjs +23 -0
- package/dist/chunk-RLPXYKSM.cjs.map +1 -0
- package/dist/chunk-TN6DXBVD.js +252 -0
- package/dist/chunk-TN6DXBVD.js.map +1 -0
- package/dist/chunk-YNBKRMK3.cjs +290 -0
- package/dist/chunk-YNBKRMK3.cjs.map +1 -0
- package/dist/chunk-ZQCGDT7D.js +19 -0
- package/dist/chunk-ZQCGDT7D.js.map +1 -0
- package/dist/chunk-ZR7TABGT.cjs +94 -0
- package/dist/chunk-ZR7TABGT.cjs.map +1 -0
- package/dist/date/index.cjs +210 -397
- package/dist/date/index.cjs.map +1 -1
- package/dist/date/index.js +2 -349
- package/dist/date/index.js.map +1 -1
- package/dist/date/org-format.cjs +13 -151
- package/dist/date/org-format.cjs.map +1 -1
- package/dist/date/org-format.js +3 -141
- package/dist/date/org-format.js.map +1 -1
- package/dist/index.cjs +286 -600
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +6 -539
- package/dist/index.js.map +1 -1
- package/dist/list/index.cjs +7 -11
- package/dist/list/index.cjs.map +1 -1
- package/dist/list/index.js +1 -12
- package/dist/list/index.js.map +1 -1
- package/dist/money/index.cjs +39 -51
- package/dist/money/index.cjs.map +1 -1
- package/dist/money/index.d.cts +129 -8
- package/dist/money/index.d.ts +129 -8
- package/dist/money/index.js +1 -49
- package/dist/money/index.js.map +1 -1
- package/dist/number/index.cjs +15 -18
- package/dist/number/index.cjs.map +1 -1
- package/dist/number/index.js +1 -17
- package/dist/number/index.js.map +1 -1
- package/dist/patch/index.cjs +22 -121
- package/dist/patch/index.cjs.map +1 -1
- package/dist/patch/index.js +1 -119
- package/dist/patch/index.js.map +1 -1
- package/package.json +1 -1
package/dist/money/index.d.ts
CHANGED
|
@@ -12,6 +12,11 @@
|
|
|
12
12
|
* (the amount's own currency, or the org's reporting currency) and CONVERTING
|
|
13
13
|
* between currencies are host-aware concerns handled by the plugin-ui `useCurrency`
|
|
14
14
|
* hook, which formats through this module.
|
|
15
|
+
*
|
|
16
|
+
* When no currency is given, `formatMoney` falls back to the organisation's
|
|
17
|
+
* reporting currency via the {@link ./orgCurrency} seam (the same module-ref +
|
|
18
|
+
* fail-open-to-`GBP` mechanism as the date org-format seam), so a plugin can render
|
|
19
|
+
* reporting-currency amounts without threading a code or hardcoding one.
|
|
15
20
|
*/
|
|
16
21
|
/**
|
|
17
22
|
* Deterministic fallback locale. Matches the date seam's UK fallback so money and
|
|
@@ -21,8 +26,12 @@
|
|
|
21
26
|
declare const MONEY_FALLBACK_LOCALE = "en-GB";
|
|
22
27
|
/** Options for {@link formatMoney}. */
|
|
23
28
|
interface FormatMoneyOptions {
|
|
24
|
-
/**
|
|
25
|
-
|
|
29
|
+
/**
|
|
30
|
+
* ISO 4217 code (e.g. `"GBP"`, `"EUR"`) - drives `Intl` currency formatting.
|
|
31
|
+
* Optional: when omitted, `formatMoney` resolves the organisation's reporting
|
|
32
|
+
* currency (see {@link ./orgCurrency}), falling back to `GBP` when none is stamped.
|
|
33
|
+
*/
|
|
34
|
+
currencyCode?: string;
|
|
26
35
|
/**
|
|
27
36
|
* Fraction digits to show. When null/undefined, `Intl`'s per-currency default is
|
|
28
37
|
* used (GBP -> 2, JPY -> 0). Pass a number to force a currency's decimal-places
|
|
@@ -39,12 +48,23 @@ interface FormatMoneyOptions {
|
|
|
39
48
|
symbol?: string | null;
|
|
40
49
|
}
|
|
41
50
|
/**
|
|
42
|
-
* Formats a monetary amount
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
51
|
+
* Formats a monetary amount. The currency is resolved in this order:
|
|
52
|
+
*
|
|
53
|
+
* - `formatMoney(amount, "EUR")` — a positional ISO code, with the currency's
|
|
54
|
+
* natural decimals.
|
|
55
|
+
* - `formatMoney(amount, { currencyCode, decimalPlaces, locale, symbol })` — the
|
|
56
|
+
* options form; any of `currencyCode`/`decimalPlaces` may be omitted.
|
|
57
|
+
* - `formatMoney(amount)` (or an options object without `currencyCode`) — the
|
|
58
|
+
* organisation's reporting currency stamped via the {@link ./orgCurrency} seam,
|
|
59
|
+
* including that currency's decimal-places override when the caller gave none.
|
|
60
|
+
* When no org currency is stamped it falls back to {@link MONEY_FALLBACK_CURRENCY}
|
|
61
|
+
* (`GBP`), the same deterministic fail-open as the date org-format seam.
|
|
62
|
+
*
|
|
63
|
+
* On an unknown/invalid currency code (which makes `Intl.NumberFormat` throw) it
|
|
64
|
+
* falls back to a symbol/code prefix plus the fixed-decimal amount, so a bad code
|
|
65
|
+
* degrades to a readable string rather than throwing on a render path.
|
|
46
66
|
*/
|
|
47
|
-
declare function formatMoney(amount: number,
|
|
67
|
+
declare function formatMoney(amount: number, currency?: string | FormatMoneyOptions): string;
|
|
48
68
|
/** Options for {@link formatCompactMoney}. */
|
|
49
69
|
interface FormatCompactMoneyOptions {
|
|
50
70
|
/** ISO 4217 code (e.g. `"GBP"`, `"EUR"`) - resolves the leading symbol. */
|
|
@@ -80,4 +100,105 @@ declare function formatCompactMoney(amount: number, options: FormatCompactMoneyO
|
|
|
80
100
|
*/
|
|
81
101
|
declare function getCurrencySymbol(currencyCode: string, locale?: string, symbol?: string | null): string;
|
|
82
102
|
|
|
83
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Organisation-driven reporting-currency resolution.
|
|
105
|
+
*
|
|
106
|
+
* Plugin (and host) surfaces render reporting-currency amounts in whatever the
|
|
107
|
+
* organisation has configured as its reporting currency, without threading a
|
|
108
|
+
* currency code through every call site. This module holds the module-level
|
|
109
|
+
* reporting-currency ref, its setter, and the readers with a deterministic
|
|
110
|
+
* fallback constant. It is pure — no React, no host access — so it lives in
|
|
111
|
+
* core-utils behind the `@ethisyscore/core-utils/money` sub-path, mirroring the
|
|
112
|
+
* date org-format seam (`@ethisyscore/core-utils/date/org-format`).
|
|
113
|
+
*
|
|
114
|
+
* The ref is stamped by the surface sync hook — the SDK
|
|
115
|
+
* `useReportingCurrencySync` reads the host
|
|
116
|
+
* `settings:get-organisation-reporting-currency` tool and calls
|
|
117
|
+
* `setOrgReportingCurrency`. When no reporting currency is stamped (a
|
|
118
|
+
* standalone/mock run, an old host, or a failed fetch) resolution falls back to
|
|
119
|
+
* the deterministic UK default `GBP`, NOT the OS locale, so output is
|
|
120
|
+
* machine-stable regardless of the host. This is the exact fail-open contract of
|
|
121
|
+
* the date seam's UK `dd/MM/yyyy` fallback.
|
|
122
|
+
*
|
|
123
|
+
* Deciding WHICH currency an amount is in (its own currency vs the org's
|
|
124
|
+
* reporting currency) and CONVERTING between currencies stay host-aware concerns
|
|
125
|
+
* of the plugin-ui `useCurrency` hook. This seam only answers "what is the org's
|
|
126
|
+
* reporting currency" for the pure {@link formatMoney} default path.
|
|
127
|
+
*/
|
|
128
|
+
/**
|
|
129
|
+
* Deterministic fallback reporting currency when no org currency is stamped.
|
|
130
|
+
* Matches the date seam's UK fallback (`ORG_DATE_FALLBACK_FORMAT`) so money and
|
|
131
|
+
* dates degrade consistently, and so output does not drift with the OS locale of
|
|
132
|
+
* whatever host renders it.
|
|
133
|
+
*/
|
|
134
|
+
declare const MONEY_FALLBACK_CURRENCY = "GBP";
|
|
135
|
+
/** The organisation's reporting currency as stamped by the surface sync hook. */
|
|
136
|
+
interface OrgReportingCurrency {
|
|
137
|
+
/** ISO 4217 code (e.g. `"GBP"`, `"EUR"`) drives `Intl` currency formatting. */
|
|
138
|
+
code: string;
|
|
139
|
+
/**
|
|
140
|
+
* Host decimal-places override for the currency, or null to use the currency's
|
|
141
|
+
* ISO default (GBP -> 2, JPY -> 0). Applied by {@link formatMoney} only when the
|
|
142
|
+
* caller supplies no explicit decimalPlaces.
|
|
143
|
+
*/
|
|
144
|
+
decimalPlaces: number | null;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Stamps the module-level reporting-currency ref. Called by the surface sync hook
|
|
148
|
+
* each time the organisation's reporting currency resolves. Pass `null` to clear
|
|
149
|
+
* it and fall back to the deterministic {@link MONEY_FALLBACK_CURRENCY}.
|
|
150
|
+
*/
|
|
151
|
+
declare function setOrgReportingCurrency(currency: OrgReportingCurrency | null): void;
|
|
152
|
+
/** The org's stamped reporting currency, or null to fall back to the UK default. */
|
|
153
|
+
declare function getOrgReportingCurrency(): OrgReportingCurrency | null;
|
|
154
|
+
/**
|
|
155
|
+
* The org's effective reporting-currency code: the stamped code when present, else
|
|
156
|
+
* the deterministic {@link MONEY_FALLBACK_CURRENCY} (`GBP`). Never null, so callers
|
|
157
|
+
* always have a well-formed code to format with.
|
|
158
|
+
*/
|
|
159
|
+
declare function resolveOrgReportingCurrencyCode(): string;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Framework-agnostic money PARSING — the inverse of {@link formatMoney} for form
|
|
163
|
+
* inputs and editable amount fields.
|
|
164
|
+
*
|
|
165
|
+
* A user (or a round-tripped display value) types an amount in the en-GB /
|
|
166
|
+
* dot-decimal display shape — a currency symbol or ISO code prefix, comma grouping
|
|
167
|
+
* separators, stray whitespace, and a dot decimal point ("£1,234.56", "GBP 1,234.56").
|
|
168
|
+
* This turns that back into a plain number, or null when there is no meaningful value
|
|
169
|
+
* to parse.
|
|
170
|
+
*
|
|
171
|
+
* It is deliberately lenient about the symbol, currency-code prefix and grouping so
|
|
172
|
+
* it tolerates the display forms {@link formatMoney} produces in the en-GB fallback
|
|
173
|
+
* locale, but it is NOT a locale-universal inverse of {@link formatMoney}. Only the
|
|
174
|
+
* dot-decimal shape is understood: a comma is always treated as a grouping separator
|
|
175
|
+
* and dropped, so comma-decimal locales (de-DE `1.234,56`) are OUT OF SCOPE and would
|
|
176
|
+
* mis-parse. The platform stores and edits amounts in the dot-decimal numeric form.
|
|
177
|
+
*
|
|
178
|
+
* Ported from the per-plugin `parseCurrency` helper so consuming plugins drop the
|
|
179
|
+
* local copy.
|
|
180
|
+
*/
|
|
181
|
+
/**
|
|
182
|
+
* Parses a formatted currency string back to a number. Returns null for
|
|
183
|
+
* null/undefined, an empty/whitespace-only string, or the literal `"N/A"`
|
|
184
|
+
* placeholder; normalises the Unicode minus, strips every non-numeric character
|
|
185
|
+
* (currency symbols, alpha currency-code prefixes, grouping commas and whitespace),
|
|
186
|
+
* then `parseFloat`s the remainder, returning null when the result is not a number.
|
|
187
|
+
*
|
|
188
|
+
* Parses only the en-GB / dot-decimal display shape (comma thousands, dot decimal);
|
|
189
|
+
* see the module docs — it is NOT a locale-universal inverse of {@link formatMoney}.
|
|
190
|
+
* A leading minus is preserved, so `"-£10.00"` parses to `-10`. Garbage that contains
|
|
191
|
+
* no leading number (`"abc"`) yields null.
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* parseMoney("£1,234.56") // 1234.56
|
|
195
|
+
* parseMoney("GBP 1,234.56") // 1234.56
|
|
196
|
+
* parseMoney("−£10.00") // -10 (Unicode minus U+2212)
|
|
197
|
+
* parseMoney("ZZZZ10.00") // 10 (invalid-code fallback prefix)
|
|
198
|
+
* parseMoney("N/A") // null
|
|
199
|
+
* parseMoney("") // null
|
|
200
|
+
* parseMoney(null) // null
|
|
201
|
+
*/
|
|
202
|
+
declare function parseMoney(value: string | null | undefined): number | null;
|
|
203
|
+
|
|
204
|
+
export { type FormatCompactMoneyOptions, type FormatMoneyOptions, MONEY_FALLBACK_CURRENCY, MONEY_FALLBACK_LOCALE, type OrgReportingCurrency, formatCompactMoney, formatMoney, getCurrencySymbol, getOrgReportingCurrency, parseMoney, resolveOrgReportingCurrencyCode, setOrgReportingCurrency };
|
package/dist/money/index.js
CHANGED
|
@@ -1,51 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
var MONEY_FALLBACK_LOCALE = "en-GB";
|
|
3
|
-
function formatMoney(amount, options) {
|
|
4
|
-
const { currencyCode, decimalPlaces, locale = MONEY_FALLBACK_LOCALE, symbol } = options;
|
|
5
|
-
const normalizedCode = currencyCode.toUpperCase();
|
|
6
|
-
try {
|
|
7
|
-
const formatter = new Intl.NumberFormat(locale, {
|
|
8
|
-
style: "currency",
|
|
9
|
-
currency: normalizedCode,
|
|
10
|
-
// Narrow symbol so a clear currency context renders "$100", not "US$100".
|
|
11
|
-
currencyDisplay: "narrowSymbol",
|
|
12
|
-
...decimalPlaces != null ? { minimumFractionDigits: decimalPlaces, maximumFractionDigits: decimalPlaces } : {}
|
|
13
|
-
});
|
|
14
|
-
return formatter.format(amount);
|
|
15
|
-
} catch {
|
|
16
|
-
const fractionDigits = decimalPlaces ?? 2;
|
|
17
|
-
const prefix = symbol ?? normalizedCode;
|
|
18
|
-
const sign = amount < 0 ? "-" : "";
|
|
19
|
-
return `${sign}${prefix}${Math.abs(amount).toFixed(fractionDigits)}`;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
function formatCompactMoney(amount, options) {
|
|
23
|
-
const { currencyCode, symbol, locale = MONEY_FALLBACK_LOCALE } = options;
|
|
24
|
-
const sym = getCurrencySymbol(currencyCode, locale, symbol);
|
|
25
|
-
const sign = amount < 0 ? "-" : "";
|
|
26
|
-
const abs = Math.abs(amount);
|
|
27
|
-
if (abs >= 1e6) {
|
|
28
|
-
return `${sign}${sym}${(abs / 1e6).toFixed(1)}m`;
|
|
29
|
-
}
|
|
30
|
-
if (abs >= 1e3) {
|
|
31
|
-
return `${sign}${sym}${(abs / 1e3).toFixed(0)}k`;
|
|
32
|
-
}
|
|
33
|
-
return `${sign}${sym}${abs.toFixed(0)}`;
|
|
34
|
-
}
|
|
35
|
-
function getCurrencySymbol(currencyCode, locale = MONEY_FALLBACK_LOCALE, symbol) {
|
|
36
|
-
const normalizedCode = currencyCode.toUpperCase();
|
|
37
|
-
try {
|
|
38
|
-
const parts = new Intl.NumberFormat(locale, {
|
|
39
|
-
style: "currency",
|
|
40
|
-
currency: normalizedCode,
|
|
41
|
-
currencyDisplay: "narrowSymbol"
|
|
42
|
-
}).formatToParts(0);
|
|
43
|
-
return parts.find((part) => part.type === "currency")?.value ?? symbol ?? normalizedCode;
|
|
44
|
-
} catch {
|
|
45
|
-
return symbol ?? normalizedCode;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export { MONEY_FALLBACK_LOCALE, formatCompactMoney, formatMoney, getCurrencySymbol };
|
|
1
|
+
export { MONEY_FALLBACK_CURRENCY, MONEY_FALLBACK_LOCALE, formatCompactMoney, formatMoney, getCurrencySymbol, getOrgReportingCurrency, parseMoney, resolveOrgReportingCurrencyCode, setOrgReportingCurrency } from '../chunk-2F5FENXB.js';
|
|
50
2
|
//# sourceMappingURL=index.js.map
|
|
51
3
|
//# sourceMappingURL=index.js.map
|
package/dist/money/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|
package/dist/number/index.cjs
CHANGED
|
@@ -1,23 +1,20 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
var NUMBER_FALLBACK_LOCALE = "en-GB";
|
|
5
|
-
function formatNumber(value, options) {
|
|
6
|
-
if (value == null) {
|
|
7
|
-
return "\u2014";
|
|
8
|
-
}
|
|
9
|
-
const dp = options?.decimalPlaces ?? 2;
|
|
10
|
-
return new Intl.NumberFormat(options?.locale ?? NUMBER_FALLBACK_LOCALE, {
|
|
11
|
-
minimumFractionDigits: dp,
|
|
12
|
-
maximumFractionDigits: dp
|
|
13
|
-
}).format(value);
|
|
14
|
-
}
|
|
15
|
-
function formatPercent(ratio, options) {
|
|
16
|
-
return `${(ratio * 100).toFixed(options?.decimalPlaces ?? 2)}%`;
|
|
17
|
-
}
|
|
3
|
+
var chunkRLPXYKSM_cjs = require('../chunk-RLPXYKSM.cjs');
|
|
18
4
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
exports
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Object.defineProperty(exports, "NUMBER_FALLBACK_LOCALE", {
|
|
8
|
+
enumerable: true,
|
|
9
|
+
get: function () { return chunkRLPXYKSM_cjs.NUMBER_FALLBACK_LOCALE; }
|
|
10
|
+
});
|
|
11
|
+
Object.defineProperty(exports, "formatNumber", {
|
|
12
|
+
enumerable: true,
|
|
13
|
+
get: function () { return chunkRLPXYKSM_cjs.formatNumber; }
|
|
14
|
+
});
|
|
15
|
+
Object.defineProperty(exports, "formatPercent", {
|
|
16
|
+
enumerable: true,
|
|
17
|
+
get: function () { return chunkRLPXYKSM_cjs.formatPercent; }
|
|
18
|
+
});
|
|
22
19
|
//# sourceMappingURL=index.cjs.map
|
|
23
20
|
//# sourceMappingURL=index.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.cjs"}
|
package/dist/number/index.js
CHANGED
|
@@ -1,19 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
var NUMBER_FALLBACK_LOCALE = "en-GB";
|
|
3
|
-
function formatNumber(value, options) {
|
|
4
|
-
if (value == null) {
|
|
5
|
-
return "\u2014";
|
|
6
|
-
}
|
|
7
|
-
const dp = options?.decimalPlaces ?? 2;
|
|
8
|
-
return new Intl.NumberFormat(options?.locale ?? NUMBER_FALLBACK_LOCALE, {
|
|
9
|
-
minimumFractionDigits: dp,
|
|
10
|
-
maximumFractionDigits: dp
|
|
11
|
-
}).format(value);
|
|
12
|
-
}
|
|
13
|
-
function formatPercent(ratio, options) {
|
|
14
|
-
return `${(ratio * 100).toFixed(options?.decimalPlaces ?? 2)}%`;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export { NUMBER_FALLBACK_LOCALE, formatNumber, formatPercent };
|
|
1
|
+
export { NUMBER_FALLBACK_LOCALE, formatNumber, formatPercent } from '../chunk-ZQCGDT7D.js';
|
|
18
2
|
//# sourceMappingURL=index.js.map
|
|
19
3
|
//# sourceMappingURL=index.js.map
|
package/dist/number/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|
package/dist/patch/index.cjs
CHANGED
|
@@ -1,127 +1,28 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkP5FSVRZO_cjs = require('../chunk-P5FSVRZO.cjs');
|
|
4
4
|
|
|
5
|
-
// src/patch/index.ts
|
|
6
|
-
function generateJsonPatch(original, modified) {
|
|
7
|
-
if (!isSameType(original, modified)) {
|
|
8
|
-
if (original === modified) return [];
|
|
9
|
-
return [{ op: "replace", path: "", value: modified }];
|
|
10
|
-
}
|
|
11
|
-
const [, patches] = mutative.create(
|
|
12
|
-
original,
|
|
13
|
-
(draft) => {
|
|
14
|
-
applyChanges(draft, modified);
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
enablePatches: {
|
|
18
|
-
pathAsArray: false
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
);
|
|
22
|
-
return patches.map(
|
|
23
|
-
(patch) => ({
|
|
24
|
-
op: patch.op,
|
|
25
|
-
path: patch.path,
|
|
26
|
-
// explicitly check for value existence to satisfy strict null checks if needed
|
|
27
|
-
...patch.op !== "remove" ? { value: patch.value } : {}
|
|
28
|
-
})
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
function applyChanges(draft, modified) {
|
|
32
|
-
if (isDeepEqual(draft, modified)) {
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
if (Array.isArray(draft) && Array.isArray(modified)) {
|
|
36
|
-
const draftArray = draft;
|
|
37
|
-
draftArray.length = 0;
|
|
38
|
-
draftArray.push(...modified);
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
if (isPlainObject(draft) && isPlainObject(modified)) {
|
|
42
|
-
const draftObj = draft;
|
|
43
|
-
const modifiedObj = modified;
|
|
44
|
-
for (const key of Object.keys(draftObj)) {
|
|
45
|
-
if (!Object.prototype.hasOwnProperty.call(modifiedObj, key)) {
|
|
46
|
-
delete draftObj[key];
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
for (const key of Object.keys(modifiedObj)) {
|
|
50
|
-
if (!isDeepEqual(draftObj[key], modifiedObj[key])) {
|
|
51
|
-
draftObj[key] = modifiedObj[key];
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
function isDeepEqual(a, b) {
|
|
58
|
-
if (a === b) return true;
|
|
59
|
-
if (a === null || a === void 0 || b === null || b === void 0) {
|
|
60
|
-
return a === b;
|
|
61
|
-
}
|
|
62
|
-
if (typeof a !== typeof b) return false;
|
|
63
|
-
if (a instanceof Date && b instanceof Date) {
|
|
64
|
-
return a.getTime() === b.getTime();
|
|
65
|
-
}
|
|
66
|
-
if (Array.isArray(a) && Array.isArray(b)) {
|
|
67
|
-
if (a.length !== b.length) return false;
|
|
68
|
-
for (let i = 0; i < a.length; i++) {
|
|
69
|
-
if (!isDeepEqual(a[i], b[i])) return false;
|
|
70
|
-
}
|
|
71
|
-
return true;
|
|
72
|
-
}
|
|
73
|
-
if (typeof a === "object" && typeof b === "object") {
|
|
74
|
-
const objA = a;
|
|
75
|
-
const objB = b;
|
|
76
|
-
const keysA = Object.keys(objA);
|
|
77
|
-
const keysB = Object.keys(objB);
|
|
78
|
-
if (keysA.length !== keysB.length) return false;
|
|
79
|
-
for (const key of keysA) {
|
|
80
|
-
if (!Object.prototype.hasOwnProperty.call(objB, key)) return false;
|
|
81
|
-
if (!isDeepEqual(objA[key], objB[key])) return false;
|
|
82
|
-
}
|
|
83
|
-
return true;
|
|
84
|
-
}
|
|
85
|
-
return false;
|
|
86
|
-
}
|
|
87
|
-
function isPlainObject(value) {
|
|
88
|
-
return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date);
|
|
89
|
-
}
|
|
90
|
-
function isSameType(a, b) {
|
|
91
|
-
if (Array.isArray(a)) return Array.isArray(b);
|
|
92
|
-
if (Array.isArray(b)) return false;
|
|
93
|
-
if (a instanceof Date) return b instanceof Date;
|
|
94
|
-
if (a && typeof a === "object" && b && typeof b === "object") return true;
|
|
95
|
-
return typeof a === typeof b;
|
|
96
|
-
}
|
|
97
|
-
function applyJsonPatch(original, patches) {
|
|
98
|
-
const rootReplacement = patches.find((patch) => patch.path === "" && patch.op === "replace");
|
|
99
|
-
if (rootReplacement) {
|
|
100
|
-
return rootReplacement.value;
|
|
101
|
-
}
|
|
102
|
-
const nonRootPatches = patches.filter((patch) => patch.path !== "");
|
|
103
|
-
if (nonRootPatches.length === 0) {
|
|
104
|
-
return original;
|
|
105
|
-
}
|
|
106
|
-
return mutative.apply(original, nonRootPatches);
|
|
107
|
-
}
|
|
108
|
-
function isNullOrEmpty(patches) {
|
|
109
|
-
return !patches || patches.length === 0;
|
|
110
|
-
}
|
|
111
|
-
function hasPatches(patches) {
|
|
112
|
-
return patches.length > 0;
|
|
113
|
-
}
|
|
114
|
-
function deepClone(obj) {
|
|
115
|
-
if (typeof structuredClone === "function") {
|
|
116
|
-
return structuredClone(obj);
|
|
117
|
-
}
|
|
118
|
-
return JSON.parse(JSON.stringify(obj));
|
|
119
|
-
}
|
|
120
5
|
|
|
121
|
-
|
|
122
|
-
exports
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
6
|
+
|
|
7
|
+
Object.defineProperty(exports, "applyJsonPatch", {
|
|
8
|
+
enumerable: true,
|
|
9
|
+
get: function () { return chunkP5FSVRZO_cjs.applyJsonPatch; }
|
|
10
|
+
});
|
|
11
|
+
Object.defineProperty(exports, "deepClone", {
|
|
12
|
+
enumerable: true,
|
|
13
|
+
get: function () { return chunkP5FSVRZO_cjs.deepClone; }
|
|
14
|
+
});
|
|
15
|
+
Object.defineProperty(exports, "generateJsonPatch", {
|
|
16
|
+
enumerable: true,
|
|
17
|
+
get: function () { return chunkP5FSVRZO_cjs.generateJsonPatch; }
|
|
18
|
+
});
|
|
19
|
+
Object.defineProperty(exports, "hasPatches", {
|
|
20
|
+
enumerable: true,
|
|
21
|
+
get: function () { return chunkP5FSVRZO_cjs.hasPatches; }
|
|
22
|
+
});
|
|
23
|
+
Object.defineProperty(exports, "isNullOrEmpty", {
|
|
24
|
+
enumerable: true,
|
|
25
|
+
get: function () { return chunkP5FSVRZO_cjs.isNullOrEmpty; }
|
|
26
|
+
});
|
|
126
27
|
//# sourceMappingURL=index.cjs.map
|
|
127
28
|
//# sourceMappingURL=index.cjs.map
|
package/dist/patch/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/patch/index.ts"],"names":["create","apply"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * RFC 6902 JSON Patch helpers shared by the EthisysCore monolith and plugins.\n *\n * The wire shape (`JsonPatchOperation`) matches the backend SDK's\n * `EthisysCore.Plugin.Sdk.JsonPatchOperation` record, so a patch produced here by\n * {@link generateJsonPatch} is applied by `IEntityPatchService` on the server.\n */\nimport { apply, create } from \"mutative\";\n\n/** JSON Patch operation type. */\nexport type PatchOperation = \"add\" | \"remove\" | \"replace\";\n\n/**\n * A single JSON Patch operation (RFC 6902 compatible; mutative's format with a string path).\n */\nexport interface JsonPatchOperation {\n /** The operation to be performed. */\n op: PatchOperation;\n /** JSON Pointer path (RFC 6902 format). */\n path: string;\n /** The value for add/replace operations. */\n value?: unknown;\n /** Source pointer for move/copy operations (backend `From`); unused by the generate/apply helpers. */\n from?: string;\n}\n\n/** Array of JSON Patch operations. */\nexport type JsonPatch = JsonPatchOperation[];\n\n/**\n * Generates JSON Patch operations (RFC 6902).\n * Strictly typed, handles Date objects, and ignores key order.\n */\nexport function generateJsonPatch<T>(original: T, modified: T): JsonPatch {\n // If types are completely different (e.g. Array vs Object), strict replacement.\n if (!isSameType(original, modified)) {\n // If both are strictly equal (e.g. both null), returns empty\n if (original === modified) return [];\n // If different, replace root\n return [{ op: \"replace\", path: \"\", value: modified }];\n }\n\n const [, patches] = create(\n original,\n (draft) => {\n // We can safely cast draft to unknown here because we control the recursion\n applyChanges(draft, modified);\n },\n {\n enablePatches: {\n pathAsArray: false,\n },\n },\n );\n\n return patches.map(\n (patch): JsonPatchOperation => ({\n op: patch.op as \"add\" | \"remove\" | \"replace\",\n path: patch.path as string,\n // explicitly check for value existence to satisfy strict null checks if needed\n ...(patch.op !== \"remove\" ? { value: patch.value } : {}),\n }),\n );\n}\n\n/**\n * Recursively applies changes to the Mutative draft.\n * Uses 'unknown' and Type Guards instead of 'any'.\n */\nfunction applyChanges(draft: unknown, modified: unknown): void {\n // This handles Primitives, Dates, and identical References early\n if (isDeepEqual(draft, modified)) {\n return;\n }\n\n // We explicitly check isArray to safely access .length and .push\n if (Array.isArray(draft) && Array.isArray(modified)) {\n const draftArray = draft as Array<unknown>;\n // Clear and Replace approach (safer for DTOs than diffing indices)\n draftArray.length = 0;\n draftArray.push(...modified);\n return;\n }\n\n // We use type guards to ensure we can treat them as dictionaries\n if (isPlainObject(draft) && isPlainObject(modified)) {\n const draftObj = draft;\n const modifiedObj = modified;\n\n // A. Remove keys that don't exist in modified\n for (const key of Object.keys(draftObj)) {\n if (!Object.prototype.hasOwnProperty.call(modifiedObj, key)) {\n delete draftObj[key];\n }\n }\n\n // B. Add or update top-level keys. We deliberately do NOT recurse into nested objects: the backend\n // patch applier (IEntityPatchService) supports only top-level property operations, so a changed\n // nested object is replaced wholesale (path \"/key\"), never diffed into \"/key/child\" paths that the\n // server would reject.\n for (const key of Object.keys(modifiedObj)) {\n if (!isDeepEqual(draftObj[key], modifiedObj[key])) {\n draftObj[key] = modifiedObj[key];\n }\n }\n return;\n }\n}\n\n/**\n * Handles: Primitives, Arrays, Dates, Objects (order-independent), null/undefined\n */\nfunction isDeepEqual(a: unknown, b: unknown): boolean {\n // Strict Reference & Primitive Check\n if (a === b) return true;\n\n // Null/Undefined Checks\n if (a === null || a === undefined || b === null || b === undefined) {\n return a === b;\n }\n\n // Type Mismatch\n if (typeof a !== typeof b) return false;\n\n // Date Objects\n if (a instanceof Date && b instanceof Date) {\n return a.getTime() === b.getTime();\n }\n\n // Arrays\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!isDeepEqual(a[i], b[i])) return false;\n }\n return true;\n }\n\n // Objects\n if (typeof a === \"object\" && typeof b === \"object\") {\n // Treat as Record<string, unknown> safely\n const objA = a as Record<string, unknown>;\n const objB = b as Record<string, unknown>;\n\n const keysA = Object.keys(objA);\n const keysB = Object.keys(objB);\n\n if (keysA.length !== keysB.length) return false;\n\n for (const key of keysA) {\n if (!Object.prototype.hasOwnProperty.call(objB, key)) return false;\n if (!isDeepEqual(objA[key], objB[key])) return false;\n }\n return true;\n }\n\n return false;\n}\n\n/**\n * Type Guard: Checks if value is a plain object (not null, array, or Date)\n * Allows safe casting to Record<string, unknown>\n */\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return (\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value) &&\n !(value instanceof Date)\n );\n}\n\n/**\n * Helper: Checks if two values share a high-level type category\n */\nfunction isSameType(a: unknown, b: unknown): boolean {\n if (Array.isArray(a)) return Array.isArray(b);\n if (Array.isArray(b)) return false;\n if (a instanceof Date) return b instanceof Date;\n if (a && typeof a === \"object\" && b && typeof b === \"object\") return true;\n return typeof a === typeof b;\n}\n\n/**\n * Applies JSON Patch operations to an object using Mutative.\n * @param original - The original object\n * @param patches - Array of JSON Patch operations to apply\n * @returns The patched object (immutable)\n */\nexport function applyJsonPatch<T>(original: T, patches: JsonPatch): T {\n // Check for root replacement first - if found, it should be the only operation\n const rootReplacement = patches.find((patch) => patch.path === \"\" && patch.op === \"replace\");\n if (rootReplacement) {\n return rootReplacement.value as T;\n }\n\n // Filter out any root operations that might interfere and apply the rest\n const nonRootPatches = patches.filter((patch) => patch.path !== \"\");\n if (nonRootPatches.length === 0) {\n return original;\n }\n\n // Mutative's apply function expects patches in RFC 6902 format, which is what we have\n return apply(original as Record<string, unknown>, nonRootPatches) as T;\n}\n\n/**\n * Checks if a patch array is empty (null, undefined, or empty array).\n * @param patches - Array of patch operations or null/undefined\n * @returns True if patches is null, undefined, or empty array\n */\nexport function isNullOrEmpty(patches: JsonPatch | null | undefined): boolean {\n return !patches || patches.length === 0;\n}\n\n/**\n * Checks if a patch array has any operations.\n * @param patches - Array of patch operations\n * @returns True if patches array has operations, false if empty\n */\nexport function hasPatches(patches: JsonPatch): boolean {\n return patches.length > 0;\n}\n\n/**\n * Creates a deep clone of an object.\n * Useful for creating the \"modified\" version before generating patches.\n * @param obj - Object to clone\n * @returns Deep cloned object\n */\nexport function deepClone<T>(obj: T): T {\n // structuredClone preserves Dates (and undefined); fall back to JSON clone on legacy runtimes.\n if (typeof structuredClone === \"function\") {\n return structuredClone(obj);\n }\n return JSON.parse(JSON.stringify(obj));\n}\n"]}
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.cjs"}
|
package/dist/patch/index.js
CHANGED
|
@@ -1,121 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
// src/patch/index.ts
|
|
4
|
-
function generateJsonPatch(original, modified) {
|
|
5
|
-
if (!isSameType(original, modified)) {
|
|
6
|
-
if (original === modified) return [];
|
|
7
|
-
return [{ op: "replace", path: "", value: modified }];
|
|
8
|
-
}
|
|
9
|
-
const [, patches] = create(
|
|
10
|
-
original,
|
|
11
|
-
(draft) => {
|
|
12
|
-
applyChanges(draft, modified);
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
enablePatches: {
|
|
16
|
-
pathAsArray: false
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
);
|
|
20
|
-
return patches.map(
|
|
21
|
-
(patch) => ({
|
|
22
|
-
op: patch.op,
|
|
23
|
-
path: patch.path,
|
|
24
|
-
// explicitly check for value existence to satisfy strict null checks if needed
|
|
25
|
-
...patch.op !== "remove" ? { value: patch.value } : {}
|
|
26
|
-
})
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
function applyChanges(draft, modified) {
|
|
30
|
-
if (isDeepEqual(draft, modified)) {
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
if (Array.isArray(draft) && Array.isArray(modified)) {
|
|
34
|
-
const draftArray = draft;
|
|
35
|
-
draftArray.length = 0;
|
|
36
|
-
draftArray.push(...modified);
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
if (isPlainObject(draft) && isPlainObject(modified)) {
|
|
40
|
-
const draftObj = draft;
|
|
41
|
-
const modifiedObj = modified;
|
|
42
|
-
for (const key of Object.keys(draftObj)) {
|
|
43
|
-
if (!Object.prototype.hasOwnProperty.call(modifiedObj, key)) {
|
|
44
|
-
delete draftObj[key];
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
for (const key of Object.keys(modifiedObj)) {
|
|
48
|
-
if (!isDeepEqual(draftObj[key], modifiedObj[key])) {
|
|
49
|
-
draftObj[key] = modifiedObj[key];
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
function isDeepEqual(a, b) {
|
|
56
|
-
if (a === b) return true;
|
|
57
|
-
if (a === null || a === void 0 || b === null || b === void 0) {
|
|
58
|
-
return a === b;
|
|
59
|
-
}
|
|
60
|
-
if (typeof a !== typeof b) return false;
|
|
61
|
-
if (a instanceof Date && b instanceof Date) {
|
|
62
|
-
return a.getTime() === b.getTime();
|
|
63
|
-
}
|
|
64
|
-
if (Array.isArray(a) && Array.isArray(b)) {
|
|
65
|
-
if (a.length !== b.length) return false;
|
|
66
|
-
for (let i = 0; i < a.length; i++) {
|
|
67
|
-
if (!isDeepEqual(a[i], b[i])) return false;
|
|
68
|
-
}
|
|
69
|
-
return true;
|
|
70
|
-
}
|
|
71
|
-
if (typeof a === "object" && typeof b === "object") {
|
|
72
|
-
const objA = a;
|
|
73
|
-
const objB = b;
|
|
74
|
-
const keysA = Object.keys(objA);
|
|
75
|
-
const keysB = Object.keys(objB);
|
|
76
|
-
if (keysA.length !== keysB.length) return false;
|
|
77
|
-
for (const key of keysA) {
|
|
78
|
-
if (!Object.prototype.hasOwnProperty.call(objB, key)) return false;
|
|
79
|
-
if (!isDeepEqual(objA[key], objB[key])) return false;
|
|
80
|
-
}
|
|
81
|
-
return true;
|
|
82
|
-
}
|
|
83
|
-
return false;
|
|
84
|
-
}
|
|
85
|
-
function isPlainObject(value) {
|
|
86
|
-
return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date);
|
|
87
|
-
}
|
|
88
|
-
function isSameType(a, b) {
|
|
89
|
-
if (Array.isArray(a)) return Array.isArray(b);
|
|
90
|
-
if (Array.isArray(b)) return false;
|
|
91
|
-
if (a instanceof Date) return b instanceof Date;
|
|
92
|
-
if (a && typeof a === "object" && b && typeof b === "object") return true;
|
|
93
|
-
return typeof a === typeof b;
|
|
94
|
-
}
|
|
95
|
-
function applyJsonPatch(original, patches) {
|
|
96
|
-
const rootReplacement = patches.find((patch) => patch.path === "" && patch.op === "replace");
|
|
97
|
-
if (rootReplacement) {
|
|
98
|
-
return rootReplacement.value;
|
|
99
|
-
}
|
|
100
|
-
const nonRootPatches = patches.filter((patch) => patch.path !== "");
|
|
101
|
-
if (nonRootPatches.length === 0) {
|
|
102
|
-
return original;
|
|
103
|
-
}
|
|
104
|
-
return apply(original, nonRootPatches);
|
|
105
|
-
}
|
|
106
|
-
function isNullOrEmpty(patches) {
|
|
107
|
-
return !patches || patches.length === 0;
|
|
108
|
-
}
|
|
109
|
-
function hasPatches(patches) {
|
|
110
|
-
return patches.length > 0;
|
|
111
|
-
}
|
|
112
|
-
function deepClone(obj) {
|
|
113
|
-
if (typeof structuredClone === "function") {
|
|
114
|
-
return structuredClone(obj);
|
|
115
|
-
}
|
|
116
|
-
return JSON.parse(JSON.stringify(obj));
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export { applyJsonPatch, deepClone, generateJsonPatch, hasPatches, isNullOrEmpty };
|
|
1
|
+
export { applyJsonPatch, deepClone, generateJsonPatch, hasPatches, isNullOrEmpty } from '../chunk-OEDAX6WN.js';
|
|
120
2
|
//# sourceMappingURL=index.js.map
|
|
121
3
|
//# sourceMappingURL=index.js.map
|
package/dist/patch/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/patch/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * RFC 6902 JSON Patch helpers shared by the EthisysCore monolith and plugins.\n *\n * The wire shape (`JsonPatchOperation`) matches the backend SDK's\n * `EthisysCore.Plugin.Sdk.JsonPatchOperation` record, so a patch produced here by\n * {@link generateJsonPatch} is applied by `IEntityPatchService` on the server.\n */\nimport { apply, create } from \"mutative\";\n\n/** JSON Patch operation type. */\nexport type PatchOperation = \"add\" | \"remove\" | \"replace\";\n\n/**\n * A single JSON Patch operation (RFC 6902 compatible; mutative's format with a string path).\n */\nexport interface JsonPatchOperation {\n /** The operation to be performed. */\n op: PatchOperation;\n /** JSON Pointer path (RFC 6902 format). */\n path: string;\n /** The value for add/replace operations. */\n value?: unknown;\n /** Source pointer for move/copy operations (backend `From`); unused by the generate/apply helpers. */\n from?: string;\n}\n\n/** Array of JSON Patch operations. */\nexport type JsonPatch = JsonPatchOperation[];\n\n/**\n * Generates JSON Patch operations (RFC 6902).\n * Strictly typed, handles Date objects, and ignores key order.\n */\nexport function generateJsonPatch<T>(original: T, modified: T): JsonPatch {\n // If types are completely different (e.g. Array vs Object), strict replacement.\n if (!isSameType(original, modified)) {\n // If both are strictly equal (e.g. both null), returns empty\n if (original === modified) return [];\n // If different, replace root\n return [{ op: \"replace\", path: \"\", value: modified }];\n }\n\n const [, patches] = create(\n original,\n (draft) => {\n // We can safely cast draft to unknown here because we control the recursion\n applyChanges(draft, modified);\n },\n {\n enablePatches: {\n pathAsArray: false,\n },\n },\n );\n\n return patches.map(\n (patch): JsonPatchOperation => ({\n op: patch.op as \"add\" | \"remove\" | \"replace\",\n path: patch.path as string,\n // explicitly check for value existence to satisfy strict null checks if needed\n ...(patch.op !== \"remove\" ? { value: patch.value } : {}),\n }),\n );\n}\n\n/**\n * Recursively applies changes to the Mutative draft.\n * Uses 'unknown' and Type Guards instead of 'any'.\n */\nfunction applyChanges(draft: unknown, modified: unknown): void {\n // This handles Primitives, Dates, and identical References early\n if (isDeepEqual(draft, modified)) {\n return;\n }\n\n // We explicitly check isArray to safely access .length and .push\n if (Array.isArray(draft) && Array.isArray(modified)) {\n const draftArray = draft as Array<unknown>;\n // Clear and Replace approach (safer for DTOs than diffing indices)\n draftArray.length = 0;\n draftArray.push(...modified);\n return;\n }\n\n // We use type guards to ensure we can treat them as dictionaries\n if (isPlainObject(draft) && isPlainObject(modified)) {\n const draftObj = draft;\n const modifiedObj = modified;\n\n // A. Remove keys that don't exist in modified\n for (const key of Object.keys(draftObj)) {\n if (!Object.prototype.hasOwnProperty.call(modifiedObj, key)) {\n delete draftObj[key];\n }\n }\n\n // B. Add or update top-level keys. We deliberately do NOT recurse into nested objects: the backend\n // patch applier (IEntityPatchService) supports only top-level property operations, so a changed\n // nested object is replaced wholesale (path \"/key\"), never diffed into \"/key/child\" paths that the\n // server would reject.\n for (const key of Object.keys(modifiedObj)) {\n if (!isDeepEqual(draftObj[key], modifiedObj[key])) {\n draftObj[key] = modifiedObj[key];\n }\n }\n return;\n }\n}\n\n/**\n * Handles: Primitives, Arrays, Dates, Objects (order-independent), null/undefined\n */\nfunction isDeepEqual(a: unknown, b: unknown): boolean {\n // Strict Reference & Primitive Check\n if (a === b) return true;\n\n // Null/Undefined Checks\n if (a === null || a === undefined || b === null || b === undefined) {\n return a === b;\n }\n\n // Type Mismatch\n if (typeof a !== typeof b) return false;\n\n // Date Objects\n if (a instanceof Date && b instanceof Date) {\n return a.getTime() === b.getTime();\n }\n\n // Arrays\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!isDeepEqual(a[i], b[i])) return false;\n }\n return true;\n }\n\n // Objects\n if (typeof a === \"object\" && typeof b === \"object\") {\n // Treat as Record<string, unknown> safely\n const objA = a as Record<string, unknown>;\n const objB = b as Record<string, unknown>;\n\n const keysA = Object.keys(objA);\n const keysB = Object.keys(objB);\n\n if (keysA.length !== keysB.length) return false;\n\n for (const key of keysA) {\n if (!Object.prototype.hasOwnProperty.call(objB, key)) return false;\n if (!isDeepEqual(objA[key], objB[key])) return false;\n }\n return true;\n }\n\n return false;\n}\n\n/**\n * Type Guard: Checks if value is a plain object (not null, array, or Date)\n * Allows safe casting to Record<string, unknown>\n */\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return (\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value) &&\n !(value instanceof Date)\n );\n}\n\n/**\n * Helper: Checks if two values share a high-level type category\n */\nfunction isSameType(a: unknown, b: unknown): boolean {\n if (Array.isArray(a)) return Array.isArray(b);\n if (Array.isArray(b)) return false;\n if (a instanceof Date) return b instanceof Date;\n if (a && typeof a === \"object\" && b && typeof b === \"object\") return true;\n return typeof a === typeof b;\n}\n\n/**\n * Applies JSON Patch operations to an object using Mutative.\n * @param original - The original object\n * @param patches - Array of JSON Patch operations to apply\n * @returns The patched object (immutable)\n */\nexport function applyJsonPatch<T>(original: T, patches: JsonPatch): T {\n // Check for root replacement first - if found, it should be the only operation\n const rootReplacement = patches.find((patch) => patch.path === \"\" && patch.op === \"replace\");\n if (rootReplacement) {\n return rootReplacement.value as T;\n }\n\n // Filter out any root operations that might interfere and apply the rest\n const nonRootPatches = patches.filter((patch) => patch.path !== \"\");\n if (nonRootPatches.length === 0) {\n return original;\n }\n\n // Mutative's apply function expects patches in RFC 6902 format, which is what we have\n return apply(original as Record<string, unknown>, nonRootPatches) as T;\n}\n\n/**\n * Checks if a patch array is empty (null, undefined, or empty array).\n * @param patches - Array of patch operations or null/undefined\n * @returns True if patches is null, undefined, or empty array\n */\nexport function isNullOrEmpty(patches: JsonPatch | null | undefined): boolean {\n return !patches || patches.length === 0;\n}\n\n/**\n * Checks if a patch array has any operations.\n * @param patches - Array of patch operations\n * @returns True if patches array has operations, false if empty\n */\nexport function hasPatches(patches: JsonPatch): boolean {\n return patches.length > 0;\n}\n\n/**\n * Creates a deep clone of an object.\n * Useful for creating the \"modified\" version before generating patches.\n * @param obj - Object to clone\n * @returns Deep cloned object\n */\nexport function deepClone<T>(obj: T): T {\n // structuredClone preserves Dates (and undefined); fall back to JSON clone on legacy runtimes.\n if (typeof structuredClone === \"function\") {\n return structuredClone(obj);\n }\n return JSON.parse(JSON.stringify(obj));\n}\n"]}
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|