@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 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("mongo.collection") || type.name), (0, moost.Inherit)());
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.min", { minValue: 0 }).annotate("expect.int", true).optional().$type).prop("$limit", (0, __atscript_typescript_utils.defineAnnotatedType)().designType("number").tags("positive", "int", "number").annotate("expect.min", { minValue: 0 }).annotate("expect.int", true).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);
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("mongo.collection") || ""}]`);
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> | undefined;
60
+ protected get queryControlsValidator(): Validator<any, unknown>;
61
61
  /** Returns (and memoises) validator for *pages* endpoint controls. */
62
- protected get pagesControlsValidator(): Validator<any, unknown> | undefined;
62
+ protected get pagesControlsValidator(): Validator<any, unknown>;
63
63
  /** Returns (and memoises) validator for *one* endpoint controls. */
64
- protected get getOneControlsValidator(): Validator<any, unknown> | undefined;
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
- * **@mongo.collection** model.
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 `@mongo.collection`.
253
+ * @param type AtScript-annotated constructor produced by `@db.table`.
254
254
  * @param prefix Optional route prefix. Defaults to
255
- * `type.metadata.get("mongo.collection")` or the class name.
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
- * `@mongo.collection`.
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("mongo.collection") || type.name), Inherit());
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.min", { minValue: 0 }).annotate("expect.int", true).optional().$type).prop("$limit", defineAnnotatedType().designType("number").tags("positive", "int", "number").annotate("expect.min", { minValue: 0 }).annotate("expect.int", true).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);
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("mongo.collection") || ""}]`);
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.27",
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.0",
46
+ "@moostjs/event-http": "^0.6.2",
42
47
  "mongodb": "^6.17.0",
43
- "moost": "^0.6.0",
48
+ "moost": "^0.6.2",
44
49
  "vitest": "3.2.4",
45
- "@atscript/core": "^0.1.27"
50
+ "@atscript/core": "^0.1.28"
46
51
  },
47
52
  "peerDependencies": {
48
- "@moostjs/event-http": "^0.6.0",
53
+ "@moostjs/event-http": "^0.6.2",
49
54
  "mongodb": "^6.17.0",
50
- "moost": "^0.6.0",
51
- "@atscript/mongo": "^0.1.27",
52
- "@atscript/typescript": "^0.1.27"
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.