@tstdl/base 0.93.28 → 0.93.29

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.
Files changed (40) hide show
  1. package/orm/data-types/tsvector.d.ts +3 -1
  2. package/orm/data-types/tsvector.js +2 -0
  3. package/orm/decorators.d.ts +166 -16
  4. package/orm/decorators.js +46 -0
  5. package/orm/index.d.ts +1 -1
  6. package/orm/index.js +1 -1
  7. package/orm/{query.d.ts → query/base.d.ts} +14 -166
  8. package/orm/{query.js → query/base.js} +3 -2
  9. package/orm/query/index.d.ts +2 -0
  10. package/orm/query/index.js +2 -0
  11. package/orm/query/parade.d.ts +172 -0
  12. package/orm/query/parade.js +4 -0
  13. package/orm/repository.types.d.ts +27 -6
  14. package/orm/schemas/index.d.ts +1 -0
  15. package/orm/schemas/index.js +1 -0
  16. package/orm/schemas/tsvector.d.ts +12 -0
  17. package/orm/schemas/tsvector.js +20 -0
  18. package/orm/server/drizzle/schema-converter.js +100 -19
  19. package/orm/server/query-converter.d.ts +2 -2
  20. package/orm/server/query-converter.js +138 -91
  21. package/orm/server/repository.d.ts +6 -6
  22. package/orm/server/repository.js +34 -23
  23. package/orm/sqls.d.ts +19 -16
  24. package/orm/sqls.js +28 -38
  25. package/package.json +6 -6
  26. package/test/drizzle/0000_natural_cannonball.sql +9 -0
  27. package/test/drizzle/meta/0000_snapshot.json +29 -10
  28. package/test/drizzle/meta/_journal.json +2 -2
  29. package/test/test.model.js +8 -2
  30. package/test1.js +9 -9
  31. package/utils/enum.js +1 -1
  32. package/utils/object/object.d.ts +5 -3
  33. package/utils/object/object.js +17 -7
  34. package/utils/string/casing.d.ts +3 -0
  35. package/utils/string/casing.js +15 -0
  36. package/utils/string/index.d.ts +1 -1
  37. package/utils/string/index.js +1 -1
  38. package/test/drizzle/0000_sturdy_patch.sql +0 -9
  39. package/utils/string/snake-case.d.ts +0 -1
  40. package/utils/string/snake-case.js +0 -4
package/orm/sqls.js CHANGED
@@ -10,6 +10,10 @@ import { sql, Table } from 'drizzle-orm';
10
10
  export const TRANSACTION_TIMESTAMP = sql `transaction_timestamp()`;
11
11
  /** Drizzle SQL helper for generating a random UUID (v4). Returns a Uuid string. */
12
12
  export const RANDOM_UUID_V4 = sql `gen_random_uuid()`;
13
+ export function array(values) {
14
+ const valueString = sql.join(values, sql.raw(', '));
15
+ return sql `ARRAY[${valueString}]`;
16
+ }
13
17
  export function autoAlias(column) {
14
18
  return sql `${column}`.as(`${column.table[Table['Symbol']['Name']]}_${column.name}`);
15
19
  }
@@ -98,11 +102,8 @@ export function greatest(...values) {
98
102
  export function unnest(array) {
99
103
  return sql `unnest(${array})`;
100
104
  }
101
- function getLanguageSql(language) {
102
- return isString(language) ? sql `'${sql.raw(language)}'` : sql `${language}`;
103
- }
104
105
  export function toTsVector(language, text) {
105
- return sql `to_tsvector(${getLanguageSql(language)}, ${text})`;
106
+ return sql `to_tsvector(${language}, ${text})`;
106
107
  }
107
108
  export function tsvectorToArray(tsvector) {
108
109
  return sql `tsvector_to_array(${tsvector})`;
@@ -115,7 +116,7 @@ export function tsvectorToArray(tsvector) {
115
116
  * @returns A Drizzle SQL object representing the tsquery.
116
117
  */
117
118
  export function toTsQuery(language, text) {
118
- return sql `to_tsquery(${getLanguageSql(language)}, ${text})`;
119
+ return sql `to_tsquery(${language}, ${text})`;
119
120
  }
120
121
  /**
121
122
  * Creates a PostgreSQL `plainto_tsquery` function call.
@@ -126,7 +127,7 @@ export function toTsQuery(language, text) {
126
127
  * @returns A Drizzle SQL object representing the tsquery.
127
128
  */
128
129
  export function plainToTsQuery(language, text) {
129
- return sql `plainto_tsquery(${getLanguageSql(language)}, ${text})`;
130
+ return sql `plainto_tsquery(${language}, ${text})`;
130
131
  }
131
132
  /**
132
133
  * Creates a PostgreSQL `phraseto_tsquery` function call.
@@ -136,7 +137,7 @@ export function plainToTsQuery(language, text) {
136
137
  * @returns A Drizzle SQL object representing the tsquery.
137
138
  */
138
139
  export function phraseToTsQuery(language, text) {
139
- return sql `phraseto_tsquery(${getLanguageSql(language)}, ${text})`;
140
+ return sql `phraseto_tsquery(${language}, ${text})`;
140
141
  }
141
142
  /**
142
143
  * Creates a PostgreSQL `websearch_to_tsquery` function call.
@@ -147,7 +148,7 @@ export function phraseToTsQuery(language, text) {
147
148
  * @returns A Drizzle SQL object representing the tsquery.
148
149
  */
149
150
  export function websearchToTsQuery(language, text) {
150
- return sql `websearch_to_tsquery(${getLanguageSql(language)}, ${text})`;
151
+ return sql `websearch_to_tsquery(${language}, ${text})`;
151
152
  }
152
153
  /**
153
154
  * Creates a PostgreSQL `setweight` function call.
@@ -170,7 +171,7 @@ export function tsRankCd(tsvector, tsquery, options) {
170
171
  const parameters = [];
171
172
  if (isDefined(options?.weights)) {
172
173
  const weightParts = options.weights.map((w) => sql.raw(String(w)));
173
- parameters.push(sql `ARRAY[${sql.join(weightParts, sql.raw(', '))}]::real[]`);
174
+ parameters.push(sql `${array(weightParts)}::real[]`);
174
175
  }
175
176
  parameters.push(tsvector, tsquery);
176
177
  if (isDefined(options?.normalization)) {
@@ -195,9 +196,9 @@ export function tsHeadline(language, document, tsquery, options) {
195
196
  const documentSql = isString(document) ? sql `${document}` : document;
196
197
  if (isDefined(options)) {
197
198
  const optionsString = Object.entries(options).map(([key, value]) => `${key[0].toUpperCase()}${key.slice(1)}=${String(value)}`).join(', ');
198
- return sql `ts_headline(${getLanguageSql(language)}, ${documentSql}, ${tsquery}, '${sql.raw(optionsString)}')`;
199
+ return sql `ts_headline(${language}, ${documentSql}, ${tsquery}, '${sql.raw(optionsString)}')`;
199
200
  }
200
- return sql `ts_headline(${getLanguageSql(language)}, ${documentSql}, ${tsquery})`;
201
+ return sql `ts_headline(${language}, ${documentSql}, ${tsquery})`;
201
202
  }
202
203
  /**
203
204
  * Creates a PostgreSQL `similarity` function call (from pg_trgm extension).
@@ -207,9 +208,7 @@ export function tsHeadline(language, document, tsquery, options) {
207
208
  * @returns A Drizzle SQL object representing the similarity score (0 to 1).
208
209
  */
209
210
  export function similarity(left, right) {
210
- const leftSql = isString(left) ? sql `${left}` : left;
211
- const rightSql = isString(right) ? sql `${right}` : right;
212
- return sql `similarity(${leftSql}, ${rightSql})`;
211
+ return sql `similarity(${left}, ${right})`;
213
212
  }
214
213
  /**
215
214
  * Creates a PostgreSQL `word_similarity` function call (from pg_trgm extension).
@@ -219,9 +218,7 @@ export function similarity(left, right) {
219
218
  * @returns A Drizzle SQL object representing the similarity score (0 to 1).
220
219
  */
221
220
  export function wordSimilarity(left, right) {
222
- const leftSql = isString(left) ? sql `${left}` : left;
223
- const rightSql = isString(right) ? sql `${right}` : right;
224
- return sql `word_similarity(${leftSql}, ${rightSql})`;
221
+ return sql `word_similarity(${left}, ${right})`;
225
222
  }
226
223
  /**
227
224
  * Creates a PostgreSQL `strict_word_similarity` function call (from pg_trgm extension).
@@ -231,37 +228,30 @@ export function wordSimilarity(left, right) {
231
228
  * @returns A Drizzle SQL object representing the similarity score (0 to 1).
232
229
  */
233
230
  export function strictWordSimilarity(left, right) {
234
- const leftSql = isString(left) ? sql `${left}` : left;
235
- const rightSql = isString(right) ? sql `${right}` : right;
236
- return sql `strict_word_similarity(${leftSql}, ${rightSql})`;
231
+ return sql `strict_word_similarity(${left}, ${right})`;
237
232
  }
238
233
  export function isSimilar(left, right) {
239
- const leftSql = isString(left) ? sql `${left}` : left;
240
- const rightSql = isString(right) ? sql `${right}` : right;
241
- return sql `(${leftSql} % ${rightSql})`;
234
+ return sql `(${left} % ${right})`;
242
235
  }
243
236
  export function isWordSimilar(left, right) {
244
- const leftSql = isString(left) ? sql `${left}` : left;
245
- const rightSql = isString(right) ? sql `${right}` : right;
246
- return sql `(${leftSql} <% ${rightSql})`;
237
+ return sql `(${left} <% ${right})`;
247
238
  }
248
239
  export function isStrictWordSimilar(left, right) {
249
- const leftSql = isString(left) ? sql `${left}` : left;
250
- const rightSql = isString(right) ? sql `${right}` : right;
251
- return sql `(${leftSql} <<% ${rightSql})`;
240
+ return sql `(${left} <<% ${right})`;
252
241
  }
253
242
  export function distance(left, right) {
254
- const leftSql = isString(left) ? sql `${left}` : left;
255
- const rightSql = isString(right) ? sql `${right}` : right;
256
- return sql `(${leftSql} <-> ${rightSql})`;
243
+ return sql `(${left} <-> ${right})`;
257
244
  }
258
245
  export function wordDistance(left, right) {
259
- const leftSql = isString(left) ? sql `${left}` : left;
260
- const rightSql = isString(right) ? sql `${right}` : right;
261
- return sql `(${leftSql} <<-> ${rightSql})`;
246
+ return sql `(${left} <<-> ${right})`;
262
247
  }
263
248
  export function strictWordDistance(left, right) {
264
- const leftSql = isString(left) ? sql `${left}` : left;
265
- const rightSql = isString(right) ? sql `${right}` : right;
266
- return sql `(${leftSql} <<<-> ${rightSql})`;
249
+ return sql `(${left} <<<-> ${right})`;
250
+ }
251
+ export function jsonbBuildObject(properties) {
252
+ const entries = Array.isArray(properties) ? properties : Object.entries(properties);
253
+ const chunks = entries
254
+ .filter(([_, propValue]) => isDefined(propValue))
255
+ .map(([propKey, propValue]) => sql `'${sql.raw(propKey)}', ${propValue}`);
256
+ return sql `jsonb_build_object(${sql.join(chunks, sql `, `)})`;
267
257
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tstdl/base",
3
- "version": "0.93.28",
3
+ "version": "0.93.29",
4
4
  "author": "Patrick Hein",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -19,7 +19,7 @@
19
19
  "tsc:watch": "tsc --watch",
20
20
  "tsc-alias:watch": "tsc-alias --watch",
21
21
  "cleanup:dist": "rm -vrf dist/tools/",
22
- "generate:migration": "./scripts/manage-orm.sh generate",
22
+ "generate:migration": "./scripts/manage-orm.sh generate && npm run copy:orm",
23
23
  "generate:readmes": "deno run --allow-run=code2prompt --allow-read --allow-write=source --allow-net=generativelanguage.googleapis.com --allow-env=GEMINI_API_KEY generate-readmes.ts",
24
24
  "generate:readmes:new-only": "npm run generate:readmes -- --skip-existing",
25
25
  "generate:llms.md": "npm run build:dts && npm run cleanup:dist && ./scripts/generate-llms-docs.sh",
@@ -132,11 +132,11 @@
132
132
  "reflect-metadata": "^0.2",
133
133
  "rxjs": "^7.8",
134
134
  "ts-pattern": "^5.9",
135
- "type-fest": "^5.1"
135
+ "type-fest": "^5.2"
136
136
  },
137
137
  "peerDependencies": {
138
138
  "@google-cloud/storage": "^7.17",
139
- "@google/genai": "^1.28",
139
+ "@google/genai": "^1.29",
140
140
  "@tstdl/angular": "^0.93",
141
141
  "@zxcvbn-ts/core": "^3.0",
142
142
  "@zxcvbn-ts/language-common": "^3.0",
@@ -171,8 +171,8 @@
171
171
  "@types/pg": "8.15",
172
172
  "concurrently": "9.2",
173
173
  "drizzle-kit": "0.31",
174
- "eslint": "9.38",
175
- "globals": "16.4",
174
+ "eslint": "9.39",
175
+ "globals": "16.5",
176
176
  "tsc-alias": "1.8",
177
177
  "typedoc-github-wiki-theme": "2.1",
178
178
  "typedoc-plugin-markdown": "4.9",
@@ -0,0 +1,9 @@
1
+ CREATE TABLE "test"."test" (
2
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
3
+ "title" text NOT NULL,
4
+ "content" text NOT NULL,
5
+ "tags" text NOT NULL,
6
+ "language" text NOT NULL
7
+ );
8
+ --> statement-breakpoint
9
+ CREATE INDEX "test_parade_idx" ON "test"."test" USING bm25 ("id","language","title","content","tags",(("title" || ' ' || "content" || ' ' || "tags")::pdb.simple('alias=search_text')),(('foo')::pdb.simple('alias=foo'))) WITH (key_field='id',mutable_segment_rows=12);
@@ -1,5 +1,5 @@
1
1
  {
2
- "id": "cfbaaee3-4960-412e-83b7-0f58ea61969d",
2
+ "id": "bbffc0f2-678b-42e9-8121-1731a04b8987",
3
3
  "prevId": "00000000-0000-0000-0000-000000000000",
4
4
  "version": "7",
5
5
  "dialect": "postgresql",
@@ -41,8 +41,8 @@
41
41
  }
42
42
  },
43
43
  "indexes": {
44
- "test_id_title_content_tags_idx": {
45
- "name": "test_id_title_content_tags_idx",
44
+ "test_parade_idx": {
45
+ "name": "test_parade_idx",
46
46
  "columns": [
47
47
  {
48
48
  "expression": "id",
@@ -51,21 +51,39 @@
51
51
  "nulls": "last"
52
52
  },
53
53
  {
54
- "expression": "title",
55
- "isExpression": false,
54
+ "expression": "\"language\"",
56
55
  "asc": true,
56
+ "isExpression": true,
57
57
  "nulls": "last"
58
58
  },
59
59
  {
60
- "expression": "content",
61
- "isExpression": false,
60
+ "expression": "\"title\"",
62
61
  "asc": true,
62
+ "isExpression": true,
63
63
  "nulls": "last"
64
64
  },
65
65
  {
66
- "expression": "tags",
67
- "isExpression": false,
66
+ "expression": "\"content\"",
67
+ "asc": true,
68
+ "isExpression": true,
69
+ "nulls": "last"
70
+ },
71
+ {
72
+ "expression": "\"tags\"",
73
+ "asc": true,
74
+ "isExpression": true,
75
+ "nulls": "last"
76
+ },
77
+ {
78
+ "expression": "((\"title\" || ' ' || \"content\" || ' ' || \"tags\")::pdb.simple('alias=search_text'))",
79
+ "asc": true,
80
+ "isExpression": true,
81
+ "nulls": "last"
82
+ },
83
+ {
84
+ "expression": "(('foo')::pdb.simple('alias=foo'))",
68
85
  "asc": true,
86
+ "isExpression": true,
69
87
  "nulls": "last"
70
88
  }
71
89
  ],
@@ -73,7 +91,8 @@
73
91
  "concurrently": false,
74
92
  "method": "bm25",
75
93
  "with": {
76
- "key_field": "'id'"
94
+ "key_field": "'id'",
95
+ "mutable_segment_rows": 12
77
96
  }
78
97
  }
79
98
  },
@@ -5,8 +5,8 @@
5
5
  {
6
6
  "idx": 0,
7
7
  "version": "7",
8
- "when": 1761929933695,
9
- "tag": "0000_sturdy_patch",
8
+ "when": 1762381204799,
9
+ "tag": "0000_natural_cannonball",
10
10
  "breakpoints": true
11
11
  }
12
12
  ]
@@ -7,7 +7,8 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  var __metadata = (this && this.__metadata) || function (k, v) {
8
8
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
9
  };
10
- import { BaseEntity, Index } from '../orm/index.js';
10
+ import { sql } from 'drizzle-orm';
11
+ import { BaseEntity, ParadeCompoundIndex, ParadeExpressionIndex, ParadeIndex } from '../orm/index.js';
11
12
  import { StringProperty } from '../schema/index.js';
12
13
  let Test = class Test extends BaseEntity {
13
14
  title;
@@ -16,14 +17,17 @@ let Test = class Test extends BaseEntity {
16
17
  language;
17
18
  };
18
19
  __decorate([
20
+ ParadeIndex(),
19
21
  StringProperty(),
20
22
  __metadata("design:type", String)
21
23
  ], Test.prototype, "title", void 0);
22
24
  __decorate([
25
+ ParadeIndex(),
23
26
  StringProperty(),
24
27
  __metadata("design:type", String)
25
28
  ], Test.prototype, "content", void 0);
26
29
  __decorate([
30
+ ParadeIndex(),
27
31
  StringProperty(),
28
32
  __metadata("design:type", String)
29
33
  ], Test.prototype, "tags", void 0);
@@ -32,7 +36,9 @@ __decorate([
32
36
  __metadata("design:type", String)
33
37
  ], Test.prototype, "language", void 0);
34
38
  Test = __decorate([
35
- Index(['id', 'title', 'content', 'tags'], { using: 'bm25', with: { key_field: `'id'` } })
39
+ ParadeExpressionIndex('foo', () => sql `'foo'`),
40
+ ParadeCompoundIndex('search_text', (table) => [table.title, 'content', 'tags']),
41
+ ParadeIndex({ columns: ['language'], mutableSegmentRows: 12 })
36
42
  ], Test);
37
43
  export { Test };
38
44
  export const testData = [
package/test1.js CHANGED
@@ -53,18 +53,18 @@ async function main(_cancellationSignal) {
53
53
  if (await repository.count() == 0) {
54
54
  await repository.insertMany(testData);
55
55
  }
56
- const result = await repository.search({
56
+ const result = await repository.loadManyByQuery({
57
+ title: { $parade: { match: { value: 'quick fox', distance: 2 } } },
58
+ });
59
+ const result2 = await repository.search({
57
60
  query: {
58
- $trigram: {
59
- fields: ['title'],
60
- query: 'lorem ipsum dolor sit amet',
61
- },
61
+ $parade: { fields: ['content'], query: 'vibrant city' },
62
62
  },
63
- distinct: ['title'],
64
- order: (x) => ['title', x.score],
65
- limit: 10,
63
+ highlight: { source: 'content', includePositions: true },
66
64
  });
67
- console.log(result);
65
+ for (const item of result2) {
66
+ console.log(item);
67
+ }
68
68
  }
69
69
  Application.run('Test', [
70
70
  provideInitializer(bootstrap),
package/utils/enum.js CHANGED
@@ -10,7 +10,7 @@ const memoizedEnumValues = memoizeSingle((enumeration) => {
10
10
  const entries = enumEntries(enumeration);
11
11
  return entries.map((entry) => entry[1]);
12
12
  }, { weak: true });
13
- const memoizedReversedEnum = memoizeSingle((enumeration) => mapObject(enumeration, (value, key) => [value, key]), { weak: true });
13
+ const memoizedReversedEnum = memoizeSingle((enumeration) => mapObject(enumeration, (key, value) => [value, key]), { weak: true });
14
14
  export function enumValueName(enumeration, value) {
15
15
  return reversedEnum(enumeration)[value];
16
16
  }
@@ -1,3 +1,4 @@
1
+ import type { SnakeCasedProperties } from 'type-fest';
1
2
  import { type JsonPathInput } from '../../json-path/json-path.js';
2
3
  import type { BaseType, Entries, FromEntries, ObjectLiteral, Optionalize, OptionalizeNull, PickBy, Record, SimplifyObject, UnionToIntersection } from '../../types/index.js';
3
4
  export declare function hasOwnProperty<T extends ObjectLiteral, K extends keyof UnionToIntersection<T>>(obj: T, key: K): obj is Extract<T, Partial<Record<K>>>;
@@ -13,15 +14,16 @@ export declare function objectKeys<T extends ObjectLiteral>(object: T): (keyof T
13
14
  export declare function objectValues<T extends ObjectLiteral>(object: T): (T[keyof T])[];
14
15
  export declare function fromEntries<A>(entries: A): FromEntries<A>;
15
16
  export declare function fromEntries<K extends PropertyKey, T>(entries: Iterable<readonly [K, T]>): Record<K, T>;
16
- export declare function mapObject<T extends ObjectLiteral, K extends PropertyKey, V>(object: T, mapper: (value: T[keyof T], key: keyof T) => [key: K, value: V]): Record<K, V>;
17
- export declare function mapObjectAsync<T extends ObjectLiteral, K extends PropertyKey, V>(object: T, mapper: (value: T[keyof T], key: keyof T) => Promise<[key: K, value: V]>): Promise<Record<K, V>>;
17
+ export declare function mapObject<T extends ObjectLiteral, K extends PropertyKey, V>(object: T, mapper: (key: keyof T, value: T[keyof T]) => [key: K, value: V] | null | undefined): Record<K, V>;
18
+ export declare function mapObjectAsync<T extends ObjectLiteral, K extends PropertyKey, V>(object: T, mapper: (key: keyof T, value: T[keyof T]) => Promise<[key: K, value: V] | null | undefined>): Promise<Record<K, V>>;
18
19
  export declare function mapObjectValues<T extends ObjectLiteral, V>(object: T, mapper: (value: T[keyof T], key: keyof T) => V): Record<keyof T, V>;
19
20
  export declare function mapObjectKeys<T extends ObjectLiteral, K extends PropertyKey>(object: T, mapper: (key: keyof T, value: T[keyof T]) => K): Record<K, T[keyof T]>;
21
+ export declare function mapObjectKeysToSnakeCase<T extends Record<string>>(object: T): SnakeCasedProperties<T>;
20
22
  export declare function mapObjectValuesAsync<T extends ObjectLiteral, V>(object: T, mapper: (value: T[keyof T], key: keyof T) => Promise<V>): Promise<Record<keyof T, V>>;
21
23
  export declare function filterObject<T extends ObjectLiteral, U extends T[keyof T]>(object: T, predicate: (value: T[keyof T], key: keyof T) => value is U): PickBy<T, U>;
22
24
  export declare function filterObject<T extends ObjectLiteral>(object: T, predicate: (value: T[keyof T], key: keyof T) => boolean): Partial<T>;
23
25
  export declare function filterObjectAsync<T extends ObjectLiteral>(object: T, predicate: (value: T[keyof T], key: keyof T) => Promise<boolean>): Promise<Partial<T>>;
24
- export declare function filterUndefinedFromRecord<K extends PropertyKey, V>(record: Record<K, V>): Record<BaseType<K>, Exclude<V, undefined>>;
26
+ export declare function filterUndefinedFromRecord<K extends PropertyKey, V>(record: Partial<Record<K, V>>): Record<BaseType<K>, Exclude<V, undefined>>;
25
27
  export declare function filterNullishFromRecord<K extends PropertyKey, V>(record: Record<K, V>): Record<BaseType<K>, Exclude<V, null | undefined>>;
26
28
  export declare function filterUndefinedObjectProperties<T extends ObjectLiteral>(object: T): SimplifyObject<Optionalize<T>>;
27
29
  export declare function filterNullishObjectProperties<T extends ObjectLiteral>(object: T): SimplifyObject<OptionalizeNull<Optionalize<T>>>;
@@ -2,7 +2,8 @@ import { JsonPath } from '../../json-path/json-path.js';
2
2
  import { filterAsync } from '../async-iterable-helpers/filter.js';
3
3
  import { mapAsync } from '../async-iterable-helpers/map.js';
4
4
  import { toArrayAsync } from '../async-iterable-helpers/to-array.js';
5
- import { isArray, isDefined, isNotNullOrUndefined, isObject, isSymbol, isUndefined } from '../type-guards.js';
5
+ import { toSnakeCase } from '../string/index.js';
6
+ import { isArray, isDefined, isNotNullOrUndefined, isNullOrUndefined, isObject, isSymbol, isUndefined } from '../type-guards.js';
6
7
  export function hasOwnProperty(obj, key) {
7
8
  return Object.hasOwn(obj, key);
8
9
  }
@@ -26,22 +27,31 @@ export function fromEntries(entries) {
26
27
  return Object.fromEntries(entries);
27
28
  }
28
29
  export function mapObject(object, mapper) {
29
- const mappedEntries = objectKeys(object).map((key) => mapper(object[key], key));
30
+ const mappedEntries = objectKeys(object).flatMap((key) => {
31
+ const result = mapper(key, object[key]);
32
+ if (isNullOrUndefined(result)) {
33
+ return [];
34
+ }
35
+ return [result];
36
+ });
30
37
  return Object.fromEntries(mappedEntries);
31
38
  }
32
39
  export async function mapObjectAsync(object, mapper) {
33
40
  const entries = objectKeys(object);
34
- const mappedEntries = await toArrayAsync(mapAsync(entries, async (key) => await mapper(object[key], key)));
35
- return Object.fromEntries(mappedEntries);
41
+ const mappedEntries = await toArrayAsync(mapAsync(entries, async (key) => await mapper(key, object[key])));
42
+ return Object.fromEntries(mappedEntries.filter(isNotNullOrUndefined));
36
43
  }
37
44
  export function mapObjectValues(object, mapper) {
38
- return mapObject(object, (value, key) => [key, mapper(value, key)]);
45
+ return mapObject(object, (key, value) => [key, mapper(value, key)]);
39
46
  }
40
47
  export function mapObjectKeys(object, mapper) {
41
- return mapObject(object, (value, key) => [mapper(key, value), value]);
48
+ return mapObject(object, (key, value) => [mapper(key, value), value]);
49
+ }
50
+ export function mapObjectKeysToSnakeCase(object) {
51
+ return mapObjectKeys(object, (key) => toSnakeCase(key));
42
52
  }
43
53
  export async function mapObjectValuesAsync(object, mapper) {
44
- return await mapObjectAsync(object, async (value, key) => [key, await mapper(value, key)]);
54
+ return await mapObjectAsync(object, async (key, value) => [key, await mapper(value, key)]);
45
55
  }
46
56
  export function filterObject(object, predicate) {
47
57
  const mappedEntries = objectEntries(object).filter(([key, value]) => predicate(value, key));
@@ -0,0 +1,3 @@
1
+ import type { CamelCase, SnakeCase } from 'type-fest';
2
+ export declare function toSnakeCase<T extends string>(value: T): SnakeCase<T>;
3
+ export declare function toCamelCase<T extends string>(value: T): CamelCase<T>;
@@ -0,0 +1,15 @@
1
+ const wordsPattern = /[\da-z]+|[A-Z]+(?![a-z])|[A-Z][\da-z]+/g;
2
+ export function toSnakeCase(value) {
3
+ const words = matchWords(value);
4
+ return words.map((word) => word.toLowerCase()).join('_');
5
+ }
6
+ export function toCamelCase(value) {
7
+ const words = matchWords(value);
8
+ return words.reduce((acc, word, index) => {
9
+ const formattedWord = (index == 0) ? word.toLowerCase() : `${word[0].toUpperCase()}${word.slice(1)}`;
10
+ return acc + formattedWord;
11
+ }, '');
12
+ }
13
+ function matchWords(value) {
14
+ return value.replace(/['\u2019]/g, '').match(wordsPattern) ?? [];
15
+ }
@@ -1,5 +1,5 @@
1
+ export * from './casing.js';
1
2
  export * from './hypenate.js';
2
3
  export * from './normalize.js';
3
- export * from './snake-case.js';
4
4
  export * from './title-case.js';
5
5
  export * from './trim.js';
@@ -1,5 +1,5 @@
1
+ export * from './casing.js';
1
2
  export * from './hypenate.js';
2
3
  export * from './normalize.js';
3
- export * from './snake-case.js';
4
4
  export * from './title-case.js';
5
5
  export * from './trim.js';
@@ -1,9 +0,0 @@
1
- CREATE TABLE "test"."test" (
2
- "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
3
- "title" text NOT NULL,
4
- "content" text NOT NULL,
5
- "tags" text NOT NULL,
6
- "language" text NOT NULL
7
- );
8
- --> statement-breakpoint
9
- CREATE INDEX "test_id_title_content_tags_idx" ON "test"."test" USING bm25 ("id","title","content","tags") WITH (key_field='id');
@@ -1 +0,0 @@
1
- export declare function toSnakeCase(value: string): string;
@@ -1,4 +0,0 @@
1
- const pattern = /(?=[A-Z])/u;
2
- export function toSnakeCase(value) {
3
- return value.split(pattern).join('_').toLowerCase();
4
- }