@rebasepro/server-postgresql 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -6
- package/dist/common/src/util/entities.d.ts +2 -2
- package/dist/common/src/util/relations.d.ts +1 -1
- package/dist/index.es.js +1250 -1665
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +1196 -1611
- package/dist/index.umd.js.map +1 -1
- package/dist/server-postgresql/src/PostgresAdapter.d.ts +6 -0
- package/dist/server-postgresql/src/PostgresBackendDriver.d.ts +2 -1
- package/dist/server-postgresql/src/PostgresBootstrapper.d.ts +0 -5
- package/dist/server-postgresql/src/auth/ensure-tables.d.ts +2 -1
- package/dist/server-postgresql/src/auth/services.d.ts +37 -15
- package/dist/server-postgresql/src/index.d.ts +1 -0
- package/dist/server-postgresql/src/schema/auth-schema.d.ts +43 -856
- package/dist/server-postgresql/src/schema/default-collections.d.ts +2 -0
- package/dist/server-postgresql/src/schema/doctor.d.ts +10 -1
- package/dist/server-postgresql/src/schema/introspect-db-logic.d.ts +1 -0
- package/dist/server-postgresql/src/services/entity-helpers.d.ts +1 -1
- package/dist/server-postgresql/src/services/realtimeService.d.ts +12 -0
- package/dist/server-postgresql/src/websocket.d.ts +2 -1
- package/dist/types/src/controllers/auth.d.ts +9 -8
- package/dist/types/src/controllers/client.d.ts +3 -0
- package/dist/types/src/types/auth_adapter.d.ts +356 -0
- package/dist/types/src/types/collections.d.ts +67 -2
- package/dist/types/src/types/database_adapter.d.ts +94 -0
- package/dist/types/src/types/entity_actions.d.ts +7 -1
- package/dist/types/src/types/entity_callbacks.d.ts +1 -1
- package/dist/types/src/types/entity_views.d.ts +36 -1
- package/dist/types/src/types/index.d.ts +2 -0
- package/dist/types/src/types/plugins.d.ts +1 -1
- package/dist/types/src/types/properties.d.ts +24 -5
- package/dist/types/src/types/property_config.d.ts +6 -2
- package/dist/types/src/types/relations.d.ts +1 -1
- package/dist/types/src/types/translations.d.ts +8 -0
- package/dist/types/src/users/user.d.ts +5 -0
- package/package.json +21 -15
- package/src/PostgresAdapter.ts +59 -0
- package/src/PostgresBackendDriver.ts +57 -8
- package/src/PostgresBootstrapper.ts +35 -15
- package/src/auth/ensure-tables.ts +82 -189
- package/src/auth/services.ts +421 -170
- package/src/cli.ts +44 -13
- package/src/data-transformer.ts +78 -8
- package/src/history/HistoryService.ts +25 -2
- package/src/index.ts +1 -0
- package/src/schema/auth-schema.ts +130 -98
- package/src/schema/default-collections.ts +68 -0
- package/src/schema/doctor-cli.ts +5 -1
- package/src/schema/doctor.ts +85 -8
- package/src/schema/generate-drizzle-schema-logic.ts +74 -27
- package/src/schema/generate-drizzle-schema.ts +13 -3
- package/src/schema/introspect-db-inference.ts +5 -5
- package/src/schema/introspect-db-logic.ts +9 -2
- package/src/schema/introspect-db.ts +14 -3
- package/src/services/EntityFetchService.ts +5 -5
- package/src/services/RelationService.ts +2 -2
- package/src/services/entity-helpers.ts +1 -1
- package/src/services/realtimeService.ts +145 -136
- package/src/utils/drizzle-conditions.ts +16 -2
- package/src/websocket.ts +113 -37
- package/test/auth-services.test.ts +163 -74
- package/test/data-transformer-hardening.test.ts +57 -0
- package/test/data-transformer.test.ts +43 -0
- package/test/generate-drizzle-schema.test.ts +7 -5
- package/test/introspect-db-utils.test.ts +4 -1
- package/test/postgresDataDriver.test.ts +17 -0
- package/test/realtimeService.test.ts +7 -7
- package/test/websocket.test.ts +139 -0
- package/examples/sdk-demo/node_modules/esbuild/LICENSE.md +0 -21
- package/examples/sdk-demo/node_modules/esbuild/README.md +0 -3
- package/examples/sdk-demo/node_modules/esbuild/bin/esbuild +0 -223
- package/examples/sdk-demo/node_modules/esbuild/install.js +0 -289
- package/examples/sdk-demo/node_modules/esbuild/lib/main.d.ts +0 -716
- package/examples/sdk-demo/node_modules/esbuild/lib/main.js +0 -2242
- package/examples/sdk-demo/node_modules/esbuild/package.json +0 -49
|
@@ -1159,7 +1159,7 @@ export class RelationService {
|
|
|
1159
1159
|
if (parentFKValue !== null && parentFKValue !== undefined) {
|
|
1160
1160
|
await tx.update(targetTable)
|
|
1161
1161
|
.set({ [targetFKColName]: null })
|
|
1162
|
-
.where(eq(targetFKCol, parentFKValue
|
|
1162
|
+
.where(eq(targetFKCol, String(parentFKValue)));
|
|
1163
1163
|
}
|
|
1164
1164
|
continue;
|
|
1165
1165
|
}
|
|
@@ -1172,7 +1172,7 @@ export class RelationService {
|
|
|
1172
1172
|
if (parentFKValue !== null && parentFKValue !== undefined) {
|
|
1173
1173
|
await tx.update(targetTable)
|
|
1174
1174
|
.set({ [targetFKColName]: null })
|
|
1175
|
-
.where(eq(targetFKCol, parentFKValue
|
|
1175
|
+
.where(eq(targetFKCol, String(parentFKValue)));
|
|
1176
1176
|
} else {
|
|
1177
1177
|
console.warn(`Cannot set joinPath relation '${relation.relationName}' because parent FK value is null/undefined`);
|
|
1178
1178
|
continue;
|
|
@@ -13,7 +13,7 @@ import { getTableName } from "@rebasepro/common";
|
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Interface for Drizzle column metadata introspection.
|
|
16
|
-
* Replaces unsafe `as
|
|
16
|
+
* Replaces unsafe `as Record<string, unknown>` double-cast chains.
|
|
17
17
|
*/
|
|
18
18
|
export interface DrizzleColumnMeta {
|
|
19
19
|
columnType?: string;
|
|
@@ -23,6 +23,10 @@ export interface SubscriptionAuthContext {
|
|
|
23
23
|
roles: string[];
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
interface DataDriverWithData extends DataDriver {
|
|
27
|
+
data: unknown;
|
|
28
|
+
}
|
|
29
|
+
|
|
26
30
|
type RealTimeListenCollectionProps = ListenCollectionProps & {
|
|
27
31
|
subscriptionId: string
|
|
28
32
|
};
|
|
@@ -283,29 +287,18 @@ export class RealtimeService extends EventEmitter implements RealtimeProvider {
|
|
|
283
287
|
});
|
|
284
288
|
|
|
285
289
|
// Send initial data
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
path: request.path,
|
|
290
|
-
collection: collection,
|
|
291
|
-
filter: request.filter,
|
|
292
|
-
orderBy: request.orderBy,
|
|
293
|
-
order: request.order,
|
|
294
|
-
limit: request.limit,
|
|
295
|
-
startAfter: request.startAfter,
|
|
296
|
-
searchString: request.searchString
|
|
297
|
-
});
|
|
298
|
-
} else {
|
|
299
|
-
entities = await this.entityService.fetchCollection(request.path, {
|
|
290
|
+
const entities = await this.fetchCollectionWithAuth(
|
|
291
|
+
request.path,
|
|
292
|
+
{
|
|
300
293
|
filter: request.filter,
|
|
301
294
|
orderBy: request.orderBy,
|
|
302
295
|
order: request.order,
|
|
303
296
|
limit: request.limit,
|
|
304
297
|
startAfter: request.startAfter as Record<string, unknown> | undefined,
|
|
305
|
-
databaseId: request.collection?.databaseId,
|
|
306
298
|
searchString: request.searchString
|
|
307
|
-
}
|
|
308
|
-
|
|
299
|
+
},
|
|
300
|
+
authContext
|
|
301
|
+
);
|
|
309
302
|
|
|
310
303
|
this.sendCollectionUpdate(clientId, subscriptionId, entities);
|
|
311
304
|
|
|
@@ -338,20 +331,11 @@ export class RealtimeService extends EventEmitter implements RealtimeProvider {
|
|
|
338
331
|
});
|
|
339
332
|
|
|
340
333
|
// Send initial data
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
collection: collection
|
|
347
|
-
});
|
|
348
|
-
} else {
|
|
349
|
-
entity = await this.entityService.fetchEntity(
|
|
350
|
-
request.path,
|
|
351
|
-
request.entityId,
|
|
352
|
-
request.collection?.databaseId
|
|
353
|
-
);
|
|
354
|
-
}
|
|
334
|
+
const entity = await this.fetchEntityWithAuth(
|
|
335
|
+
request.path,
|
|
336
|
+
String(request.entityId),
|
|
337
|
+
authContext
|
|
338
|
+
);
|
|
355
339
|
|
|
356
340
|
this.sendEntityUpdate(clientId, subscriptionId, entity || null);
|
|
357
341
|
|
|
@@ -565,29 +549,28 @@ export class RealtimeService extends EventEmitter implements RealtimeProvider {
|
|
|
565
549
|
searchString: collectionRequest.searchString
|
|
566
550
|
});
|
|
567
551
|
|
|
568
|
-
//
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
{
|
|
552
|
+
// Always wrap in a transaction with session vars, defaulting to anonymous context if missing
|
|
553
|
+
const activeAuth = authContext || { userId: "anon", roles: ["anon"] };
|
|
554
|
+
return await this.db.transaction(async (tx) => {
|
|
555
|
+
await tx.execute(drizzleSql`SELECT set_config('app.user_id', ${activeAuth.userId}, true)`);
|
|
556
|
+
await tx.execute(drizzleSql`SELECT set_config('app.user_roles', ${activeAuth.roles.join(",")}, true)`);
|
|
557
|
+
await tx.execute(drizzleSql`SELECT set_config('app.jwt', ${JSON.stringify({ sub: activeAuth.userId, roles: activeAuth.roles })}, true)`);
|
|
558
|
+
const txEntityService = new EntityService(tx, this.registry);
|
|
559
|
+
let fetchedEntities;
|
|
560
|
+
if (collectionRequest.searchString) {
|
|
561
|
+
fetchedEntities = await txEntityService.searchEntities(
|
|
562
|
+
notifyPath,
|
|
563
|
+
collectionRequest.searchString,
|
|
564
|
+
{
|
|
582
565
|
filter: collectionRequest.filter as FilterValues<string>,
|
|
583
566
|
orderBy: collectionRequest.orderBy,
|
|
584
567
|
order: collectionRequest.order,
|
|
585
568
|
limit: collectionRequest.limit,
|
|
586
569
|
databaseId: collectionRequest.databaseId
|
|
587
570
|
}
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
571
|
+
);
|
|
572
|
+
} else {
|
|
573
|
+
fetchedEntities = await txEntityService.fetchCollection(notifyPath, {
|
|
591
574
|
filter: collectionRequest.filter as FilterValues<string>,
|
|
592
575
|
orderBy: collectionRequest.orderBy,
|
|
593
576
|
order: collectionRequest.order,
|
|
@@ -596,52 +579,48 @@ roles: authContext.roles })}, true)`);
|
|
|
596
579
|
startAfter: collectionRequest.startAfter,
|
|
597
580
|
databaseId: collectionRequest.databaseId
|
|
598
581
|
});
|
|
599
|
-
|
|
582
|
+
}
|
|
600
583
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
584
|
+
// Re-apply `afterRead` lifecycle hooks to ensure consistent data structures
|
|
585
|
+
// between the initial driver fetch and this RLS-bound refetch.
|
|
586
|
+
const registryCollection = this.registry.getCollectionByPath(notifyPath);
|
|
587
|
+
const resolvedCollection = collection ? { ...collection,
|
|
605
588
|
...registryCollection } as EntityCollection : registryCollection as EntityCollection;
|
|
606
589
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
return fetchedEntities;
|
|
641
|
-
});
|
|
642
|
-
}
|
|
590
|
+
const callbacks = resolvedCollection?.callbacks;
|
|
591
|
+
const propertyCallbacks = resolvedCollection?.properties ? buildPropertyCallbacks(resolvedCollection.properties) : undefined;
|
|
592
|
+
|
|
593
|
+
if (callbacks?.afterRead || propertyCallbacks?.afterRead) {
|
|
594
|
+
const contextForCallback = {
|
|
595
|
+
user: { uid: activeAuth.userId, roles: activeAuth.roles },
|
|
596
|
+
driver: this.driver,
|
|
597
|
+
data: (this.driver && "data" in this.driver) ? (this.driver as DataDriverWithData).data : undefined
|
|
598
|
+
} as unknown as RebaseCallContext;
|
|
599
|
+
|
|
600
|
+
return await Promise.all(fetchedEntities.map(async (entity) => {
|
|
601
|
+
let processedEntity = entity;
|
|
602
|
+
if (callbacks?.afterRead) {
|
|
603
|
+
processedEntity = await callbacks.afterRead({
|
|
604
|
+
collection: resolvedCollection,
|
|
605
|
+
path: notifyPath,
|
|
606
|
+
entity: processedEntity,
|
|
607
|
+
context: contextForCallback
|
|
608
|
+
}) ?? processedEntity;
|
|
609
|
+
}
|
|
610
|
+
if (propertyCallbacks?.afterRead) {
|
|
611
|
+
processedEntity = await propertyCallbacks.afterRead({
|
|
612
|
+
collection: resolvedCollection,
|
|
613
|
+
path: notifyPath,
|
|
614
|
+
entity: processedEntity,
|
|
615
|
+
context: contextForCallback
|
|
616
|
+
}) ?? processedEntity;
|
|
617
|
+
}
|
|
618
|
+
return processedEntity;
|
|
619
|
+
}));
|
|
620
|
+
}
|
|
643
621
|
|
|
644
|
-
|
|
622
|
+
return fetchedEntities;
|
|
623
|
+
});
|
|
645
624
|
}
|
|
646
625
|
|
|
647
626
|
// No driver — use entityService directly (no auth wrapping possible)
|
|
@@ -726,7 +705,7 @@ roles: authContext.roles },
|
|
|
726
705
|
*/
|
|
727
706
|
private async fetchEntityWithAuth(
|
|
728
707
|
notifyPath: string,
|
|
729
|
-
entityId: string,
|
|
708
|
+
entityId: string | number,
|
|
730
709
|
authContext?: SubscriptionAuthContext
|
|
731
710
|
): Promise<Entity | undefined> {
|
|
732
711
|
if (this.driver) {
|
|
@@ -737,56 +716,51 @@ roles: authContext.roles },
|
|
|
737
716
|
collection
|
|
738
717
|
});
|
|
739
718
|
|
|
740
|
-
//
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
const resolvedCollection = collection ? { ...collection,
|
|
719
|
+
// Always wrap in a transaction with session vars, defaulting to anonymous context if missing
|
|
720
|
+
const activeAuth = authContext || { userId: "anon", roles: ["anon"] };
|
|
721
|
+
return await this.db.transaction(async (tx) => {
|
|
722
|
+
await tx.execute(drizzleSql`SELECT set_config('app.user_id', ${activeAuth.userId}, true)`);
|
|
723
|
+
await tx.execute(drizzleSql`SELECT set_config('app.user_roles', ${activeAuth.roles.join(",")}, true)`);
|
|
724
|
+
await tx.execute(drizzleSql`SELECT set_config('app.jwt', ${JSON.stringify({ sub: activeAuth.userId, roles: activeAuth.roles })}, true)`);
|
|
725
|
+
const txEntityService = new EntityService(tx, this.registry);
|
|
726
|
+
let processedEntity = await txEntityService.fetchEntity(notifyPath, entityId, collection?.databaseId);
|
|
727
|
+
|
|
728
|
+
if (processedEntity) {
|
|
729
|
+
const registryCollection = this.registry.getCollectionByPath(notifyPath);
|
|
730
|
+
const resolvedCollection = collection ? { ...collection,
|
|
753
731
|
...registryCollection } as EntityCollection : registryCollection as EntityCollection;
|
|
754
732
|
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
}) ?? processedEntity;
|
|
781
|
-
}
|
|
733
|
+
const callbacks = resolvedCollection?.callbacks;
|
|
734
|
+
const propertyCallbacks = resolvedCollection?.properties ? buildPropertyCallbacks(resolvedCollection.properties) : undefined;
|
|
735
|
+
|
|
736
|
+
if (callbacks?.afterRead || propertyCallbacks?.afterRead) {
|
|
737
|
+
const contextForCallback = {
|
|
738
|
+
user: { uid: activeAuth.userId, roles: activeAuth.roles },
|
|
739
|
+
driver: this.driver,
|
|
740
|
+
data: (this.driver && "data" in this.driver) ? (this.driver as DataDriverWithData).data : undefined
|
|
741
|
+
} as unknown as RebaseCallContext;
|
|
742
|
+
|
|
743
|
+
if (callbacks?.afterRead) {
|
|
744
|
+
processedEntity = await callbacks.afterRead({
|
|
745
|
+
collection: resolvedCollection,
|
|
746
|
+
path: notifyPath,
|
|
747
|
+
entity: processedEntity,
|
|
748
|
+
context: contextForCallback
|
|
749
|
+
}) ?? processedEntity;
|
|
750
|
+
}
|
|
751
|
+
if (propertyCallbacks?.afterRead) {
|
|
752
|
+
processedEntity = await propertyCallbacks.afterRead({
|
|
753
|
+
collection: resolvedCollection,
|
|
754
|
+
path: notifyPath,
|
|
755
|
+
entity: processedEntity,
|
|
756
|
+
context: contextForCallback
|
|
757
|
+
}) ?? processedEntity;
|
|
782
758
|
}
|
|
783
759
|
}
|
|
760
|
+
}
|
|
784
761
|
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
return fetchFn();
|
|
762
|
+
return processedEntity;
|
|
763
|
+
});
|
|
790
764
|
}
|
|
791
765
|
|
|
792
766
|
return await this.entityService.fetchEntity(notifyPath, entityId);
|
|
@@ -865,6 +839,41 @@ roles: authContext.roles },
|
|
|
865
839
|
|
|
866
840
|
return parentPaths;
|
|
867
841
|
}
|
|
842
|
+
// =============================================================================
|
|
843
|
+
// Lifecycle / Cleanup
|
|
844
|
+
// =============================================================================
|
|
845
|
+
|
|
846
|
+
/**
|
|
847
|
+
* Gracefully tear down all realtime resources.
|
|
848
|
+
*
|
|
849
|
+
* This MUST be called during process shutdown, **before** `pool.end()`.
|
|
850
|
+
* It ensures:
|
|
851
|
+
* 1. All debounced refetch timers are cancelled (prevents queries after pool closes).
|
|
852
|
+
* 2. All subscription state and callbacks are cleared.
|
|
853
|
+
* 3. The dedicated LISTEN client (outside the pool) is disconnected.
|
|
854
|
+
* 4. All WebSocket clients are removed (but not forcefully closed — the
|
|
855
|
+
* HTTP server close will handle that).
|
|
856
|
+
*/
|
|
857
|
+
async destroy(): Promise<void> {
|
|
858
|
+
// 1. Cancel every pending debounced refetch timer
|
|
859
|
+
for (const [key, timer] of this.refetchTimers) {
|
|
860
|
+
clearTimeout(timer);
|
|
861
|
+
this.refetchTimers.delete(key);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// 2. Clear subscriptions and callbacks
|
|
865
|
+
this._subscriptions.clear();
|
|
866
|
+
this.subscriptionCallbacks.clear();
|
|
867
|
+
|
|
868
|
+
// 3. Disconnect the dedicated LISTEN client
|
|
869
|
+
await this.stopListening();
|
|
870
|
+
|
|
871
|
+
// 4. Drop client references (don't close — server.close drains them)
|
|
872
|
+
this.clients.clear();
|
|
873
|
+
|
|
874
|
+
this.debugLog("🧹 [RealtimeService] destroy() complete — all resources released.");
|
|
875
|
+
}
|
|
876
|
+
|
|
868
877
|
// =============================================================================
|
|
869
878
|
// Cross-Instance LISTEN/NOTIFY
|
|
870
879
|
// =============================================================================
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { and, eq, or, sql, SQL, ilike, inArray } from "drizzle-orm";
|
|
2
|
-
import { AnyPgColumn, PgTable } from "drizzle-orm/pg-core";
|
|
2
|
+
import { AnyPgColumn, PgTable, PgVarchar, PgText, PgChar } from "drizzle-orm/pg-core";
|
|
3
3
|
import { FilterValues, WhereFilterOp, Relation, JoinStep } from "@rebasepro/types";
|
|
4
4
|
import { getColumnName, resolveCollectionRelations } from "@rebasepro/common";
|
|
5
5
|
import { PostgresCollectionRegistry } from "../collections/PostgresCollectionRegistry";
|
|
@@ -64,8 +64,14 @@ export class DrizzleConditionBuilder {
|
|
|
64
64
|
): SQL | null {
|
|
65
65
|
switch (op) {
|
|
66
66
|
case "==":
|
|
67
|
+
if (value === null || value === undefined) {
|
|
68
|
+
return sql`${column} IS NULL`;
|
|
69
|
+
}
|
|
67
70
|
return eq(column, value);
|
|
68
71
|
case "!=":
|
|
72
|
+
if (value === null || value === undefined) {
|
|
73
|
+
return sql`${column} IS NOT NULL`;
|
|
74
|
+
}
|
|
69
75
|
return sql`${column} != ${value}`;
|
|
70
76
|
case ">":
|
|
71
77
|
return sql`${column} > ${value}`;
|
|
@@ -647,7 +653,15 @@ export class DrizzleConditionBuilder {
|
|
|
647
653
|
if (p.type === "string" && !p.enum && p.isId !== "uuid") {
|
|
648
654
|
const fieldColumn = table[key as keyof typeof table] as AnyPgColumn;
|
|
649
655
|
if (fieldColumn) {
|
|
650
|
-
|
|
656
|
+
// Verify that the underlying database column supports string pattern-matching
|
|
657
|
+
const supportsILike =
|
|
658
|
+
fieldColumn instanceof PgVarchar ||
|
|
659
|
+
fieldColumn instanceof PgText ||
|
|
660
|
+
fieldColumn instanceof PgChar ||
|
|
661
|
+
(fieldColumn && typeof fieldColumn === "object" && !("columnType" in fieldColumn));
|
|
662
|
+
if (supportsILike) {
|
|
663
|
+
searchConditions.push(ilike(fieldColumn, `%${searchString}%`));
|
|
664
|
+
}
|
|
651
665
|
}
|
|
652
666
|
}
|
|
653
667
|
}
|