nucleus-core-ts 0.9.802 → 0.9.803

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.
@@ -1,4 +1,5 @@
1
1
  import type { ReactNode } from 'react';
2
+ import type { FilterCondition, SortCondition } from '../../src/types';
2
3
  export type SortDirection = 'asc' | 'desc' | null;
3
4
  export interface QueryParams {
4
5
  page?: number;
@@ -10,15 +11,19 @@ export interface QueryParams {
10
11
  filters?: string;
11
12
  with?: string;
12
13
  }
13
- export interface FilterCondition {
14
- field: string;
15
- operator: 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'like' | 'ilike' | 'in' | 'notIn' | 'isNull' | 'isNotNull';
16
- value: unknown;
17
- }
18
- export interface SortCondition {
19
- field: string;
20
- direction: 'asc' | 'desc';
21
- }
14
+ /**
15
+ * Re-exported from the server's own definition, not redeclared.
16
+ *
17
+ * This file used to carry a NARROWER copy: `field` was required and the
18
+ * operator list stopped before `arrayOverlaps`, `arrayContains`, `arrayEmpty`,
19
+ * `or` and `and`. So a query the backend accepts — an `or` group, or a filter
20
+ * over an array column — was a type error on the client, and the two drifted
21
+ * every time an operator was added on one side only.
22
+ *
23
+ * Types flow from the server outward. A second definition of the same contract
24
+ * is a second thing to keep right.
25
+ */
26
+ export type { FilterCondition, FilterOperator, SortCondition } from '../../src/types';
22
27
  export interface PaginationMeta {
23
28
  page: number;
24
29
  limit: number;
@@ -229,4 +234,3 @@ export interface UseNucleusEntityReturn<T extends Record<string, unknown>> {
229
234
  setCurrentPage: (page: number) => void;
230
235
  refetch: () => void;
231
236
  }
232
- export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nucleus-core-ts",
3
- "version": "0.9.802",
3
+ "version": "0.9.803",
4
4
  "description": "Production-ready, enterprise-grade TypeScript framework for building multi-tenant APIs",
5
5
  "author": "Hidayet Can Özcan <hidayetcan@gmail.com>",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from 'bun:test'
2
- import { selectEmittedTables } from './generate-schema'
2
+ import { generateColumnCode, selectEmittedTables } from './generate-schema'
3
3
 
4
4
  const table = (table_name: string) => ({ table_name })
5
5
 
@@ -76,3 +76,34 @@ describe('selectEmittedTables', () => {
76
76
  expect(selectEmittedTables([], sets({}))).toEqual([])
77
77
  })
78
78
  })
79
+
80
+ /**
81
+ * `numeric` and `decimal` are STRING-mode columns in drizzle — Postgres numeric
82
+ * is arbitrary precision and a JS number cannot carry it. The generator emitted
83
+ * `.default(0)` for them, producing a schema that ran and did not typecheck.
84
+ * Nobody reads a generated file, so only `tsc` ever objected — and the project's
85
+ * typecheck did not cover the folder it landed in.
86
+ */
87
+ describe('numeric defaults are quoted', () => {
88
+ const emit = (col: Record<string, unknown>) =>
89
+ generateColumnCode(col as never, [], 'widgets')
90
+
91
+ it('quotes a numeric default, because the column reads as a string', () => {
92
+ expect(emit({ name: 'position_x', type: 'numeric', notNull: true, default: 0 })).toContain(
93
+ ".default('0')"
94
+ )
95
+ expect(emit({ name: 'rate', type: 'decimal', default: 0.4 })).toContain(".default('0.4')")
96
+ })
97
+
98
+ it('leaves a real number column alone', () => {
99
+ expect(emit({ name: 'count', type: 'integer', default: 0 })).toContain('.default(0)')
100
+ expect(emit({ name: 'ratio', type: 'real', default: 1.5 })).toContain('.default(1.5)')
101
+ })
102
+
103
+ it('quotes it with a precision too, where the column carries options', () => {
104
+ const code = emit({ name: 'lat', type: 'numeric', precision: 10, scale: 8, default: 0 })
105
+
106
+ expect(code).toContain('precision: 10')
107
+ expect(code).toContain(".default('0')")
108
+ })
109
+ })
@@ -108,6 +108,18 @@ const BASE_COLUMNS: Column[] = [
108
108
  { name: 'updated_by', type: 'uuid' },
109
109
  ]
110
110
 
111
+ /**
112
+ * Columns drizzle reads and writes as STRINGS.
113
+ *
114
+ * Postgres `numeric` is arbitrary precision, so drizzle refuses to round-trip
115
+ * it through a JS number and types the column as a string. A default therefore
116
+ * has to be quoted — `.default(0)` compiles to a schema that RUNS and does not
117
+ * typecheck, which is exactly the kind of defect a generated file hides.
118
+ */
119
+ function isNumericType(type: string): boolean {
120
+ return type === 'numeric' || type === 'decimal'
121
+ }
122
+
111
123
  const TYPE_MAP: Record<string, string> = {
112
124
  integer: 'integer',
113
125
  smallint: 'smallint',
@@ -200,7 +212,7 @@ export function selectEmittedTables<T extends { table_name: string }>(
200
212
  )
201
213
  }
202
214
 
203
- function generateColumnCode(col: Column, allTables: string[], currentTable?: string): string {
215
+ export function generateColumnCode(col: Column, allTables: string[], currentTable?: string): string {
204
216
  const drizzleType = TYPE_MAP[col.type] || 'text'
205
217
  let code = ''
206
218
 
@@ -255,7 +267,12 @@ function generateColumnCode(col: Column, allTables: string[], currentTable?: str
255
267
  } else if (typeof col.default === 'boolean') {
256
268
  code += `.default(${col.default})`
257
269
  } else if (typeof col.default === 'number') {
258
- code += `.default(${col.default})`
270
+ // `numeric`/`decimal` are STRING-mode columns in drizzle — Postgres numeric
271
+ // is arbitrary precision and a JS number cannot carry it — so a numeric
272
+ // default has to be quoted. Emitting the bare number produced a schema
273
+ // that ran fine and failed `tsc`, which is how it survived: the file is
274
+ // generated, so nobody reads it, and only a typecheck ever objected.
275
+ code += isNumericType(col.type) ? `.default('${col.default}')` : `.default(${col.default})`
259
276
  } else if (Array.isArray(col.default)) {
260
277
  code += `.default(sql\`'{}'\`)`
261
278
  } else if (typeof col.default === 'object') {