@tstdl/base 0.93.28 → 0.93.30

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 (43) 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 +2 -0
  15. package/orm/schemas/index.js +2 -0
  16. package/orm/schemas/numeric.d.ts +17 -0
  17. package/orm/schemas/numeric.js +23 -0
  18. package/orm/schemas/tsvector.d.ts +12 -0
  19. package/orm/schemas/tsvector.js +20 -0
  20. package/orm/server/drizzle/schema-converter.js +102 -24
  21. package/orm/server/query-converter.d.ts +2 -2
  22. package/orm/server/query-converter.js +137 -91
  23. package/orm/server/repository.d.ts +6 -6
  24. package/orm/server/repository.js +34 -23
  25. package/orm/sqls.d.ts +19 -16
  26. package/orm/sqls.js +28 -38
  27. package/orm/types.d.ts +3 -8
  28. package/package.json +6 -6
  29. package/test/drizzle/0000_natural_cannonball.sql +9 -0
  30. package/test/drizzle/meta/0000_snapshot.json +29 -10
  31. package/test/drizzle/meta/_journal.json +2 -2
  32. package/test/test.model.js +8 -6
  33. package/test1.js +13 -10
  34. package/utils/enum.js +1 -1
  35. package/utils/object/object.d.ts +5 -3
  36. package/utils/object/object.js +17 -7
  37. package/utils/string/casing.d.ts +3 -0
  38. package/utils/string/casing.js +15 -0
  39. package/utils/string/index.d.ts +1 -1
  40. package/utils/string/index.js +1 -1
  41. package/test/drizzle/0000_sturdy_patch.sql +0 -9
  42. package/utils/string/snake-case.d.ts +0 -1
  43. 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/orm/types.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * like primary keys and default values. It also re-exports common decorators and schemas.
6
6
  */
7
7
  import type { $Type, HasDefault as DrizzleHasDefault, IsPrimaryKey as DrizzleIsPrimaryKey } from 'drizzle-orm';
8
- import type { boolean, doublePrecision, integer, jsonb, PgColumnBuilder, PgColumnBuilderBase, PgEnumColumnBuilderInitial, text, uuid } from 'drizzle-orm/pg-core';
8
+ import type { boolean, doublePrecision, integer, jsonb, numeric, PgColumnBuilder, PgColumnBuilderBase, PgEnumColumnBuilderInitial, text, uuid } from 'drizzle-orm/pg-core';
9
9
  import type { AbstractConstructor, EnumerationObject, EnumerationValue, GetTagMetadata, HasTag, ObjectLiteral, Tagged, UnionToTuple, UnwrapTagged } from '../types/index.js';
10
10
  import type { bytea, numericDate, timestamp } from './data-types/index.js';
11
11
  /** Tag identifier for column type information. */
@@ -64,21 +64,16 @@ export type Json<T> = Tagged<T, ColumnTypeTag, $Type<ReturnType<typeof jsonb>, T
64
64
  * @template T - The enumeration object type.
65
65
  */
66
66
  export type Enum<T extends string | number> = Tagged<T, ColumnTypeTag, EnumColumn<T>>;
67
- /** Tagged type representing a `text` column. */
68
67
  export type Text<T extends string = string> = Tagged<string, ColumnTypeTag, ReturnType<typeof text<string, TextTuple<T>>>>;
69
- /** Tagged type representing a `uuid` column. Stores the UUID as a string. */
70
68
  export type Uuid = Tagged<string, ColumnTypeTag, ReturnType<typeof uuid>>;
71
- /** Tagged type representing an `integer` column. */
72
69
  export type Integer = Tagged<number, ColumnTypeTag, ReturnType<typeof integer>>;
73
- /** Tagged type representing a `double precision` column. */
74
70
  export type DoublePrecision = Tagged<number, ColumnTypeTag, ReturnType<typeof doublePrecision>>;
75
- /** Tagged type representing a `boolean` column. */
76
71
  export type Boolean = Tagged<number, ColumnTypeTag, ReturnType<typeof boolean>>;
77
- /** Tagged type representing a custom `numericDate` column (stores date as number YYYYMMDD). */
72
+ export type Numeric<T extends 'number' | 'string' | 'bigint' = 'number'> = Tagged<T extends 'number' ? number : T extends 'string' ? string : bigint, ColumnTypeTag, ReturnType<typeof numeric<T>>>;
73
+ /** Tagged type representing a custom `numericDate` column (stores date as number - days since epoch). */
78
74
  export type NumericDate = Tagged<number, ColumnTypeTag, ReturnType<typeof numericDate>>;
79
75
  /** Tagged type representing a `timestamp` column (stores timestamp as number - milliseconds since epoch). */
80
76
  export type Timestamp = Tagged<number, ColumnTypeTag, ReturnType<typeof timestamp>>;
81
- /** Tagged type representing a `bytea` (byte array) column. */
82
77
  export type Bytea = Tagged<Uint8Array, ColumnTypeTag, ReturnType<typeof bytea>>;
83
78
  /**
84
79
  * Tagged type representing an encrypted column.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tstdl/base",
3
- "version": "0.93.28",
3
+ "version": "0.93.30",
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'] })
36
42
  ], Test);
37
43
  export { Test };
38
44
  export const testData = [
@@ -126,7 +132,6 @@ export const testData = [
126
132
  tags: ['testing', 'database', 'automation', 'best practices'],
127
133
  language: 'english',
128
134
  },
129
- // --- Cooking Posts (16-25) ---
130
135
  {
131
136
  title: 'The Perfect Weeknight Pasta Recipe',
132
137
  content: 'A quick and delicious pasta recipe that you can make in under 30 minutes. Perfect for a busy weeknight. This recipe uses fresh tomatoes and basil.',
@@ -187,7 +192,6 @@ export const testData = [
187
192
  tags: ['smoothie', 'recipe', 'breakfast', 'healthy'],
188
193
  language: 'english',
189
194
  },
190
- // --- Travel Posts (26-35) ---
191
195
  {
192
196
  title: 'A Backpacker\'s Guide to Southeast Asia',
193
197
  content: 'Explore the wonders of Southeast Asia on a budget. This guide covers top destinations, packing tips, and how to travel safely and affordably.',
@@ -248,7 +252,6 @@ export const testData = [
248
252
  tags: ['solo travel', 'travel tips', 'adventure'],
249
253
  language: 'english',
250
254
  },
251
- // --- Science & Lifestyle Posts (36-45) ---
252
255
  {
253
256
  title: 'The Mysteries of Black Holes Explained',
254
257
  content: 'What are black holes and how do they form? This article provides a simple explanation of one of the universe\'s most fascinating phenomena.',
@@ -309,7 +312,6 @@ export const testData = [
309
312
  tags: ['fitness', 'health', 'wellness', 'exercise'],
310
313
  language: 'english',
311
314
  },
312
- // --- Multi-Language Posts (46-50) ---
313
315
  {
314
316
  title: 'Einführung in die Volltextsuche mit PostgreSQL',
315
317
  content: 'Dieser Leitfaden erklärt die Grundlagen der Volltextsuche in einer PostgreSQL Datenbank. Wir behandeln Konfiguration und Abfragen.',
package/test1.js CHANGED
@@ -10,6 +10,7 @@ import { configureOrm, injectRepository } from './orm/server/index.js';
10
10
  import { configurePostgresQueue, migratePostgresQueueSchema } from './queue/postgres/index.js';
11
11
  import { migrateTestSchema } from './test/module.js';
12
12
  import { Test, testData } from './test/test.model.js';
13
+ import { timedBenchmarkAsync } from './utils/benchmark.js';
13
14
  import * as configParser from './utils/config-parser.js';
14
15
  const config = {
15
16
  database: {
@@ -53,18 +54,20 @@ async function main(_cancellationSignal) {
53
54
  if (await repository.count() == 0) {
54
55
  await repository.insertMany(testData);
55
56
  }
56
- const result = await repository.search({
57
- query: {
58
- $trigram: {
59
- fields: ['title'],
60
- query: 'lorem ipsum dolor sit amet',
57
+ let result;
58
+ const benchmarkResult = await timedBenchmarkAsync(1000, async () => {
59
+ result = await repository.search({
60
+ query: {
61
+ $parade: { fields: ['content'], query: 'vitamins' },
61
62
  },
62
- },
63
- distinct: ['title'],
64
- order: (x) => ['title', x.score],
65
- limit: 10,
63
+ highlight: { source: 'content', includePositions: true },
64
+ score: true,
65
+ });
66
66
  });
67
- console.log(result);
67
+ for (const item of result) {
68
+ console.log(item);
69
+ }
70
+ console.log('Benchmark result:', benchmarkResult);
68
71
  }
69
72
  Application.run('Test', [
70
73
  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
- }