@atscript/moost-mongo 0.1.27 → 0.1.28
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 +4 -4
- package/dist/index.d.ts +7 -7
- package/dist/index.mjs +4 -4
- package/package.json +16 -10
- package/scripts/setup-skills.js +78 -0
- package/skills/atscript-moost-mongo/.placeholder +0 -0
- package/skills/atscript-moost-mongo/SKILL.md +32 -0
- package/skills/atscript-moost-mongo/controllers.md +138 -0
- package/skills/atscript-moost-mongo/core.md +62 -0
- package/skills/atscript-moost-mongo/decorators.md +88 -0
package/dist/index.cjs
CHANGED
|
@@ -30,7 +30,7 @@ const __atscript_mongo = __toESM(require("@atscript/mongo"));
|
|
|
30
30
|
|
|
31
31
|
//#region packages/moost-mongo/src/decorators.ts
|
|
32
32
|
const COLLECTION_DEF = "__atscript_mongo_collection_def";
|
|
33
|
-
const CollectionController = (type, prefix) => (0, moost.ApplyDecorators)((0, moost.Provide)(COLLECTION_DEF, () => type), (0, moost.Controller)(prefix || type.metadata.get("
|
|
33
|
+
const CollectionController = (type, prefix) => (0, moost.ApplyDecorators)((0, moost.Provide)(COLLECTION_DEF, () => type), (0, moost.Controller)(prefix || type.metadata.get("db.table") || type.name), (0, moost.Inherit)());
|
|
34
34
|
const InjectCollection = (type) => (0, moost.Resolve)(async ({ instantiate }) => {
|
|
35
35
|
const asMongo = await instantiate(__atscript_mongo.AsMongo);
|
|
36
36
|
return asMongo.getCollection(type);
|
|
@@ -93,7 +93,7 @@ _define_property$1(SelectControlDto, "__is_atscript_annotated_type", true);
|
|
|
93
93
|
_define_property$1(SelectControlDto, "type", {});
|
|
94
94
|
_define_property$1(SelectControlDto, "metadata", new Map());
|
|
95
95
|
_define_property$1(SelectControlDto, "id", "SelectControlDto");
|
|
96
|
-
(0, __atscript_typescript_utils.defineAnnotatedType)("object", QueryControlsDto).prop("$skip", (0, __atscript_typescript_utils.defineAnnotatedType)().designType("number").tags("positive", "int", "number").annotate("expect.
|
|
96
|
+
(0, __atscript_typescript_utils.defineAnnotatedType)("object", QueryControlsDto).prop("$skip", (0, __atscript_typescript_utils.defineAnnotatedType)().designType("number").tags("positive", "int", "number").annotate("expect.int", true).annotate("expect.min", { minValue: 0 }).optional().$type).prop("$limit", (0, __atscript_typescript_utils.defineAnnotatedType)().designType("number").tags("positive", "int", "number").annotate("expect.int", true).annotate("expect.min", { minValue: 0 }).optional().$type).prop("$count", (0, __atscript_typescript_utils.defineAnnotatedType)().designType("boolean").tags("boolean").optional().$type).prop("$sort", (0, __atscript_typescript_utils.defineAnnotatedType)().refTo(SortControlDto).optional().$type).prop("$select", (0, __atscript_typescript_utils.defineAnnotatedType)().refTo(SelectControlDto).optional().$type).prop("$search", (0, __atscript_typescript_utils.defineAnnotatedType)().designType("string").tags("string").optional().$type).prop("$index", (0, __atscript_typescript_utils.defineAnnotatedType)().designType("string").tags("string").optional().$type);
|
|
97
97
|
(0, __atscript_typescript_utils.defineAnnotatedType)("object", PagesControlsDto).prop("$page", (0, __atscript_typescript_utils.defineAnnotatedType)().designType("string").tags("string").annotate("expect.pattern", {
|
|
98
98
|
pattern: "^\\d+$",
|
|
99
99
|
flags: "u",
|
|
@@ -144,7 +144,7 @@ var AsMongoController = class {
|
|
|
144
144
|
* Override to seed data, register change streams, etc. Both sync and async
|
|
145
145
|
* return types are supported.
|
|
146
146
|
*/ init() {
|
|
147
|
-
if (this.type.metadata.get("mongo.autoIndexes") === false) {} else return this.asCollection.syncIndexes();
|
|
147
|
+
if (this.type.metadata.get("db.mongo.autoIndexes") === false) {} else return this.asCollection.syncIndexes();
|
|
148
148
|
}
|
|
149
149
|
/** Returns (and memoises) validator for *query* endpoint controls. */ get queryControlsValidator() {
|
|
150
150
|
if (!this._queryControlsValidator) this._queryControlsValidator = QueryControlsDto.validator();
|
|
@@ -468,7 +468,7 @@ else return new __moostjs_event_http.HttpError(500, "Not saved");
|
|
|
468
468
|
_define_property(this, "_getOneControlsValidator", void 0);
|
|
469
469
|
this.asMongo = asMongo;
|
|
470
470
|
this.type = type;
|
|
471
|
-
this.logger = app.getLogger(`mongo [${type.metadata.get("
|
|
471
|
+
this.logger = app.getLogger(`mongo [${type.metadata.get("db.table") || ""}]`);
|
|
472
472
|
this.asCollection = this.asMongo.getCollection(type, this.logger);
|
|
473
473
|
this.logger.info(`Initializing Collection`);
|
|
474
474
|
try {
|
package/dist/index.d.ts
CHANGED
|
@@ -57,11 +57,11 @@ declare class AsMongoController<T extends TAtscriptAnnotatedType = TAtscriptAnno
|
|
|
57
57
|
private _pagesControlsValidator?;
|
|
58
58
|
private _getOneControlsValidator?;
|
|
59
59
|
/** Returns (and memoises) validator for *query* endpoint controls. */
|
|
60
|
-
protected get queryControlsValidator(): Validator<any, unknown
|
|
60
|
+
protected get queryControlsValidator(): Validator<any, unknown>;
|
|
61
61
|
/** Returns (and memoises) validator for *pages* endpoint controls. */
|
|
62
|
-
protected get pagesControlsValidator(): Validator<any, unknown
|
|
62
|
+
protected get pagesControlsValidator(): Validator<any, unknown>;
|
|
63
63
|
/** Returns (and memoises) validator for *one* endpoint controls. */
|
|
64
|
-
protected get getOneControlsValidator(): Validator<any, unknown
|
|
64
|
+
protected get getOneControlsValidator(): Validator<any, unknown>;
|
|
65
65
|
/**
|
|
66
66
|
* Validates `$limit`, `$skip`, `$sort`, `$select`, `$count` controls for the
|
|
67
67
|
* **query** endpoint.
|
|
@@ -241,7 +241,7 @@ declare const COLLECTION_DEF = "__atscript_mongo_collection_def";
|
|
|
241
241
|
/**
|
|
242
242
|
* Combines the boilerplate needed to turn an {@link AsMongoController}
|
|
243
243
|
* subclass into a fully wired HTTP controller for a given
|
|
244
|
-
* **@
|
|
244
|
+
* **@db.table** model.
|
|
245
245
|
*
|
|
246
246
|
* Internally applies three decorators:
|
|
247
247
|
* 1. **Provide** – registers the collection constructor under {@link COLLECTION_DEF}.
|
|
@@ -250,9 +250,9 @@ declare const COLLECTION_DEF = "__atscript_mongo_collection_def";
|
|
|
250
250
|
* 3. **Inherit** – copies metadata (routes, guards, etc.) from the
|
|
251
251
|
* parent class so they stay active in the derived controller.
|
|
252
252
|
*
|
|
253
|
-
* @param type AtScript-annotated constructor produced by `@
|
|
253
|
+
* @param type AtScript-annotated constructor produced by `@db.table`.
|
|
254
254
|
* @param prefix Optional route prefix. Defaults to
|
|
255
|
-
* `type.metadata.get("
|
|
255
|
+
* `type.metadata.get("db.table")` or the class name.
|
|
256
256
|
*
|
|
257
257
|
* @example
|
|
258
258
|
* ```ts
|
|
@@ -271,7 +271,7 @@ declare const CollectionController: (type: TAtscriptAnnotatedType & {
|
|
|
271
271
|
* > (e.g. `@Provide(AsMongo, () => new AsMongo(url))`).
|
|
272
272
|
*
|
|
273
273
|
* @param type AtScript-annotated constructor produced by
|
|
274
|
-
* `@
|
|
274
|
+
* `@db.table`.
|
|
275
275
|
*
|
|
276
276
|
* @example
|
|
277
277
|
* ```ts
|
package/dist/index.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { AsMongo as AsMongo$1 } from "@atscript/mongo";
|
|
|
6
6
|
|
|
7
7
|
//#region packages/moost-mongo/src/decorators.ts
|
|
8
8
|
const COLLECTION_DEF = "__atscript_mongo_collection_def";
|
|
9
|
-
const CollectionController = (type, prefix) => ApplyDecorators(Provide(COLLECTION_DEF, () => type), Controller(prefix || type.metadata.get("
|
|
9
|
+
const CollectionController = (type, prefix) => ApplyDecorators(Provide(COLLECTION_DEF, () => type), Controller(prefix || type.metadata.get("db.table") || type.name), Inherit());
|
|
10
10
|
const InjectCollection = (type) => Resolve(async ({ instantiate }) => {
|
|
11
11
|
const asMongo = await instantiate(AsMongo$1);
|
|
12
12
|
return asMongo.getCollection(type);
|
|
@@ -69,7 +69,7 @@ _define_property$1(SelectControlDto, "__is_atscript_annotated_type", true);
|
|
|
69
69
|
_define_property$1(SelectControlDto, "type", {});
|
|
70
70
|
_define_property$1(SelectControlDto, "metadata", new Map());
|
|
71
71
|
_define_property$1(SelectControlDto, "id", "SelectControlDto");
|
|
72
|
-
defineAnnotatedType("object", QueryControlsDto).prop("$skip", defineAnnotatedType().designType("number").tags("positive", "int", "number").annotate("expect.
|
|
72
|
+
defineAnnotatedType("object", QueryControlsDto).prop("$skip", defineAnnotatedType().designType("number").tags("positive", "int", "number").annotate("expect.int", true).annotate("expect.min", { minValue: 0 }).optional().$type).prop("$limit", defineAnnotatedType().designType("number").tags("positive", "int", "number").annotate("expect.int", true).annotate("expect.min", { minValue: 0 }).optional().$type).prop("$count", defineAnnotatedType().designType("boolean").tags("boolean").optional().$type).prop("$sort", defineAnnotatedType().refTo(SortControlDto).optional().$type).prop("$select", defineAnnotatedType().refTo(SelectControlDto).optional().$type).prop("$search", defineAnnotatedType().designType("string").tags("string").optional().$type).prop("$index", defineAnnotatedType().designType("string").tags("string").optional().$type);
|
|
73
73
|
defineAnnotatedType("object", PagesControlsDto).prop("$page", defineAnnotatedType().designType("string").tags("string").annotate("expect.pattern", {
|
|
74
74
|
pattern: "^\\d+$",
|
|
75
75
|
flags: "u",
|
|
@@ -120,7 +120,7 @@ var AsMongoController = class {
|
|
|
120
120
|
* Override to seed data, register change streams, etc. Both sync and async
|
|
121
121
|
* return types are supported.
|
|
122
122
|
*/ init() {
|
|
123
|
-
if (this.type.metadata.get("mongo.autoIndexes") === false) {} else return this.asCollection.syncIndexes();
|
|
123
|
+
if (this.type.metadata.get("db.mongo.autoIndexes") === false) {} else return this.asCollection.syncIndexes();
|
|
124
124
|
}
|
|
125
125
|
/** Returns (and memoises) validator for *query* endpoint controls. */ get queryControlsValidator() {
|
|
126
126
|
if (!this._queryControlsValidator) this._queryControlsValidator = QueryControlsDto.validator();
|
|
@@ -444,7 +444,7 @@ else return new HttpError(500, "Not saved");
|
|
|
444
444
|
_define_property(this, "_getOneControlsValidator", void 0);
|
|
445
445
|
this.asMongo = asMongo;
|
|
446
446
|
this.type = type;
|
|
447
|
-
this.logger = app.getLogger(`mongo [${type.metadata.get("
|
|
447
|
+
this.logger = app.getLogger(`mongo [${type.metadata.get("db.table") || ""}]`);
|
|
448
448
|
this.asCollection = this.asMongo.getCollection(type, this.logger);
|
|
449
449
|
this.logger.info(`Initializing Collection`);
|
|
450
450
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atscript/moost-mongo",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.28",
|
|
4
4
|
"description": "Atscript Mongo for Moost.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"annotations",
|
|
@@ -20,8 +20,13 @@
|
|
|
20
20
|
"url": "git+https://github.com/moostjs/atscript.git",
|
|
21
21
|
"directory": "packages/moost-mongo"
|
|
22
22
|
},
|
|
23
|
+
"bin": {
|
|
24
|
+
"atscript-moost-mongo-skill": "./scripts/setup-skills.js"
|
|
25
|
+
},
|
|
23
26
|
"files": [
|
|
24
|
-
"dist"
|
|
27
|
+
"dist",
|
|
28
|
+
"skills",
|
|
29
|
+
"scripts/setup-skills.js"
|
|
25
30
|
],
|
|
26
31
|
"type": "module",
|
|
27
32
|
"main": "dist/index.mjs",
|
|
@@ -38,22 +43,23 @@
|
|
|
38
43
|
"urlql": "^0.0.5"
|
|
39
44
|
},
|
|
40
45
|
"devDependencies": {
|
|
41
|
-
"@moostjs/event-http": "^0.6.
|
|
46
|
+
"@moostjs/event-http": "^0.6.2",
|
|
42
47
|
"mongodb": "^6.17.0",
|
|
43
|
-
"moost": "^0.6.
|
|
48
|
+
"moost": "^0.6.2",
|
|
44
49
|
"vitest": "3.2.4",
|
|
45
|
-
"@atscript/core": "^0.1.
|
|
50
|
+
"@atscript/core": "^0.1.28"
|
|
46
51
|
},
|
|
47
52
|
"peerDependencies": {
|
|
48
|
-
"@moostjs/event-http": "^0.6.
|
|
53
|
+
"@moostjs/event-http": "^0.6.2",
|
|
49
54
|
"mongodb": "^6.17.0",
|
|
50
|
-
"moost": "^0.6.
|
|
51
|
-
"@atscript/mongo": "^0.1.
|
|
52
|
-
"@atscript/typescript": "^0.1.
|
|
55
|
+
"moost": "^0.6.2",
|
|
56
|
+
"@atscript/mongo": "^0.1.28",
|
|
57
|
+
"@atscript/typescript": "^0.1.28"
|
|
53
58
|
},
|
|
54
59
|
"scripts": {
|
|
55
60
|
"pub": "pnpm publish --access public",
|
|
56
61
|
"before-build": "node ../typescript/cli.cjs -f js",
|
|
57
|
-
"test": "vitest"
|
|
62
|
+
"test": "vitest",
|
|
63
|
+
"setup-skills": "node ./scripts/setup-skills.js"
|
|
58
64
|
}
|
|
59
65
|
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* prettier-ignore */
|
|
3
|
+
import fs from 'fs'
|
|
4
|
+
import path from 'path'
|
|
5
|
+
import os from 'os'
|
|
6
|
+
import { fileURLToPath } from 'url'
|
|
7
|
+
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
9
|
+
|
|
10
|
+
const SKILL_NAME = 'atscript-moost-mongo'
|
|
11
|
+
const SKILL_SRC = path.join(__dirname, '..', 'skills', SKILL_NAME)
|
|
12
|
+
|
|
13
|
+
if (!fs.existsSync(SKILL_SRC)) {
|
|
14
|
+
console.error(`No skills found at ${SKILL_SRC}`)
|
|
15
|
+
console.error('Add your SKILL.md files to the skills/' + SKILL_NAME + '/ directory first.')
|
|
16
|
+
process.exit(1)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const AGENTS = {
|
|
20
|
+
'Claude Code': { dir: '.claude/skills', global: path.join(os.homedir(), '.claude', 'skills') },
|
|
21
|
+
'Cursor': { dir: '.cursor/skills', global: path.join(os.homedir(), '.cursor', 'skills') },
|
|
22
|
+
'Windsurf': { dir: '.windsurf/skills', global: path.join(os.homedir(), '.windsurf', 'skills') },
|
|
23
|
+
'Codex': { dir: '.codex/skills', global: path.join(os.homedir(), '.codex', 'skills') },
|
|
24
|
+
'OpenCode': { dir: '.opencode/skills', global: path.join(os.homedir(), '.opencode', 'skills') },
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const args = process.argv.slice(2)
|
|
28
|
+
const isGlobal = args.includes('--global') || args.includes('-g')
|
|
29
|
+
const isPostinstall = args.includes('--postinstall')
|
|
30
|
+
let installed = 0, skipped = 0
|
|
31
|
+
const installedDirs = []
|
|
32
|
+
|
|
33
|
+
for (const [agentName, cfg] of Object.entries(AGENTS)) {
|
|
34
|
+
const targetBase = isGlobal ? cfg.global : path.join(process.cwd(), cfg.dir)
|
|
35
|
+
const agentRootDir = path.dirname(cfg.global) // Check if the agent has ever been installed globally
|
|
36
|
+
|
|
37
|
+
// In postinstall mode: silently skip agents that aren't set up globally
|
|
38
|
+
if (isPostinstall || isGlobal) {
|
|
39
|
+
if (!fs.existsSync(agentRootDir)) { skipped++; continue }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const dest = path.join(targetBase, SKILL_NAME)
|
|
43
|
+
try {
|
|
44
|
+
fs.mkdirSync(dest, { recursive: true })
|
|
45
|
+
fs.cpSync(SKILL_SRC, dest, { recursive: true })
|
|
46
|
+
console.log(`✅ ${agentName}: installed to ${dest}`)
|
|
47
|
+
installed++
|
|
48
|
+
if (!isGlobal) installedDirs.push(cfg.dir + '/' + SKILL_NAME)
|
|
49
|
+
} catch (err) {
|
|
50
|
+
console.warn(`⚠️ ${agentName}: failed — ${err.message}`)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Add locally-installed skill dirs to .gitignore
|
|
55
|
+
if (!isGlobal && installedDirs.length > 0) {
|
|
56
|
+
const gitignorePath = path.join(process.cwd(), '.gitignore')
|
|
57
|
+
let gitignoreContent = ''
|
|
58
|
+
try { gitignoreContent = fs.readFileSync(gitignorePath, 'utf8') } catch {}
|
|
59
|
+
const linesToAdd = installedDirs.filter(d => !gitignoreContent.includes(d))
|
|
60
|
+
if (linesToAdd.length > 0) {
|
|
61
|
+
const hasHeader = gitignoreContent.includes('# AI agent skills')
|
|
62
|
+
const block = (gitignoreContent && !gitignoreContent.endsWith('\n') ? '\n' : '')
|
|
63
|
+
+ (hasHeader ? '' : '\n# AI agent skills (auto-generated by setup-skills)\n')
|
|
64
|
+
+ linesToAdd.join('\n') + '\n'
|
|
65
|
+
fs.appendFileSync(gitignorePath, block)
|
|
66
|
+
console.log(`📝 Added ${linesToAdd.length} entries to .gitignore`)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (installed === 0 && isPostinstall) {
|
|
71
|
+
// Silence is fine — no agents present, nothing to do
|
|
72
|
+
} else if (installed === 0 && skipped === Object.keys(AGENTS).length) {
|
|
73
|
+
console.log('No agent directories detected. Try --global or run without it for project-local install.')
|
|
74
|
+
} else if (installed === 0) {
|
|
75
|
+
console.log('Nothing installed. Run without --global to install project-locally.')
|
|
76
|
+
} else {
|
|
77
|
+
console.log(`\n✨ Done! Restart your AI agent to pick up the "${SKILL_NAME}" skill.`)
|
|
78
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: atscript-moost-mongo
|
|
3
|
+
description: Use this skill when working with @atscript/moost-mongo — to create REST controllers for MongoDB collections with AsMongoController, use CollectionController decorator, inject collections with InjectCollection, configure query/pagination/sorting with URLQL DTOs, override controller hooks (onWrite/onRemove/transformFilter/prepareSearch), or integrate @atscript/mongo collections into a Moost application.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @atscript/moost-mongo
|
|
7
|
+
|
|
8
|
+
Moost framework integration for Atscript MongoDB. Provides automated REST controllers for MongoDB collections defined via `.as` types, with full validation, pagination, and search support.
|
|
9
|
+
|
|
10
|
+
## How to use this skill
|
|
11
|
+
|
|
12
|
+
Read the domain file that matches the task. Do not load all files — only what you need.
|
|
13
|
+
|
|
14
|
+
| Domain | File | Load when... |
|
|
15
|
+
|--------|------|-------------|
|
|
16
|
+
| Core setup & architecture | [core.md](core.md) | Installing, configuring, understanding controller architecture |
|
|
17
|
+
| Controllers & hooks | [controllers.md](controllers.md) | Creating controllers, overriding hooks, customizing CRUD behavior |
|
|
18
|
+
| Decorators & DI | [decorators.md](decorators.md) | Using CollectionController, InjectCollection, understanding DI wiring |
|
|
19
|
+
|
|
20
|
+
## Quick reference
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { AsMongoController, CollectionController } from '@atscript/moost-mongo'
|
|
24
|
+
import { AsMongo } from '@atscript/mongo'
|
|
25
|
+
import { Provide } from 'moost'
|
|
26
|
+
import { User } from './user.as'
|
|
27
|
+
|
|
28
|
+
@Provide(AsMongo, () => new AsMongo('mongodb://localhost:27017/mydb'))
|
|
29
|
+
@CollectionController(User)
|
|
30
|
+
export class UsersController extends AsMongoController<typeof User> {}
|
|
31
|
+
// That's it — GET/POST/PUT/PATCH/DELETE endpoints are all automatic
|
|
32
|
+
```
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Controllers & Hooks — @atscript/moost-mongo
|
|
2
|
+
|
|
3
|
+
> Creating REST controllers and customizing behavior via hooks.
|
|
4
|
+
|
|
5
|
+
## Basic Controller
|
|
6
|
+
|
|
7
|
+
The simplest controller is an empty subclass:
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { AsMongoController, CollectionController } from '@atscript/moost-mongo'
|
|
11
|
+
import { AsMongo } from '@atscript/mongo'
|
|
12
|
+
import { Provide } from 'moost'
|
|
13
|
+
import { User } from './user.as'
|
|
14
|
+
|
|
15
|
+
@Provide(AsMongo, () => new AsMongo(process.env.MONGO_URI!))
|
|
16
|
+
@CollectionController(User)
|
|
17
|
+
export class UsersController extends AsMongoController<typeof User> {}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
This gives you all 7 REST endpoints automatically.
|
|
21
|
+
|
|
22
|
+
## Overridable Hooks
|
|
23
|
+
|
|
24
|
+
### `init()`
|
|
25
|
+
|
|
26
|
+
Called during controller initialization. Use for one-time setup like syncing indexes:
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
@CollectionController(User)
|
|
30
|
+
export class UsersController extends AsMongoController<typeof User> {
|
|
31
|
+
async init() {
|
|
32
|
+
await this.collection.syncIndexes()
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### `onWrite(action, data, opts)`
|
|
38
|
+
|
|
39
|
+
Intercepts write operations. `action` is `'insert'`, `'replace'`, or `'update'`.
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
async onWrite(action: string, data: any, opts: any) {
|
|
43
|
+
if (action === 'insert') {
|
|
44
|
+
data.createdAt = new Date().toISOString()
|
|
45
|
+
}
|
|
46
|
+
data.updatedAt = new Date().toISOString()
|
|
47
|
+
return super.onWrite(action, data, opts)
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### `onRemove(id, opts)`
|
|
52
|
+
|
|
53
|
+
Intercepts delete operations. Return the result or throw to prevent deletion.
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
async onRemove(id: string, opts: any) {
|
|
57
|
+
// Soft delete instead
|
|
58
|
+
await this.collection.update({ _id: id, deletedAt: new Date().toISOString() })
|
|
59
|
+
return { deletedCount: 1 }
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### `transformFilter(filter)`
|
|
64
|
+
|
|
65
|
+
Modify the MongoDB filter before any query. Use for multi-tenancy, soft deletes, etc.
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
transformFilter(filter: Filter<any>) {
|
|
69
|
+
return { ...filter, tenantId: this.currentTenantId }
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### `transformProjection(projection)`
|
|
74
|
+
|
|
75
|
+
Modify field projection before queries.
|
|
76
|
+
|
|
77
|
+
### `prepareSearch(query, index?)`
|
|
78
|
+
|
|
79
|
+
Override to customize how text/vector search queries are built.
|
|
80
|
+
|
|
81
|
+
### `prepareTextSearch(query, index?)`
|
|
82
|
+
|
|
83
|
+
Override for text search customization.
|
|
84
|
+
|
|
85
|
+
### `prepareVectorSearch(query, index?)`
|
|
86
|
+
|
|
87
|
+
Override for vector search customization.
|
|
88
|
+
|
|
89
|
+
## Query Controls
|
|
90
|
+
|
|
91
|
+
The GET endpoints accept URLQL query parameters:
|
|
92
|
+
|
|
93
|
+
### `/query` endpoint
|
|
94
|
+
|
|
95
|
+
Uses `QueryControlsDto`:
|
|
96
|
+
- `$skip` — Number of documents to skip
|
|
97
|
+
- `$limit` — Maximum documents to return
|
|
98
|
+
- `$count` — If true, return count instead of documents
|
|
99
|
+
- `$sort` — Sort specification (e.g. `$sort[name]=1`)
|
|
100
|
+
- `$select` — Field projection (e.g. `$select[name]=1`)
|
|
101
|
+
- `$search` — Text search query
|
|
102
|
+
- `$index` — Search index name
|
|
103
|
+
|
|
104
|
+
### `/pages` endpoint
|
|
105
|
+
|
|
106
|
+
Uses `PagesControlsDto`:
|
|
107
|
+
- `$page` — Page number (1-based)
|
|
108
|
+
- `$size` — Page size
|
|
109
|
+
- `$sort`, `$select`, `$search`, `$index` — Same as query
|
|
110
|
+
|
|
111
|
+
### `/one/:id` endpoint
|
|
112
|
+
|
|
113
|
+
Uses `GetOneControlsDto`:
|
|
114
|
+
- `$select` — Field projection
|
|
115
|
+
- `:id` — Tries `_id` first, then falls back to `@db.index.unique` fields
|
|
116
|
+
|
|
117
|
+
## Custom Route Prefix
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
@CollectionController(User, 'api/v2/users')
|
|
121
|
+
export class UsersV2Controller extends AsMongoController<typeof User> {}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
If not specified, defaults to the `@db.table` name.
|
|
125
|
+
|
|
126
|
+
## Accessing the Collection
|
|
127
|
+
|
|
128
|
+
Inside controller methods, use `this.collection` to access the underlying `AsCollection`:
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
@CollectionController(User)
|
|
132
|
+
export class UsersController extends AsMongoController<typeof User> {
|
|
133
|
+
async customMethod() {
|
|
134
|
+
const users = await this.collection.collection.find({ isActive: true }).toArray()
|
|
135
|
+
return users
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Core Setup — @atscript/moost-mongo
|
|
2
|
+
|
|
3
|
+
> Installation, configuration, and architecture overview.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @atscript/moost-mongo
|
|
9
|
+
# peer dependencies:
|
|
10
|
+
npm install @atscript/mongo @atscript/typescript moost @moostjs/event-http mongodb
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Configuration
|
|
14
|
+
|
|
15
|
+
Add both `ts()` and `MongoPlugin()` to your `atscript.config.ts`:
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { defineConfig } from '@atscript/core'
|
|
19
|
+
import { ts } from '@atscript/typescript'
|
|
20
|
+
import { MongoPlugin } from '@atscript/mongo'
|
|
21
|
+
|
|
22
|
+
export default defineConfig({
|
|
23
|
+
rootDir: 'src',
|
|
24
|
+
plugins: [ts(), MongoPlugin()],
|
|
25
|
+
})
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Architecture
|
|
29
|
+
|
|
30
|
+
The package provides three main exports:
|
|
31
|
+
|
|
32
|
+
- **`AsMongoController<T>`** — Generic base class with all CRUD endpoints
|
|
33
|
+
- **`CollectionController(type, prefix?)`** — Class decorator combining `@Provide`, `@Controller`, `@Inherit`
|
|
34
|
+
- **`InjectCollection(type)`** — Parameter decorator for DI injection of `AsCollection<T>`
|
|
35
|
+
|
|
36
|
+
### REST Endpoints (automatic)
|
|
37
|
+
|
|
38
|
+
| Route | Method | Description |
|
|
39
|
+
|-------|--------|-------------|
|
|
40
|
+
| `/<prefix>/query` | GET | List with URLQL filtering/sorting |
|
|
41
|
+
| `/<prefix>/pages` | GET | Paginated list |
|
|
42
|
+
| `/<prefix>/one/:id` | GET | Single doc by `_id` or unique field |
|
|
43
|
+
| `/<prefix>` | POST | Insert one or many |
|
|
44
|
+
| `/<prefix>` | PUT | Replace by `_id` |
|
|
45
|
+
| `/<prefix>` | PATCH | Partial update by `_id` |
|
|
46
|
+
| `/<prefix>/:id` | DELETE | Remove by `_id` |
|
|
47
|
+
|
|
48
|
+
The route prefix defaults to the `@db.table` name but can be overridden in `CollectionController`.
|
|
49
|
+
|
|
50
|
+
## Regenerating atscript.d.ts
|
|
51
|
+
|
|
52
|
+
After annotation changes:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
cd packages/moost-mongo && node ../typescript/dist/cli.cjs -f dts
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Best Practices
|
|
59
|
+
|
|
60
|
+
- Use `@CollectionController(Type)` on an empty subclass for zero-config CRUD
|
|
61
|
+
- Override hooks (`onWrite`, `onRemove`, `transformFilter`) for custom business logic
|
|
62
|
+
- Provide `AsMongo` at the controller level via `@Provide(AsMongo, () => new AsMongo(url))`
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Decorators & DI — @atscript/moost-mongo
|
|
2
|
+
|
|
3
|
+
> Using CollectionController, InjectCollection, and understanding DI wiring.
|
|
4
|
+
|
|
5
|
+
## `CollectionController(type, prefix?)`
|
|
6
|
+
|
|
7
|
+
Class decorator that combines three Moost decorators:
|
|
8
|
+
|
|
9
|
+
1. **`@Provide(COLLECTION_DEF, () => type)`** — Registers the Atscript annotated type in DI
|
|
10
|
+
2. **`@Controller(prefix)`** — Registers the class as a Moost HTTP controller
|
|
11
|
+
3. **`@Inherit()`** — Copies metadata (routes, guards) from the parent class
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { CollectionController } from '@atscript/moost-mongo'
|
|
15
|
+
import { User } from './user.as'
|
|
16
|
+
|
|
17
|
+
@CollectionController(User) // prefix defaults to @db.table name ("users")
|
|
18
|
+
@CollectionController(User, 'people') // explicit prefix
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Parameters
|
|
22
|
+
|
|
23
|
+
- **`type`** — Atscript annotated constructor from a `.as` import
|
|
24
|
+
- **`prefix?`** — Optional route prefix. Defaults to `type.metadata.get('db.table')` or `type.name`
|
|
25
|
+
|
|
26
|
+
## `InjectCollection(type)`
|
|
27
|
+
|
|
28
|
+
Parameter decorator that injects a lazily-resolved `AsCollection` instance. Use in any DI-managed class, not just controllers.
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { InjectCollection } from '@atscript/moost-mongo'
|
|
32
|
+
import { AsCollection } from '@atscript/mongo'
|
|
33
|
+
import { Injectable } from 'moost'
|
|
34
|
+
import { User } from './user.as'
|
|
35
|
+
|
|
36
|
+
@Injectable()
|
|
37
|
+
export class UserService {
|
|
38
|
+
constructor(
|
|
39
|
+
@InjectCollection(User)
|
|
40
|
+
private users: AsCollection<typeof User>
|
|
41
|
+
) {}
|
|
42
|
+
|
|
43
|
+
async findActiveUsers() {
|
|
44
|
+
return this.users.collection.find({ isActive: true }).toArray()
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Requirements
|
|
50
|
+
|
|
51
|
+
`AsMongo` must be provided in the current DI scope:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
@Provide(AsMongo, () => new AsMongo(connectionString))
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## DI Wiring Pattern
|
|
58
|
+
|
|
59
|
+
The typical setup:
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
import { AsMongo } from '@atscript/mongo'
|
|
63
|
+
import { AsMongoController, CollectionController } from '@atscript/moost-mongo'
|
|
64
|
+
import { Provide } from 'moost'
|
|
65
|
+
import { User } from './user.as'
|
|
66
|
+
import { Product } from './product.as'
|
|
67
|
+
|
|
68
|
+
// Provide AsMongo at the module/app level
|
|
69
|
+
@Provide(AsMongo, () => new AsMongo(process.env.MONGO_URI!))
|
|
70
|
+
class AppModule {}
|
|
71
|
+
|
|
72
|
+
// Each controller gets its own collection automatically
|
|
73
|
+
@CollectionController(User)
|
|
74
|
+
export class UsersController extends AsMongoController<typeof User> {}
|
|
75
|
+
|
|
76
|
+
@CollectionController(Product)
|
|
77
|
+
export class ProductsController extends AsMongoController<typeof Product> {}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## COLLECTION_DEF Token
|
|
81
|
+
|
|
82
|
+
The DI token under which the Atscript annotated type is registered:
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
import { COLLECTION_DEF } from '@atscript/moost-mongo'
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
This is used internally by `AsMongoController` to resolve the collection type. You typically don't need to use it directly.
|