@wordrhyme/auto-crud-server 1.0.4 → 1.0.6

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/dist/index.cjs CHANGED
@@ -44,8 +44,15 @@ const publicProcedure = t.procedure;
44
44
  /**
45
45
  * 检查列是否为空
46
46
  */
47
+ function toSqlTarget(column) {
48
+ return drizzle_orm.sql`${column}`;
49
+ }
50
+ function isBooleanColumn(column) {
51
+ return "dataType" in column && column.dataType === "boolean";
52
+ }
47
53
  function isEmpty(column) {
48
- return (0, drizzle_orm.or)((0, drizzle_orm.isNull)(column), (0, drizzle_orm.eq)(column, ""));
54
+ const expr = toSqlTarget(column);
55
+ return (0, drizzle_orm.or)((0, drizzle_orm.isNull)(expr), (0, drizzle_orm.eq)(expr, ""));
49
56
  }
50
57
  /**
51
58
  * 安全解析日期时间戳
@@ -68,22 +75,24 @@ function safeParseNumber(value) {
68
75
  const num = Number(value);
69
76
  return Number.isNaN(num) ? null : num;
70
77
  }
71
- function filterColumns({ table, filters, joinOperator }) {
78
+ function filterColumns({ table, filters, joinOperator, resolveColumn }) {
72
79
  const joinFn = joinOperator === "and" ? drizzle_orm.and : drizzle_orm.or;
73
80
  const validConditions = filters.map((filter) => {
74
- const column = getColumn(table, filter.id);
81
+ const column = resolveColumn ? resolveColumn(filter.id) : getColumn(table, filter.id);
82
+ if (!column) return void 0;
83
+ const expr = toSqlTarget(column);
75
84
  switch (filter.operator) {
76
- case "iLike": return filter.variant === "text" && typeof filter.value === "string" ? (0, drizzle_orm.ilike)(column, `%${filter.value}%`) : void 0;
77
- case "notILike": return filter.variant === "text" && typeof filter.value === "string" ? (0, drizzle_orm.notIlike)(column, `%${filter.value}%`) : void 0;
85
+ case "iLike": return filter.variant === "text" && typeof filter.value === "string" ? (0, drizzle_orm.ilike)(expr, `%${filter.value}%`) : void 0;
86
+ case "notILike": return filter.variant === "text" && typeof filter.value === "string" ? (0, drizzle_orm.notIlike)(expr, `%${filter.value}%`) : void 0;
78
87
  case "eq":
79
- if (column.dataType === "boolean" && typeof filter.value === "string") return (0, drizzle_orm.eq)(column, filter.value === "true");
88
+ if (isBooleanColumn(column) && typeof filter.value === "string") return (0, drizzle_orm.eq)(expr, filter.value === "true");
80
89
  if (filter.variant === "dateRange" && Array.isArray(filter.value) && filter.value.length === 2) {
81
90
  const startDate = safeParseDate(filter.value[0]);
82
91
  const endDate = safeParseDate(filter.value[1]);
83
92
  if (!startDate && !endDate) return void 0;
84
93
  if (startDate) startDate.setHours(0, 0, 0, 0);
85
94
  if (endDate) endDate.setHours(23, 59, 59, 999);
86
- return (0, drizzle_orm.and)(startDate ? (0, drizzle_orm.gte)(column, startDate) : void 0, endDate ? (0, drizzle_orm.lte)(column, endDate) : void 0);
95
+ return (0, drizzle_orm.and)(startDate ? (0, drizzle_orm.gte)(expr, startDate) : void 0, endDate ? (0, drizzle_orm.lte)(expr, endDate) : void 0);
87
96
  }
88
97
  if (filter.variant === "date" && typeof filter.value === "string") {
89
98
  const date = safeParseDate(filter.value);
@@ -91,72 +100,72 @@ function filterColumns({ table, filters, joinOperator }) {
91
100
  date.setHours(0, 0, 0, 0);
92
101
  const end = new Date(date);
93
102
  end.setHours(23, 59, 59, 999);
94
- return (0, drizzle_orm.and)((0, drizzle_orm.gte)(column, date), (0, drizzle_orm.lte)(column, end));
103
+ return (0, drizzle_orm.and)((0, drizzle_orm.gte)(expr, date), (0, drizzle_orm.lte)(expr, end));
95
104
  }
96
- return (0, drizzle_orm.eq)(column, filter.value);
105
+ return (0, drizzle_orm.eq)(expr, filter.value);
97
106
  case "ne":
98
- if (column.dataType === "boolean" && typeof filter.value === "string") return (0, drizzle_orm.ne)(column, filter.value === "true");
107
+ if (isBooleanColumn(column) && typeof filter.value === "string") return (0, drizzle_orm.ne)(expr, filter.value === "true");
99
108
  if (filter.variant === "date" || filter.variant === "dateRange") {
100
109
  const date = safeParseDate(filter.value);
101
110
  if (!date) return void 0;
102
111
  date.setHours(0, 0, 0, 0);
103
112
  const end = new Date(date);
104
113
  end.setHours(23, 59, 59, 999);
105
- return (0, drizzle_orm.or)((0, drizzle_orm.lt)(column, date), (0, drizzle_orm.gt)(column, end));
114
+ return (0, drizzle_orm.or)((0, drizzle_orm.lt)(expr, date), (0, drizzle_orm.gt)(expr, end));
106
115
  }
107
- return (0, drizzle_orm.ne)(column, filter.value);
116
+ return (0, drizzle_orm.ne)(expr, filter.value);
108
117
  case "inArray":
109
- if (Array.isArray(filter.value)) return (0, drizzle_orm.inArray)(column, filter.value);
118
+ if (Array.isArray(filter.value)) return (0, drizzle_orm.inArray)(expr, filter.value);
110
119
  return;
111
120
  case "notInArray":
112
- if (Array.isArray(filter.value)) return (0, drizzle_orm.notInArray)(column, filter.value);
121
+ if (Array.isArray(filter.value)) return (0, drizzle_orm.notInArray)(expr, filter.value);
113
122
  return;
114
123
  case "lt":
115
124
  if (filter.variant === "number" || filter.variant === "range") {
116
125
  const num = safeParseNumber(filter.value);
117
- return num !== null ? (0, drizzle_orm.lt)(column, num) : void 0;
126
+ return num !== null ? (0, drizzle_orm.lt)(expr, num) : void 0;
118
127
  }
119
128
  if (filter.variant === "date" && typeof filter.value === "string") {
120
129
  const date = safeParseDate(filter.value);
121
130
  if (!date) return void 0;
122
131
  date.setHours(23, 59, 59, 999);
123
- return (0, drizzle_orm.lt)(column, date);
132
+ return (0, drizzle_orm.lt)(expr, date);
124
133
  }
125
134
  return;
126
135
  case "lte":
127
136
  if (filter.variant === "number" || filter.variant === "range") {
128
137
  const num = safeParseNumber(filter.value);
129
- return num !== null ? (0, drizzle_orm.lte)(column, num) : void 0;
138
+ return num !== null ? (0, drizzle_orm.lte)(expr, num) : void 0;
130
139
  }
131
140
  if (filter.variant === "date" && typeof filter.value === "string") {
132
141
  const date = safeParseDate(filter.value);
133
142
  if (!date) return void 0;
134
143
  date.setHours(23, 59, 59, 999);
135
- return (0, drizzle_orm.lte)(column, date);
144
+ return (0, drizzle_orm.lte)(expr, date);
136
145
  }
137
146
  return;
138
147
  case "gt":
139
148
  if (filter.variant === "number" || filter.variant === "range") {
140
149
  const num = safeParseNumber(filter.value);
141
- return num !== null ? (0, drizzle_orm.gt)(column, num) : void 0;
150
+ return num !== null ? (0, drizzle_orm.gt)(expr, num) : void 0;
142
151
  }
143
152
  if (filter.variant === "date" && typeof filter.value === "string") {
144
153
  const date = safeParseDate(filter.value);
145
154
  if (!date) return void 0;
146
155
  date.setHours(0, 0, 0, 0);
147
- return (0, drizzle_orm.gt)(column, date);
156
+ return (0, drizzle_orm.gt)(expr, date);
148
157
  }
149
158
  return;
150
159
  case "gte":
151
160
  if (filter.variant === "number" || filter.variant === "range") {
152
161
  const num = safeParseNumber(filter.value);
153
- return num !== null ? (0, drizzle_orm.gte)(column, num) : void 0;
162
+ return num !== null ? (0, drizzle_orm.gte)(expr, num) : void 0;
154
163
  }
155
164
  if (filter.variant === "date" && typeof filter.value === "string") {
156
165
  const date = safeParseDate(filter.value);
157
166
  if (!date) return void 0;
158
167
  date.setHours(0, 0, 0, 0);
159
- return (0, drizzle_orm.gte)(column, date);
168
+ return (0, drizzle_orm.gte)(expr, date);
160
169
  }
161
170
  return;
162
171
  case "isBetween":
@@ -166,15 +175,15 @@ function filterColumns({ table, filters, joinOperator }) {
166
175
  if (!startDate && !endDate) return void 0;
167
176
  if (startDate) startDate.setHours(0, 0, 0, 0);
168
177
  if (endDate) endDate.setHours(23, 59, 59, 999);
169
- return (0, drizzle_orm.and)(startDate ? (0, drizzle_orm.gte)(column, startDate) : void 0, endDate ? (0, drizzle_orm.lte)(column, endDate) : void 0);
178
+ return (0, drizzle_orm.and)(startDate ? (0, drizzle_orm.gte)(expr, startDate) : void 0, endDate ? (0, drizzle_orm.lte)(expr, endDate) : void 0);
170
179
  }
171
180
  if ((filter.variant === "number" || filter.variant === "range") && Array.isArray(filter.value) && filter.value.length === 2) {
172
181
  const firstValue = safeParseNumber(filter.value[0]);
173
182
  const secondValue = safeParseNumber(filter.value[1]);
174
183
  if (firstValue === null && secondValue === null) return;
175
- if (firstValue !== null && secondValue === null) return (0, drizzle_orm.eq)(column, firstValue);
176
- if (firstValue === null && secondValue !== null) return (0, drizzle_orm.eq)(column, secondValue);
177
- return (0, drizzle_orm.and)(firstValue !== null ? (0, drizzle_orm.gte)(column, firstValue) : void 0, secondValue !== null ? (0, drizzle_orm.lte)(column, secondValue) : void 0);
184
+ if (firstValue !== null && secondValue === null) return (0, drizzle_orm.eq)(expr, firstValue);
185
+ if (firstValue === null && secondValue !== null) return (0, drizzle_orm.eq)(expr, secondValue);
186
+ return (0, drizzle_orm.and)(firstValue !== null ? (0, drizzle_orm.gte)(expr, firstValue) : void 0, secondValue !== null ? (0, drizzle_orm.lte)(expr, secondValue) : void 0);
178
187
  }
179
188
  return;
180
189
  case "isRelativeToToday":
@@ -199,11 +208,11 @@ function filterColumns({ table, filters, joinOperator }) {
199
208
  break;
200
209
  default: return;
201
210
  }
202
- return (0, drizzle_orm.and)((0, drizzle_orm.gte)(column, startDate), (0, drizzle_orm.lte)(column, endDate));
211
+ return (0, drizzle_orm.and)((0, drizzle_orm.gte)(expr, startDate), (0, drizzle_orm.lte)(expr, endDate));
203
212
  }
204
213
  return;
205
- case "isEmpty": return isEmpty(column);
206
- case "isNotEmpty": return (0, drizzle_orm.not)(isEmpty(column));
214
+ case "isEmpty": return isEmpty(expr);
215
+ case "isNotEmpty": return (0, drizzle_orm.not)(isEmpty(expr));
207
216
  default: throw new Error(`Unsupported operator: ${filter.operator}`);
208
217
  }
209
218
  }).filter((condition) => condition !== void 0);
@@ -500,7 +509,7 @@ const filterItemSchema = zod.z.object({
500
509
  operator: zod.z.enum(dataTableConfig.operators),
501
510
  filterId: zod.z.string()
502
511
  });
503
- const listInputSchema = zod.z.object({
512
+ const baseListInputSchema = zod.z.object({
504
513
  page: zod.z.number().min(1).default(1),
505
514
  perPage: zod.z.number().min(1).max(100).default(10),
506
515
  sort: zod.z.array(zod.z.object({
@@ -510,7 +519,9 @@ const listInputSchema = zod.z.object({
510
519
  filters: zod.z.array(filterItemSchema).optional(),
511
520
  joinOperator: zod.z.enum(["and", "or"]).default("and")
512
521
  });
513
- const exportInputSchema = zod.z.object({
522
+ const baseGetInputSchema = zod.z.object({ id: zod.z.string() });
523
+ const defaultGetInputSchema = zod.z.union([zod.z.string(), baseGetInputSchema]);
524
+ const baseExportInputSchema = zod.z.object({
514
525
  sort: zod.z.array(zod.z.object({
515
526
  id: zod.z.string(),
516
527
  desc: zod.z.boolean()
@@ -560,9 +571,43 @@ function resolveSchemas(config) {
560
571
  updateSchema
561
572
  };
562
573
  }
574
+ function getColumnRefId(columnRef) {
575
+ return typeof columnRef === "string" ? columnRef : columnRef.id;
576
+ }
577
+ function findColumnRef(columnId, allowedColumns) {
578
+ return allowedColumns?.find((columnRef) => getColumnRefId(columnRef) === columnId);
579
+ }
563
580
  function validateColumn(columnId, allowedColumns) {
564
581
  if (!allowedColumns || allowedColumns.length === 0) return true;
565
- return allowedColumns.includes(columnId);
582
+ return findColumnRef(columnId, allowedColumns) !== void 0;
583
+ }
584
+ function getTableColumn(table, columnId) {
585
+ return table[columnId];
586
+ }
587
+ function normalizeJsonFields(jsonField) {
588
+ return (Array.isArray(jsonField) ? jsonField : [jsonField]).filter((field) => field.length > 0);
589
+ }
590
+ function buildJsonFieldTextExpression(column, jsonField) {
591
+ const fields = normalizeJsonFields(jsonField);
592
+ if (fields.length === 0) throw new Error("[createCrudRouter] jsonField must include at least one json key.");
593
+ const objectFields = fields.map((field) => drizzle_orm.sql`${column} ->> ${field}`);
594
+ return drizzle_orm.sql`case jsonb_typeof(${column})
595
+ when 'object' then coalesce(${drizzle_orm.sql.join(objectFields, drizzle_orm.sql`, `)}, '')
596
+ when 'string' then ${column} #>> '{}'
597
+ else ''
598
+ end`;
599
+ }
600
+ function resolveColumnTarget({ table, columnId, allowedColumns }) {
601
+ const columnRef = findColumnRef(columnId, allowedColumns);
602
+ if ((allowedColumns?.length ?? 0) > 0 && columnRef === void 0) return;
603
+ if (columnRef && typeof columnRef !== "string") {
604
+ if (columnRef.expression) return columnRef.expression({ table });
605
+ const column = getTableColumn(table, columnRef.id);
606
+ if (!column) return void 0;
607
+ if (columnRef.jsonField) return buildJsonFieldTextExpression(column, columnRef.jsonField);
608
+ return column;
609
+ }
610
+ return getTableColumn(table, columnId);
566
611
  }
567
612
  /**
568
613
  * 创建 CRUD Router
@@ -613,6 +658,9 @@ function createCrudRouter(config) {
613
658
  });
614
659
  const resolved = resolveConfig(config);
615
660
  const softDelete = resolveSoftDelete(softDeleteOption);
661
+ const resolvedListInputSchema = config.listInputSchema ?? baseListInputSchema;
662
+ const resolvedGetInputSchema = config.getInputSchema ? zod.z.union([zod.z.string(), config.getInputSchema]) : defaultGetInputSchema;
663
+ const resolvedExportInputSchema = config.exportInputSchema ?? baseExportInputSchema;
616
664
  const m = middleware;
617
665
  const getIdColumn = () => table[idField];
618
666
  const getSoftDeleteColumn = () => softDelete ? table[softDelete.column] : null;
@@ -654,7 +702,7 @@ function createCrudRouter(config) {
654
702
  });
655
703
  };
656
704
  const procedures = {
657
- list: resolved.procedureFactory("list").input(listInputSchema).output(listOutputSchema).query(async ({ ctx, input }) => {
705
+ list: resolved.procedureFactory("list").input(resolvedListInputSchema).output(listOutputSchema).query(async ({ ctx, input }) => {
658
706
  await withGuard(ctx, "list");
659
707
  const doList = async (listInput$1) => {
660
708
  const offset = (listInput$1.page - 1) * listInput$1.perPage;
@@ -662,14 +710,23 @@ function createCrudRouter(config) {
662
710
  const where = buildWhere(ctx, "list", validatedFilters?.length ? filterColumns({
663
711
  table,
664
712
  filters: validatedFilters,
665
- joinOperator: listInput$1.joinOperator
713
+ joinOperator: listInput$1.joinOperator,
714
+ resolveColumn: (columnId) => resolveColumnTarget({
715
+ table,
716
+ columnId: String(columnId),
717
+ allowedColumns: filterableColumns
718
+ })
666
719
  }) : void 0);
667
720
  let query = ctx.db.select().from(table).$dynamic();
668
721
  if (where) query = query.where(where);
669
722
  if (listInput$1.sort?.length) {
670
723
  const sortField = listInput$1.sort[0];
671
724
  if (sortField && validateColumn(sortField.id, sortableColumns)) {
672
- const column = table[sortField.id];
725
+ const column = resolveColumnTarget({
726
+ table,
727
+ columnId: sortField.id,
728
+ allowedColumns: sortableColumns
729
+ });
673
730
  if (column) query = query.orderBy(sortField.desc ? (0, drizzle_orm.desc)(column) : (0, drizzle_orm.asc)(column));
674
731
  }
675
732
  }
@@ -693,20 +750,23 @@ function createCrudRouter(config) {
693
750
  });
694
751
  return doList(listInput);
695
752
  }),
696
- get: resolved.procedureFactory("get").input(zod.z.string()).output(entityOutputSchema.nullable()).query(async ({ ctx, input }) => {
753
+ get: resolved.procedureFactory("get").input(resolvedGetInputSchema).output(entityOutputSchema.nullable()).query(async ({ ctx, input }) => {
697
754
  await withGuard(ctx, "get");
698
- const doGet = async () => {
699
- const where = buildWhere(ctx, "get", (0, drizzle_orm.eq)(getIdColumn(), input));
755
+ const doGet = async (getInput) => {
756
+ const id$1 = typeof getInput === "string" ? getInput : getInput.id;
757
+ const where = buildWhere(ctx, "get", (0, drizzle_orm.eq)(getIdColumn(), id$1));
700
758
  const [item] = await ctx.db.select().from(table).where(where);
701
759
  await withAuthorize(ctx, item, "get");
702
760
  return item ?? null;
703
761
  };
762
+ const id = typeof input === "string" ? input : input.id;
704
763
  if (m.get) return m.get({
705
764
  ctx,
706
- id: input,
707
- next: doGet
765
+ id,
766
+ input,
767
+ next: async (modifiedInput) => doGet(modifiedInput ?? input)
708
768
  });
709
- return doGet();
769
+ return doGet(input);
710
770
  }),
711
771
  create: resolved.procedureFactory("create").input(schema).output(entityOutputSchema).mutation(async ({ ctx, input }) => {
712
772
  await withGuard(ctx, "create");
@@ -852,7 +912,7 @@ function createCrudRouter(config) {
852
912
  });
853
913
  return doUpsert(input);
854
914
  }),
855
- export: resolved.procedureFactory("export").input(exportInputSchema).output(exportOutputSchema).query(async ({ ctx, input }) => {
915
+ export: resolved.procedureFactory("export").input(resolvedExportInputSchema).output(exportOutputSchema).query(async ({ ctx, input }) => {
856
916
  await withGuard(ctx, "export");
857
917
  const doExport = async (exportInput$1) => {
858
918
  const effectiveLimit = Math.min(Math.max(exportInput$1.limit ?? maxExportSize, 1), maxExportSize);
@@ -860,14 +920,23 @@ function createCrudRouter(config) {
860
920
  const where = buildWhere(ctx, "export", validatedFilters?.length ? filterColumns({
861
921
  table,
862
922
  filters: validatedFilters,
863
- joinOperator: exportInput$1.joinOperator ?? "and"
923
+ joinOperator: exportInput$1.joinOperator ?? "and",
924
+ resolveColumn: (columnId) => resolveColumnTarget({
925
+ table,
926
+ columnId: String(columnId),
927
+ allowedColumns: filterableColumns
928
+ })
864
929
  }) : void 0);
865
930
  let query = ctx.db.select().from(table).$dynamic();
866
931
  if (where) query = query.where(where);
867
932
  if (exportInput$1.sort?.length) {
868
933
  const sortField = exportInput$1.sort[0];
869
934
  if (sortField && validateColumn(sortField.id, sortableColumns)) {
870
- const column = table[sortField.id];
935
+ const column = resolveColumnTarget({
936
+ table,
937
+ columnId: sortField.id,
938
+ allowedColumns: sortableColumns
939
+ });
871
940
  if (column) query = query.orderBy(sortField.desc ? (0, drizzle_orm.desc)(column) : (0, drizzle_orm.asc)(column));
872
941
  }
873
942
  }
@@ -1057,6 +1126,9 @@ const appRouter = router({});
1057
1126
  //#endregion
1058
1127
  exports.afterMiddleware = afterMiddleware;
1059
1128
  exports.appRouter = appRouter;
1129
+ exports.baseExportInputSchema = baseExportInputSchema;
1130
+ exports.baseGetInputSchema = baseGetInputSchema;
1131
+ exports.baseListInputSchema = baseListInputSchema;
1060
1132
  exports.beforeMiddleware = beforeMiddleware;
1061
1133
  exports.composeMiddleware = composeMiddleware;
1062
1134
  exports.createCrudRouter = createCrudRouter;