@relations-dil/relations 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/LICENSE +21 -0
- package/README.md +824 -0
- package/dist/dynamic.d.ts +122 -0
- package/dist/dynamic.js +164 -0
- package/dist/errors.d.ts +31 -0
- package/dist/errors.js +50 -0
- package/dist/field.d.ts +157 -0
- package/dist/field.js +660 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.js +75 -0
- package/dist/kinds.d.ts +99 -0
- package/dist/kinds.js +169 -0
- package/dist/migrations.d.ts +62 -0
- package/dist/migrations.js +293 -0
- package/dist/mock.d.ts +88 -0
- package/dist/mock.js +511 -0
- package/dist/model.d.ts +277 -0
- package/dist/model.js +1159 -0
- package/dist/overscore.d.ts +66 -0
- package/dist/overscore.js +226 -0
- package/dist/record.d.ts +58 -0
- package/dist/record.js +246 -0
- package/dist/registry.d.ts +24 -0
- package/dist/registry.js +27 -0
- package/dist/relation.d.ts +114 -0
- package/dist/relation.js +145 -0
- package/dist/source.d.ts +147 -0
- package/dist/source.js +409 -0
- package/dist/titles.d.ts +43 -0
- package/dist/titles.js +115 -0
- package/dist/typed.d.ts +99 -0
- package/dist/typed.js +44 -0
- package/dist/util.d.ts +16 -0
- package/dist/util.js +117 -0
- package/package.json +68 -0
package/dist/typed.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed models.
|
|
3
|
+
*
|
|
4
|
+
* Field values are reached through a Proxy at runtime, which TypeScript can't see into
|
|
5
|
+
* on its own. `fields()` closes that gap: it declares the fields once and hands back a
|
|
6
|
+
* base class that already knows their types.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { fields, int, str, list } from '@relations-dil/relations'
|
|
10
|
+
*
|
|
11
|
+
* class Unit extends fields({ id: int, name: str, tags: list }) {
|
|
12
|
+
* static source = 'example'
|
|
13
|
+
* }
|
|
14
|
+
*
|
|
15
|
+
* const unit = await Unit.one({ name: 'yep' }).retrieve()
|
|
16
|
+
*
|
|
17
|
+
* unit.name.toUpperCase() // string, checked
|
|
18
|
+
* unit.nmae // error: no such field
|
|
19
|
+
* unit.name = 5 // error: not a string
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* Relations are declared separately from the model, so add them with `declare` when you
|
|
23
|
+
* want them typed too. `declare` is erased at compile time and costs nothing to run:
|
|
24
|
+
*
|
|
25
|
+
* ```ts
|
|
26
|
+
* class Unit extends fields({ id: int, name: str }) {
|
|
27
|
+
* static source = 'example'
|
|
28
|
+
* declare test: Test
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
import { Model } from './model.js';
|
|
33
|
+
/**
|
|
34
|
+
* A base class with these fields, typed. Extend it instead of `Model` and every field
|
|
35
|
+
* is checked at the point you touch it. Pass a base to share configuration, the way a
|
|
36
|
+
* `Base` class shares a source across models.
|
|
37
|
+
*/
|
|
38
|
+
export function fields(spec, base = Model) {
|
|
39
|
+
class Fielded extends base {
|
|
40
|
+
static fields = spec;
|
|
41
|
+
}
|
|
42
|
+
return Fielded;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=typed.js.map
|
package/dist/util.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Small internal helpers. Nothing here is part of the public contract.
|
|
3
|
+
*/
|
|
4
|
+
/** Structural deep clone of plain data (objects, arrays, Sets, Dates, primitives). */
|
|
5
|
+
export declare function clone<T>(value: T): T;
|
|
6
|
+
/** Structural equality for exported values. Key order is ignored, array order is not. */
|
|
7
|
+
export declare function equal(first: unknown, second: unknown): boolean;
|
|
8
|
+
/** Stable JSON, used for unique index keys. */
|
|
9
|
+
export declare function stable(value: unknown): string;
|
|
10
|
+
/** Natural ascending sort: numbers numerically, everything else by string. */
|
|
11
|
+
export declare function sorted<T>(values: Iterable<T>): T[];
|
|
12
|
+
/** Three-way comparison used for sorting and ordering. */
|
|
13
|
+
export declare function compare(first: any, second: any): number;
|
|
14
|
+
/** Turn CamelCase into under_scored. */
|
|
15
|
+
export declare function underscore(name: string): string;
|
|
16
|
+
//# sourceMappingURL=util.d.ts.map
|
package/dist/util.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Small internal helpers. Nothing here is part of the public contract.
|
|
3
|
+
*/
|
|
4
|
+
import { isDict } from './overscore.js';
|
|
5
|
+
/** Structural deep clone of plain data (objects, arrays, Sets, Dates, primitives). */
|
|
6
|
+
export function clone(value) {
|
|
7
|
+
if (value === null || typeof value !== 'object') {
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
if (Array.isArray(value)) {
|
|
11
|
+
return value.map(clone);
|
|
12
|
+
}
|
|
13
|
+
if (value instanceof Set) {
|
|
14
|
+
return new Set([...value].map(clone));
|
|
15
|
+
}
|
|
16
|
+
if (value instanceof Map) {
|
|
17
|
+
return new Map([...value].map(([key, item]) => [clone(key), clone(item)]));
|
|
18
|
+
}
|
|
19
|
+
if (value instanceof Date) {
|
|
20
|
+
return new Date(value.getTime());
|
|
21
|
+
}
|
|
22
|
+
if (isDict(value)) {
|
|
23
|
+
const copy = {};
|
|
24
|
+
for (const key of Object.keys(value)) {
|
|
25
|
+
copy[key] = clone(value[key]);
|
|
26
|
+
}
|
|
27
|
+
return copy;
|
|
28
|
+
}
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
/** Structural equality for exported values. Key order is ignored, array order is not. */
|
|
32
|
+
export function equal(first, second) {
|
|
33
|
+
if (first === second) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
if (first === null || second === null || first === undefined || second === undefined) {
|
|
37
|
+
return first == null && second == null;
|
|
38
|
+
}
|
|
39
|
+
if (typeof first !== 'object' || typeof second !== 'object') {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
if (Array.isArray(first) || Array.isArray(second)) {
|
|
43
|
+
if (!Array.isArray(first) || !Array.isArray(second) || first.length !== second.length) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
return first.every((item, index) => equal(item, second[index]));
|
|
47
|
+
}
|
|
48
|
+
if (first instanceof Set || second instanceof Set) {
|
|
49
|
+
if (!(first instanceof Set) || !(second instanceof Set) || first.size !== second.size) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
return [...first].every((item) => second.has(item));
|
|
53
|
+
}
|
|
54
|
+
const firstKeys = Object.keys(first);
|
|
55
|
+
const secondKeys = Object.keys(second);
|
|
56
|
+
if (firstKeys.length !== secondKeys.length) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
return firstKeys.every((key) => Object.prototype.hasOwnProperty.call(second, key) &&
|
|
60
|
+
equal(first[key], second[key]));
|
|
61
|
+
}
|
|
62
|
+
/** Stable JSON, used for unique index keys. */
|
|
63
|
+
export function stable(value) {
|
|
64
|
+
return JSON.stringify(value, (_key, item) => {
|
|
65
|
+
if (item instanceof Set) {
|
|
66
|
+
return sorted([...item]);
|
|
67
|
+
}
|
|
68
|
+
if (isDict(item)) {
|
|
69
|
+
const ordered = {};
|
|
70
|
+
for (const key of Object.keys(item).sort()) {
|
|
71
|
+
ordered[key] = item[key];
|
|
72
|
+
}
|
|
73
|
+
return ordered;
|
|
74
|
+
}
|
|
75
|
+
return item;
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/** Natural ascending sort: numbers numerically, everything else by string. */
|
|
79
|
+
export function sorted(values) {
|
|
80
|
+
return [...values].sort(compare);
|
|
81
|
+
}
|
|
82
|
+
/** Three-way comparison used for sorting and ordering. */
|
|
83
|
+
export function compare(first, second) {
|
|
84
|
+
if (first === second) {
|
|
85
|
+
return 0;
|
|
86
|
+
}
|
|
87
|
+
if (first === null || first === undefined) {
|
|
88
|
+
return -1;
|
|
89
|
+
}
|
|
90
|
+
if (second === null || second === undefined) {
|
|
91
|
+
return 1;
|
|
92
|
+
}
|
|
93
|
+
if (typeof first === 'number' && typeof second === 'number') {
|
|
94
|
+
return first - second;
|
|
95
|
+
}
|
|
96
|
+
if (typeof first === 'boolean' && typeof second === 'boolean') {
|
|
97
|
+
return Number(first) - Number(second);
|
|
98
|
+
}
|
|
99
|
+
const firstText = String(first);
|
|
100
|
+
const secondText = String(second);
|
|
101
|
+
return firstText < secondText ? -1 : firstText > secondText ? 1 : 0;
|
|
102
|
+
}
|
|
103
|
+
/** Turn CamelCase into under_scored. */
|
|
104
|
+
export function underscore(name) {
|
|
105
|
+
const parts = [];
|
|
106
|
+
let previous = true;
|
|
107
|
+
for (const letter of name) {
|
|
108
|
+
const lowered = letter.toLowerCase();
|
|
109
|
+
if (!previous && lowered !== letter) {
|
|
110
|
+
parts.push('_');
|
|
111
|
+
}
|
|
112
|
+
parts.push(lowered);
|
|
113
|
+
previous = lowered !== letter;
|
|
114
|
+
}
|
|
115
|
+
return parts.join('');
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=util.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@relations-dil/relations",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A simple, flexible DIL (data interface layer). Define models once, independent of backend; set the source at runtime.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/relations-dil/typescript-relations.git"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/relations-dil/typescript-relations#readme",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/relations-dil/typescript-relations/issues"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"dil",
|
|
16
|
+
"orm",
|
|
17
|
+
"model",
|
|
18
|
+
"relations",
|
|
19
|
+
"database",
|
|
20
|
+
"microservice"
|
|
21
|
+
],
|
|
22
|
+
"type": "module",
|
|
23
|
+
"main": "./dist/index.js",
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"default": "./dist/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./mock": {
|
|
31
|
+
"types": "./dist/mock.d.ts",
|
|
32
|
+
"default": "./dist/mock.js"
|
|
33
|
+
},
|
|
34
|
+
"./overscore": {
|
|
35
|
+
"types": "./dist/overscore.d.ts",
|
|
36
|
+
"default": "./dist/overscore.js"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"dist",
|
|
41
|
+
"!dist/**/*.map",
|
|
42
|
+
"README.md",
|
|
43
|
+
"LICENSE",
|
|
44
|
+
"CHANGELOG.md"
|
|
45
|
+
],
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=18"
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "tsc -p tsconfig.json",
|
|
51
|
+
"clean": "rm -rf dist dist-test",
|
|
52
|
+
"typecheck": "tsc -p tsconfig.test.json",
|
|
53
|
+
"test": "node --import tsx --test test/*.test.ts",
|
|
54
|
+
"coverage": "tsc -p tsconfig.test.json --noEmit false --outDir dist-test && node --test --experimental-test-coverage --test-coverage-exclude='dist-test/test/**' --test-reporter=spec dist-test/test/*.test.js",
|
|
55
|
+
"lint": "npm run typecheck && eslint src test",
|
|
56
|
+
"example": "npm run build && for e in example/*.mjs; do echo \"== $e\"; node $e; done",
|
|
57
|
+
"prepublishOnly": "npm run build"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@eslint/js": "^9.39.5",
|
|
61
|
+
"@types/node": "^22.0.0",
|
|
62
|
+
"@unum-pillars/uikit": "^0.1.0",
|
|
63
|
+
"eslint": "^9.39.5",
|
|
64
|
+
"tsx": "^4.23.15",
|
|
65
|
+
"typescript": "^5.6.0",
|
|
66
|
+
"typescript-eslint": "^8.71.0"
|
|
67
|
+
}
|
|
68
|
+
}
|