@mostajs/orm-samples 0.1.0
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/CHANGELOG.md +53 -0
- package/LICENSE +29 -0
- package/README.md +78 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +175 -0
- package/examples/01-quickstart-sqlite/.env.example +6 -0
- package/examples/01-quickstart-sqlite/01-quickstart-sqlite.sh +23 -0
- package/examples/01-quickstart-sqlite/README.md +52 -0
- package/examples/01-quickstart-sqlite/app.ts +52 -0
- package/examples/01-quickstart-sqlite/package.json +18 -0
- package/examples/01-quickstart-sqlite/schemas/user.schema.ts +27 -0
- package/examples/02-multi-dialect-switch/.env.example +18 -0
- package/examples/02-multi-dialect-switch/02-multi-dialect-switch.sh +13 -0
- package/examples/02-multi-dialect-switch/README.md +68 -0
- package/examples/02-multi-dialect-switch/app.ts +68 -0
- package/examples/02-multi-dialect-switch/package.json +22 -0
- package/examples/02-multi-dialect-switch/schemas/user.schema.ts +25 -0
- package/examples/03-isolated-connections/.env.example +2 -0
- package/examples/03-isolated-connections/03-isolated-connections.sh +12 -0
- package/examples/03-isolated-connections/README.md +56 -0
- package/examples/03-isolated-connections/app.ts +79 -0
- package/examples/03-isolated-connections/package.json +18 -0
- package/examples/03-isolated-connections/schemas/user.schema.ts +23 -0
- package/examples/04-schema-registry/.env.example +2 -0
- package/examples/04-schema-registry/04-schema-registry.sh +10 -0
- package/examples/04-schema-registry/README.md +56 -0
- package/examples/04-schema-registry/app.ts +78 -0
- package/examples/04-schema-registry/package.json +17 -0
- package/examples/04-schema-registry/schemas/index.ts +35 -0
- package/examples/05-types-cles-entity-schema/.env.example +7 -0
- package/examples/05-types-cles-entity-schema/05-types-cles-entity-schema.sh +12 -0
- package/examples/05-types-cles-entity-schema/README.md +60 -0
- package/examples/05-types-cles-entity-schema/app.ts +85 -0
- package/examples/05-types-cles-entity-schema/package.json +18 -0
- package/examples/05-types-cles-entity-schema/schemas/article.schema.ts +93 -0
- package/llms.txt +76 -0
- package/package.json +62 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Multi-dialect switch — un seul code, plusieurs SGBD via .env.
|
|
2
|
+
//
|
|
3
|
+
// Démontre :
|
|
4
|
+
// - getSupportedDialects() : énumération des dialects supportés
|
|
5
|
+
// - getDialectConfig(name) : métadata d'un dialect (driver, etc.)
|
|
6
|
+
// - getConfigFromEnv() : lit DB_DIALECT + SGBD_URI depuis .env
|
|
7
|
+
// - createConnection(config) : config-driven, dialect-agnostic
|
|
8
|
+
// - getCurrentDialectType() : type du dialect actuellement connecté
|
|
9
|
+
//
|
|
10
|
+
// Author: Dr Hamid MADANI <drmdh@msn.com>
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
createConnection,
|
|
14
|
+
getConfigFromEnv,
|
|
15
|
+
getSupportedDialects,
|
|
16
|
+
getDialectConfig,
|
|
17
|
+
getCurrentDialectType,
|
|
18
|
+
BaseRepository,
|
|
19
|
+
} from '@mostajs/orm'
|
|
20
|
+
import { UserSchema, type UserRow } from './schemas/user.schema.js'
|
|
21
|
+
|
|
22
|
+
async function main(): Promise<void> {
|
|
23
|
+
console.log('─── Multi-dialect switch — @mostajs/orm ───')
|
|
24
|
+
|
|
25
|
+
// 1. Liste exhaustive des dialects supportés par la version installée.
|
|
26
|
+
console.log('\nDialects supportés :')
|
|
27
|
+
for (const name of getSupportedDialects()) {
|
|
28
|
+
const cfg = getDialectConfig(name)
|
|
29
|
+
console.log(` - ${name.padEnd(12)} ${cfg?.label ?? cfg?.driver ?? ''}`)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 2. Config lue depuis .env (DB_DIALECT + SGBD_URI). Si rien n'est défini,
|
|
33
|
+
// on fournit un fallback sqlite local — utile pour la première exécution
|
|
34
|
+
// sans configurer .env.
|
|
35
|
+
let config
|
|
36
|
+
try {
|
|
37
|
+
config = getConfigFromEnv()
|
|
38
|
+
} catch {
|
|
39
|
+
console.log('\nℹ DB_DIALECT non défini — fallback SQLite local.')
|
|
40
|
+
config = { dialect: 'sqlite' as const, uri: './app.db' }
|
|
41
|
+
}
|
|
42
|
+
config.schemaStrategy = 'update'
|
|
43
|
+
|
|
44
|
+
console.log('\n✓ Config courante :', config)
|
|
45
|
+
|
|
46
|
+
// 3. Connexion sur le dialect choisi.
|
|
47
|
+
const dialect = await createConnection(config, [UserSchema])
|
|
48
|
+
const currentDialect = getCurrentDialectType()
|
|
49
|
+
console.log(`✓ Connecté à : ${currentDialect}`)
|
|
50
|
+
|
|
51
|
+
// 4. Le code applicatif est strictement le même quel que soit le SGBD.
|
|
52
|
+
const users = new BaseRepository<UserRow>(UserSchema, dialect)
|
|
53
|
+
const email = `multi-${currentDialect}-${Date.now()}@example.com`
|
|
54
|
+
const created = await users.create({ email, name: 'MultiDialect' })
|
|
55
|
+
console.log(`✓ User créé sur ${currentDialect} (id=${created.id})`)
|
|
56
|
+
|
|
57
|
+
const found = await users.findOne({ email })
|
|
58
|
+
if (!found) throw new Error('findOne failed after create')
|
|
59
|
+
|
|
60
|
+
console.log('✅ Smoke OK — le même code marche sur n\'importe quel dialect.')
|
|
61
|
+
|
|
62
|
+
await dialect.disconnect?.()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
main().catch((err) => {
|
|
66
|
+
console.error('❌ Sample failed:', err)
|
|
67
|
+
process.exit(1)
|
|
68
|
+
})
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "02-multi-dialect-switch",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "tsx app.ts"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@mostajs/orm": "^2.1.0",
|
|
11
|
+
"better-sqlite3": "^12.0.0"
|
|
12
|
+
},
|
|
13
|
+
"optionalDependencies": {
|
|
14
|
+
"pg": "^8.20.0",
|
|
15
|
+
"mysql2": "^3.0.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"tsx": "^4.0.0",
|
|
19
|
+
"typescript": "^5.6.0",
|
|
20
|
+
"@types/node": "^22.0.0"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Schéma User identique au sample 01 — la portabilité dialect repose sur
|
|
2
|
+
// le fait que le SAME EntitySchema fonctionne pour tous les dialects SQL.
|
|
3
|
+
// Author: Dr Hamid MADANI <drmdh@msn.com>
|
|
4
|
+
|
|
5
|
+
import type { EntitySchema } from '@mostajs/orm'
|
|
6
|
+
|
|
7
|
+
export const UserSchema: EntitySchema = {
|
|
8
|
+
name: 'User',
|
|
9
|
+
collection: 'users',
|
|
10
|
+
fields: {
|
|
11
|
+
email: { type: 'string', required: true, unique: true },
|
|
12
|
+
name: { type: 'string' },
|
|
13
|
+
},
|
|
14
|
+
relations: {},
|
|
15
|
+
indexes: [{ fields: { email: 'asc' }, unique: true }],
|
|
16
|
+
timestamps: true,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface UserRow {
|
|
20
|
+
id: string
|
|
21
|
+
email: string
|
|
22
|
+
name?: string
|
|
23
|
+
createdAt?: Date
|
|
24
|
+
updatedAt?: Date
|
|
25
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# 03-isolated-connections
|
|
2
|
+
|
|
3
|
+
> Multi-tenant via `createIsolatedDialect` + connexions nommées — démontre la différence entre singleton `getDialect()` et instances isolées.
|
|
4
|
+
|
|
5
|
+
**Couvre** : `createIsolatedDialect`, `getDialect` (piège singleton),
|
|
6
|
+
`registerNamedConnection`, `getNamedConnection`, `removeNamedConnection`,
|
|
7
|
+
`listNamedConnections`, `clearNamedConnections`.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
mkdir tmp && cd tmp && npm init -y && npm install @mostajs/orm-samples
|
|
13
|
+
cp -r node_modules/@mostajs/orm-samples/examples/03-isolated-connections ~/my-isolated-app
|
|
14
|
+
cd ~/my-isolated-app
|
|
15
|
+
rm -rf ../tmp
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## External resources
|
|
19
|
+
|
|
20
|
+
aucune *(SQLite via better-sqlite3)*.
|
|
21
|
+
|
|
22
|
+
## Run
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
./03-isolated-connections.sh
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Expected output
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
─── Isolated connections — @mostajs/orm ───
|
|
32
|
+
✓ tenant-a connecté à ./tenant-a.db
|
|
33
|
+
✓ tenant-b connecté à ./tenant-b.db
|
|
34
|
+
✓ named connections enregistrées : tenant-a, tenant-b
|
|
35
|
+
✓ count tenant-a = 1
|
|
36
|
+
✓ count tenant-b = 2
|
|
37
|
+
✓ DBs physiquement distinctes (count diffère)
|
|
38
|
+
✅ Smoke OK
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## What it shows
|
|
42
|
+
|
|
43
|
+
- **Piège singleton** : `getDialect()` retourne **toujours la même
|
|
44
|
+
instance**. Pour deux tenants, utiliser `createIsolatedDialect()` qui
|
|
45
|
+
bypass le singleton.
|
|
46
|
+
- **Named connections** : registre clé→dialect pour récupérer une
|
|
47
|
+
connexion par nom métier sans la passer en paramètre partout.
|
|
48
|
+
- **Lifecycle complet** : register → use → unregister → clear.
|
|
49
|
+
|
|
50
|
+
## Files
|
|
51
|
+
|
|
52
|
+
- `app.ts` — main code multi-tenant
|
|
53
|
+
- `schemas/user.schema.ts` — schéma partagé entre les 2 tenants
|
|
54
|
+
- `.env.example` — config
|
|
55
|
+
|
|
56
|
+
**Author** : Dr Hamid MADANI <drmdh@msn.com>
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Isolated connections — multi-tenant via createIsolatedDialect + named connections.
|
|
2
|
+
//
|
|
3
|
+
// Démontre :
|
|
4
|
+
// - Piège getDialect() singleton vs createIsolatedDialect()
|
|
5
|
+
// - registerNamedConnection / getNamedConnection / listNamedConnections /
|
|
6
|
+
// removeNamedConnection / clearNamedConnections
|
|
7
|
+
//
|
|
8
|
+
// Cas d'usage : SaaS multi-tenant où chaque client a sa propre DB.
|
|
9
|
+
//
|
|
10
|
+
// Author: Dr Hamid MADANI <drmdh@msn.com>
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
createIsolatedDialect,
|
|
14
|
+
registerNamedConnection,
|
|
15
|
+
getNamedConnection,
|
|
16
|
+
listNamedConnections,
|
|
17
|
+
removeNamedConnection,
|
|
18
|
+
clearNamedConnections,
|
|
19
|
+
BaseRepository,
|
|
20
|
+
} from '@mostajs/orm'
|
|
21
|
+
import { UserSchema, type UserRow } from './schemas/user.schema.js'
|
|
22
|
+
|
|
23
|
+
async function main(): Promise<void> {
|
|
24
|
+
console.log('─── Isolated connections — @mostajs/orm ───')
|
|
25
|
+
|
|
26
|
+
// 1. Deux tenants = deux DB physiquement séparées. createIsolatedDialect
|
|
27
|
+
// bypass le singleton interne — chaque appel retourne une instance neuve.
|
|
28
|
+
const dialectA = await createIsolatedDialect(
|
|
29
|
+
{ dialect: 'sqlite', uri: './tenant-a.db', schemaStrategy: 'update' },
|
|
30
|
+
[UserSchema],
|
|
31
|
+
)
|
|
32
|
+
console.log('✓ tenant-a connecté à ./tenant-a.db')
|
|
33
|
+
|
|
34
|
+
const dialectB = await createIsolatedDialect(
|
|
35
|
+
{ dialect: 'sqlite', uri: './tenant-b.db', schemaStrategy: 'update' },
|
|
36
|
+
[UserSchema],
|
|
37
|
+
)
|
|
38
|
+
console.log('✓ tenant-b connecté à ./tenant-b.db')
|
|
39
|
+
|
|
40
|
+
// 2. Registre nommé pour récupérer une connexion par nom métier.
|
|
41
|
+
registerNamedConnection('tenant-a', dialectA)
|
|
42
|
+
registerNamedConnection('tenant-b', dialectB)
|
|
43
|
+
console.log('✓ named connections enregistrées :', listNamedConnections().join(', '))
|
|
44
|
+
|
|
45
|
+
// 3. Repositories tirés depuis les named connections.
|
|
46
|
+
const repoA = new BaseRepository<UserRow>(UserSchema, getNamedConnection('tenant-a')!)
|
|
47
|
+
const repoB = new BaseRepository<UserRow>(UserSchema, getNamedConnection('tenant-b')!)
|
|
48
|
+
|
|
49
|
+
// 4. Données différentes par tenant — preuve d'isolement.
|
|
50
|
+
await repoA.create({ email: `a-${Date.now()}@tenant-a.example.com`, name: 'Alice A' })
|
|
51
|
+
await repoB.create({ email: `b1-${Date.now()}@tenant-b.example.com`, name: 'Bob B1' })
|
|
52
|
+
await repoB.create({ email: `b2-${Date.now()}@tenant-b.example.com`, name: 'Bob B2' })
|
|
53
|
+
|
|
54
|
+
const countA = await repoA.count({})
|
|
55
|
+
const countB = await repoB.count({})
|
|
56
|
+
console.log(`✓ count tenant-a = ${countA}`)
|
|
57
|
+
console.log(`✓ count tenant-b = ${countB}`)
|
|
58
|
+
|
|
59
|
+
if (countA !== 1 || countB !== 2) {
|
|
60
|
+
throw new Error(`isolation assertion failed: A=${countA}, B=${countB} (expected 1, 2)`)
|
|
61
|
+
}
|
|
62
|
+
console.log('✓ DBs physiquement distinctes (count diffère)')
|
|
63
|
+
|
|
64
|
+
// 5. Cleanup — removeNamedConnection puis clearNamedConnections.
|
|
65
|
+
removeNamedConnection('tenant-a')
|
|
66
|
+
console.log(`✓ après remove tenant-a : ${listNamedConnections().join(', ') || '(vide)'}`)
|
|
67
|
+
clearNamedConnections()
|
|
68
|
+
console.log(`✓ après clearAll : ${listNamedConnections().length} connexions`)
|
|
69
|
+
|
|
70
|
+
console.log('✅ Smoke OK')
|
|
71
|
+
|
|
72
|
+
await dialectA.disconnect?.()
|
|
73
|
+
await dialectB.disconnect?.()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
main().catch((err) => {
|
|
77
|
+
console.error('❌ Sample failed:', err)
|
|
78
|
+
process.exit(1)
|
|
79
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "03-isolated-connections",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "tsx app.ts"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@mostajs/orm": "^2.1.0",
|
|
11
|
+
"better-sqlite3": "^12.0.0"
|
|
12
|
+
},
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"tsx": "^4.0.0",
|
|
15
|
+
"typescript": "^5.6.0",
|
|
16
|
+
"@types/node": "^22.0.0"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Schéma User partagé entre les tenants — la séparation est au niveau
|
|
2
|
+
// DB physique, pas au niveau schéma.
|
|
3
|
+
// Author: Dr Hamid MADANI <drmdh@msn.com>
|
|
4
|
+
|
|
5
|
+
import type { EntitySchema } from '@mostajs/orm'
|
|
6
|
+
|
|
7
|
+
export const UserSchema: EntitySchema = {
|
|
8
|
+
name: 'User',
|
|
9
|
+
collection: 'users',
|
|
10
|
+
fields: {
|
|
11
|
+
email: { type: 'string', required: true, unique: true },
|
|
12
|
+
name: { type: 'string' },
|
|
13
|
+
},
|
|
14
|
+
relations: {},
|
|
15
|
+
indexes: [{ fields: { email: 'asc' }, unique: true }],
|
|
16
|
+
timestamps: true,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface UserRow {
|
|
20
|
+
id: string
|
|
21
|
+
email: string
|
|
22
|
+
name?: string
|
|
23
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# 04-schema-registry
|
|
2
|
+
|
|
3
|
+
> Registre global de schémas : register / lookup / validate / clear, indépendant du dialect.
|
|
4
|
+
|
|
5
|
+
**Couvre** : `registerSchema`, `registerSchemas`, `getSchema`,
|
|
6
|
+
`getSchemaByCollection`, `getAllSchemas`, `getEntityNames`, `hasSchema`,
|
|
7
|
+
`validateSchemas`, `clearRegistry`.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
mkdir tmp && cd tmp && npm init -y && npm install @mostajs/orm-samples
|
|
13
|
+
cp -r node_modules/@mostajs/orm-samples/examples/04-schema-registry ~/my-registry-app
|
|
14
|
+
cd ~/my-registry-app
|
|
15
|
+
rm -rf ../tmp
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## External resources
|
|
19
|
+
|
|
20
|
+
aucune.
|
|
21
|
+
|
|
22
|
+
## Run
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
./04-schema-registry.sh
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Expected output
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
─── Schema registry — @mostajs/orm ───
|
|
32
|
+
✓ 3 schémas enregistrés en batch : User, Project, Registration
|
|
33
|
+
✓ getEntityNames() = [ 'User', 'Project', 'Registration' ]
|
|
34
|
+
✓ hasSchema('User') = true
|
|
35
|
+
✓ getSchema('User').collection = 'users'
|
|
36
|
+
✓ getSchemaByCollection('projects').name = 'Project'
|
|
37
|
+
✓ validateSchemas() = { valid: true, errors: [] }
|
|
38
|
+
✓ clearRegistry() → 0 schémas restants
|
|
39
|
+
✓ ré-registerSchema('Tag') → hasSchema('Tag') = true
|
|
40
|
+
✅ Smoke OK
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## What it shows
|
|
44
|
+
|
|
45
|
+
- `registerSchemas([…])` (batch) vs `registerSchema(s)` (single)
|
|
46
|
+
- 3 lookups : par nom, par collection, all
|
|
47
|
+
- `validateSchemas()` vérifie que les cibles de relations existent dans le registre
|
|
48
|
+
- Cycle complet : register → use → clear → re-register
|
|
49
|
+
|
|
50
|
+
## Files
|
|
51
|
+
|
|
52
|
+
- `app.ts` — main code
|
|
53
|
+
- `schemas/` — 3 schémas (User, Project, Registration) avec relations
|
|
54
|
+
- `.env.example` — N/A (pas de DB pour ce sample)
|
|
55
|
+
|
|
56
|
+
**Author** : Dr Hamid MADANI <drmdh@msn.com>
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// Schema registry — registre global indépendant du dialect.
|
|
2
|
+
//
|
|
3
|
+
// Démontre :
|
|
4
|
+
// - registerSchema(s) / registerSchemas([…])
|
|
5
|
+
// - getSchema, getSchemaByCollection, getAllSchemas, getEntityNames, hasSchema
|
|
6
|
+
// - validateSchemas (vérifie les cibles de relations)
|
|
7
|
+
// - clearRegistry + cycle de re-registration
|
|
8
|
+
//
|
|
9
|
+
// Pas besoin de connexion DB : le registre est purement en mémoire et
|
|
10
|
+
// peut être utilisé pour de l'introspection avant tout dialect.
|
|
11
|
+
//
|
|
12
|
+
// Author: Dr Hamid MADANI <drmdh@msn.com>
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
registerSchema,
|
|
16
|
+
registerSchemas,
|
|
17
|
+
getSchema,
|
|
18
|
+
getSchemaByCollection,
|
|
19
|
+
getAllSchemas,
|
|
20
|
+
getEntityNames,
|
|
21
|
+
hasSchema,
|
|
22
|
+
validateSchemas,
|
|
23
|
+
clearRegistry,
|
|
24
|
+
} from '@mostajs/orm'
|
|
25
|
+
import { UserSchema, ProjectSchema, RegistrationSchema, TagSchema } from './schemas/index.js'
|
|
26
|
+
|
|
27
|
+
async function main(): Promise<void> {
|
|
28
|
+
console.log('─── Schema registry — @mostajs/orm ───')
|
|
29
|
+
|
|
30
|
+
// Bonne pratique : clear avant en cas de re-run dans la même process.
|
|
31
|
+
clearRegistry()
|
|
32
|
+
|
|
33
|
+
// 1. registerSchemas — batch.
|
|
34
|
+
registerSchemas([UserSchema, ProjectSchema, RegistrationSchema])
|
|
35
|
+
const names = getEntityNames()
|
|
36
|
+
console.log(`✓ 3 schémas enregistrés en batch : ${names.join(', ')}`)
|
|
37
|
+
|
|
38
|
+
if (names.length !== 3) throw new Error(`expected 3 schemas, got ${names.length}`)
|
|
39
|
+
|
|
40
|
+
// 2. getEntityNames — liste des noms enregistrés.
|
|
41
|
+
console.log('✓ getEntityNames() =', names)
|
|
42
|
+
|
|
43
|
+
// 3. hasSchema — check d'existence.
|
|
44
|
+
console.log(`✓ hasSchema('User') = ${hasSchema('User')}`)
|
|
45
|
+
if (!hasSchema('User')) throw new Error('User missing')
|
|
46
|
+
|
|
47
|
+
// 4. getSchema — lookup par nom (throw si absent).
|
|
48
|
+
console.log(`✓ getSchema('User').collection = '${getSchema('User').collection}'`)
|
|
49
|
+
|
|
50
|
+
// 5. getSchemaByCollection — lookup par table/collection name.
|
|
51
|
+
const byColl = getSchemaByCollection('projects')
|
|
52
|
+
console.log(`✓ getSchemaByCollection('projects').name = '${byColl?.name}'`)
|
|
53
|
+
|
|
54
|
+
// 6. getAllSchemas — récupère tous les schémas pour iteration.
|
|
55
|
+
const all = getAllSchemas()
|
|
56
|
+
if (all.length !== 3) throw new Error(`getAllSchemas count mismatch: ${all.length}`)
|
|
57
|
+
|
|
58
|
+
// 7. validateSchemas — vérifie que les `target` des relations existent.
|
|
59
|
+
const validation = validateSchemas()
|
|
60
|
+
console.log('✓ validateSchemas() =', validation)
|
|
61
|
+
if (!validation.valid) throw new Error(`validation failed: ${validation.errors.join(', ')}`)
|
|
62
|
+
|
|
63
|
+
// 8. clearRegistry — vide tout. Utile en tests, dans les hot-reload, etc.
|
|
64
|
+
clearRegistry()
|
|
65
|
+
console.log(`✓ clearRegistry() → ${getEntityNames().length} schémas restants`)
|
|
66
|
+
if (getEntityNames().length !== 0) throw new Error('clearRegistry failed')
|
|
67
|
+
|
|
68
|
+
// 9. registerSchema unitaire — alternative au batch.
|
|
69
|
+
registerSchema(TagSchema)
|
|
70
|
+
console.log(`✓ ré-registerSchema('Tag') → hasSchema('Tag') = ${hasSchema('Tag')}`)
|
|
71
|
+
|
|
72
|
+
console.log('✅ Smoke OK')
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
main().catch((err) => {
|
|
76
|
+
console.error('❌ Sample failed:', err)
|
|
77
|
+
process.exit(1)
|
|
78
|
+
})
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "04-schema-registry",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "tsx app.ts"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@mostajs/orm": "^2.1.0"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"tsx": "^4.0.0",
|
|
14
|
+
"typescript": "^5.6.0",
|
|
15
|
+
"@types/node": "^22.0.0"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// 3 schémas avec relations croisées pour démontrer validateSchemas() :
|
|
2
|
+
// User ← Project (many-to-one author) ← Registration (many-to-one project).
|
|
3
|
+
// Author: Dr Hamid MADANI <drmdh@msn.com>
|
|
4
|
+
|
|
5
|
+
import type { EntitySchema } from '@mostajs/orm'
|
|
6
|
+
|
|
7
|
+
export const UserSchema: EntitySchema = {
|
|
8
|
+
name: 'User', collection: 'users',
|
|
9
|
+
fields: { email: { type: 'string', required: true, unique: true } },
|
|
10
|
+
relations: {}, indexes: [], timestamps: true,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const ProjectSchema: EntitySchema = {
|
|
14
|
+
name: 'Project', collection: 'projects',
|
|
15
|
+
fields: { name: { type: 'string', required: true } },
|
|
16
|
+
relations: {
|
|
17
|
+
author: { type: 'many-to-one', target: 'User', onDelete: 'set-null' },
|
|
18
|
+
},
|
|
19
|
+
indexes: [], timestamps: true,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const RegistrationSchema: EntitySchema = {
|
|
23
|
+
name: 'Registration', collection: 'registrations',
|
|
24
|
+
fields: { code: { type: 'string', required: true } },
|
|
25
|
+
relations: {
|
|
26
|
+
project: { type: 'many-to-one', target: 'Project', onDelete: 'cascade' },
|
|
27
|
+
},
|
|
28
|
+
indexes: [], timestamps: true,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const TagSchema: EntitySchema = {
|
|
32
|
+
name: 'Tag', collection: 'tags',
|
|
33
|
+
fields: { label: { type: 'string', required: true, unique: true } },
|
|
34
|
+
relations: {}, indexes: [], timestamps: true,
|
|
35
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Default — SQLite local, zéro install externe.
|
|
2
|
+
DB_DIALECT=sqlite
|
|
3
|
+
SGBD_URI=./app.db
|
|
4
|
+
|
|
5
|
+
# Note : ce sample illustre les TYPES de field/index ; le dialect importe
|
|
6
|
+
# peu car la définition est portable. Sur Postgres/MySQL les types SQL
|
|
7
|
+
# précis (TEXT vs VARCHAR, JSONB vs JSON…) diffèrent automatiquement.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# 05-types-cles-entity-schema
|
|
2
|
+
|
|
3
|
+
> Tous les types-clés EntitySchema démontrés : chaque FieldType, chaque FieldDef option, IndexDef, discriminator, softDelete.
|
|
4
|
+
|
|
5
|
+
**Couvre** : `EntitySchema` exhaustif, `FieldDef` *(required, unique, sparse,
|
|
6
|
+
default, enum, lowercase, trim, arrayOf)*, `FieldType`
|
|
7
|
+
*(string, text, number, boolean, date, json, array)*, `EmbeddedSchemaDef`,
|
|
8
|
+
`IndexDef`, `IndexType` *(asc, desc, text)*, `discriminator`,
|
|
9
|
+
`discriminatorValue`, `softDelete`, `timestamps`.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
mkdir tmp && cd tmp && npm init -y && npm install @mostajs/orm-samples
|
|
15
|
+
cp -r node_modules/@mostajs/orm-samples/examples/05-types-cles-entity-schema ~/my-types-app
|
|
16
|
+
cd ~/my-types-app
|
|
17
|
+
rm -rf ../tmp
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## External resources
|
|
21
|
+
|
|
22
|
+
aucune *(SQLite via better-sqlite3)*.
|
|
23
|
+
|
|
24
|
+
## Run
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
./05-types-cles-entity-schema.sh
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Expected output
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
─── Types clés EntitySchema — @mostajs/orm ───
|
|
34
|
+
✓ Schéma Article enregistré (softDelete=true, discriminator='_type')
|
|
35
|
+
✓ FieldType démontrés : string, text, number, boolean, date, json, array
|
|
36
|
+
✓ FieldDef options démontrées : required, unique, default, enum, lowercase, trim, arrayOf
|
|
37
|
+
✓ IndexDef : 4 indexes dont 1 unique+sparse, 1 desc, 1 text, 1 composite
|
|
38
|
+
✓ Article créé avec id=… title='Hello' status='draft' (enum default)
|
|
39
|
+
✓ slug lowercase appliqué : 'hello-world' (input était 'Hello-World')
|
|
40
|
+
✓ tags arrayOf string OK : [ 'one', 'two', 'three' ]
|
|
41
|
+
✓ metadata JSON OK : { meta: 'sample' }
|
|
42
|
+
✓ Soft-delete : count={ active: 0, total: 1 } après delete
|
|
43
|
+
✅ Smoke OK
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## What it shows
|
|
47
|
+
|
|
48
|
+
- Un seul schéma `Article` qui exerce **chaque field type, chaque option
|
|
49
|
+
FieldDef, chaque type d'index**
|
|
50
|
+
- `discriminator` (single-table inheritance Drupal-style) + `discriminatorValue`
|
|
51
|
+
- `softDelete: true` natif + index `sparse: true` cohérent
|
|
52
|
+
- Validation runtime des transformations : `lowercase`, `trim`, `enum default`
|
|
53
|
+
|
|
54
|
+
## Files
|
|
55
|
+
|
|
56
|
+
- `app.ts` — main code avec assertions sur chaque option
|
|
57
|
+
- `schemas/article.schema.ts` — le schéma exhaustif
|
|
58
|
+
- `.env.example` — config SQLite
|
|
59
|
+
|
|
60
|
+
**Author** : Dr Hamid MADANI <drmdh@msn.com>
|