@tstdl/base 0.93.86 → 0.93.89
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ai/genkit/helpers.d.ts +3 -1
- package/ai/genkit/helpers.js +3 -3
- package/api/server/gateway.d.ts +3 -0
- package/api/server/gateway.js +15 -4
- package/api/server/middlewares/catch-error.middleware.js +2 -4
- package/api/server/middlewares/cors.middleware.js +2 -3
- package/api/server/middlewares/csrf.middleware.d.ts +41 -0
- package/api/server/middlewares/csrf.middleware.js +108 -0
- package/api/server/middlewares/index.d.ts +1 -0
- package/api/server/middlewares/index.js +1 -0
- package/api/server/module.d.ts +8 -2
- package/api/server/module.js +14 -8
- package/api/server/tests/csrf.middleware.test.js +91 -0
- package/audit/drizzle/{0000_bored_stick.sql → 0000_lumpy_thunderball.sql} +3 -3
- package/audit/drizzle/meta/0000_snapshot.json +4 -4
- package/audit/drizzle/meta/_journal.json +2 -9
- package/audit/module.d.ts +4 -1
- package/audit/module.js +3 -2
- package/audit/schemas.d.ts +1 -1
- package/audit/types.d.ts +1 -1
- package/audit/types.js +1 -1
- package/authentication/client/authentication.service.d.ts +14 -1
- package/authentication/client/authentication.service.js +82 -23
- package/authentication/client/http-client.middleware.d.ts +6 -0
- package/authentication/client/http-client.middleware.js +36 -0
- package/authentication/client/module.js +8 -2
- package/authentication/models/service-account.model.d.ts +2 -2
- package/authentication/models/service-account.model.js +10 -5
- package/authentication/models/subject.model.d.ts +19 -5
- package/authentication/models/subject.model.js +25 -29
- package/authentication/models/system-account.model.d.ts +3 -2
- package/authentication/models/system-account.model.js +11 -5
- package/authentication/models/user.model.d.ts +2 -11
- package/authentication/models/user.model.js +5 -16
- package/authentication/server/authentication-api-request-token.provider.d.ts +0 -2
- package/authentication/server/authentication-api-request-token.provider.js +3 -11
- package/authentication/server/authentication.api-controller.d.ts +1 -2
- package/authentication/server/authentication.api-controller.js +8 -9
- package/authentication/server/authentication.audit.d.ts +3 -2
- package/authentication/server/authentication.service.d.ts +27 -1
- package/authentication/server/authentication.service.js +67 -18
- package/authentication/server/drizzle/{0000_normal_paper_doll.sql → 0000_soft_tag.sql} +25 -32
- package/authentication/server/drizzle/meta/0000_snapshot.json +180 -205
- package/authentication/server/drizzle/meta/_journal.json +2 -2
- package/authentication/server/helper.js +9 -2
- package/authentication/server/module.d.ts +4 -1
- package/authentication/server/module.js +9 -5
- package/authentication/server/schemas.d.ts +2 -1
- package/authentication/server/schemas.js +2 -2
- package/authentication/server/subject.service.d.ts +14 -8
- package/authentication/server/subject.service.js +86 -84
- package/authentication/tests/authentication-ancillary.service.test.d.ts +1 -0
- package/authentication/tests/authentication-ancillary.service.test.js +13 -0
- package/authentication/tests/authentication-secret-requirements.validator.test.d.ts +1 -0
- package/authentication/tests/authentication-secret-requirements.validator.test.js +29 -0
- package/authentication/tests/authentication.api-controller.test.d.ts +1 -0
- package/authentication/tests/authentication.api-controller.test.js +88 -0
- package/authentication/tests/authentication.api-request-token.provider.test.d.ts +1 -0
- package/authentication/tests/authentication.api-request-token.provider.test.js +48 -0
- package/authentication/tests/authentication.client-middleware.test.d.ts +1 -0
- package/authentication/tests/authentication.client-middleware.test.js +23 -0
- package/authentication/tests/authentication.client-service.test.d.ts +1 -0
- package/authentication/tests/authentication.client-service.test.js +70 -0
- package/authentication/tests/authentication.service.test.d.ts +1 -0
- package/authentication/tests/authentication.service.test.js +186 -0
- package/authentication/tests/authentication.test-ancillary-service.d.ts +9 -0
- package/authentication/tests/authentication.test-ancillary-service.js +27 -0
- package/authentication/tests/helper.test.d.ts +1 -0
- package/authentication/tests/helper.test.js +107 -0
- package/authentication/tests/secret-requirements.error.test.d.ts +1 -0
- package/authentication/tests/secret-requirements.error.test.js +14 -0
- package/authentication/tests/subject.service.test.d.ts +1 -0
- package/authentication/tests/subject.service.test.js +140 -0
- package/circuit-breaker/postgres/drizzle/meta/0000_snapshot.json +1 -1
- package/circuit-breaker/postgres/drizzle/meta/_journal.json +2 -2
- package/circuit-breaker/postgres/module.d.ts +7 -1
- package/circuit-breaker/postgres/module.js +8 -6
- package/circuit-breaker/tests/circuit-breaker.test.js +2 -22
- package/document-management/api/document-management.api.js +2 -6
- package/document-management/server/services/document-validation.service.js +6 -5
- package/document-management/server/services/document-workflow.service.js +5 -5
- package/document-management/service-models/document-folders.view-model.d.ts +5 -2
- package/document-management/service-models/document-folders.view-model.js +42 -9
- package/document-management/service-models/enriched/enriched-document-management-data.view.js +1 -1
- package/examples/document-management/main.js +4 -4
- package/http/client/adapters/undici.adapter.d.ts +7 -5
- package/http/client/adapters/undici.adapter.js +13 -10
- package/http/client/module.d.ts +3 -1
- package/http/client/module.js +8 -9
- package/http/server/http-server.d.ts +2 -0
- package/http/server/node/module.d.ts +6 -2
- package/http/server/node/module.js +6 -4
- package/http/server/node/node-http-server.d.ts +2 -0
- package/http/server/node/node-http-server.js +7 -0
- package/http/types.d.ts +1 -1
- package/key-value-store/postgres/module.d.ts +7 -1
- package/key-value-store/postgres/module.js +7 -3
- package/lock/postgres/lock.js +0 -1
- package/lock/postgres/module.d.ts +7 -1
- package/lock/postgres/module.js +9 -5
- package/logger/formatter.d.ts +2 -0
- package/logger/formatters/json.js +2 -2
- package/logger/formatters/pretty-print.js +8 -10
- package/logger/logger.d.ts +1 -1
- package/logger/logger.js +15 -12
- package/message-bus/local/module.d.ts +5 -2
- package/message-bus/local/module.js +5 -4
- package/module/module.d.ts +2 -1
- package/module/module.js +3 -0
- package/module/modules/web-server.module.d.ts +11 -6
- package/module/modules/web-server.module.js +15 -10
- package/orm/decorators.d.ts +24 -1
- package/orm/decorators.js +40 -4
- package/orm/index.d.ts +1 -1
- package/orm/index.js +1 -1
- package/orm/query/base.d.ts +17 -17
- package/orm/query/base.js +1 -1
- package/orm/repository.types.d.ts +46 -2
- package/orm/schemas/tsvector.js +1 -1
- package/orm/server/drizzle/schema-converter.d.ts +3 -1
- package/orm/server/drizzle/schema-converter.js +120 -14
- package/orm/server/index.d.ts +1 -0
- package/orm/server/index.js +1 -0
- package/orm/server/module.d.ts +4 -2
- package/orm/server/module.js +6 -5
- package/orm/server/query-converter.d.ts +6 -3
- package/orm/server/query-converter.js +33 -21
- package/orm/server/repository-config.d.ts +8 -0
- package/orm/server/repository-config.js +8 -0
- package/orm/server/repository.d.ts +117 -43
- package/orm/server/repository.js +758 -254
- package/orm/server/transaction.d.ts +4 -2
- package/orm/server/transaction.js +14 -5
- package/orm/server/transactional.d.ts +6 -2
- package/orm/server/transactional.js +39 -9
- package/orm/server/types.d.ts +2 -0
- package/orm/sqls/case-when.d.ts +25 -0
- package/orm/sqls/case-when.js +54 -0
- package/orm/sqls/index.d.ts +2 -0
- package/orm/sqls/index.js +2 -0
- package/orm/{sqls.d.ts → sqls/sqls.d.ts} +67 -19
- package/orm/{sqls.js → sqls/sqls.js} +116 -22
- package/orm/tests/data-types.test.d.ts +1 -0
- package/orm/tests/data-types.test.js +39 -0
- package/orm/tests/decorators.test.d.ts +1 -0
- package/orm/tests/decorators.test.js +77 -0
- package/orm/tests/encryption.test.d.ts +1 -0
- package/orm/tests/encryption.test.js +34 -0
- package/orm/tests/query-complex.test.d.ts +1 -0
- package/orm/tests/query-complex.test.js +203 -0
- package/orm/tests/query-converter-complex.test.d.ts +1 -0
- package/orm/tests/query-converter-complex.test.js +126 -0
- package/orm/tests/query-converter.test.d.ts +1 -0
- package/orm/tests/query-converter.test.js +123 -0
- package/orm/tests/repository-advanced.test.d.ts +1 -0
- package/orm/tests/repository-advanced.test.js +232 -0
- package/orm/tests/repository-attributes.test.d.ts +1 -0
- package/orm/tests/repository-attributes.test.js +99 -0
- package/orm/tests/repository-comprehensive.test.d.ts +1 -0
- package/orm/tests/repository-comprehensive.test.js +187 -0
- package/orm/tests/repository-coverage.test.d.ts +1 -0
- package/orm/tests/repository-coverage.test.js +303 -0
- package/orm/tests/repository-cti-complex.test.d.ts +1 -0
- package/orm/tests/repository-cti-complex.test.js +170 -0
- package/orm/tests/repository-cti-embedded.test.d.ts +1 -0
- package/orm/tests/repository-cti-embedded.test.js +188 -0
- package/orm/tests/repository-cti-extensive.test.d.ts +1 -0
- package/orm/tests/repository-cti-extensive.test.js +308 -0
- package/orm/tests/repository-cti-mapping.test.d.ts +1 -0
- package/orm/tests/repository-cti-mapping.test.js +121 -0
- package/orm/tests/repository-cti-search.test.d.ts +1 -0
- package/orm/tests/repository-cti-search.test.js +152 -0
- package/orm/tests/repository-cti-soft-delete.test.d.ts +1 -0
- package/orm/tests/repository-cti-soft-delete.test.js +115 -0
- package/orm/tests/repository-cti-transactions.test.d.ts +1 -0
- package/orm/tests/repository-cti-transactions.test.js +126 -0
- package/orm/tests/repository-cti-upsert-many.test.d.ts +1 -0
- package/orm/tests/repository-cti-upsert-many.test.js +127 -0
- package/orm/tests/repository-cti.test.d.ts +1 -0
- package/orm/tests/repository-cti.test.js +456 -0
- package/orm/tests/repository-edge-cases.test.d.ts +1 -0
- package/orm/tests/repository-edge-cases.test.js +216 -0
- package/orm/tests/repository-expiration.test.d.ts +1 -0
- package/orm/tests/repository-expiration.test.js +153 -0
- package/orm/tests/repository-extra-coverage.test.d.ts +1 -0
- package/orm/tests/repository-extra-coverage.test.js +546 -0
- package/orm/tests/repository-mapping.test.d.ts +1 -0
- package/orm/tests/repository-mapping.test.js +71 -0
- package/orm/tests/repository-regression.test.d.ts +1 -0
- package/orm/tests/repository-regression.test.js +330 -0
- package/orm/tests/repository-search-coverage.test.d.ts +1 -0
- package/orm/tests/repository-search-coverage.test.js +129 -0
- package/orm/tests/repository-search.test.d.ts +1 -0
- package/orm/tests/repository-search.test.js +116 -0
- package/orm/tests/repository-soft-delete.test.d.ts +1 -0
- package/orm/tests/repository-soft-delete.test.js +143 -0
- package/orm/tests/repository-transactions-nested.test.d.ts +1 -0
- package/orm/tests/repository-transactions-nested.test.js +202 -0
- package/orm/tests/repository-types.test.d.ts +1 -0
- package/orm/tests/repository-types.test.js +218 -0
- package/orm/tests/schema-converter.test.d.ts +1 -0
- package/orm/tests/schema-converter.test.js +81 -0
- package/orm/tests/schema-generation.test.d.ts +1 -0
- package/orm/tests/schema-generation.test.js +127 -0
- package/orm/tests/sql-helpers.test.d.ts +1 -0
- package/orm/tests/sql-helpers.test.js +67 -0
- package/orm/tests/transaction-safety.test.d.ts +1 -0
- package/orm/tests/transaction-safety.test.js +81 -0
- package/orm/tests/transactional.test.d.ts +1 -0
- package/orm/tests/transactional.test.js +224 -0
- package/orm/tests/utils.test.d.ts +1 -0
- package/orm/tests/utils.test.js +70 -0
- package/orm/utils.d.ts +7 -0
- package/orm/utils.js +26 -6
- package/package.json +12 -7
- package/pool/pool.js +1 -1
- package/rate-limit/index.d.ts +2 -0
- package/rate-limit/index.js +2 -0
- package/rate-limit/postgres/drizzle/0000_watery_rage.sql +7 -0
- package/{queue → rate-limit}/postgres/drizzle/meta/0000_snapshot.json +14 -39
- package/rate-limit/postgres/drizzle/meta/_journal.json +13 -0
- package/{queue → rate-limit}/postgres/drizzle.config.js +1 -1
- package/rate-limit/postgres/index.d.ts +4 -0
- package/rate-limit/postgres/index.js +4 -0
- package/rate-limit/postgres/module.d.ts +12 -0
- package/rate-limit/postgres/module.js +28 -0
- package/rate-limit/postgres/postgres-rate-limiter.d.ts +9 -0
- package/rate-limit/postgres/postgres-rate-limiter.js +56 -0
- package/rate-limit/postgres/rate-limit.model.d.ts +8 -0
- package/rate-limit/postgres/rate-limit.model.js +35 -0
- package/rate-limit/postgres/rate-limiter.provider.d.ts +6 -0
- package/rate-limit/postgres/rate-limiter.provider.js +21 -0
- package/rate-limit/postgres/schemas.d.ts +3 -0
- package/rate-limit/postgres/schemas.js +4 -0
- package/rate-limit/provider.d.ts +9 -0
- package/rate-limit/provider.js +2 -0
- package/rate-limit/rate-limiter.d.ts +35 -0
- package/rate-limit/rate-limiter.js +3 -0
- package/rate-limit/tests/postgres-rate-limiter.test.d.ts +1 -0
- package/rate-limit/tests/postgres-rate-limiter.test.js +92 -0
- package/signals/implementation/configure.d.ts +3 -0
- package/signals/implementation/configure.js +3 -0
- package/sse/data-stream-source.d.ts +1 -1
- package/sse/data-stream-source.js +6 -6
- package/task-queue/enqueue-batch.d.ts +17 -0
- package/task-queue/enqueue-batch.js +24 -0
- package/{queue → task-queue}/index.d.ts +1 -1
- package/{queue → task-queue}/index.js +1 -1
- package/task-queue/postgres/drizzle/0000_thin_black_panther.sql +74 -0
- package/task-queue/postgres/drizzle/meta/0000_snapshot.json +592 -0
- package/task-queue/postgres/drizzle/meta/_journal.json +13 -0
- package/task-queue/postgres/drizzle.config.d.ts +2 -0
- package/task-queue/postgres/drizzle.config.js +11 -0
- package/task-queue/postgres/index.d.ts +4 -0
- package/task-queue/postgres/index.js +4 -0
- package/task-queue/postgres/module.d.ts +12 -0
- package/task-queue/postgres/module.js +28 -0
- package/task-queue/postgres/schemas.d.ts +16 -0
- package/task-queue/postgres/schemas.js +8 -0
- package/task-queue/postgres/task-queue.d.ts +83 -0
- package/task-queue/postgres/task-queue.js +1054 -0
- package/task-queue/postgres/task-queue.provider.d.ts +7 -0
- package/{queue/postgres/queue.provider.js → task-queue/postgres/task-queue.provider.js} +8 -8
- package/task-queue/postgres/task.model.d.ts +39 -0
- package/task-queue/postgres/task.model.js +178 -0
- package/{queue → task-queue}/provider.d.ts +3 -3
- package/task-queue/provider.js +2 -0
- package/{queue → task-queue}/task-context.d.ts +7 -7
- package/{queue → task-queue}/task-context.js +8 -8
- package/{queue/queue.d.ts → task-queue/task-queue.d.ts} +128 -59
- package/task-queue/task-queue.js +200 -0
- package/task-queue/tests/complex.test.d.ts +1 -0
- package/task-queue/tests/complex.test.js +299 -0
- package/task-queue/tests/dependencies.test.d.ts +1 -0
- package/task-queue/tests/dependencies.test.js +174 -0
- package/task-queue/tests/queue.test.d.ts +1 -0
- package/task-queue/tests/queue.test.js +334 -0
- package/task-queue/tests/worker.test.d.ts +1 -0
- package/task-queue/tests/worker.test.js +163 -0
- package/test1.js +1 -1
- package/test4.js +2 -2
- package/unit-test/index.d.ts +1 -0
- package/unit-test/index.js +1 -0
- package/unit-test/integration-setup.d.ts +55 -0
- package/unit-test/integration-setup.js +182 -0
- package/utils/patterns.d.ts +3 -0
- package/utils/patterns.js +6 -1
- package/audit/drizzle/0001_previous_network.sql +0 -2
- package/audit/drizzle/meta/0001_snapshot.json +0 -195
- package/queue/enqueue-batch.d.ts +0 -17
- package/queue/enqueue-batch.js +0 -18
- package/queue/postgres/drizzle/0000_zippy_moondragon.sql +0 -11
- package/queue/postgres/drizzle/0001_certain_wild_pack.sql +0 -2
- package/queue/postgres/drizzle/0002_dear_meggan.sql +0 -2
- package/queue/postgres/drizzle/0003_tricky_venom.sql +0 -30
- package/queue/postgres/drizzle/meta/0001_snapshot.json +0 -103
- package/queue/postgres/drizzle/meta/0002_snapshot.json +0 -90
- package/queue/postgres/drizzle/meta/0003_snapshot.json +0 -288
- package/queue/postgres/drizzle/meta/_journal.json +0 -34
- package/queue/postgres/index.d.ts +0 -4
- package/queue/postgres/index.js +0 -4
- package/queue/postgres/module.d.ts +0 -9
- package/queue/postgres/module.js +0 -29
- package/queue/postgres/queue.d.ts +0 -60
- package/queue/postgres/queue.js +0 -681
- package/queue/postgres/queue.provider.d.ts +0 -7
- package/queue/postgres/schemas.d.ts +0 -14
- package/queue/postgres/schemas.js +0 -6
- package/queue/postgres/task.model.d.ts +0 -24
- package/queue/postgres/task.model.js +0 -115
- package/queue/provider.js +0 -2
- package/queue/queue.js +0 -131
- package/queue/tests/queue.test.js +0 -623
- package/test3.d.ts +0 -1
- package/test3.js +0 -47
- /package/{queue/tests/queue.test.d.ts → api/server/tests/csrf.middleware.test.d.ts} +0 -0
- /package/circuit-breaker/postgres/drizzle/{0000_hard_shocker.sql → 0000_cooing_korath.sql} +0 -0
- /package/{queue → rate-limit}/postgres/drizzle.config.d.ts +0 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { defineEnum } from '../../enumeration/index.js';
|
|
11
|
+
import { NotFoundError } from '../../errors/not-found.error.js';
|
|
12
|
+
import { Injector, runInInjectionContext } from '../../injector/index.js';
|
|
13
|
+
import { Array, Enumeration, Integer, string, StringProperty } from '../../schema/index.js';
|
|
14
|
+
import { sql } from 'drizzle-orm';
|
|
15
|
+
import { beforeAll, describe, expect, test } from 'vitest';
|
|
16
|
+
import { Column, EmbeddedProperty, EncryptedProperty, Reference, Table } from '../decorators.js';
|
|
17
|
+
import { Entity } from '../entity.js';
|
|
18
|
+
import { JsonProperty, NumericDateProperty } from '../schemas/index.js';
|
|
19
|
+
import { configureOrm, Database } from '../server/index.js';
|
|
20
|
+
import { injectRepository } from '../server/repository.js';
|
|
21
|
+
describe('ORM Repository Regression (Integration)', () => {
|
|
22
|
+
let injector;
|
|
23
|
+
let db;
|
|
24
|
+
const schema = 'test_orm_regression';
|
|
25
|
+
const UserRole = defineEnum('UserRole', {
|
|
26
|
+
Admin: 'admin',
|
|
27
|
+
User: 'user',
|
|
28
|
+
});
|
|
29
|
+
class Address {
|
|
30
|
+
street;
|
|
31
|
+
number;
|
|
32
|
+
}
|
|
33
|
+
__decorate([
|
|
34
|
+
StringProperty(),
|
|
35
|
+
__metadata("design:type", String)
|
|
36
|
+
], Address.prototype, "street", void 0);
|
|
37
|
+
__decorate([
|
|
38
|
+
Integer(),
|
|
39
|
+
__metadata("design:type", Number)
|
|
40
|
+
], Address.prototype, "number", void 0);
|
|
41
|
+
let StandardEntity = class StandardEntity extends Entity {
|
|
42
|
+
name;
|
|
43
|
+
address;
|
|
44
|
+
secret;
|
|
45
|
+
role;
|
|
46
|
+
data;
|
|
47
|
+
tags;
|
|
48
|
+
birthday;
|
|
49
|
+
};
|
|
50
|
+
__decorate([
|
|
51
|
+
StringProperty(),
|
|
52
|
+
Column({ name: 'display_name' }),
|
|
53
|
+
__metadata("design:type", String)
|
|
54
|
+
], StandardEntity.prototype, "name", void 0);
|
|
55
|
+
__decorate([
|
|
56
|
+
EmbeddedProperty(Address),
|
|
57
|
+
__metadata("design:type", Address)
|
|
58
|
+
], StandardEntity.prototype, "address", void 0);
|
|
59
|
+
__decorate([
|
|
60
|
+
StringProperty(),
|
|
61
|
+
EncryptedProperty(),
|
|
62
|
+
__metadata("design:type", String)
|
|
63
|
+
], StandardEntity.prototype, "secret", void 0);
|
|
64
|
+
__decorate([
|
|
65
|
+
Enumeration(UserRole),
|
|
66
|
+
__metadata("design:type", String)
|
|
67
|
+
], StandardEntity.prototype, "role", void 0);
|
|
68
|
+
__decorate([
|
|
69
|
+
JsonProperty(),
|
|
70
|
+
__metadata("design:type", Object)
|
|
71
|
+
], StandardEntity.prototype, "data", void 0);
|
|
72
|
+
__decorate([
|
|
73
|
+
Array(string()),
|
|
74
|
+
__metadata("design:type", Array)
|
|
75
|
+
], StandardEntity.prototype, "tags", void 0);
|
|
76
|
+
__decorate([
|
|
77
|
+
NumericDateProperty(),
|
|
78
|
+
Column({ name: 'dob' }),
|
|
79
|
+
__metadata("design:type", Number)
|
|
80
|
+
], StandardEntity.prototype, "birthday", void 0);
|
|
81
|
+
StandardEntity = __decorate([
|
|
82
|
+
Table('standard_entities', { schema })
|
|
83
|
+
], StandardEntity);
|
|
84
|
+
let Post = class Post extends Entity {
|
|
85
|
+
title;
|
|
86
|
+
authorId;
|
|
87
|
+
};
|
|
88
|
+
__decorate([
|
|
89
|
+
StringProperty(),
|
|
90
|
+
__metadata("design:type", String)
|
|
91
|
+
], Post.prototype, "title", void 0);
|
|
92
|
+
__decorate([
|
|
93
|
+
StringProperty(),
|
|
94
|
+
Column({ name: 'author_id' }),
|
|
95
|
+
Reference(() => StandardEntity),
|
|
96
|
+
__metadata("design:type", String)
|
|
97
|
+
], Post.prototype, "authorId", void 0);
|
|
98
|
+
Post = __decorate([
|
|
99
|
+
Table('posts', { schema })
|
|
100
|
+
], Post);
|
|
101
|
+
beforeAll(async () => {
|
|
102
|
+
injector = new Injector('Test');
|
|
103
|
+
const encryptionSecret = new Uint8Array(32).fill(1);
|
|
104
|
+
configureOrm({
|
|
105
|
+
repositoryConfig: { schema },
|
|
106
|
+
connection: {
|
|
107
|
+
host: '127.0.0.1',
|
|
108
|
+
port: 5432,
|
|
109
|
+
user: 'tstdl',
|
|
110
|
+
password: 'wf7rq6glrk5jykne',
|
|
111
|
+
database: 'tstdl',
|
|
112
|
+
},
|
|
113
|
+
encryptionSecret
|
|
114
|
+
});
|
|
115
|
+
db = injector.resolve(Database);
|
|
116
|
+
await db.execute(sql `CREATE SCHEMA IF NOT EXISTS ${sql.identifier(schema)}`);
|
|
117
|
+
await db.execute(sql `DROP TABLE IF EXISTS ${sql.identifier(schema)}.${sql.identifier('posts')} CASCADE`);
|
|
118
|
+
await db.execute(sql `DROP TABLE IF EXISTS ${sql.identifier(schema)}.${sql.identifier('standard_entities')} CASCADE`);
|
|
119
|
+
await db.execute(sql `DROP TYPE IF EXISTS ${sql.identifier(schema)}.${sql.identifier('user_role')} CASCADE`);
|
|
120
|
+
await db.execute(sql `CREATE TYPE ${sql.identifier(schema)}.${sql.identifier('user_role')} AS ENUM ('admin', 'user')`);
|
|
121
|
+
await db.execute(sql `
|
|
122
|
+
CREATE TABLE ${sql.identifier(schema)}.${sql.identifier('standard_entities')} (
|
|
123
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
124
|
+
display_name TEXT NOT NULL,
|
|
125
|
+
address_street TEXT NOT NULL,
|
|
126
|
+
address_number INTEGER NOT NULL,
|
|
127
|
+
secret BYTEA NOT NULL,
|
|
128
|
+
role ${sql.identifier(schema)}.${sql.identifier('user_role')} NOT NULL,
|
|
129
|
+
data JSONB NOT NULL,
|
|
130
|
+
tags TEXT[] NOT NULL,
|
|
131
|
+
dob DATE NOT NULL,
|
|
132
|
+
revision INTEGER NOT NULL,
|
|
133
|
+
revision_timestamp TIMESTAMP WITH TIME ZONE NOT NULL,
|
|
134
|
+
create_timestamp TIMESTAMP WITH TIME ZONE NOT NULL,
|
|
135
|
+
delete_timestamp TIMESTAMP WITH TIME ZONE,
|
|
136
|
+
attributes JSONB NOT NULL DEFAULT '{}'
|
|
137
|
+
)
|
|
138
|
+
`);
|
|
139
|
+
await db.execute(sql `
|
|
140
|
+
CREATE TABLE ${sql.identifier(schema)}.${sql.identifier('posts')} (
|
|
141
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
142
|
+
title TEXT NOT NULL,
|
|
143
|
+
author_id UUID NOT NULL REFERENCES ${sql.identifier(schema)}.${sql.identifier('standard_entities')} (id),
|
|
144
|
+
revision INTEGER NOT NULL,
|
|
145
|
+
revision_timestamp TIMESTAMP WITH TIME ZONE NOT NULL,
|
|
146
|
+
create_timestamp TIMESTAMP WITH TIME ZONE NOT NULL,
|
|
147
|
+
delete_timestamp TIMESTAMP WITH TIME ZONE,
|
|
148
|
+
attributes JSONB NOT NULL DEFAULT '{}'
|
|
149
|
+
)
|
|
150
|
+
`);
|
|
151
|
+
});
|
|
152
|
+
test('should perform basic CRUD on standard entity', async () => {
|
|
153
|
+
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('standard_entities')} CASCADE`);
|
|
154
|
+
await runInInjectionContext(injector, async () => {
|
|
155
|
+
const repository = injectRepository(StandardEntity);
|
|
156
|
+
const entity = new StandardEntity();
|
|
157
|
+
entity.name = 'Test';
|
|
158
|
+
entity.address = { street: 'Main St', number: 123 };
|
|
159
|
+
entity.secret = 'my-secret-password';
|
|
160
|
+
entity.role = UserRole.User;
|
|
161
|
+
entity.data = { foo: 'bar' };
|
|
162
|
+
entity.tags = ['a', 'b'];
|
|
163
|
+
entity.birthday = 19716; // 2023-12-25
|
|
164
|
+
const inserted = await repository.insert(entity);
|
|
165
|
+
expect(inserted.id).toBeDefined();
|
|
166
|
+
expect(inserted.name).toBe('Test');
|
|
167
|
+
expect(inserted.address.street).toBe('Main St');
|
|
168
|
+
expect(inserted.secret).toBe('my-secret-password');
|
|
169
|
+
expect(inserted.role).toBe(UserRole.User);
|
|
170
|
+
expect(inserted.data).toEqual({ foo: 'bar' });
|
|
171
|
+
expect(inserted.tags).toEqual(['a', 'b']);
|
|
172
|
+
expect(inserted.birthday).toBe(19716);
|
|
173
|
+
// Verify DB encrypted state
|
|
174
|
+
const { rows: [row] } = await db.execute(sql `SELECT secret, dob FROM ${sql.identifier(schema)}.${sql.identifier('standard_entities')} WHERE id = ${inserted.id}`);
|
|
175
|
+
expect(row['secret']).toBeInstanceOf(Uint8Array);
|
|
176
|
+
expect(row['secret']).not.toEqual(new TextEncoder().encode('my-secret-password'));
|
|
177
|
+
expect(row['dob']).toBe('2023-12-25');
|
|
178
|
+
// Update
|
|
179
|
+
await repository.update(inserted.id, { name: 'Updated' });
|
|
180
|
+
const updated = await repository.load(inserted.id);
|
|
181
|
+
expect(updated.name).toBe('Updated');
|
|
182
|
+
expect(updated.secret).toBe('my-secret-password'); // Still readable
|
|
183
|
+
// Soft Delete
|
|
184
|
+
await repository.delete(inserted.id);
|
|
185
|
+
const deleted = await repository.tryLoad(inserted.id);
|
|
186
|
+
expect(deleted).toBeUndefined();
|
|
187
|
+
const { rows: [deletedRow] } = await db.execute(sql `SELECT delete_timestamp FROM ${sql.identifier(schema)}.${sql.identifier('standard_entities')} WHERE id = ${inserted.id}`);
|
|
188
|
+
expect(deletedRow['delete_timestamp']).not.toBeNull();
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
test('should support Reference decorator', async () => {
|
|
192
|
+
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('standard_entities')} CASCADE`);
|
|
193
|
+
await runInInjectionContext(injector, async () => {
|
|
194
|
+
const standardRepository = injectRepository(StandardEntity);
|
|
195
|
+
const postRepository = injectRepository(Post);
|
|
196
|
+
const author = Object.assign(new StandardEntity(), { name: 'Author', address: { street: 'A', number: 1 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 });
|
|
197
|
+
const insertedAuthor = await standardRepository.insert(author);
|
|
198
|
+
const post = Object.assign(new Post(), { title: 'Hello World', authorId: insertedAuthor.id });
|
|
199
|
+
const insertedPost = await postRepository.insert(post);
|
|
200
|
+
expect(insertedPost.authorId).toBe(insertedAuthor.id);
|
|
201
|
+
const loadedPost = await postRepository.load(insertedPost.id);
|
|
202
|
+
expect(loadedPost.authorId).toBe(insertedAuthor.id);
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
test('should perform insertMany and loadMany', async () => {
|
|
206
|
+
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('standard_entities')} CASCADE`);
|
|
207
|
+
await runInInjectionContext(injector, async () => {
|
|
208
|
+
const repository = injectRepository(StandardEntity);
|
|
209
|
+
const entities = [
|
|
210
|
+
Object.assign(new StandardEntity(), { name: 'E1', address: { street: 'S1', number: 1 }, secret: 's1', role: UserRole.User, data: {}, tags: [], birthday: 0 }),
|
|
211
|
+
Object.assign(new StandardEntity(), { name: 'E2', address: { street: 'S2', number: 2 }, secret: 's2', role: UserRole.Admin, data: {}, tags: [], birthday: 0 }),
|
|
212
|
+
];
|
|
213
|
+
const inserted = await repository.insertMany(entities);
|
|
214
|
+
expect(inserted).toHaveLength(2);
|
|
215
|
+
const loaded = await repository.loadMany(inserted.map((e) => e.id));
|
|
216
|
+
expect(loaded).toHaveLength(2);
|
|
217
|
+
expect(loaded.map((e) => e.name).sort()).toEqual(['E1', 'E2']);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
test('should support upsert', async () => {
|
|
221
|
+
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('standard_entities')} CASCADE`);
|
|
222
|
+
await runInInjectionContext(injector, async () => {
|
|
223
|
+
const repository = injectRepository(StandardEntity);
|
|
224
|
+
const entity = Object.assign(new StandardEntity(), { name: 'Original', address: { street: 'S', number: 1 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 });
|
|
225
|
+
const inserted = await repository.insert(entity);
|
|
226
|
+
// Upsert: update existing
|
|
227
|
+
const updateEntity = Object.assign(new StandardEntity(), { id: inserted.id, name: 'Upserted', address: { street: 'S', number: 1 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 });
|
|
228
|
+
const upserted = await repository.upsert('id', updateEntity);
|
|
229
|
+
expect(upserted.id).toBe(inserted.id);
|
|
230
|
+
expect(upserted.name).toBe('Upserted');
|
|
231
|
+
// Verify DB
|
|
232
|
+
const loaded = await repository.load(inserted.id);
|
|
233
|
+
expect(loaded.name).toBe('Upserted');
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
test('should support complex queries with $or and $and', async () => {
|
|
237
|
+
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('standard_entities')} CASCADE`);
|
|
238
|
+
await runInInjectionContext(injector, async () => {
|
|
239
|
+
const repository = injectRepository(StandardEntity);
|
|
240
|
+
await repository.insertMany([
|
|
241
|
+
Object.assign(new StandardEntity(), { name: 'A', address: { street: 'S1', number: 10 }, secret: 's', role: UserRole.Admin, data: {}, tags: [], birthday: 0 }),
|
|
242
|
+
Object.assign(new StandardEntity(), { name: 'B', address: { street: 'S2', number: 20 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 }),
|
|
243
|
+
Object.assign(new StandardEntity(), { name: 'C', address: { street: 'S3', number: 30 }, secret: 's', role: UserRole.Admin, data: {}, tags: [], birthday: 0 }),
|
|
244
|
+
]);
|
|
245
|
+
const results = await repository.loadManyByQuery({
|
|
246
|
+
$or: [
|
|
247
|
+
{ name: 'B' },
|
|
248
|
+
{ $and: [{ role: UserRole.Admin }, { 'address.number': { $gt: 25 } }] }
|
|
249
|
+
]
|
|
250
|
+
});
|
|
251
|
+
expect(results).toHaveLength(2);
|
|
252
|
+
expect(results.map((r) => r.name).sort()).toEqual(['B', 'C']);
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
test('should support hasAll', async () => {
|
|
256
|
+
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('standard_entities')} CASCADE`);
|
|
257
|
+
await runInInjectionContext(injector, async () => {
|
|
258
|
+
const repository = injectRepository(StandardEntity);
|
|
259
|
+
const inserted = await repository.insertMany([
|
|
260
|
+
Object.assign(new StandardEntity(), { name: 'E1', address: { street: 'S1', number: 1 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 }),
|
|
261
|
+
Object.assign(new StandardEntity(), { name: 'E2', address: { street: 'S2', number: 2 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 }),
|
|
262
|
+
]);
|
|
263
|
+
expect(await repository.hasAll(inserted.map((e) => e.id))).toBe(true);
|
|
264
|
+
expect(await repository.hasAll([...inserted.map((e) => e.id), '00000000-0000-0000-0000-000000000000'])).toBe(false);
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
test('should throw NotFoundError on missing load', async () => {
|
|
268
|
+
await runInInjectionContext(injector, async () => {
|
|
269
|
+
const repository = injectRepository(StandardEntity);
|
|
270
|
+
await expect(repository.load('00000000-0000-0000-0000-000000000000')).rejects.toThrow(NotFoundError);
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
test('should support upsertMany', async () => {
|
|
274
|
+
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('standard_entities')} CASCADE`);
|
|
275
|
+
await runInInjectionContext(injector, async () => {
|
|
276
|
+
const repository = injectRepository(StandardEntity);
|
|
277
|
+
const e1 = await repository.insert(Object.assign(new StandardEntity(), { name: 'E1', address: { street: 'S1', number: 1 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 }));
|
|
278
|
+
const upsertData = [
|
|
279
|
+
Object.assign(new StandardEntity(), { id: e1.id, name: 'E1-Updated', address: { street: 'S1', number: 1 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 }),
|
|
280
|
+
Object.assign(new StandardEntity(), { name: 'E2', address: { street: 'S2', number: 2 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 }),
|
|
281
|
+
];
|
|
282
|
+
await repository.upsertMany('id', upsertData);
|
|
283
|
+
const all = await repository.loadAll();
|
|
284
|
+
expect(all).toHaveLength(2);
|
|
285
|
+
expect(all.find((e) => e.id === e1.id).name).toBe('E1-Updated');
|
|
286
|
+
expect(all.find((e) => e.name === 'E2')).toBeDefined();
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
test('should support deleteManyByQuery', async () => {
|
|
290
|
+
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('standard_entities')} CASCADE`);
|
|
291
|
+
await runInInjectionContext(injector, async () => {
|
|
292
|
+
const repository = injectRepository(StandardEntity);
|
|
293
|
+
await repository.insertMany([
|
|
294
|
+
Object.assign(new StandardEntity(), { name: 'A', address: { street: 'S', number: 1 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 }),
|
|
295
|
+
Object.assign(new StandardEntity(), { name: 'A', address: { street: 'S', number: 2 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 }),
|
|
296
|
+
Object.assign(new StandardEntity(), { name: 'B', address: { street: 'S', number: 3 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 }),
|
|
297
|
+
]);
|
|
298
|
+
await repository.deleteManyByQuery({ name: 'A' });
|
|
299
|
+
const remaining = await repository.loadAll();
|
|
300
|
+
expect(remaining).toHaveLength(1);
|
|
301
|
+
expect(remaining[0].name).toBe('B');
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
test('should support tryInsert', async () => {
|
|
305
|
+
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('standard_entities')} CASCADE`);
|
|
306
|
+
await runInInjectionContext(injector, async () => {
|
|
307
|
+
const repository = injectRepository(StandardEntity);
|
|
308
|
+
const entity = Object.assign(new StandardEntity(), { name: 'E1', address: { street: 'S', number: 1 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 });
|
|
309
|
+
const inserted = await repository.tryInsert(entity);
|
|
310
|
+
expect(inserted).toBeDefined();
|
|
311
|
+
// Conflict
|
|
312
|
+
const conflict = await repository.tryInsert(Object.assign(new StandardEntity(), { id: inserted.id, name: 'Conflict', address: { street: 'S', number: 1 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 }));
|
|
313
|
+
expect(conflict).toBeUndefined();
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
test('should support insertManyIfNotExists', async () => {
|
|
317
|
+
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('standard_entities')} CASCADE`);
|
|
318
|
+
await runInInjectionContext(injector, async () => {
|
|
319
|
+
const repository = injectRepository(StandardEntity);
|
|
320
|
+
const e1 = await repository.insert(Object.assign(new StandardEntity(), { name: 'E1', address: { street: 'S', number: 1 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 }));
|
|
321
|
+
const data = [
|
|
322
|
+
Object.assign(new StandardEntity(), { id: e1.id, name: 'E1-Conflict', address: { street: 'S', number: 1 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 }),
|
|
323
|
+
Object.assign(new StandardEntity(), { name: 'E2', address: { street: 'S', number: 2 }, secret: 's', role: UserRole.User, data: {}, tags: [], birthday: 0 }),
|
|
324
|
+
];
|
|
325
|
+
const results = await repository.insertManyIfNotExists('id', data);
|
|
326
|
+
expect(results).toHaveLength(1);
|
|
327
|
+
expect(results[0].name).toBe('E2');
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { beforeAll, describe, expect, test } from 'vitest';
|
|
11
|
+
import { sql } from 'drizzle-orm';
|
|
12
|
+
import { StringProperty } from '../../schema/index.js';
|
|
13
|
+
import { Entity } from '../entity.js';
|
|
14
|
+
import { Table } from '../decorators.js';
|
|
15
|
+
import { injectRepository } from '../server/repository.js';
|
|
16
|
+
import { configureOrm, Database } from '../server/index.js';
|
|
17
|
+
import { Injector, runInInjectionContext } from '../../injector/index.js';
|
|
18
|
+
describe('ORM Repository Search Coverage', () => {
|
|
19
|
+
let injector;
|
|
20
|
+
let db;
|
|
21
|
+
const schema = 'test_orm_search_coverage';
|
|
22
|
+
let Document = class Document extends Entity {
|
|
23
|
+
content;
|
|
24
|
+
};
|
|
25
|
+
__decorate([
|
|
26
|
+
StringProperty(),
|
|
27
|
+
__metadata("design:type", String)
|
|
28
|
+
], Document.prototype, "content", void 0);
|
|
29
|
+
Document = __decorate([
|
|
30
|
+
Table('docs', { schema })
|
|
31
|
+
], Document);
|
|
32
|
+
beforeAll(async () => {
|
|
33
|
+
injector = new Injector('TestSearchCoverage');
|
|
34
|
+
configureOrm({
|
|
35
|
+
repositoryConfig: { schema },
|
|
36
|
+
connection: {
|
|
37
|
+
host: '127.0.0.1', port: 5432, user: 'tstdl', password: 'wf7rq6glrk5jykne', database: 'tstdl',
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
db = injector.resolve(Database);
|
|
41
|
+
await db.execute(sql `CREATE SCHEMA IF NOT EXISTS ${sql.identifier(schema)}`);
|
|
42
|
+
await db.execute(sql `DROP TABLE IF EXISTS ${sql.identifier(schema)}.${sql.identifier('docs')} CASCADE`);
|
|
43
|
+
await db.execute(sql `CREATE EXTENSION IF NOT EXISTS pg_trgm`); // Enable trigram
|
|
44
|
+
await db.execute(sql `
|
|
45
|
+
CREATE TABLE ${sql.identifier(schema)}.${sql.identifier('docs')} (
|
|
46
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
47
|
+
content TEXT NOT NULL,
|
|
48
|
+
revision INTEGER NOT NULL,
|
|
49
|
+
revision_timestamp TIMESTAMP WITH TIME ZONE NOT NULL,
|
|
50
|
+
create_timestamp TIMESTAMP WITH TIME ZONE NOT NULL,
|
|
51
|
+
delete_timestamp TIMESTAMP WITH TIME ZONE,
|
|
52
|
+
attributes JSONB NOT NULL DEFAULT '{}'
|
|
53
|
+
)
|
|
54
|
+
`);
|
|
55
|
+
});
|
|
56
|
+
test('tsvector search with order function accessing score', async () => {
|
|
57
|
+
await runInInjectionContext(injector, async () => {
|
|
58
|
+
const repository = injectRepository(Document);
|
|
59
|
+
await repository.insert(Object.assign(new Document(), { content: 'Apple Banana' }));
|
|
60
|
+
const results = await repository.search({
|
|
61
|
+
query: { $tsvector: { fields: ['content'], query: 'Apple' } },
|
|
62
|
+
order: (cols) => [[cols.score, 'desc']]
|
|
63
|
+
});
|
|
64
|
+
expect(results).toHaveLength(1);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
test('trigram search basic', async () => {
|
|
68
|
+
await runInInjectionContext(injector, async () => {
|
|
69
|
+
const repository = injectRepository(Document);
|
|
70
|
+
await repository.insert(Object.assign(new Document(), { content: 'Trigram Test' }));
|
|
71
|
+
const results = await repository.search({
|
|
72
|
+
query: { $trigram: { fields: ['content'], query: 'Trigram' } },
|
|
73
|
+
});
|
|
74
|
+
expect(results).toHaveLength(1);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
test('trigram search with threshold (phrase)', async () => {
|
|
78
|
+
await runInInjectionContext(injector, async () => {
|
|
79
|
+
const repository = injectRepository(Document);
|
|
80
|
+
await repository.insert(Object.assign(new Document(), { content: 'Phrase Match' }));
|
|
81
|
+
// Set threshold to a value that allows matching (default is 0.3)
|
|
82
|
+
await db.execute(sql `SET pg_trgm.similarity_threshold = 0.1`);
|
|
83
|
+
const results = await repository.search({
|
|
84
|
+
query: { $trigram: { fields: ['content'], query: 'Phrase', type: 'phrase', threshold: 0.1 } },
|
|
85
|
+
});
|
|
86
|
+
expect(results).toHaveLength(1);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
test('trigram search with threshold (word)', async () => {
|
|
90
|
+
await runInInjectionContext(injector, async () => {
|
|
91
|
+
const repository = injectRepository(Document);
|
|
92
|
+
await repository.insert(Object.assign(new Document(), { content: 'Word Match' }));
|
|
93
|
+
await db.execute(sql `SET pg_trgm.word_similarity_threshold = 0.1`);
|
|
94
|
+
const results = await repository.search({
|
|
95
|
+
query: { $trigram: { fields: ['content'], query: 'Word', type: 'word', threshold: 0.1 } },
|
|
96
|
+
});
|
|
97
|
+
expect(results).toHaveLength(1);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
test('trigram search with threshold (strict-word)', async () => {
|
|
101
|
+
await runInInjectionContext(injector, async () => {
|
|
102
|
+
const repository = injectRepository(Document);
|
|
103
|
+
await repository.insert(Object.assign(new Document(), { content: 'Strict Match' }));
|
|
104
|
+
await db.execute(sql `SET pg_trgm.strict_word_similarity_threshold = 0.1`);
|
|
105
|
+
const results = await repository.search({
|
|
106
|
+
query: { $trigram: { fields: ['content'], query: 'Strict', type: 'strict-word', threshold: 0.1 } },
|
|
107
|
+
});
|
|
108
|
+
expect(results).toHaveLength(1);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
test('trigram search with order function', async () => {
|
|
112
|
+
await runInInjectionContext(injector, async () => {
|
|
113
|
+
const repository = injectRepository(Document);
|
|
114
|
+
const results = await repository.search({
|
|
115
|
+
query: { $trigram: { fields: ['content'], query: 'Order' } },
|
|
116
|
+
order: ({ score }) => [[score, 'desc']]
|
|
117
|
+
});
|
|
118
|
+
// Even if no results, code path is exercised?
|
|
119
|
+
// No, order function is called only if query is executed.
|
|
120
|
+
// Insert something first
|
|
121
|
+
await repository.insert(Object.assign(new Document(), { content: 'Order Test' }));
|
|
122
|
+
const results2 = await repository.search({
|
|
123
|
+
query: { $trigram: { fields: ['content'], query: 'Order' } },
|
|
124
|
+
order: ({ score }) => [[score, 'desc']]
|
|
125
|
+
});
|
|
126
|
+
expect(results2).toHaveLength(1);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { sql } from 'drizzle-orm';
|
|
11
|
+
import { beforeAll, describe, expect, test } from 'vitest';
|
|
12
|
+
import { Injector, runInInjectionContext } from '../../injector/index.js';
|
|
13
|
+
import { StringProperty } from '../../schema/index.js';
|
|
14
|
+
import { dropTables, setupIntegrationTest } from '../../unit-test/index.js';
|
|
15
|
+
import { Table } from '../decorators.js';
|
|
16
|
+
import { Entity } from '../entity.js';
|
|
17
|
+
import { Database } from '../server/index.js';
|
|
18
|
+
import { injectRepository } from '../server/repository.js';
|
|
19
|
+
describe('ORM Repository Search', () => {
|
|
20
|
+
let injector;
|
|
21
|
+
let database;
|
|
22
|
+
const schema = 'test_orm_search';
|
|
23
|
+
let SearchEntity = class SearchEntity extends Entity {
|
|
24
|
+
title;
|
|
25
|
+
content;
|
|
26
|
+
};
|
|
27
|
+
__decorate([
|
|
28
|
+
StringProperty(),
|
|
29
|
+
__metadata("design:type", String)
|
|
30
|
+
], SearchEntity.prototype, "title", void 0);
|
|
31
|
+
__decorate([
|
|
32
|
+
StringProperty(),
|
|
33
|
+
__metadata("design:type", String)
|
|
34
|
+
], SearchEntity.prototype, "content", void 0);
|
|
35
|
+
SearchEntity = __decorate([
|
|
36
|
+
Table('search_entities', { schema })
|
|
37
|
+
], SearchEntity);
|
|
38
|
+
beforeAll(async () => {
|
|
39
|
+
({ injector, database } = await setupIntegrationTest({ orm: { schema } }));
|
|
40
|
+
await dropTables(database, schema, ['search_entities']);
|
|
41
|
+
await database.execute(sql `
|
|
42
|
+
CREATE TABLE ${sql.identifier(schema)}.${sql.identifier('search_entities')} (
|
|
43
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
44
|
+
title TEXT NOT NULL,
|
|
45
|
+
content TEXT NOT NULL,
|
|
46
|
+
revision INTEGER NOT NULL,
|
|
47
|
+
revision_timestamp TIMESTAMP WITH TIME ZONE NOT NULL,
|
|
48
|
+
create_timestamp TIMESTAMP WITH TIME ZONE NOT NULL,
|
|
49
|
+
delete_timestamp TIMESTAMP WITH TIME ZONE,
|
|
50
|
+
attributes JSONB NOT NULL DEFAULT '{}'
|
|
51
|
+
)
|
|
52
|
+
`);
|
|
53
|
+
});
|
|
54
|
+
test('should support search with score transformer', async () => {
|
|
55
|
+
await runInInjectionContext(injector, async () => {
|
|
56
|
+
const repository = injectRepository(SearchEntity);
|
|
57
|
+
await repository.insert(Object.assign(new SearchEntity(), { title: 'Hello World', content: 'Some content here' }));
|
|
58
|
+
const results = await repository.search({
|
|
59
|
+
query: { $tsvector: { fields: ['title'], query: 'hello' } },
|
|
60
|
+
score: (score) => sql `(${score}) * 100`,
|
|
61
|
+
});
|
|
62
|
+
expect(results).toHaveLength(1);
|
|
63
|
+
expect(results[0].score).toBeGreaterThan(1); // normalized score is small, * 100 should be > 1
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
test('should support search with highlight (column names)', async () => {
|
|
67
|
+
await runInInjectionContext(injector, async () => {
|
|
68
|
+
const repository = injectRepository(SearchEntity);
|
|
69
|
+
await repository.insert(Object.assign(new SearchEntity(), { title: 'Highlander', content: 'There can be only one' }));
|
|
70
|
+
const results = await repository.search({
|
|
71
|
+
query: { $tsvector: { fields: ['title', 'content'], query: 'Highlander' } },
|
|
72
|
+
highlight: { source: 'title' },
|
|
73
|
+
});
|
|
74
|
+
expect(results).toHaveLength(1);
|
|
75
|
+
expect(results[0].highlight).toContain('<b>Highlander</b>');
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
test('should support search with highlight (raw SQL)', async () => {
|
|
79
|
+
await runInInjectionContext(injector, async () => {
|
|
80
|
+
const repository = injectRepository(SearchEntity);
|
|
81
|
+
await repository.insert(Object.assign(new SearchEntity(), { title: 'SQL Master', content: 'Database wizard' }));
|
|
82
|
+
const results = await repository.search({
|
|
83
|
+
query: { $tsvector: { fields: ['title', 'content'], query: 'Master' } },
|
|
84
|
+
highlight: { source: sql `title || ' ' || content` },
|
|
85
|
+
});
|
|
86
|
+
expect(results).toHaveLength(1);
|
|
87
|
+
expect(results[0].highlight).toContain('<b>Master</b>');
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
test('should support ParadeDB search with highlightPositions', async () => {
|
|
91
|
+
// This test requires pg_search extension to be installed.
|
|
92
|
+
try {
|
|
93
|
+
await database.execute(sql `CREATE EXTENSION IF NOT EXISTS pg_search`);
|
|
94
|
+
await database.execute(sql `CREATE INDEX idx_search_parade ON ${sql.identifier(schema)}.${sql.identifier('search_entities')} USING bm25 (id, title, content) WITH (key_field='id')`);
|
|
95
|
+
}
|
|
96
|
+
catch (e) {
|
|
97
|
+
console.warn('ParadeDB (pg_search) not available or index creation failed, skipping ParadeDB test.', e);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
await runInInjectionContext(injector, async () => {
|
|
101
|
+
const repository = injectRepository(SearchEntity);
|
|
102
|
+
await repository.insert(Object.assign(new SearchEntity(), { title: 'Parade', content: 'ParadeDB is fast' }));
|
|
103
|
+
const results = await repository.search({
|
|
104
|
+
query: { $parade: { fields: ['title'], query: 'Parade' } },
|
|
105
|
+
highlight: { source: 'title', includePositions: true },
|
|
106
|
+
});
|
|
107
|
+
expect(results).toHaveLength(1);
|
|
108
|
+
expect(results[0].highlight).toBeDefined();
|
|
109
|
+
expect(results[0].highlightPositions).toBeDefined();
|
|
110
|
+
expect(isArray(results[0].highlightPositions)).toBe(true);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
function isArray(val) {
|
|
115
|
+
return Array.isArray(val);
|
|
116
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|