Skip to content

Commit c24e394

Browse files
committed
Replace lodash with es-toolkit
Change-type: patch
1 parent da8c070 commit c24e394

File tree

7 files changed

+274
-257
lines changed

7 files changed

+274
-257
lines changed

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,9 @@
2121
"dependencies": {
2222
"@balena/abstract-sql-compiler": "^11.2.0",
2323
"@balena/odata-parser": "^4.2.6",
24-
"@types/lodash": "^4.17.20",
2524
"@types/memoizee": "^0.4.12",
2625
"@types/string-hash": "^1.1.3",
27-
"lodash": "^4.17.21",
26+
"es-toolkit": "^1.43.0",
2827
"memoizee": "^0.4.17",
2928
"string-hash": "^1.1.3"
3029
},

src/odata-to-abstract-sql.ts

Lines changed: 103 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
import _ from 'lodash';
1+
import {
2+
differenceWith,
3+
isEqual,
4+
capitalize,
5+
pick,
6+
omitBy,
7+
isNil,
8+
} from 'es-toolkit';
9+
import { get } from 'es-toolkit/compat';
210
import memoize from 'memoizee';
311
import stringHash from 'string-hash';
412
import {
@@ -433,20 +441,21 @@ export class OData2AbstractSQL {
433441
if (minimizeAliases === false && aliasLength <= MAX_ALIAS_LENGTH) {
434442
return alias;
435443
}
436-
alias = _(alias.toLowerCase())
444+
alias = alias
445+
.toLowerCase()
437446
.split('.')
438447
.map((part) => {
439448
if (minimizeAliases === false && aliasLength <= MAX_ALIAS_LENGTH) {
440449
return part;
441450
}
442451
aliasLength -= part.length;
443-
const shortAlias = _(part)
452+
const shortAlias = part
444453
.split('-')
445454
.map((part2) => {
446-
part2 = _(part2)
455+
part2 = part2
447456
.split(' ')
448457
.map((part3) => {
449-
part3 = _(part3)
458+
part3 = part3
450459
.split('$')
451460
.map((part4) => shortAliases[part4] ?? part4)
452461
.join('$');
@@ -500,7 +509,7 @@ export class OData2AbstractSQL {
500509
this.reset();
501510
this.bindVarsLength = bindVarsLength;
502511
let tree: AbstractSqlQuery;
503-
if (_.isEmpty(path)) {
512+
if (Object.keys(path).length === 0) {
504513
tree = ['$serviceroot'];
505514
} else if (['$metadata', '$serviceroot'].includes(path.resource)) {
506515
tree = [path.resource];
@@ -908,7 +917,7 @@ export class OData2AbstractSQL {
908917
((index.type === 'UNIQUE' && index.predicate == null) ||
909918
index.type === 'PRIMARY KEY') &&
910919
sqlFieldNames.length === index.fields.length &&
911-
_.isEqual(index.fields.slice().sort(), sqlFieldNames)
920+
isEqual(index.fields.slice().sort(), sqlFieldNames)
912921
);
913922
})
914923
) {
@@ -1073,12 +1082,11 @@ export class OData2AbstractSQL {
10731082
`Could not resolve relationship for '${resourceName}'`,
10741083
);
10751084
}
1076-
const relationshipPath = _(relationship)
1085+
const relationshipPath = relationship
10771086
.split('__')
10781087
.map(odataNameToSqlName)
1079-
.flatMap((sqlName) => this.Synonym(sqlName).split('-'))
1080-
.value();
1081-
const relationshipMapping = _.get(resourceRelations, relationshipPath);
1088+
.flatMap((sqlName) => this.Synonym(sqlName).split('-'));
1089+
const relationshipMapping = get(resourceRelations, relationshipPath);
10821090
if (!relationshipMapping?.$) {
10831091
throw new SyntaxError(
10841092
`Could not resolve relationship mapping from '${resourceName}' to '${relationshipPath}'`,
@@ -1110,10 +1118,10 @@ export class OData2AbstractSQL {
11101118
sqlNameToODataName(field.fieldName),
11111119
]);
11121120
}
1113-
const fields = _.differenceWith(
1121+
const fields = differenceWith(
11141122
odataFieldNames,
11151123
query.select,
1116-
(a, b) => a[1] === _.last(b),
1124+
(a, b) => a[1] === b.at(-1),
11171125
).map((args) => this.AliasSelectField(...args));
11181126
query.select = query.select.concat(fields);
11191127
}
@@ -1195,7 +1203,7 @@ export class OData2AbstractSQL {
11951203
case 'and':
11961204
case 'or':
11971205
return [
1198-
_.capitalize(type),
1206+
capitalize(type),
11991207
...rest.map((v) => this.BooleanMatch(v)),
12001208
];
12011209
case 'not': {
@@ -1270,7 +1278,7 @@ export class OData2AbstractSQL {
12701278
throw new SyntaxError('Unexpected function name');
12711279
}
12721280
const args = properties.args.map((v: any) => this.Operand(v));
1273-
return [sqlName ?? (_.capitalize(name) as Capitalize<T>), ...args];
1281+
return [sqlName ?? (capitalize(name) as Capitalize<T>), ...args];
12741282
}
12751283
Operand(
12761284
match: any,
@@ -1558,7 +1566,7 @@ export class OData2AbstractSQL {
15581566
DateMatch(match: any, optional: true): StrictDateTypeNodes | undefined;
15591567
DateMatch(match: any): StrictDateTypeNodes;
15601568
DateMatch(match: any, optional = false): StrictDateTypeNodes | undefined {
1561-
if (_.isDate(match)) {
1569+
if (match instanceof Date) {
15621570
return ['Date', match];
15631571
} else if (Array.isArray(match) && match[0] === 'call') {
15641572
const { method } = match[1];
@@ -1592,11 +1600,17 @@ export class OData2AbstractSQL {
15921600
if (match == null || typeof match !== 'object') {
15931601
return;
15941602
}
1595-
const duration = _(match)
1596-
.pick('negative', 'day', 'hour', 'minute', 'second')
1597-
.omitBy(_.isNil)
1598-
.value();
1599-
if (_(duration).omit('negative').isEmpty()) {
1603+
const picked = pick(match, ['negative', 'day', 'hour', 'minute', 'second']);
1604+
1605+
const duration = omitBy(picked, isNil);
1606+
1607+
// Destructure 'negative' out, collect the 'rest' into a new object
1608+
const { negative, ...rest } = duration;
1609+
1610+
// Check if 'rest' is empty
1611+
const isEmptyDuration = Object.keys(rest).length === 0;
1612+
1613+
if (isEmptyDuration) {
16001614
return;
16011615
}
16021616
return ['Duration', duration];
@@ -1786,7 +1800,7 @@ export class OData2AbstractSQL {
17861800
});
17871801
if (existingJoin != null) {
17881802
const existingJoinPredicate = existingJoin[2]?.[1];
1789-
if (!_.isEqual(navigation.where, existingJoinPredicate)) {
1803+
if (!isEqual(navigation.where, existingJoinPredicate)) {
17901804
// When we reach this point we have found an already existing JOIN with the
17911805
// same alias as the one we just created but different ON predicate.
17921806
// TODO: In this case we need to be able to generate a new alias for the newly JOINed resource.
@@ -1837,7 +1851,7 @@ export class OData2AbstractSQL {
18371851
this.defaultResource = undefined;
18381852
}
18391853
Synonym(sqlName: string) {
1840-
return _(sqlName)
1854+
return sqlName
18411855
.split('-')
18421856
.map((namePart) => {
18431857
const synonym = this.clientModel.synonyms[namePart];
@@ -1894,7 +1908,7 @@ export class OData2AbstractSQL {
18941908
extraBindVars: ODataBinds,
18951909
bindVarsLength: number,
18961910
): ModernDefinition {
1897-
const rewrittenDefinition = _.cloneDeep(
1911+
const rewrittenDefinition = structuredClone(
18981912
convertToModernDefinition(definition),
18991913
);
19001914
rewriteBinds(rewrittenDefinition, extraBindVars, bindVarsLength);
@@ -1953,12 +1967,12 @@ const addAliases = (
19531967
// We then traverse any non suffix nodes to make sure those parts get their short versions. This should only happen
19541968
// in the case of a '' suffix because it means that the other parts are supersets, eg `of`/`often` and must be added
19551969
// with a longer short alias, eg `of`/`oft`
1956-
_.forEach(node, (value, key) => {
1970+
for (const [key, value] of Object.entries(node)) {
19571971
if (key === '$suffix') {
1958-
return;
1972+
continue; // Skips this iteration
19591973
}
19601974
traverseNodes(str + key, value);
1961-
});
1975+
}
19621976
};
19631977

19641978
lowerCaseAliasParts.sort().forEach(buildTrie);
@@ -1988,69 +2002,80 @@ const getRelationships = (
19882002
const generateShortAliases = (clientModel: RequiredAbstractSqlModelSubset) => {
19892003
const shortAliases: Dictionary<string> = {};
19902004

1991-
const aliasParts = _(getRelationships(clientModel.relationships))
1992-
.union(Object.keys(clientModel.synonyms))
1993-
.reject((key) => key === '$')
1994-
.map((alias) => alias.toLowerCase())
1995-
.value();
2005+
// 1. Safely get your source arrays (using ?? [] to handle nulls like Lodash would)
2006+
const rels = getRelationships(clientModel.relationships) ?? [];
2007+
const synonyms = Object.keys(clientModel.synonyms ?? {});
2008+
2009+
// 2. Create the Union (Spread into a Set to remove duplicates, then back to Array)
2010+
const uniqueKeys = Array.from(new Set([...rels, ...synonyms]));
2011+
2012+
// 3. Filter and Map
2013+
const aliasParts = uniqueKeys
2014+
.filter((key) => key !== '$') // Native replacement for .reject()
2015+
.map((alias) => alias.toLowerCase());
19962016

19972017
// Add the first level of aliases, of names split by `-`/` `/`$`, for short aliases on a word by word basis
1998-
let origAliasParts = _(aliasParts)
1999-
.flatMap((aliasPart) => aliasPart.split(/-| |\$/))
2000-
.uniq()
2001-
.value();
2018+
let origAliasParts = Array.from(
2019+
new Set(aliasParts.flatMap((aliasPart) => aliasPart.split(/-| |\$/))),
2020+
);
20022021
addAliases(shortAliases, origAliasParts);
20032022

20042023
// Add aliases for $ containing names
2005-
origAliasParts = _(aliasParts)
2006-
.flatMap((aliasPart) => aliasPart.split(/-| /))
2007-
.filter((aliasPart) => aliasPart.includes('$'))
2008-
.flatMap((aliasPart) => {
2009-
shortAliases[aliasPart] = aliasPart
2010-
.split('$')
2011-
.map((part) => shortAliases[part])
2012-
.join('$');
2013-
return [];
2014-
})
2015-
.uniq()
2016-
.value();
2024+
origAliasParts = Array.from(
2025+
new Set(
2026+
aliasParts
2027+
.flatMap((aliasPart) => aliasPart.split(/-| /))
2028+
.filter((aliasPart) => aliasPart.includes('$'))
2029+
.flatMap((aliasPart) => {
2030+
shortAliases[aliasPart] = aliasPart
2031+
.split('$')
2032+
.map((part) => shortAliases[part])
2033+
.join('$');
2034+
return [];
2035+
}),
2036+
),
2037+
);
20172038
addAliases(shortAliases, origAliasParts);
20182039

20192040
// Add the second level of aliases, of names that include a ` `, split by `-`, for short aliases on a verb/term basis
2020-
origAliasParts = _(aliasParts)
2021-
.flatMap((aliasPart) => aliasPart.split('-'))
2022-
.filter((aliasPart) => aliasPart.includes(' '))
2023-
.flatMap((aliasPart) =>
2024-
// Generate the 2nd level aliases for both the short and long versions
2025-
[
2026-
aliasPart,
2027-
aliasPart
2028-
.split(' ')
2029-
.map((part) => shortAliases[part])
2030-
.join(' '),
2031-
],
2032-
)
2033-
.uniq()
2034-
.value();
2041+
origAliasParts = Array.from(
2042+
new Set(
2043+
aliasParts
2044+
.flatMap((aliasPart) => aliasPart.split('-'))
2045+
.filter((aliasPart) => aliasPart.includes(' '))
2046+
.flatMap((aliasPart) =>
2047+
// Generate the 2nd level aliases for both the short and long versions
2048+
[
2049+
aliasPart,
2050+
aliasPart
2051+
.split(' ')
2052+
.map((part) => shortAliases[part])
2053+
.join(' '),
2054+
],
2055+
),
2056+
),
2057+
);
20352058

20362059
addAliases(shortAliases, origAliasParts);
20372060

20382061
// Add the third level of aliases, of names that include a `-`, for short aliases on a fact type basis
2039-
origAliasParts = _(aliasParts)
2040-
.filter((aliasPart) => aliasPart.includes('-'))
2041-
.flatMap((aliasPart) =>
2042-
// Generate the 3rd level aliases for both the short and long versions
2043-
2044-
[
2045-
aliasPart,
2046-
aliasPart
2047-
.split('-')
2048-
.map((part) => shortAliases[part])
2049-
.join('-'),
2050-
],
2051-
)
2052-
.uniq()
2053-
.value();
2062+
origAliasParts = Array.from(
2063+
new Set(
2064+
aliasParts
2065+
.filter((aliasPart) => aliasPart.includes('-'))
2066+
.flatMap((aliasPart) =>
2067+
// Generate the 3rd level aliases for both the short and long versions
2068+
2069+
[
2070+
aliasPart,
2071+
aliasPart
2072+
.split('-')
2073+
.map((part) => shortAliases[part])
2074+
.join('-'),
2075+
],
2076+
),
2077+
),
2078+
);
20542079

20552080
addAliases(shortAliases, origAliasParts);
20562081

test/chai-sql.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import _ from 'lodash';
1+
import { get } from 'es-toolkit/compat';
22
import * as chai from 'chai';
33
import chaiThings from 'chai-things';
44
import fs from 'node:fs';
@@ -205,7 +205,7 @@ export function operandToAbstractSQLFactory(
205205
binds.push(['Number', operand]);
206206
return ['Bind', binds.length - 1];
207207
}
208-
if (_.isDate(operand)) {
208+
if (operand instanceof Date) {
209209
binds.push(['Date', operand]);
210210
return ['Bind', binds.length - 1];
211211
}
@@ -238,11 +238,11 @@ export function operandToAbstractSQLFactory(
238238
const fieldParts = operand.split('/');
239239
if (fieldParts.length > 1) {
240240
let alias = parentAlias;
241-
let previousResource = _(parentAlias).split('.').last()!;
241+
let previousResource = parentAlias.split('.').at(-1)!;
242242
for (const resourceName of fieldParts.slice(0, -1)) {
243243
const sqlName = odataNameToSqlName(resourceName);
244244
const sqlNameParts = sqlName.split('-');
245-
mapping = _.get(
245+
mapping = get(
246246
clientModel.relationships[previousResource],
247247
sqlNameParts,
248248
).$;
@@ -255,7 +255,7 @@ export function operandToAbstractSQLFactory(
255255
}
256256
previousResource = refTable;
257257
}
258-
mapping = [alias, _.last(fieldParts)];
258+
mapping = [alias, fieldParts.at(-1)];
259259
} else {
260260
mapping = [resource, odataNameToSqlName(operand)];
261261
}
@@ -289,7 +289,7 @@ export const operandToOData = function (operand): string {
289289
if (operand.odata != null) {
290290
return operand.odata;
291291
}
292-
if (_.isDate(operand)) {
292+
if (operand instanceof Date) {
293293
return "datetime'" + encodeURIComponent(operand.toISOString()) + "'";
294294
}
295295
if (Array.isArray(operand)) {
@@ -369,7 +369,7 @@ export const aliasFields = (function () {
369369
} else {
370370
verb = '';
371371
}
372-
return fields.map(_.partial(aliasField, resourceAlias, verb));
372+
return fields.map((field) => aliasField(resourceAlias, verb, field));
373373
};
374374
})();
375375

0 commit comments

Comments
 (0)