nucleus-core-ts 0.10.13 → 0.10.15

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.
@@ -14,11 +14,15 @@ import { type SQL } from 'drizzle-orm';
14
14
  *
15
15
  * So the term is split, and the shape is inverted where it matters:
16
16
  *
17
- * AND over words( OR over columns( column contains word ) )
17
+ * AND over words( OR over columns( a word in the column STARTS with it ) )
18
18
  *
19
19
  * Every word must appear SOMEWHERE, not all in the same column. That also makes
20
20
  * word order irrelevant, which is right: nobody should have to know whether the
21
21
  * directory stores "Ayşe Yılmaz" or "Yılmaz, Ayşe".
22
+ *
23
+ * `WORD_START` below is the second half of the lesson, and it cost a second
24
+ * report from the same screen: splitting the term is not enough if each piece
25
+ * may then match in the middle of a word.
22
26
  */
23
27
  /**
24
28
  * A ceiling on how many words one search may ask about.
package/package.json CHANGED
@@ -1,126 +1,126 @@
1
1
  {
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
- }
2
+ "name": "nucleus-core-ts",
3
+ "version": "0.10.15",
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
  }
@@ -1,5 +1,10 @@
1
1
  import { describe, expect, it } from 'bun:test'
2
- import { generateColumnCode, generateTableCode, selectEmittedTables } from './generate-schema'
2
+ import {
3
+ generateColumnCode,
4
+ generateRelationsFile,
5
+ generateTableCode,
6
+ selectEmittedTables,
7
+ } from './generate-schema'
3
8
 
4
9
  const table = (table_name: string) => ({ table_name })
5
10
 
@@ -214,3 +219,57 @@ describe('table-level constraints', () => {
214
219
  expect(code).not.toContain('check(')
215
220
  })
216
221
  })
222
+
223
+ describe('generateRelationsFile', () => {
224
+ const branchTable = {
225
+ table_name: 'survey_branches',
226
+ columns: [
227
+ {
228
+ name: 'trigger_question_id',
229
+ type: 'uuid',
230
+ references: { table: 'survey_questions', column: 'id' },
231
+ },
232
+ {
233
+ name: 'target_question_id',
234
+ type: 'uuid',
235
+ references: { table: 'survey_questions', column: 'id' },
236
+ },
237
+ {
238
+ name: 'survey_id',
239
+ type: 'uuid',
240
+ references: { table: 'surveys', column: 'id' },
241
+ },
242
+ ],
243
+ }
244
+
245
+ it('names two references to the SAME table after their columns', () => {
246
+ /*
247
+ * Both used to be emitted as `surveyQuestions:`, so the object literal had
248
+ * the same key twice and the generated file did not compile — a config
249
+ * change that produces broken output rather than a wrong result. A branch
250
+ * whose trigger and target are both questions is what found it; `created_by`
251
+ * with `updated_by` would have found it just as well.
252
+ */
253
+ const code = generateRelationsFile([
254
+ branchTable,
255
+ { table_name: 'survey_questions' },
256
+ { table_name: 'surveys' },
257
+ ] as never)
258
+
259
+ expect(code).toContain('triggerQuestion: one(surveyQuestions')
260
+ expect(code).toContain('targetQuestion: one(surveyQuestions')
261
+ expect(code.match(/surveyQuestions: one\(/g)).toBeNull()
262
+ })
263
+
264
+ it('and leaves a lone reference named after its table, as before', () => {
265
+ // The control: renaming everything would churn every existing generated
266
+ // file and rename relations consumers already query by.
267
+ const code = generateRelationsFile([
268
+ branchTable,
269
+ { table_name: 'survey_questions' },
270
+ { table_name: 'surveys' },
271
+ ] as never)
272
+
273
+ expect(code).toContain('surveys: one(surveys')
274
+ })
275
+ })
@@ -607,7 +607,7 @@ function topologicalSort(tables: TableDef[], allTableNames: string[]): TableDef[
607
607
  return result
608
608
  }
609
609
 
610
- function generateRelationsFile(tables: TableDef[]): string {
610
+ export function generateRelationsFile(tables: TableDef[]): string {
611
611
  const allTableNames = new Set(tables.map((t) => t.table_name))
612
612
  const relationsByTable: Map<
613
613
  string,
@@ -640,9 +640,37 @@ function generateRelationsFile(tables: TableDef[]): string {
640
640
  const fromTable = toCamelCase(tableName)
641
641
  code += `export const ${fromTable}Relations = relations(${fromTable}, ({ one, many }) => ({\n`
642
642
 
643
+ /*
644
+ * How many of this table's foreign keys point at the same table.
645
+ *
646
+ * The relation used to be named after the TARGET TABLE, which is fine until
647
+ * a table references the same one twice — and then the generator emits two
648
+ * properties with the same key and the output does not compile:
649
+ *
650
+ * surveyQuestions: one(surveyQuestions, { fields: [...triggerQuestionId] }),
651
+ * surveyQuestions: one(surveyQuestions, { fields: [...targetQuestionId] }),
652
+ *
653
+ * That is not an exotic shape. `created_by`/`updated_by`, `from_user`/
654
+ * `to_user`, and here a branch's trigger question and its target question
655
+ * all land on it. Found when survey branching grew a second kind of target.
656
+ */
657
+ const referencesToTable = new Map<string, number>()
658
+ for (const rel of rels) {
659
+ referencesToTable.set(rel.to, (referencesToTable.get(rel.to) ?? 0) + 1)
660
+ }
661
+
643
662
  for (const rel of rels) {
644
663
  const toTable = toCamelCase(rel.to)
645
- const relationName = rel.isSelf ? 'parent' : toTable
664
+ // Only the ambiguous ones are renamed, so a config with one reference per
665
+ // table generates exactly what it generated before.
666
+ const ambiguous = (referencesToTable.get(rel.to) ?? 0) > 1
667
+ const relationName = ambiguous
668
+ ? // `trigger_question_id` -> `triggerQuestion`: the column says which of
669
+ // the two this is, which is the only thing that tells them apart.
670
+ toCamelCase(rel.fromCol.replace(/_id$/, ''))
671
+ : rel.isSelf
672
+ ? 'parent'
673
+ : toTable
646
674
  const fields = `${fromTable}.${toCamelCase(rel.fromCol)}`
647
675
  const references = `${toTable}.${toCamelCase(rel.toCol)}`
648
676
 
@@ -27,7 +27,14 @@ async function readPackageJson(): Promise<PackageJson> {
27
27
  }
28
28
 
29
29
  async function writePackageJson(pkg: PackageJson): Promise<void> {
30
- await writeFile('package.json', `${JSON.stringify(pkg, null, '\t')}\n`)
30
+ /*
31
+ * Two spaces, because that is what `biome.json` asks for.
32
+ *
33
+ * This wrote tabs, which reformatted the WHOLE file on every release and made
34
+ * the Lint gate fail on the release commit itself — a red CI caused by cutting
35
+ * a version, with a diff of 126 lines hiding the one line that changed.
36
+ */
37
+ await writeFile('package.json', `${JSON.stringify(pkg, null, 2)}\n`)
31
38
  }
32
39
 
33
40
  function parseVersion(version: string): {