nucleus-core-ts 0.10.11 → 0.10.13

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.
@@ -0,0 +1,52 @@
1
+ import { type SQL } from 'drizzle-orm';
2
+ /**
3
+ * What a list search actually asks the database.
4
+ *
5
+ * The whole search term used to be handed to every searchable column at once:
6
+ *
7
+ * first_name ILIKE '%Ayşe Yılmaz%' OR last_name ILIKE '%Ayşe Yılmaz%' OR …
8
+ *
9
+ * No single column holds a first name and a last name together, so typing a
10
+ * person's full name — the most ordinary thing anyone does in a staff directory
11
+ * — matched nobody. Each half found her on its own; the space is what emptied
12
+ * the screen. Measured on a real installation: "Ayşe" 1 row, "Yılmaz" 1 row,
13
+ * "Ayşe Yılmaz" 0 rows.
14
+ *
15
+ * So the term is split, and the shape is inverted where it matters:
16
+ *
17
+ * AND over words( OR over columns( column contains word ) )
18
+ *
19
+ * Every word must appear SOMEWHERE, not all in the same column. That also makes
20
+ * word order irrelevant, which is right: nobody should have to know whether the
21
+ * directory stores "Ayşe Yılmaz" or "Yılmaz, Ayşe".
22
+ */
23
+ /**
24
+ * A ceiling on how many words one search may ask about.
25
+ *
26
+ * Each word costs one comparison per searchable column, so a pasted paragraph
27
+ * would turn a single request into hundreds of full-column scans. Ten is far
28
+ * past any real name or title and cheap to serve; the rest of the term is
29
+ * ignored rather than refused, because a search box that answers nothing at all
30
+ * is worse than one that answers the first ten words.
31
+ */
32
+ export declare const MAX_SEARCH_TOKENS = 10;
33
+ /** The words a search term is asking about, in order, capped. */
34
+ export declare function searchTokens(term: string): string[];
35
+ /**
36
+ * The same fold the SQL applies, in TypeScript, so both sides of the comparison
37
+ * are normalised identically.
38
+ *
39
+ * The Turkish letters are replaced BEFORE lowering because JavaScript lowers
40
+ * `İ` to `i` followed by a combining dot — two code points that then match
41
+ * nothing.
42
+ */
43
+ export declare function foldForSearch(value: string): string;
44
+ /**
45
+ * The WHERE fragment for `?search=…&searchFields=…`, or null when there is
46
+ * nothing to ask.
47
+ *
48
+ * Null rather than a condition that matches everything: the caller pushes onto a
49
+ * list of conditions, and a term of nothing but spaces must not narrow the list
50
+ * NOR silently drop other conditions.
51
+ */
52
+ export declare function buildSearchCondition(columns: SQL[], term: string): SQL | null;
@@ -59,6 +59,15 @@ export type ExpectedColumn = {
59
59
  sqlType: string;
60
60
  notNull: boolean;
61
61
  hasDefault: boolean;
62
+ /**
63
+ * The default as SQL, when it is a plain literal — `'startsWith'`, `42`, `true`.
64
+ *
65
+ * Only literals. A `sql\`…\`` default or a function call is left undefined and
66
+ * the column is reported rather than repaired: rendering one wrong writes a
67
+ * WRONG default, which is worse than none because the push would then see the
68
+ * column as settled and never revisit it.
69
+ */
70
+ defaultLiteral?: string;
62
71
  };
63
72
  export type ExpectedTable = {
64
73
  name: string;
@@ -66,6 +75,16 @@ export type ExpectedTable = {
66
75
  };
67
76
  /** What the database actually has: table name → column names. */
68
77
  export type ActualSchema = Readonly<Record<string, ReadonlySet<string>>>;
78
+ /**
79
+ * Which columns the database already gives a default, table by table.
80
+ *
81
+ * Kept beside `ActualSchema` rather than folded into it: that type is a set of
82
+ * NAMES and is what every caller and every test already passes. Widening it
83
+ * would make a column's absence and a column's default the same question, and
84
+ * they are answered at different times — the name decides whether to ADD, the
85
+ * default decides whether to SET.
86
+ */
87
+ export type ActualDefaults = Readonly<Record<string, ReadonlySet<string>>>;
69
88
  export type Drift = {
70
89
  /** Tables the code declares that the database does not have at all. */
71
90
  missingTables: string[];
@@ -74,6 +93,22 @@ export type Drift = {
74
93
  table: string;
75
94
  column: ExpectedColumn;
76
95
  }[];
96
+ /**
97
+ * Columns that EXIST but do not carry the default the model declares.
98
+ *
99
+ * Invisible until now, and the gap cost a live cutover. `claims.mode` is
100
+ * declared `notNull` with a default; the column was already there, so the
101
+ * name check below passed it and nothing looked further. The push would
102
+ * normally have set it — `isAdditiveStatement` allows SET DEFAULT precisely
103
+ * because it touches nothing stored — but a push that ABORTS applies no
104
+ * statements at all, and this repair is what runs in its place. It put the
105
+ * table back and left the default off, so the 2,715 claim inserts that
106
+ * followed all failed 23502 and the install booted with no permissions.
107
+ */
108
+ columnsMissingDefault: {
109
+ table: string;
110
+ column: ExpectedColumn;
111
+ }[];
77
112
  };
78
113
  /**
79
114
  * What the code expects minus what the database has.
@@ -83,7 +118,9 @@ export type Drift = {
83
118
  * another application owns, and dropping it is exactly the destructive act this
84
119
  * refuses to perform.
85
120
  */
86
- export declare function findDrift(expected: readonly ExpectedTable[], actual: ActualSchema): Drift;
121
+ export declare function findDrift(expected: readonly ExpectedTable[], actual: ActualSchema,
122
+ /** Omit and no default drift is reported — every existing caller keeps its answer. */
123
+ actualDefaults?: ActualDefaults): Drift;
87
124
  /**
88
125
  * The statement that adds one column, or null when it cannot be added safely.
89
126
  *
@@ -97,6 +134,14 @@ export declare function findDrift(expected: readonly ExpectedTable[], actual: Ac
97
134
  * column missing.
98
135
  */
99
136
  export declare function addColumnStatement(schema: string, table: string, column: ExpectedColumn): string | null;
137
+ /**
138
+ * `alter column … set default …`, or null when the default cannot be rendered.
139
+ *
140
+ * Safe to run unattended for the same reason the push is allowed to run it:
141
+ * a default decides what a FUTURE insert gets when it omits the column. No
142
+ * stored row changes, and no write that worked a moment ago starts failing.
143
+ */
144
+ export declare function setDefaultStatement(schema: string, table: string, column: ExpectedColumn): string | null;
100
145
  export type RepairPlan = {
101
146
  statements: string[];
102
147
  /** Columns that exist in the model and cannot be added without a value. */
@@ -191,10 +236,33 @@ type ReconcileLogger = {
191
236
  * list is worse than none — it would report a clean schema over a broken one.
192
237
  */
193
238
  export declare function expectedFromTables(tables: Record<string, unknown>): ExpectedTable[];
239
+ /**
240
+ * A default as SQL, for the three shapes that can be rendered without guessing.
241
+ *
242
+ * Anything else — `sql\`now()\``, an object, an array, a function — returns
243
+ * undefined, and the column is then reported rather than repaired. The
244
+ * asymmetry is deliberate: a missing default is a bug that announces itself
245
+ * (23502 on the first insert), while a WRONG default is silent and permanent,
246
+ * because once the column has one the push stops proposing the right one.
247
+ */
248
+ export declare function renderDefaultLiteral(value: unknown): string | undefined;
194
249
  /** What the database has right now, for the tables asked about. */
195
250
  export declare function readActualSchema(execute: (statement: string) => Promise<{
196
251
  rows?: unknown[];
197
252
  } | unknown[]>, schema: string): Promise<ActualSchema>;
253
+ /**
254
+ * The same read, plus which columns already carry a default.
255
+ *
256
+ * One query, not two: `column_default` costs nothing extra to select and the
257
+ * caller needs both answers at the same moment. `readActualSchema` delegates
258
+ * here so there is one statement to keep correct rather than two.
259
+ */
260
+ export declare function readActualSchemaDetailed(execute: (statement: string) => Promise<{
261
+ rows?: unknown[];
262
+ } | unknown[]>, schema: string): Promise<{
263
+ columns: ActualSchema;
264
+ withDefault: ActualDefaults;
265
+ }>;
198
266
  /**
199
267
  * Puts back the columns a failed push did not add.
200
268
  *
package/package.json CHANGED
@@ -1,126 +1,126 @@
1
1
  {
2
- "name": "nucleus-core-ts",
3
- "version": "0.10.11",
4
- "description": "Production-ready, enterprise-grade TypeScript framework for building multi-tenant APIs",
5
- "author": "Hidayet Can Özcan <hidayetcan@gmail.com>",
6
- "license": "SEE LICENSE IN LICENSE",
7
- "keywords": [
8
- "backend",
9
- "framework",
10
- "drizzle",
11
- "typescript"
12
- ],
13
- "files": [
14
- "bin",
15
- "dist",
16
- "schemas",
17
- "scripts",
18
- "infra/templates",
19
- "infra/scripts/generate-project.ts",
20
- "src/system.tables.json",
21
- "src/Services/SchemaTables/resolveEntities.ts",
22
- "src/Services/Logger/auditTaxonomy.ts",
23
- "public",
24
- "LICENSE"
25
- ],
26
- "module": "dist/index.js",
27
- "type": "module",
28
- "types": "dist/index.d.ts",
29
- "exports": {
30
- ".": {
31
- "types": "./dist/index.d.ts",
32
- "import": "./dist/index.js"
33
- },
34
- "./server": {
35
- "types": "./dist/server.d.ts",
36
- "import": "./dist/server.js"
37
- },
38
- "./client": {
39
- "types": "./dist/client.d.ts",
40
- "import": "./dist/client.js"
41
- },
42
- "./fe": {
43
- "types": "./dist/fe/index.d.ts",
44
- "import": "./dist/fe/index.js"
45
- },
46
- "./fe/auth": {
47
- "types": "./dist/fe/components/AuthGuard/index.d.ts",
48
- "import": "./dist/fe/components/AuthGuard/index.js"
49
- },
50
- "./proxy": {
51
- "types": "./dist/src/Client/Proxy/index.d.ts",
52
- "import": "./dist/src/Client/Proxy/index.js"
53
- }
54
- },
55
- "scripts": {
56
- "test": "bun test src scripts fe",
57
- "build": "bun run scripts/build.ts",
58
- "build:quick": "bun run build:js && bun run build:types",
59
- "build:js": "bun build ./index.ts ./server.ts ./client.ts ./fe/index.ts ./src/Client/Proxy/index.ts --outdir=dist --target=bun --format=esm --splitting --minify --external react --external react-dom --external gsap --external @gsap/react --external h-state --external drizzle-orm --external drizzle-kit --external ioredis --external googleapis --external @dapr/dapr --external pg --external @xyflow/react --external @xyflow/system --external @azure/communication-email --external nodemailer --external @azure/identity --external stripe --external sharp",
60
- "build:types": "tsc --declaration --emitDeclarationOnly --outDir dist --skipLibCheck",
61
- "version:patch": "bun run scripts/version.ts patch",
62
- "version:minor": "bun run scripts/version.ts minor",
63
- "version:major": "bun run scripts/version.ts major",
64
- "publish:npm": "bun run scripts/publish.ts",
65
- "publish:dry": "bun run scripts/publish.ts --dry-run",
66
- "release": "bun run version:patch && bun run publish:npm",
67
- "release:minor": "bun run version:minor && bun run publish:npm",
68
- "release:major": "bun run version:major && bun run publish:npm",
69
- "generate:schemas": "bun run scripts/generate-types-schema.ts",
70
- "lint": "biome check . --write",
71
- "lint:fix": "biome check --config-path ./biome.json --write",
72
- "prepublishOnly": "bun run scripts/verify-publishable.ts"
73
- },
74
- "bin": {
75
- "nucleus-core-ts": "./bin/cli.ts",
76
- "nucleus": "./bin/cli.ts",
77
- "nucleus-generate": "./bin/cli.ts"
78
- },
79
- "devDependencies": {
80
- "@biomejs/biome": "2.5.10",
81
- "googleapis": "^171.4.0",
82
- "@types/bun": "latest",
83
- "@types/pg": "latest",
84
- "@types/react": "latest",
85
- "@happy-dom/global-registrator": "^20.11.2",
86
- "@types/react-dom": "latest",
87
- "bun-plugin-tailwind": "latest",
88
- "drizzle-kit": "latest",
89
- "javascript-obfuscator": "latest",
90
- "lucide-react": "latest",
91
- "nodemailer": "^9.0.3"
92
- },
93
- "peerDependencies": {
94
- "typescript": "latest",
95
- "@dapr/dapr": "latest",
96
- "react": "latest",
97
- "react-dom": "latest",
98
- "pg": "latest",
99
- "tailwindcss": "latest",
100
- "tailwind-merge": "latest",
101
- "clsx": "latest",
102
- "googleapis": "*"
103
- },
104
- "dependencies": {
105
- "@sinclair/typebox": "0.34.47",
106
- "@gsap/react": "latest",
107
- "@simplewebauthn/server": "^13.3.0",
108
- "@xyflow/react": "latest",
109
- "drizzle-orm": "latest",
110
- "gsap": "latest",
111
- "h-state": "latest",
112
- "ioredis": "latest",
113
- "reflect-metadata": "^0.2.2"
114
- },
115
- "optionalDependencies": {
116
- "@azure/communication-email": "latest",
117
- "@marsaud/smb2": "^0.18.0",
118
- "sharp": "^0.33.0",
119
- "stripe": "latest"
120
- },
121
- "peerDependenciesMeta": {
122
- "googleapis": {
123
- "optional": true
124
- }
125
- }
2
+ "name": "nucleus-core-ts",
3
+ "version": "0.10.13",
4
+ "description": "Production-ready, enterprise-grade TypeScript framework for building multi-tenant APIs",
5
+ "author": "Hidayet Can Özcan <hidayetcan@gmail.com>",
6
+ "license": "SEE LICENSE IN LICENSE",
7
+ "keywords": [
8
+ "backend",
9
+ "framework",
10
+ "drizzle",
11
+ "typescript"
12
+ ],
13
+ "files": [
14
+ "bin",
15
+ "dist",
16
+ "schemas",
17
+ "scripts",
18
+ "infra/templates",
19
+ "infra/scripts/generate-project.ts",
20
+ "src/system.tables.json",
21
+ "src/Services/SchemaTables/resolveEntities.ts",
22
+ "src/Services/Logger/auditTaxonomy.ts",
23
+ "public",
24
+ "LICENSE"
25
+ ],
26
+ "module": "dist/index.js",
27
+ "type": "module",
28
+ "types": "dist/index.d.ts",
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/index.d.ts",
32
+ "import": "./dist/index.js"
33
+ },
34
+ "./server": {
35
+ "types": "./dist/server.d.ts",
36
+ "import": "./dist/server.js"
37
+ },
38
+ "./client": {
39
+ "types": "./dist/client.d.ts",
40
+ "import": "./dist/client.js"
41
+ },
42
+ "./fe": {
43
+ "types": "./dist/fe/index.d.ts",
44
+ "import": "./dist/fe/index.js"
45
+ },
46
+ "./fe/auth": {
47
+ "types": "./dist/fe/components/AuthGuard/index.d.ts",
48
+ "import": "./dist/fe/components/AuthGuard/index.js"
49
+ },
50
+ "./proxy": {
51
+ "types": "./dist/src/Client/Proxy/index.d.ts",
52
+ "import": "./dist/src/Client/Proxy/index.js"
53
+ }
54
+ },
55
+ "scripts": {
56
+ "test": "bun test src scripts fe",
57
+ "build": "bun run scripts/build.ts",
58
+ "build:quick": "bun run build:js && bun run build:types",
59
+ "build:js": "bun build ./index.ts ./server.ts ./client.ts ./fe/index.ts ./src/Client/Proxy/index.ts --outdir=dist --target=bun --format=esm --splitting --minify --external react --external react-dom --external gsap --external @gsap/react --external h-state --external drizzle-orm --external drizzle-kit --external ioredis --external googleapis --external @dapr/dapr --external pg --external @xyflow/react --external @xyflow/system --external @azure/communication-email --external nodemailer --external @azure/identity --external stripe --external sharp",
60
+ "build:types": "tsc --declaration --emitDeclarationOnly --outDir dist --skipLibCheck",
61
+ "version:patch": "bun run scripts/version.ts patch",
62
+ "version:minor": "bun run scripts/version.ts minor",
63
+ "version:major": "bun run scripts/version.ts major",
64
+ "publish:npm": "bun run scripts/publish.ts",
65
+ "publish:dry": "bun run scripts/publish.ts --dry-run",
66
+ "release": "bun run version:patch && bun run publish:npm",
67
+ "release:minor": "bun run version:minor && bun run publish:npm",
68
+ "release:major": "bun run version:major && bun run publish:npm",
69
+ "generate:schemas": "bun run scripts/generate-types-schema.ts",
70
+ "lint": "biome check . --write",
71
+ "lint:fix": "biome check --config-path ./biome.json --write",
72
+ "prepublishOnly": "bun run scripts/verify-publishable.ts"
73
+ },
74
+ "bin": {
75
+ "nucleus-core-ts": "./bin/cli.ts",
76
+ "nucleus": "./bin/cli.ts",
77
+ "nucleus-generate": "./bin/cli.ts"
78
+ },
79
+ "devDependencies": {
80
+ "@biomejs/biome": "2.5.10",
81
+ "googleapis": "^171.4.0",
82
+ "@types/bun": "latest",
83
+ "@types/pg": "latest",
84
+ "@types/react": "latest",
85
+ "@happy-dom/global-registrator": "^20.11.2",
86
+ "@types/react-dom": "latest",
87
+ "bun-plugin-tailwind": "latest",
88
+ "drizzle-kit": "latest",
89
+ "javascript-obfuscator": "latest",
90
+ "lucide-react": "latest",
91
+ "nodemailer": "^9.0.3"
92
+ },
93
+ "peerDependencies": {
94
+ "typescript": "latest",
95
+ "@dapr/dapr": "latest",
96
+ "react": "latest",
97
+ "react-dom": "latest",
98
+ "pg": "latest",
99
+ "tailwindcss": "latest",
100
+ "tailwind-merge": "latest",
101
+ "clsx": "latest",
102
+ "googleapis": "*"
103
+ },
104
+ "dependencies": {
105
+ "@sinclair/typebox": "0.34.47",
106
+ "@gsap/react": "latest",
107
+ "@simplewebauthn/server": "^13.3.0",
108
+ "@xyflow/react": "latest",
109
+ "drizzle-orm": "latest",
110
+ "gsap": "latest",
111
+ "h-state": "latest",
112
+ "ioredis": "latest",
113
+ "reflect-metadata": "^0.2.2"
114
+ },
115
+ "optionalDependencies": {
116
+ "@azure/communication-email": "latest",
117
+ "@marsaud/smb2": "^0.18.0",
118
+ "sharp": "^0.33.0",
119
+ "stripe": "latest"
120
+ },
121
+ "peerDependenciesMeta": {
122
+ "googleapis": {
123
+ "optional": true
124
+ }
125
+ }
126
126
  }