@bunyad/orm 0.1.0-alpha.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.
@@ -0,0 +1,36 @@
1
+ import type { ModelQuery } from "./model.js";
2
+ export type GlobalScopeCallback = (query: ModelQuery) => void;
3
+ type ModelClassLike = abstract new (...args: never[]) => unknown;
4
+ /** Register a named global scope on a model class. */
5
+ export declare function addGlobalScope(model: ModelClassLike, name: string, scope: GlobalScopeCallback): void;
6
+ /** Remove a named global scope from a model class. */
7
+ export declare function removeGlobalScope(model: ModelClassLike, name: string): void;
8
+ /** All registered global scopes for a model class. */
9
+ export declare function getGlobalScopes(model: ModelClassLike): Map<string, GlobalScopeCallback>;
10
+ export declare function hasGlobalScopes(model: ModelClassLike): boolean;
11
+ /** Clear scopes (tests). */
12
+ export declare function clearGlobalScopes(model?: ModelClassLike): void;
13
+ type CallStaticModel = {
14
+ newQuery(): ModelQuery;
15
+ };
16
+ /**
17
+ * Safe ModelQuery terminals forwarded by Model `__callStatic`
18
+ * (`Model.get()` → `Model.query().get()`).
19
+ *
20
+ * Allowlist only — avoid constructors, `then`, starters already owned as
21
+ * Model statics, and arbitrary junk. Own Model / subclass statics always win
22
+ * over this Proxy. Local `scopeFoo` still wins when present.
23
+ *
24
+ * @see https://laravel.com/docs/eloquent#retrieving-models
25
+ */
26
+ export declare const MODEL_STATIC_BUILDER_TERMINALS: ReadonlySet<string>;
27
+ /**
28
+ * Laravel `__callStatic`:
29
+ * - Local scopes: `Model.foo()` → `Model.query().foo()` when `scopeFoo` exists
30
+ * - Builder terminals: `Model.get()` / `pluck` / `count` / … → `Model.query()[method](…)`
31
+ *
32
+ * Installed as Model's [[Prototype]] so missing static lookups on subclasses
33
+ * (e.g. `Shop.active`, `User.get`) reach this trap before `Function.prototype`.
34
+ */
35
+ export declare function installLocalScopeCallStatic(ModelCtor: CallStaticModel): void;
36
+ export {};
package/dist/scopes.js ADDED
@@ -0,0 +1,128 @@
1
+ const registry = new WeakMap();
2
+ function scopesFor(model) {
3
+ let map = registry.get(model);
4
+ if (!map) {
5
+ map = new Map();
6
+ registry.set(model, map);
7
+ }
8
+ return map;
9
+ }
10
+ /** Register a named global scope on a model class. */
11
+ export function addGlobalScope(model, name, scope) {
12
+ scopesFor(model).set(name, scope);
13
+ }
14
+ /** Remove a named global scope from a model class. */
15
+ export function removeGlobalScope(model, name) {
16
+ scopesFor(model).delete(name);
17
+ }
18
+ const EMPTY_SCOPES = new Map();
19
+ /** All registered global scopes for a model class. */
20
+ export function getGlobalScopes(model) {
21
+ return registry.get(model) ?? EMPTY_SCOPES;
22
+ }
23
+ export function hasGlobalScopes(model) {
24
+ const map = registry.get(model);
25
+ return map != null && map.size > 0;
26
+ }
27
+ /** Clear scopes (tests). */
28
+ export function clearGlobalScopes(model) {
29
+ if (model) {
30
+ registry.delete(model);
31
+ return;
32
+ }
33
+ }
34
+ /**
35
+ * Safe ModelQuery terminals forwarded by Model `__callStatic`
36
+ * (`Model.get()` → `Model.query().get()`).
37
+ *
38
+ * Allowlist only — avoid constructors, `then`, starters already owned as
39
+ * Model statics, and arbitrary junk. Own Model / subclass statics always win
40
+ * over this Proxy. Local `scopeFoo` still wins when present.
41
+ *
42
+ * @see https://laravel.com/docs/eloquent#retrieving-models
43
+ */
44
+ export const MODEL_STATIC_BUILDER_TERMINALS = new Set([
45
+ "get",
46
+ "pluck",
47
+ "value",
48
+ "count",
49
+ "sum",
50
+ "avg",
51
+ "min",
52
+ "max",
53
+ "exists",
54
+ "doesntExist",
55
+ "first",
56
+ "firstOrFail",
57
+ "sole",
58
+ "valueOrFail",
59
+ "soleValue",
60
+ "paginate",
61
+ "simplePaginate",
62
+ "cursorPaginate",
63
+ "getSync",
64
+ "firstSync",
65
+ "toSql",
66
+ "toRawSql",
67
+ "getBindings",
68
+ ]);
69
+ /**
70
+ * Laravel `__callStatic`:
71
+ * - Local scopes: `Model.foo()` → `Model.query().foo()` when `scopeFoo` exists
72
+ * - Builder terminals: `Model.get()` / `pluck` / `count` / … → `Model.query()[method](…)`
73
+ *
74
+ * Installed as Model's [[Prototype]] so missing static lookups on subclasses
75
+ * (e.g. `Shop.active`, `User.get`) reach this trap before `Function.prototype`.
76
+ */
77
+ export function installLocalScopeCallStatic(ModelCtor) {
78
+ const parent = Object.getPrototypeOf(ModelCtor);
79
+ if (parent &&
80
+ parent
81
+ .__bunyadLocalScopeCallStatic) {
82
+ return;
83
+ }
84
+ const trap = new Proxy(parent, {
85
+ get(target, prop, receiver) {
86
+ if (typeof prop === "symbol") {
87
+ return Reflect.get(target, prop, receiver);
88
+ }
89
+ if (typeof prop === "string" &&
90
+ prop.length > 0 &&
91
+ !prop.startsWith("scope") &&
92
+ prop !== "prototype" &&
93
+ prop !== "name" &&
94
+ prop !== "length") {
95
+ const scopeName = `scope${prop.charAt(0).toUpperCase()}${prop.slice(1)}`;
96
+ const scopeFn = receiver[scopeName];
97
+ if (typeof scopeFn === "function") {
98
+ return (...args) => {
99
+ const q = receiver.newQuery();
100
+ const onQuery = q[prop];
101
+ if (typeof onQuery === "function") {
102
+ return onQuery.apply(q, args);
103
+ }
104
+ scopeFn.call(receiver, q, ...args);
105
+ return q;
106
+ };
107
+ }
108
+ if (MODEL_STATIC_BUILDER_TERMINALS.has(prop)) {
109
+ return (...args) => {
110
+ const q = receiver.newQuery();
111
+ const fn = q[prop];
112
+ if (typeof fn !== "function") {
113
+ throw new TypeError(`${receiver.name ?? "Model"}.${prop} is not available on the query builder.`);
114
+ }
115
+ return fn.apply(q, args);
116
+ };
117
+ }
118
+ }
119
+ return Reflect.get(target, prop, receiver);
120
+ },
121
+ });
122
+ Object.defineProperty(trap, "__bunyadLocalScopeCallStatic", {
123
+ value: true,
124
+ enumerable: false,
125
+ configurable: false,
126
+ });
127
+ Object.setPrototypeOf(ModelCtor, trap);
128
+ }
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@bunyad/orm",
3
+ "version": "0.1.0-alpha.0",
4
+ "description": "Active-record ORM for Bunyad: models, relations, casts, scopes, events, factories.",
5
+ "license": "MIT",
6
+ "author": "Shah Khalid",
7
+ "type": "module",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/bunyadjs/framework.git",
11
+ "directory": "packages/orm"
12
+ },
13
+ "homepage": "https://github.com/bunyadjs/framework#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/bunyadjs/framework/issues"
16
+ },
17
+ "keywords": [
18
+ "bunyad",
19
+ "bun",
20
+ "typescript",
21
+ "orm",
22
+ "active-record"
23
+ ],
24
+ "files": [
25
+ "dist",
26
+ "README.md",
27
+ "LICENSE"
28
+ ],
29
+ "engines": {
30
+ "node": ">=20"
31
+ },
32
+ "exports": {
33
+ ".": {
34
+ "types": "./dist/index.d.ts",
35
+ "default": "./dist/index.js"
36
+ }
37
+ },
38
+ "dependencies": {
39
+ "@bunyad/common": "^0.1.0-alpha.0",
40
+ "@bunyad/database": "^0.1.0-alpha.0"
41
+ },
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "scripts": {
46
+ "test": "bun test",
47
+ "build": "bun ../../scripts/build-package.ts ."
48
+ }
49
+ }