ansuko 1.1.1

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.
@@ -0,0 +1,196 @@
1
+ import _ from "lodash";
2
+ /**
3
+ * Checks if the value is a non-empty string. null/undefined/empty string -> false.
4
+ * @param str - Value to check
5
+ * @returns true if non-empty string
6
+ * @example isValidStr('hello') // true
7
+ * @example isValidStr('') // false
8
+ * @category Type Guards
9
+ */
10
+ declare const isValidStr: (str: unknown) => str is string;
11
+ /**
12
+ * Returns value or a default. Detects functions and Promises automatically.
13
+ * @param value - Value or thunk
14
+ * @param els - Default value or thunk
15
+ * @returns Value or default
16
+ * @example valueOr('v','d') // 'v'
17
+ * @example valueOr(asyncFetch(), 'fallback') // Promise resolves to fetched or fallback
18
+ * @example await valueOr(() => cache.get(id), () => api.fetch(id))
19
+ * @category Promise Utilities
20
+ */
21
+ type MaybePromise<T> = T | Promise<T>;
22
+ type MaybeFunction<T> = T | (() => MaybePromise<T>);
23
+ declare const valueOr: <T, E>(value: MaybeFunction<MaybePromise<T | null | undefined>>, els?: E | (() => MaybePromise<E>)) => MaybePromise<T | E | undefined | null>;
24
+ declare const emptyOr: <T, E>(value: MaybeFunction<MaybePromise<T | null | undefined>>, els?: E | ((val: T | null | undefined) => MaybePromise<E>)) => MaybePromise<T | E | undefined | null>;
25
+ /**
26
+ * Checks emptiness with intuitive rules: numbers and booleans are NOT empty.
27
+ * @param value - Value to check
28
+ * @returns true if empty
29
+ * @example isEmpty(0) // false
30
+ * @example isEmpty([]) // true
31
+ * @category Core Functions
32
+ */
33
+ declare const isEmpty: (value: unknown) => boolean;
34
+ /**
35
+ * Converts a value to number (full-width and comma aware). Returns null when invalid.
36
+ * @param value - Value to convert
37
+ * @returns number or null
38
+ * @example toNumber('1,234.5') // 1234.5
39
+ * @example toNumber('123') // 123
40
+ * @example toNumber('abc') // null
41
+ * @category Core Functions
42
+ */
43
+ declare const toNumber: (value: unknown) => number | null;
44
+ /**
45
+ * Converts various inputs to boolean. Numbers: 0 -> false, non-zero -> true.
46
+ * Strings: 'true'|'t'|'y'|'yes'|'ok' -> true; 'false'|'f'|'n'|'no'|'ng' -> false.
47
+ * If a function or Promise is provided, it will be resolved recursively.
48
+ * Returns the `undetected` fallback when the value cannot be interpreted (default null).
49
+ * @param value - Value, thunk, or Promise
50
+ * @param undetected - Fallback when value cannot be interpreted (default null)
51
+ * @returns boolean or null (sync or Promise)
52
+ * @category Core Functions
53
+ */
54
+ declare const toBool: (value: unknown, undetected?: boolean | null) => MaybePromise<boolean | null>;
55
+ /**
56
+ * Safely converts to boolean; numbers use zero check; otherwise returns the provided default.
57
+ * @param value - Value
58
+ * @param defaultValue - Default when value is not number/boolean (false)
59
+ * @returns boolean
60
+ * @example boolIf(1) // true
61
+ * @example boolIf('x', true) // true
62
+ * @category Core Functions
63
+ */
64
+ declare const boolIf: (value: unknown, defaultValue?: boolean) => boolean;
65
+ /**
66
+ * Runs a function after N frames using requestAnimationFrame.
67
+ * @param func - Function to run
68
+ * @param frameCount - Frames to wait (default 0)
69
+ * @example waited(() => doMeasure(), 1)
70
+ * @example waited(startAnimation, 2)
71
+ * @category Core Functions
72
+ */
73
+ declare const waited: (func: () => void, frameCount?: number) => void;
74
+ /**
75
+ * Compares two values; if equal returns the value, otherwise returns the default.
76
+ * null and undefined are considered equal. Promise-aware.
77
+ * @param param1 - First value (or thunk/promise)
78
+ * @param param2 - Second value (or thunk/promise)
79
+ * @param els - Default value (or thunk)
80
+ * @returns Value or default (sync or Promise)
81
+ * @example equalsOr('a','a','d') // 'a'
82
+ * @example await equalsOr(fetchStatus(),'ok','ng')
83
+ * @example equalsOr(null, undefined, 'd') // null
84
+ * @category Promise Utilities
85
+ */
86
+ declare const equalsOr: <T, E>(...args: any[]) => MaybePromise<T | E | null>;
87
+ /**
88
+ * Safely parses JSON/JSON5; returns null on error; passes objects through.
89
+ * @param str - String or object
90
+ * @returns Parsed object or null
91
+ * @example parseJSON('{"a":1}') // {a:1}
92
+ * @example parseJSON('{a:1}') // {a:1} (JSON5)
93
+ * @category Conversion
94
+ */
95
+ declare const parseJSON: <T = any>(str: string | object) => T | null;
96
+ /**
97
+ * Stringifies objects/arrays; returns null for strings or numbers.
98
+ * @param obj - Target object
99
+ * @returns JSON string or null
100
+ * @example jsonStringify({a:1}) // '{"a":1}'
101
+ * @example jsonStringify('{a:1}') // '{"a":1}' (normalize)
102
+ * @category Conversion
103
+ */
104
+ declare const jsonStringify: <T = any>(obj: T) => string | null;
105
+ /**
106
+ * Casts value to array; null/undefined become [] (not [null]).
107
+ * @param value - Value
108
+ * @returns Array
109
+ * @example castArray(1) // [1]
110
+ * @example castArray(null) // []
111
+ * @category Array Utilities
112
+ */
113
+ declare const castArray: <T>(value: T | T[] | null | undefined) => T[];
114
+ /**
115
+ * Computes differences between two objects. Supports deep paths. When `keyExcludes` is true,
116
+ * the provided keys are excluded (top-level only) from diffing.
117
+ * @param sourceValue - Source object
118
+ * @param currentValue - Current object
119
+ * @param keys - Keys to check (deep paths allowed)
120
+ * @param options - { keyExcludes } to treat keys as excludes (top-level only)
121
+ * @param finallyCallback - Called after diff computation (always if notEmptyCallback returned truthy)
122
+ * @param notEmptyCallback - Called only when diff is not empty
123
+ * @returns Diff object
124
+ * @example changes(o1,o2,['name','profile.bio'])
125
+ * @example changes(orig, curr, ['id','created_at'], { keyExcludes:true })
126
+ * @example await changes(a,b,['x'],{}, async diff => save(diff))
127
+ * @example changes(
128
+ * { profile:{ tags:['a','b'] } },
129
+ * { profile:{ tags:['a','c'] } },
130
+ * ['profile.tags[1]']
131
+ * ) // => { profile:{ tags:{ 1:'c' } } }
132
+ * @category Object Utilities
133
+ */
134
+ declare const changes: <T extends Record<string, any>, E extends Record<string, any>>(sourceValue: T, currentValue: E, keys: string[], options?: ChangesOptions, finallyCallback?: ChangesAfterFinallyCallback<Record<string, any>>, notEmptyCallback?: ChangesAfterCallback<Record<string, any>>) => Record<string, any>;
135
+ /**
136
+ * Returns nesting depth of arrays. Non-array: 0; empty array: 1. Uses minimum depth for mixed nesting.
137
+ * @param ary - Array
138
+ * @returns Depth
139
+ * @example arrayDepth([1]) // 1
140
+ * @example arrayDepth([[1],[2]]) // 2
141
+ * @example arrayDepth([[[1]]]) // 3
142
+ * @example arrayDepth([[1,2], [[3],[4,5]]]) // 2 (uses minimum depth)
143
+ * @example arrayDepth([]) // 1
144
+ * @example arrayDepth('not array') // 0
145
+ * @category Array Utilities
146
+ */
147
+ declare const arrayDepth: (ary: unknown) => number;
148
+ /**
149
+ * Extends ansuko with a plugin and returns the augmented instance.
150
+ * @param plugin - Plugin function
151
+ * @returns Extended instance
152
+ * @example const extended = _.extend(jaPlugin)
153
+ * @category Core Functions
154
+ */
155
+ declare const extend: <T>(this: any, plugin: (a: any) => T) => any & T;
156
+ export type ChangesOptions = {
157
+ keyExcludes?: boolean;
158
+ };
159
+ type ChangesAfterCallback<T> = (value: T) => any | Promise<any>;
160
+ type ChangesAfterFinallyCallback<T> = (value: T, res: any) => any | Promise<any>;
161
+ export interface AnsukoType extends Omit<_.LoDashStatic, "castArray" | "isEmpty" | "toNumber"> {
162
+ extend: typeof extend;
163
+ isValidStr: typeof isValidStr;
164
+ valueOr: typeof valueOr;
165
+ emptyOr: typeof emptyOr;
166
+ isEmpty: typeof isEmpty;
167
+ toNumber: typeof toNumber;
168
+ toBool: typeof toBool;
169
+ boolIf: typeof boolIf;
170
+ waited: typeof waited;
171
+ equalsOr: typeof equalsOr;
172
+ parseJSON: typeof parseJSON;
173
+ jsonStringify: typeof jsonStringify;
174
+ castArray: typeof castArray;
175
+ changes: typeof changes;
176
+ size: typeof _.size;
177
+ isNil: typeof _.isNil;
178
+ debounce: typeof _.debounce;
179
+ isEqual: typeof _.isEqual;
180
+ isBoolean: typeof _.isBoolean;
181
+ isString: typeof _.isString;
182
+ first: typeof _.first;
183
+ last: typeof _.last;
184
+ uniq: typeof _.uniq;
185
+ has: typeof _.has;
186
+ keys: typeof _.keys;
187
+ values: typeof _.values;
188
+ some: typeof _.some;
189
+ arrayDepth: typeof arrayDepth;
190
+ isEmptyOrg: typeof _.isEmpty;
191
+ toNumberOrg: typeof _.toNumber;
192
+ castArrayOrg: typeof _.castArray;
193
+ }
194
+ declare const _default: AnsukoType;
195
+ export default _default;
196
+ export { isEmpty, toNumber, boolIf, isValidStr, valueOr, equalsOr, waited, parseJSON, jsonStringify, castArray, changes, arrayDepth, };
package/dist/index.js ADDED
@@ -0,0 +1,506 @@
1
+ import _ from "lodash";
2
+ import JSON5 from "json5";
3
+ import { toHalfWidth } from "./util.js";
4
+ /**
5
+ * Checks if the value is a non-empty string. null/undefined/empty string -> false.
6
+ * @param str - Value to check
7
+ * @returns true if non-empty string
8
+ * @example isValidStr('hello') // true
9
+ * @example isValidStr('') // false
10
+ * @category Type Guards
11
+ */
12
+ const isValidStr = (str) => {
13
+ if (_.isNil(str)) {
14
+ return false;
15
+ }
16
+ if (_.isEmpty(str)) {
17
+ return false;
18
+ }
19
+ return typeof str === "string";
20
+ };
21
+ const valueOr = (value, els) => {
22
+ // 関数を解決
23
+ const resolvedValue = typeof value === "function"
24
+ ? value()
25
+ : value;
26
+ // Promiseかチェック
27
+ if (resolvedValue instanceof Promise) {
28
+ return Promise.resolve(resolvedValue).then(res => {
29
+ if (_.isNil(res) || isEmpty(res)) {
30
+ if (typeof els === "function") {
31
+ return els();
32
+ }
33
+ return els;
34
+ }
35
+ return res;
36
+ });
37
+ }
38
+ if (!_.isNil(resolvedValue) && !isEmpty(resolvedValue)) {
39
+ return resolvedValue;
40
+ }
41
+ if (typeof els === "function") {
42
+ return els();
43
+ }
44
+ return els;
45
+ };
46
+ const emptyOr = (value, els) => {
47
+ // 関数を解決
48
+ const resolvedValue = typeof value === "function"
49
+ ? value()
50
+ : value;
51
+ // Promiseかチェック
52
+ if (resolvedValue instanceof Promise) {
53
+ return Promise.resolve(resolvedValue).then(res => {
54
+ if (_.isNil(res) || isEmpty(res)) {
55
+ return null;
56
+ }
57
+ if (typeof els === "function") {
58
+ return els(res);
59
+ }
60
+ return els;
61
+ });
62
+ }
63
+ if (_.isNil(resolvedValue) || isEmpty(resolvedValue)) {
64
+ return null;
65
+ }
66
+ if (typeof els === "function") {
67
+ return els(resolvedValue);
68
+ }
69
+ return els;
70
+ };
71
+ /**
72
+ * Ensures that all given paths exist on the resolved value; otherwise returns a default.
73
+ * Supports functions and Promises.
74
+ * @param value - Object or thunk
75
+ * @param paths - Paths to check
76
+ * @param els - Default value or thunk receiving the resolved value
77
+ * @returns Value or default
78
+ * @example await hasOr(fetchUser(), ['profile.name','id'], null)
79
+ * @example hasOr({a:{b:1}}, 'a.b', {}) // returns original object
80
+ * @category Promise Utilities
81
+ */
82
+ const hasOr = (value, paths, els) => {
83
+ // 関数を解決
84
+ const resolvedValue = typeof value === "function"
85
+ ? value()
86
+ : value;
87
+ const pathArray = Array.isArray(paths) ? paths : [paths];
88
+ // パスが全て存在するかチェック
89
+ const checkPaths = (val) => {
90
+ if (_.isNil(val) || isEmpty(val))
91
+ return false;
92
+ return pathArray.every(path => _.has(val, path));
93
+ };
94
+ // Promiseかチェック
95
+ if (resolvedValue instanceof Promise) {
96
+ return Promise.resolve(resolvedValue).then(res => {
97
+ if (!checkPaths(res)) {
98
+ if (typeof els === "function") {
99
+ return els(res);
100
+ }
101
+ return els;
102
+ }
103
+ return res;
104
+ });
105
+ }
106
+ if (!checkPaths(resolvedValue)) {
107
+ if (typeof els === "function") {
108
+ return els(resolvedValue);
109
+ }
110
+ return els;
111
+ }
112
+ return resolvedValue;
113
+ };
114
+ /**
115
+ * Checks emptiness with intuitive rules: numbers and booleans are NOT empty.
116
+ * @param value - Value to check
117
+ * @returns true if empty
118
+ * @example isEmpty(0) // false
119
+ * @example isEmpty([]) // true
120
+ * @category Core Functions
121
+ */
122
+ const isEmpty = (value) => {
123
+ if (_.isNil(value)) {
124
+ return true;
125
+ }
126
+ if (_.isNumber(value)) {
127
+ return false;
128
+ }
129
+ if (_.isBoolean(value)) {
130
+ return false;
131
+ }
132
+ return _.isEmpty(value);
133
+ };
134
+ /**
135
+ * Converts a value to number (full-width and comma aware). Returns null when invalid.
136
+ * @param value - Value to convert
137
+ * @returns number or null
138
+ * @example toNumber('1,234.5') // 1234.5
139
+ * @example toNumber('123') // 123
140
+ * @example toNumber('abc') // null
141
+ * @category Core Functions
142
+ */
143
+ const toNumber = (value) => {
144
+ if (_.isNil(value)) {
145
+ return null;
146
+ }
147
+ if (_.isNumber(value)) {
148
+ return value;
149
+ }
150
+ if (isEmpty(value)) {
151
+ return null;
152
+ }
153
+ let v = toHalfWidth(value);
154
+ if (typeof v === "string" && v.trim().match(/^[0-9][0-9,.]*$/)) {
155
+ v = _.toNumber(v.trim().replace(/,/g, ""));
156
+ }
157
+ else {
158
+ v = _.toNumber(v);
159
+ }
160
+ return _.isNaN(v) ? null : v;
161
+ };
162
+ /**
163
+ * Converts various inputs to boolean. Numbers: 0 -> false, non-zero -> true.
164
+ * Strings: 'true'|'t'|'y'|'yes'|'ok' -> true; 'false'|'f'|'n'|'no'|'ng' -> false.
165
+ * If a function or Promise is provided, it will be resolved recursively.
166
+ * Returns the `undetected` fallback when the value cannot be interpreted (default null).
167
+ * @param value - Value, thunk, or Promise
168
+ * @param undetected - Fallback when value cannot be interpreted (default null)
169
+ * @returns boolean or null (sync or Promise)
170
+ * @category Core Functions
171
+ */
172
+ const toBool = (value, undetected = null) => {
173
+ if (_.isNil(value)) {
174
+ return false;
175
+ }
176
+ if (isEmpty(value)) {
177
+ return false;
178
+ }
179
+ if (_.isBoolean(value)) {
180
+ return value;
181
+ }
182
+ if (_.isNumber(value)) {
183
+ return value !== 0;
184
+ }
185
+ const n = toNumber(value);
186
+ if (n !== null)
187
+ return !!n;
188
+ if (typeof value === "string") {
189
+ switch (value.toLowerCase()) {
190
+ case "true":
191
+ case "t":
192
+ case "y":
193
+ case "yes":
194
+ case "ok":
195
+ return true;
196
+ case "false":
197
+ case "f":
198
+ case "n":
199
+ case "no":
200
+ case "ng":
201
+ return false;
202
+ default:
203
+ }
204
+ }
205
+ if (typeof value === "function") {
206
+ const r = value();
207
+ if (r instanceof Promise) {
208
+ return r.then(toBool);
209
+ }
210
+ return toBool(r);
211
+ }
212
+ return undetected;
213
+ };
214
+ /**
215
+ * Safely converts to boolean; numbers use zero check; otherwise returns the provided default.
216
+ * @param value - Value
217
+ * @param defaultValue - Default when value is not number/boolean (false)
218
+ * @returns boolean
219
+ * @example boolIf(1) // true
220
+ * @example boolIf('x', true) // true
221
+ * @category Core Functions
222
+ */
223
+ const boolIf = (value, defaultValue = false) => {
224
+ if (_.isBoolean(value)) {
225
+ return value;
226
+ }
227
+ if (_.isNumber(value)) {
228
+ return !!value;
229
+ }
230
+ return defaultValue;
231
+ };
232
+ /**
233
+ * Runs a function after N frames using requestAnimationFrame.
234
+ * @param func - Function to run
235
+ * @param frameCount - Frames to wait (default 0)
236
+ * @example waited(() => doMeasure(), 1)
237
+ * @example waited(startAnimation, 2)
238
+ * @category Core Functions
239
+ */
240
+ const waited = (func, frameCount = 0) => {
241
+ requestAnimationFrame(() => {
242
+ if (frameCount > 0) {
243
+ return waited(func, frameCount - 1);
244
+ }
245
+ func();
246
+ });
247
+ };
248
+ /**
249
+ * Compares two values; if equal returns the value, otherwise returns the default.
250
+ * null and undefined are considered equal. Promise-aware.
251
+ * @param param1 - First value (or thunk/promise)
252
+ * @param param2 - Second value (or thunk/promise)
253
+ * @param els - Default value (or thunk)
254
+ * @returns Value or default (sync or Promise)
255
+ * @example equalsOr('a','a','d') // 'a'
256
+ * @example await equalsOr(fetchStatus(),'ok','ng')
257
+ * @example equalsOr(null, undefined, 'd') // null
258
+ * @category Promise Utilities
259
+ */
260
+ const equalsOr = (...args) => {
261
+ if (args.length === 2) {
262
+ return valueOr(args[0], args[1]);
263
+ }
264
+ const [param1, param2, els] = args;
265
+ // 関数を解決するヘルパー
266
+ const resolveIfFunction = (val) => {
267
+ return typeof val === "function" ? val() : val;
268
+ };
269
+ // Promiseが含まれているかチェック
270
+ const p1 = resolveIfFunction(param1);
271
+ const p2 = resolveIfFunction(param2);
272
+ const hasPromise = (p1 instanceof Promise) || (p2 instanceof Promise) || (els instanceof Promise);
273
+ if (hasPromise) {
274
+ // Promise処理ブランチ
275
+ return Promise.all([
276
+ Promise.resolve(p1),
277
+ Promise.resolve(p2)
278
+ ]).then(([v1, v2]) => {
279
+ if (_.isNil(v1) && _.isNil(v2)) {
280
+ return null;
281
+ }
282
+ if (_.isEqual(v1, v2)) {
283
+ return v1;
284
+ }
285
+ // elsの解決
286
+ if (typeof els === "function") {
287
+ return els();
288
+ }
289
+ return els;
290
+ });
291
+ }
292
+ else {
293
+ // 同期処理ブランチ
294
+ if (_.isNil(p1) && _.isNil(p2)) {
295
+ return null;
296
+ }
297
+ if (_.isEqual(p1, p2)) {
298
+ return p1;
299
+ }
300
+ // elsの解決
301
+ if (typeof els === "function") {
302
+ return els();
303
+ }
304
+ return els;
305
+ }
306
+ };
307
+ /**
308
+ * Safely parses JSON/JSON5; returns null on error; passes objects through.
309
+ * @param str - String or object
310
+ * @returns Parsed object or null
311
+ * @example parseJSON('{"a":1}') // {a:1}
312
+ * @example parseJSON('{a:1}') // {a:1} (JSON5)
313
+ * @category Conversion
314
+ */
315
+ const parseJSON = (str) => {
316
+ if (_.isNil(str)) {
317
+ return null;
318
+ }
319
+ if (typeof str === "object") {
320
+ return str;
321
+ }
322
+ try {
323
+ return JSON5.parse(str);
324
+ }
325
+ catch {
326
+ return null;
327
+ }
328
+ };
329
+ /**
330
+ * Stringifies objects/arrays; returns null for strings or numbers.
331
+ * @param obj - Target object
332
+ * @returns JSON string or null
333
+ * @example jsonStringify({a:1}) // '{"a":1}'
334
+ * @example jsonStringify('{a:1}') // '{"a":1}' (normalize)
335
+ * @category Conversion
336
+ */
337
+ const jsonStringify = (obj) => {
338
+ if (_.isNil(obj)) {
339
+ return null;
340
+ }
341
+ if (typeof obj === "string") {
342
+ try {
343
+ const j = JSON5.parse(obj);
344
+ return JSON.stringify(j);
345
+ }
346
+ catch {
347
+ return null;
348
+ }
349
+ }
350
+ if (typeof obj === "object") {
351
+ try {
352
+ return JSON.stringify(obj);
353
+ }
354
+ catch {
355
+ return null;
356
+ }
357
+ }
358
+ return null;
359
+ };
360
+ /**
361
+ * Casts value to array; null/undefined become [] (not [null]).
362
+ * @param value - Value
363
+ * @returns Array
364
+ * @example castArray(1) // [1]
365
+ * @example castArray(null) // []
366
+ * @category Array Utilities
367
+ */
368
+ const castArray = (value) => {
369
+ if (_.isNil(value)) {
370
+ return [];
371
+ }
372
+ return _.castArray(value);
373
+ };
374
+ /**
375
+ * Computes differences between two objects. Supports deep paths. When `keyExcludes` is true,
376
+ * the provided keys are excluded (top-level only) from diffing.
377
+ * @param sourceValue - Source object
378
+ * @param currentValue - Current object
379
+ * @param keys - Keys to check (deep paths allowed)
380
+ * @param options - { keyExcludes } to treat keys as excludes (top-level only)
381
+ * @param finallyCallback - Called after diff computation (always if notEmptyCallback returned truthy)
382
+ * @param notEmptyCallback - Called only when diff is not empty
383
+ * @returns Diff object
384
+ * @example changes(o1,o2,['name','profile.bio'])
385
+ * @example changes(orig, curr, ['id','created_at'], { keyExcludes:true })
386
+ * @example await changes(a,b,['x'],{}, async diff => save(diff))
387
+ * @example changes(
388
+ * { profile:{ tags:['a','b'] } },
389
+ * { profile:{ tags:['a','c'] } },
390
+ * ['profile.tags[1]']
391
+ * ) // => { profile:{ tags:{ 1:'c' } } }
392
+ * @category Object Utilities
393
+ */
394
+ const changes = (sourceValue, currentValue, keys, options, finallyCallback, notEmptyCallback) => {
395
+ const diff = {};
396
+ // keyExcludes時にdeep pathが指定されていたら警告
397
+ if (options?.keyExcludes === true) {
398
+ const hasDeepPath = keys.some(k => k.includes('.') || k.includes('['));
399
+ if (hasDeepPath) {
400
+ console.warn('[ansuko.changes] keyExcludes mode does not support deep paths. ' +
401
+ 'Keys with "." or "[" will be treated as literal property names.');
402
+ }
403
+ }
404
+ const targetKeys = options?.keyExcludes === true
405
+ ? _.difference(_.uniq([...Object.keys(sourceValue), ...Object.keys(currentValue)]), keys)
406
+ : keys;
407
+ for (const key of targetKeys) {
408
+ const v1 = options?.keyExcludes === true ? sourceValue[key] : _.get(sourceValue, key);
409
+ const v2 = options?.keyExcludes === true ? currentValue[key] : _.get(currentValue, key);
410
+ if (_.isNil(v1) && _.isNil(v2))
411
+ continue;
412
+ if (_.isNil(v1) || _.isNil(v2)) {
413
+ if (options?.keyExcludes === true) {
414
+ diff[key] = v2 ?? null;
415
+ }
416
+ else {
417
+ _.set(diff, key, v2 ?? null);
418
+ }
419
+ continue;
420
+ }
421
+ if (!_.isEqual(v1, v2)) {
422
+ if (options?.keyExcludes === true) {
423
+ diff[key] = v2 ?? null;
424
+ }
425
+ else {
426
+ _.set(diff, key, v2 ?? null);
427
+ }
428
+ }
429
+ }
430
+ let notEmptyRes = true;
431
+ if (!isEmpty(diff) && notEmptyCallback) {
432
+ notEmptyRes = notEmptyCallback(diff);
433
+ if (notEmptyRes instanceof Promise) {
434
+ return notEmptyRes.then(async (res) => {
435
+ res && await Promise.resolve(finallyCallback?.(diff, res));
436
+ return diff;
437
+ });
438
+ }
439
+ }
440
+ if (finallyCallback && notEmptyRes) {
441
+ const finallyRes = finallyCallback(diff, notEmptyRes);
442
+ if (finallyRes instanceof Promise) {
443
+ return finallyRes.then(() => diff);
444
+ }
445
+ }
446
+ return diff;
447
+ };
448
+ /**
449
+ * Returns nesting depth of arrays. Non-array: 0; empty array: 1. Uses minimum depth for mixed nesting.
450
+ * @param ary - Array
451
+ * @returns Depth
452
+ * @example arrayDepth([1]) // 1
453
+ * @example arrayDepth([[1],[2]]) // 2
454
+ * @example arrayDepth([[[1]]]) // 3
455
+ * @example arrayDepth([[1,2], [[3],[4,5]]]) // 2 (uses minimum depth)
456
+ * @example arrayDepth([]) // 1
457
+ * @example arrayDepth('not array') // 0
458
+ * @category Array Utilities
459
+ */
460
+ const arrayDepth = (ary) => {
461
+ if (!Array.isArray(ary)) {
462
+ return 0;
463
+ }
464
+ if (_.size(ary) === 0) {
465
+ return 1;
466
+ }
467
+ return 1 + Math.min(...ary.map(arrayDepth));
468
+ };
469
+ /**
470
+ * Extends ansuko with a plugin and returns the augmented instance.
471
+ * @param plugin - Plugin function
472
+ * @returns Extended instance
473
+ * @example const extended = _.extend(jaPlugin)
474
+ * @category Core Functions
475
+ */
476
+ const extend = function (plugin) {
477
+ if (typeof plugin === 'function') {
478
+ plugin(this); // ← this が undefined になってる?
479
+ }
480
+ return this;
481
+ };
482
+ // Ansuko型へのキャストを外し、より安全な unknown as LoDashStatic に変更
483
+ export default {
484
+ ..._,
485
+ extend,
486
+ isEmptyOrg: _.isEmpty,
487
+ toNumberOrg: _.toNumber,
488
+ castArrayOrg: _.castArray,
489
+ isEmpty,
490
+ toNumber,
491
+ toBool,
492
+ boolIf,
493
+ isValidStr,
494
+ valueOr,
495
+ equalsOr,
496
+ emptyOr,
497
+ hasOr,
498
+ waited,
499
+ parseJSON,
500
+ jsonStringify,
501
+ castArray,
502
+ changes,
503
+ arrayDepth,
504
+ };
505
+ // 個別エクスポートはそのまま
506
+ export { isEmpty, toNumber, boolIf, isValidStr, valueOr, equalsOr, waited, parseJSON, jsonStringify, castArray, changes, arrayDepth, };