@wordrhyme/auto-crud-server 1.2.0 → 1.3.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/README.md CHANGED
@@ -939,6 +939,40 @@ export const db = drizzle(client, { schema });
939
939
  export const createContext = () => ({ db });
940
940
  ```
941
941
 
942
+ ### 使用业务时区解析日期过滤
943
+
944
+ AutoCrud 默认保留历史行为:使用服务器本地时区解析日期过滤。如果宿主已经有用户或租户的业务时区,可在 tRPC context 中提供 `resolveDateRange`,将日历日期转换为 UTC 半开区间:
945
+
946
+ ```typescript
947
+ import type { DateRangeResolver } from '@wordrhyme/auto-crud-server';
948
+
949
+ const resolveDateRange: DateRangeResolver = (filter) => {
950
+ // 由宿主根据 filter.value、filter.operator 和当前业务时区进行转换。
951
+ // resolveCalendarFilterToUtc 应返回 UTC Date 对象。
952
+ return resolveCalendarFilterToUtc(filter, getBusinessTimeZone());
953
+ };
954
+
955
+ export const createContext = () => ({
956
+ db,
957
+ resolveDateRange,
958
+ });
959
+ ```
960
+
961
+ Resolver 返回值遵循以下契约:
962
+
963
+ ```typescript
964
+ interface DateFilterRange {
965
+ start?: Date; // 日历范围的 UTC 起点,包含
966
+ endExclusive?: Date; // UTC 终点,不包含
967
+ }
968
+ ```
969
+
970
+ - 返回 `undefined`:使用原有服务器本地时区逻辑。
971
+ - 返回非空范围:该结果是权威结果,不再回退到本地时区。
972
+ - `lt` / `gte` 需要 `start`;`lte` / `gt` 需要 `endExclusive`。
973
+ - `eq`、`ne`、`isBetween`、`isRelativeToToday` 可使用单边或双边范围。
974
+ - 无效日期、空范围、反向范围或缺少操作符所需边界会抛出 `RangeError`,避免过滤条件意外扩大。
975
+
942
976
  ### 与 Next.js 集成
943
977
 
944
978
  ```typescript
package/dist/index.cjs CHANGED
@@ -31,8 +31,12 @@ let __trpc_server = require("@trpc/server");
31
31
  __trpc_server = __toESM(__trpc_server);
32
32
  let superjson = require("superjson");
33
33
  superjson = __toESM(superjson);
34
- let date_fns = require("date-fns");
35
- date_fns = __toESM(date_fns);
34
+ let date_fns_addDays = require("date-fns/addDays");
35
+ date_fns_addDays = __toESM(date_fns_addDays);
36
+ let date_fns_endOfDay = require("date-fns/endOfDay");
37
+ date_fns_endOfDay = __toESM(date_fns_endOfDay);
38
+ let date_fns_startOfDay = require("date-fns/startOfDay");
39
+ date_fns_startOfDay = __toESM(date_fns_startOfDay);
36
40
 
37
41
  //#region src/trpc.ts
38
42
  const t = __trpc_server.initTRPC.context().create({ transformer: superjson.default });
@@ -75,12 +79,73 @@ function safeParseNumber(value) {
75
79
  const num = Number(value);
76
80
  return Number.isNaN(num) ? null : num;
77
81
  }
78
- function filterColumns({ table, filters, joinOperator, resolveColumn }) {
82
+ function isValidDate(value) {
83
+ return value instanceof Date && !Number.isNaN(value.getTime());
84
+ }
85
+ function isDateRangeOperator(operator) {
86
+ switch (operator) {
87
+ case "eq":
88
+ case "ne":
89
+ case "lt":
90
+ case "lte":
91
+ case "gt":
92
+ case "gte":
93
+ case "isBetween":
94
+ case "isRelativeToToday": return true;
95
+ case "iLike":
96
+ case "notILike":
97
+ case "inArray":
98
+ case "notInArray":
99
+ case "isEmpty":
100
+ case "isNotEmpty": return false;
101
+ }
102
+ return false;
103
+ }
104
+ function normalizeDateRange(range) {
105
+ if (range.start !== void 0 && !isValidDate(range.start)) throw new RangeError("resolveDateRange returned an invalid start boundary");
106
+ if (range.endExclusive !== void 0 && !isValidDate(range.endExclusive)) throw new RangeError("resolveDateRange returned an invalid endExclusive boundary");
107
+ const { start, endExclusive } = range;
108
+ if (!start && !endExclusive) throw new RangeError("resolveDateRange must return at least one date boundary");
109
+ if (start && endExclusive && start >= endExclusive) throw new RangeError("resolveDateRange requires start to be before endExclusive");
110
+ return {
111
+ start,
112
+ endExclusive
113
+ };
114
+ }
115
+ function requireDateBoundary(boundary, name, operator) {
116
+ if (!boundary) throw new RangeError(`resolveDateRange must return ${name} for operator ${operator}`);
117
+ return boundary;
118
+ }
119
+ function resolvedDateCondition(expr, operator, range) {
120
+ const { start, endExclusive } = range;
121
+ switch (operator) {
122
+ case "eq":
123
+ case "isBetween":
124
+ case "isRelativeToToday":
125
+ if (start && endExclusive) return (0, drizzle_orm.and)((0, drizzle_orm.gte)(expr, start), (0, drizzle_orm.lt)(expr, endExclusive));
126
+ if (start) return (0, drizzle_orm.gte)(expr, start);
127
+ return (0, drizzle_orm.lt)(expr, requireDateBoundary(endExclusive, "endExclusive", operator));
128
+ case "ne":
129
+ if (start && endExclusive) return (0, drizzle_orm.or)((0, drizzle_orm.lt)(expr, start), (0, drizzle_orm.gte)(expr, endExclusive));
130
+ if (start) return (0, drizzle_orm.lt)(expr, start);
131
+ return (0, drizzle_orm.gte)(expr, requireDateBoundary(endExclusive, "endExclusive", operator));
132
+ case "lt": return (0, drizzle_orm.lt)(expr, requireDateBoundary(start, "start", operator));
133
+ case "lte": return (0, drizzle_orm.lt)(expr, requireDateBoundary(endExclusive, "endExclusive", operator));
134
+ case "gt": return (0, drizzle_orm.gte)(expr, requireDateBoundary(endExclusive, "endExclusive", operator));
135
+ case "gte": return (0, drizzle_orm.gte)(expr, requireDateBoundary(start, "start", operator));
136
+ }
137
+ throw new Error("Unsupported resolved date operator");
138
+ }
139
+ function filterColumns({ table, filters, joinOperator, resolveColumn, resolveDateRange }) {
79
140
  const joinFn = joinOperator === "and" ? drizzle_orm.and : drizzle_orm.or;
80
141
  const validConditions = filters.map((filter) => {
81
142
  const column = resolveColumn ? resolveColumn(filter.id) : getColumn(table, filter.id);
82
143
  if (!column) return void 0;
83
144
  const expr = toSqlTarget(column);
145
+ if ((filter.variant === "date" || filter.variant === "dateRange") && isDateRangeOperator(filter.operator)) {
146
+ const resolvedRange = resolveDateRange?.(filter);
147
+ if (resolvedRange !== void 0) return resolvedDateCondition(expr, filter.operator, normalizeDateRange(resolvedRange));
148
+ }
84
149
  switch (filter.operator) {
85
150
  case "iLike": return filter.variant === "text" && typeof filter.value === "string" ? (0, drizzle_orm.ilike)(expr, `%${filter.value}%`) : void 0;
86
151
  case "notILike": return filter.variant === "text" && typeof filter.value === "string" ? (0, drizzle_orm.notIlike)(expr, `%${filter.value}%`) : void 0;
@@ -195,16 +260,16 @@ function filterColumns({ table, filters, joinOperator, resolveColumn }) {
195
260
  if (!amount || !unit) return void 0;
196
261
  switch (unit) {
197
262
  case "days":
198
- startDate = (0, date_fns.startOfDay)((0, date_fns.addDays)(today, Number.parseInt(amount, 10)));
199
- endDate = (0, date_fns.endOfDay)(startDate);
263
+ startDate = (0, date_fns_startOfDay.startOfDay)((0, date_fns_addDays.addDays)(today, Number.parseInt(amount, 10)));
264
+ endDate = (0, date_fns_endOfDay.endOfDay)(startDate);
200
265
  break;
201
266
  case "weeks":
202
- startDate = (0, date_fns.startOfDay)((0, date_fns.addDays)(today, Number.parseInt(amount, 10) * 7));
203
- endDate = (0, date_fns.endOfDay)((0, date_fns.addDays)(startDate, 6));
267
+ startDate = (0, date_fns_startOfDay.startOfDay)((0, date_fns_addDays.addDays)(today, Number.parseInt(amount, 10) * 7));
268
+ endDate = (0, date_fns_endOfDay.endOfDay)((0, date_fns_addDays.addDays)(startDate, 6));
204
269
  break;
205
270
  case "months":
206
- startDate = (0, date_fns.startOfDay)((0, date_fns.addDays)(today, Number.parseInt(amount, 10) * 30));
207
- endDate = (0, date_fns.endOfDay)((0, date_fns.addDays)(startDate, 29));
271
+ startDate = (0, date_fns_startOfDay.startOfDay)((0, date_fns_addDays.addDays)(today, Number.parseInt(amount, 10) * 30));
272
+ endDate = (0, date_fns_endOfDay.endOfDay)((0, date_fns_addDays.addDays)(startDate, 29));
208
273
  break;
209
274
  default: return;
210
275
  }
@@ -1161,7 +1226,8 @@ function createCrudRouter(config) {
1161
1226
  columnId: id,
1162
1227
  allowedColumns: filterableColumns
1163
1228
  });
1164
- }
1229
+ },
1230
+ resolveDateRange: ctx.resolveDateRange
1165
1231
  }) : void 0, buildCrudSearchCondition({
1166
1232
  table,
1167
1233
  search: effectiveInput.search,
@@ -1455,7 +1521,8 @@ function createCrudRouter(config) {
1455
1521
  columnId: id,
1456
1522
  allowedColumns: filterableColumns
1457
1523
  });
1458
- }
1524
+ },
1525
+ resolveDateRange: ctx.resolveDateRange
1459
1526
  }) : void 0, buildCrudSearchCondition({
1460
1527
  table,
1461
1528
  search: effectiveInput.search,
package/dist/index.d.cts CHANGED
@@ -1,9 +1,36 @@
1
+ import { AnyColumn, SQL, Table } from "drizzle-orm";
1
2
  import { z } from "zod";
2
3
  import { PgTable } from "drizzle-orm/pg-core";
3
- import * as _trpc_server0 from "@trpc/server";
4
+ import * as _trpc_server2 from "@trpc/server";
4
5
  import { PostgresJsDatabase } from "drizzle-orm/postgres-js";
5
- import { AnyColumn, SQL } from "drizzle-orm";
6
6
 
7
+ //#region src/lib/filter-columns.d.ts
8
+ /** 过滤器变体类型 */
9
+ type FilterVariant = 'text' | 'number' | 'range' | 'date' | 'dateRange' | 'boolean' | 'select' | 'multiSelect';
10
+ /** 过滤器操作符 */
11
+ type FilterOperator = 'eq' | 'ne' | 'lt' | 'lte' | 'gt' | 'gte' | 'iLike' | 'notILike' | 'inArray' | 'notInArray' | 'isBetween' | 'isRelativeToToday' | 'isEmpty' | 'isNotEmpty';
12
+ /** 扩展的列过滤器 */
13
+ interface ExtendedColumnFilter<T extends Table = Table> {
14
+ id: keyof T | string;
15
+ value: unknown;
16
+ variant: FilterVariant;
17
+ operator: FilterOperator;
18
+ }
19
+ /** Host-resolved UTC boundaries for a calendar-date filter. */
20
+ interface DateFilterRange {
21
+ start?: Date;
22
+ endExclusive?: Date;
23
+ }
24
+ /**
25
+ * Optional host adapter for date filters.
26
+ *
27
+ * AutoCrud intentionally does not choose a time zone. A host that has business
28
+ * time-zone context can resolve the filter to UTC boundaries. Returning
29
+ * undefined preserves the legacy server-local parsing behavior. A non-empty
30
+ * result is authoritative and must contain valid boundaries for the operator.
31
+ */
32
+ type DateRangeResolver<T extends Table = Table> = (filter: ExtendedColumnFilter<T>) => DateFilterRange | undefined;
33
+ //#endregion
7
34
  //#region src/trpc.d.ts
8
35
  /**
9
36
  * tRPC Context 类型
@@ -11,14 +38,15 @@ import { AnyColumn, SQL } from "drizzle-orm";
11
38
  */
12
39
  interface Context {
13
40
  db: PostgresJsDatabase<Record<string, never>>;
41
+ resolveDateRange?: DateRangeResolver;
14
42
  }
15
- declare const router: _trpc_server0.TRPCRouterBuilder<{
43
+ declare const router: _trpc_server2.TRPCRouterBuilder<{
16
44
  ctx: Context;
17
45
  meta: object;
18
- errorShape: _trpc_server0.TRPCDefaultErrorShape;
46
+ errorShape: _trpc_server2.TRPCDefaultErrorShape;
19
47
  transformer: true;
20
48
  }>;
21
- declare const publicProcedure: _trpc_server0.TRPCProcedureBuilder<Context, object, object, _trpc_server0.TRPCUnsetMarker, _trpc_server0.TRPCUnsetMarker, _trpc_server0.TRPCUnsetMarker, _trpc_server0.TRPCUnsetMarker, false>;
49
+ declare const publicProcedure: _trpc_server2.TRPCProcedureBuilder<Context, object, object, _trpc_server2.TRPCUnsetMarker, _trpc_server2.TRPCUnsetMarker, _trpc_server2.TRPCUnsetMarker, _trpc_server2.TRPCUnsetMarker, false>;
22
50
  //#endregion
23
51
  //#region src/types/config.d.ts
24
52
  type CrudOperation = 'list' | 'get' | 'create' | 'update' | 'delete' | 'deleteMany' | 'updateMany' | 'upsert' | 'export' | 'import' | 'createMany';
@@ -936,12 +964,12 @@ type CrudHookEventMap<PluginId extends string, ResourceName extends string, TCre
936
964
  } } & { [K in `${PluginId}.${ResourceName}.createMany`]: TCreate[] };
937
965
  //#endregion
938
966
  //#region src/routers/index.d.ts
939
- declare const appRouter: _trpc_server0.TRPCBuiltRouter<{
967
+ declare const appRouter: _trpc_server2.TRPCBuiltRouter<{
940
968
  ctx: Context;
941
969
  meta: object;
942
- errorShape: _trpc_server0.TRPCDefaultErrorShape;
970
+ errorShape: _trpc_server2.TRPCDefaultErrorShape;
943
971
  transformer: true;
944
- }, _trpc_server0.TRPCDecorateCreateRouterOptions<{}>>;
972
+ }, _trpc_server2.TRPCDecorateCreateRouterOptions<{}>>;
945
973
  type AppRouter = typeof appRouter;
946
974
  //#endregion
947
- export { type AnyProcedure, type AppRouter, type CreateManyMiddlewareParams, type CreateMiddlewareParams, type CrudCapabilityMetadata, type CrudColumnCapability, type CrudColumnConfig, type CrudColumnExpression, type CrudColumnRef, type CrudExtensionFilter, type CrudExtensionMetadata, type CrudExtensionsConfig, type CrudExtensionsProvider, type CrudHookEventMap, type CrudMiddleware, type CrudOperation, type CrudProcedures, type CrudRouterConfig, type DeleteManyMiddlewareParams, type DeleteMiddlewareParams, type ExportInput, type ExportMiddlewareParams, type ExportResult, type GetInput, type GetMiddlewareParams, type ImportFailedRow, type ImportInput, type ImportMiddlewareParams, type ImportResult, type ListInput, type ListMiddlewareParams, type ListResult, type ProcedureConfig, type ProcedureFactory, type ProcedureMap, type SoftDeleteConfig, type SoftDeleteOption, type UpdateManyMiddlewareParams, type UpdateMiddlewareParams, type UpsertMiddlewareParams, type WriteOperation, afterMiddleware, appRouter, baseExportInputSchema, baseGetInputSchema, baseListInputSchema, beforeMiddleware, composeMiddleware, createCrudRouter, publicProcedure, router };
975
+ export { type AnyProcedure, type AppRouter, type CreateManyMiddlewareParams, type CreateMiddlewareParams, type CrudCapabilityMetadata, type CrudColumnCapability, type CrudColumnConfig, type CrudColumnExpression, type CrudColumnRef, type CrudExtensionFilter, type CrudExtensionMetadata, type CrudExtensionsConfig, type CrudExtensionsProvider, type CrudHookEventMap, type CrudMiddleware, type CrudOperation, type CrudProcedures, type CrudRouterConfig, type DateFilterRange, type DateRangeResolver, type DeleteManyMiddlewareParams, type DeleteMiddlewareParams, type ExportInput, type ExportMiddlewareParams, type ExportResult, type ExtendedColumnFilter, type FilterOperator, type FilterVariant, type GetInput, type GetMiddlewareParams, type ImportFailedRow, type ImportInput, type ImportMiddlewareParams, type ImportResult, type ListInput, type ListMiddlewareParams, type ListResult, type ProcedureConfig, type ProcedureFactory, type ProcedureMap, type SoftDeleteConfig, type SoftDeleteOption, type UpdateManyMiddlewareParams, type UpdateMiddlewareParams, type UpsertMiddlewareParams, type WriteOperation, afterMiddleware, appRouter, baseExportInputSchema, baseGetInputSchema, baseListInputSchema, beforeMiddleware, composeMiddleware, createCrudRouter, publicProcedure, router };
package/dist/index.d.ts CHANGED
@@ -1,10 +1,37 @@
1
1
  import { z } from "zod";
2
- import { AnyColumn, SQL } from "drizzle-orm";
3
- import * as _trpc_server0 from "@trpc/server";
2
+ import { AnyColumn, SQL, Table } from "drizzle-orm";
3
+ import * as _trpc_server2 from "@trpc/server";
4
4
  import superjson from "superjson";
5
5
  import { PgTable } from "drizzle-orm/pg-core";
6
6
  import { PostgresJsDatabase } from "drizzle-orm/postgres-js";
7
7
 
8
+ //#region src/lib/filter-columns.d.ts
9
+ /** 过滤器变体类型 */
10
+ type FilterVariant = 'text' | 'number' | 'range' | 'date' | 'dateRange' | 'boolean' | 'select' | 'multiSelect';
11
+ /** 过滤器操作符 */
12
+ type FilterOperator = 'eq' | 'ne' | 'lt' | 'lte' | 'gt' | 'gte' | 'iLike' | 'notILike' | 'inArray' | 'notInArray' | 'isBetween' | 'isRelativeToToday' | 'isEmpty' | 'isNotEmpty';
13
+ /** 扩展的列过滤器 */
14
+ interface ExtendedColumnFilter<T extends Table = Table> {
15
+ id: keyof T | string;
16
+ value: unknown;
17
+ variant: FilterVariant;
18
+ operator: FilterOperator;
19
+ }
20
+ /** Host-resolved UTC boundaries for a calendar-date filter. */
21
+ interface DateFilterRange {
22
+ start?: Date;
23
+ endExclusive?: Date;
24
+ }
25
+ /**
26
+ * Optional host adapter for date filters.
27
+ *
28
+ * AutoCrud intentionally does not choose a time zone. A host that has business
29
+ * time-zone context can resolve the filter to UTC boundaries. Returning
30
+ * undefined preserves the legacy server-local parsing behavior. A non-empty
31
+ * result is authoritative and must contain valid boundaries for the operator.
32
+ */
33
+ type DateRangeResolver<T extends Table = Table> = (filter: ExtendedColumnFilter<T>) => DateFilterRange | undefined;
34
+ //#endregion
8
35
  //#region src/trpc.d.ts
9
36
  /**
10
37
  * tRPC Context 类型
@@ -12,14 +39,15 @@ import { PostgresJsDatabase } from "drizzle-orm/postgres-js";
12
39
  */
13
40
  interface Context {
14
41
  db: PostgresJsDatabase<Record<string, never>>;
42
+ resolveDateRange?: DateRangeResolver;
15
43
  }
16
- declare const router: _trpc_server0.TRPCRouterBuilder<{
44
+ declare const router: _trpc_server2.TRPCRouterBuilder<{
17
45
  ctx: Context;
18
46
  meta: object;
19
- errorShape: _trpc_server0.TRPCDefaultErrorShape;
47
+ errorShape: _trpc_server2.TRPCDefaultErrorShape;
20
48
  transformer: true;
21
49
  }>;
22
- declare const publicProcedure: _trpc_server0.TRPCProcedureBuilder<Context, object, object, _trpc_server0.TRPCUnsetMarker, _trpc_server0.TRPCUnsetMarker, _trpc_server0.TRPCUnsetMarker, _trpc_server0.TRPCUnsetMarker, false>;
50
+ declare const publicProcedure: _trpc_server2.TRPCProcedureBuilder<Context, object, object, _trpc_server2.TRPCUnsetMarker, _trpc_server2.TRPCUnsetMarker, _trpc_server2.TRPCUnsetMarker, _trpc_server2.TRPCUnsetMarker, false>;
23
51
  //#endregion
24
52
  //#region src/types/config.d.ts
25
53
  type CrudOperation = 'list' | 'get' | 'create' | 'update' | 'delete' | 'deleteMany' | 'updateMany' | 'upsert' | 'export' | 'import' | 'createMany';
@@ -937,12 +965,12 @@ type CrudHookEventMap<PluginId extends string, ResourceName extends string, TCre
937
965
  } } & { [K in `${PluginId}.${ResourceName}.createMany`]: TCreate[] };
938
966
  //#endregion
939
967
  //#region src/routers/index.d.ts
940
- declare const appRouter: _trpc_server0.TRPCBuiltRouter<{
968
+ declare const appRouter: _trpc_server2.TRPCBuiltRouter<{
941
969
  ctx: Context;
942
970
  meta: object;
943
- errorShape: _trpc_server0.TRPCDefaultErrorShape;
971
+ errorShape: _trpc_server2.TRPCDefaultErrorShape;
944
972
  transformer: true;
945
- }, _trpc_server0.TRPCDecorateCreateRouterOptions<{}>>;
973
+ }, _trpc_server2.TRPCDecorateCreateRouterOptions<{}>>;
946
974
  type AppRouter = typeof appRouter;
947
975
  //#endregion
948
- export { type AnyProcedure, type AppRouter, type CreateManyMiddlewareParams, type CreateMiddlewareParams, type CrudCapabilityMetadata, type CrudColumnCapability, type CrudColumnConfig, type CrudColumnExpression, type CrudColumnRef, type CrudExtensionFilter, type CrudExtensionMetadata, type CrudExtensionsConfig, type CrudExtensionsProvider, type CrudHookEventMap, type CrudMiddleware, type CrudOperation, type CrudProcedures, type CrudRouterConfig, type DeleteManyMiddlewareParams, type DeleteMiddlewareParams, type ExportInput, type ExportMiddlewareParams, type ExportResult, type GetInput, type GetMiddlewareParams, type ImportFailedRow, type ImportInput, type ImportMiddlewareParams, type ImportResult, type ListInput, type ListMiddlewareParams, type ListResult, type ProcedureConfig, type ProcedureFactory, type ProcedureMap, type SoftDeleteConfig, type SoftDeleteOption, type UpdateManyMiddlewareParams, type UpdateMiddlewareParams, type UpsertMiddlewareParams, type WriteOperation, afterMiddleware, appRouter, baseExportInputSchema, baseGetInputSchema, baseListInputSchema, beforeMiddleware, composeMiddleware, createCrudRouter, publicProcedure, router };
976
+ export { type AnyProcedure, type AppRouter, type CreateManyMiddlewareParams, type CreateMiddlewareParams, type CrudCapabilityMetadata, type CrudColumnCapability, type CrudColumnConfig, type CrudColumnExpression, type CrudColumnRef, type CrudExtensionFilter, type CrudExtensionMetadata, type CrudExtensionsConfig, type CrudExtensionsProvider, type CrudHookEventMap, type CrudMiddleware, type CrudOperation, type CrudProcedures, type CrudRouterConfig, type DateFilterRange, type DateRangeResolver, type DeleteManyMiddlewareParams, type DeleteMiddlewareParams, type ExportInput, type ExportMiddlewareParams, type ExportResult, type ExtendedColumnFilter, type FilterOperator, type FilterVariant, type GetInput, type GetMiddlewareParams, type ImportFailedRow, type ImportInput, type ImportMiddlewareParams, type ImportResult, type ListInput, type ListMiddlewareParams, type ListResult, type ProcedureConfig, type ProcedureFactory, type ProcedureMap, type SoftDeleteConfig, type SoftDeleteOption, type UpdateManyMiddlewareParams, type UpdateMiddlewareParams, type UpsertMiddlewareParams, type WriteOperation, afterMiddleware, appRouter, baseExportInputSchema, baseGetInputSchema, baseListInputSchema, beforeMiddleware, composeMiddleware, createCrudRouter, publicProcedure, router };
package/dist/index.js CHANGED
@@ -3,7 +3,9 @@ import { and, asc, desc, eq, getTableColumns, gt, gte, ilike, inArray, isNull, l
3
3
  import { createInsertSchema, createSelectSchema } from "drizzle-zod";
4
4
  import { TRPCError, initTRPC } from "@trpc/server";
5
5
  import superjson from "superjson";
6
- import { addDays, endOfDay, startOfDay } from "date-fns";
6
+ import { addDays } from "date-fns/addDays";
7
+ import { endOfDay } from "date-fns/endOfDay";
8
+ import { startOfDay } from "date-fns/startOfDay";
7
9
 
8
10
  //#region src/trpc.ts
9
11
  const t = initTRPC.context().create({ transformer: superjson });
@@ -46,12 +48,73 @@ function safeParseNumber(value) {
46
48
  const num = Number(value);
47
49
  return Number.isNaN(num) ? null : num;
48
50
  }
49
- function filterColumns({ table, filters, joinOperator, resolveColumn }) {
51
+ function isValidDate(value) {
52
+ return value instanceof Date && !Number.isNaN(value.getTime());
53
+ }
54
+ function isDateRangeOperator(operator) {
55
+ switch (operator) {
56
+ case "eq":
57
+ case "ne":
58
+ case "lt":
59
+ case "lte":
60
+ case "gt":
61
+ case "gte":
62
+ case "isBetween":
63
+ case "isRelativeToToday": return true;
64
+ case "iLike":
65
+ case "notILike":
66
+ case "inArray":
67
+ case "notInArray":
68
+ case "isEmpty":
69
+ case "isNotEmpty": return false;
70
+ }
71
+ return false;
72
+ }
73
+ function normalizeDateRange(range) {
74
+ if (range.start !== void 0 && !isValidDate(range.start)) throw new RangeError("resolveDateRange returned an invalid start boundary");
75
+ if (range.endExclusive !== void 0 && !isValidDate(range.endExclusive)) throw new RangeError("resolveDateRange returned an invalid endExclusive boundary");
76
+ const { start, endExclusive } = range;
77
+ if (!start && !endExclusive) throw new RangeError("resolveDateRange must return at least one date boundary");
78
+ if (start && endExclusive && start >= endExclusive) throw new RangeError("resolveDateRange requires start to be before endExclusive");
79
+ return {
80
+ start,
81
+ endExclusive
82
+ };
83
+ }
84
+ function requireDateBoundary(boundary, name, operator) {
85
+ if (!boundary) throw new RangeError(`resolveDateRange must return ${name} for operator ${operator}`);
86
+ return boundary;
87
+ }
88
+ function resolvedDateCondition(expr, operator, range) {
89
+ const { start, endExclusive } = range;
90
+ switch (operator) {
91
+ case "eq":
92
+ case "isBetween":
93
+ case "isRelativeToToday":
94
+ if (start && endExclusive) return and(gte(expr, start), lt(expr, endExclusive));
95
+ if (start) return gte(expr, start);
96
+ return lt(expr, requireDateBoundary(endExclusive, "endExclusive", operator));
97
+ case "ne":
98
+ if (start && endExclusive) return or(lt(expr, start), gte(expr, endExclusive));
99
+ if (start) return lt(expr, start);
100
+ return gte(expr, requireDateBoundary(endExclusive, "endExclusive", operator));
101
+ case "lt": return lt(expr, requireDateBoundary(start, "start", operator));
102
+ case "lte": return lt(expr, requireDateBoundary(endExclusive, "endExclusive", operator));
103
+ case "gt": return gte(expr, requireDateBoundary(endExclusive, "endExclusive", operator));
104
+ case "gte": return gte(expr, requireDateBoundary(start, "start", operator));
105
+ }
106
+ throw new Error("Unsupported resolved date operator");
107
+ }
108
+ function filterColumns({ table, filters, joinOperator, resolveColumn, resolveDateRange }) {
50
109
  const joinFn = joinOperator === "and" ? and : or;
51
110
  const validConditions = filters.map((filter) => {
52
111
  const column = resolveColumn ? resolveColumn(filter.id) : getColumn(table, filter.id);
53
112
  if (!column) return void 0;
54
113
  const expr = toSqlTarget(column);
114
+ if ((filter.variant === "date" || filter.variant === "dateRange") && isDateRangeOperator(filter.operator)) {
115
+ const resolvedRange = resolveDateRange?.(filter);
116
+ if (resolvedRange !== void 0) return resolvedDateCondition(expr, filter.operator, normalizeDateRange(resolvedRange));
117
+ }
55
118
  switch (filter.operator) {
56
119
  case "iLike": return filter.variant === "text" && typeof filter.value === "string" ? ilike(expr, `%${filter.value}%`) : void 0;
57
120
  case "notILike": return filter.variant === "text" && typeof filter.value === "string" ? notIlike(expr, `%${filter.value}%`) : void 0;
@@ -1132,7 +1195,8 @@ function createCrudRouter(config) {
1132
1195
  columnId: id,
1133
1196
  allowedColumns: filterableColumns
1134
1197
  });
1135
- }
1198
+ },
1199
+ resolveDateRange: ctx.resolveDateRange
1136
1200
  }) : void 0, buildCrudSearchCondition({
1137
1201
  table,
1138
1202
  search: effectiveInput.search,
@@ -1426,7 +1490,8 @@ function createCrudRouter(config) {
1426
1490
  columnId: id,
1427
1491
  allowedColumns: filterableColumns
1428
1492
  });
1429
- }
1493
+ },
1494
+ resolveDateRange: ctx.resolveDateRange
1430
1495
  }) : void 0, buildCrudSearchCondition({
1431
1496
  table,
1432
1497
  search: effectiveInput.search,
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@wordrhyme/auto-crud-server",
3
3
  "type": "module",
4
- "version": "1.2.0",
4
+ "version": "1.3.1",
5
5
  "description": "tRPC server utilities for auto-crud - automatic CRUD routers for Drizzle ORM",
6
6
  "author": "wordrhyme",
7
7
  "license": "MIT",
8
8
  "repository": {
9
9
  "type": "git",
10
- "url": "https://github.com/pixpilot/shadcn-components.git",
10
+ "url": "https://github.com/denvey/wordrhyme-components",
11
11
  "directory": "packages/auto-crud-server"
12
12
  },
13
13
  "main": "./dist/index.cjs",
@@ -49,10 +49,10 @@
49
49
  "vitest": "^4.0.18",
50
50
  "zod": "^4.3.6",
51
51
  "@internal/eslint-config": "0.3.0",
52
- "@internal/prettier-config": "0.0.1",
53
52
  "@internal/tsconfig": "0.1.0",
54
53
  "@internal/tsdown-config": "0.1.0",
55
- "@internal/vitest-config": "0.1.0"
54
+ "@internal/vitest-config": "0.1.0",
55
+ "@internal/prettier-config": "0.0.1"
56
56
  },
57
57
  "prettier": "@internal/prettier-config",
58
58
  "scripts": {