@stonyx/orm 0.2.1-beta.83 → 0.2.1-beta.85
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/aggregates.d.ts +21 -0
- package/dist/aggregates.js +90 -0
- package/dist/attr.d.ts +2 -0
- package/dist/attr.js +22 -0
- package/dist/belongs-to.d.ts +11 -0
- package/dist/belongs-to.js +59 -0
- package/dist/cli.d.ts +22 -0
- package/dist/cli.js +148 -0
- package/dist/commands.d.ts +7 -0
- package/dist/commands.js +146 -0
- package/dist/db.d.ts +21 -0
- package/dist/db.js +174 -0
- package/dist/exports/db.d.ts +7 -0
- package/{src → dist}/exports/db.js +2 -4
- package/dist/has-many.d.ts +11 -0
- package/dist/has-many.js +58 -0
- package/dist/hooks.d.ts +47 -0
- package/dist/hooks.js +106 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +34 -0
- package/dist/main.d.ts +46 -0
- package/dist/main.js +179 -0
- package/dist/manage-record.d.ts +13 -0
- package/dist/manage-record.js +114 -0
- package/dist/meta-request.d.ts +6 -0
- package/dist/meta-request.js +52 -0
- package/dist/migrate.d.ts +2 -0
- package/dist/migrate.js +57 -0
- package/dist/model-property.d.ts +9 -0
- package/dist/model-property.js +29 -0
- package/dist/model.d.ts +15 -0
- package/dist/model.js +18 -0
- package/dist/mysql/connection.d.ts +14 -0
- package/dist/mysql/connection.js +24 -0
- package/dist/mysql/migration-generator.d.ts +45 -0
- package/dist/mysql/migration-generator.js +245 -0
- package/dist/mysql/migration-runner.d.ts +12 -0
- package/dist/mysql/migration-runner.js +83 -0
- package/dist/mysql/mysql-db.d.ts +100 -0
- package/dist/mysql/mysql-db.js +415 -0
- package/dist/mysql/query-builder.d.ts +10 -0
- package/dist/mysql/query-builder.js +44 -0
- package/dist/mysql/schema-introspector.d.ts +19 -0
- package/dist/mysql/schema-introspector.js +286 -0
- package/dist/mysql/type-map.d.ts +21 -0
- package/dist/mysql/type-map.js +36 -0
- package/dist/orm-request.d.ts +38 -0
- package/dist/orm-request.js +455 -0
- package/dist/plural-registry.d.ts +4 -0
- package/{src → dist}/plural-registry.js +3 -6
- package/dist/postgres/connection.d.ts +15 -0
- package/dist/postgres/connection.js +30 -0
- package/dist/postgres/migration-generator.d.ts +45 -0
- package/dist/postgres/migration-generator.js +257 -0
- package/dist/postgres/migration-runner.d.ts +10 -0
- package/dist/postgres/migration-runner.js +82 -0
- package/dist/postgres/postgres-db.d.ts +119 -0
- package/dist/postgres/postgres-db.js +476 -0
- package/dist/postgres/query-builder.d.ts +27 -0
- package/dist/postgres/query-builder.js +98 -0
- package/dist/postgres/schema-introspector.d.ts +29 -0
- package/dist/postgres/schema-introspector.js +309 -0
- package/dist/postgres/type-map.d.ts +23 -0
- package/dist/postgres/type-map.js +53 -0
- package/dist/record.d.ts +75 -0
- package/dist/record.js +115 -0
- package/dist/relationships.d.ts +10 -0
- package/dist/relationships.js +39 -0
- package/dist/serializer.d.ts +17 -0
- package/dist/serializer.js +136 -0
- package/dist/setup-rest-server.d.ts +1 -0
- package/dist/setup-rest-server.js +54 -0
- package/dist/standalone-db.d.ts +58 -0
- package/dist/standalone-db.js +142 -0
- package/dist/store.d.ts +62 -0
- package/dist/store.js +271 -0
- package/dist/timescale/query-builder.d.ts +41 -0
- package/dist/timescale/query-builder.js +87 -0
- package/dist/timescale/timescale-db.d.ts +45 -0
- package/dist/timescale/timescale-db.js +84 -0
- package/dist/transforms.d.ts +2 -0
- package/dist/transforms.js +17 -0
- package/dist/types/orm-types.d.ts +142 -0
- package/dist/types/orm-types.js +1 -0
- package/dist/utils.d.ts +5 -0
- package/dist/utils.js +13 -0
- package/dist/view-resolver.d.ts +8 -0
- package/dist/view-resolver.js +169 -0
- package/dist/view.d.ts +11 -0
- package/dist/view.js +18 -0
- package/package.json +34 -11
- package/src/{aggregates.js → aggregates.ts} +27 -13
- package/src/{attr.js → attr.ts} +2 -2
- package/src/belongs-to.ts +90 -0
- package/src/{cli.js → cli.ts} +17 -11
- package/src/{commands.js → commands.ts} +179 -170
- package/src/{db.js → db.ts} +35 -26
- package/src/exports/db.ts +7 -0
- package/src/has-many.ts +92 -0
- package/src/{hooks.js → hooks.ts} +23 -27
- package/src/{index.js → index.ts} +4 -4
- package/src/{main.js → main.ts} +60 -34
- package/src/{manage-record.js → manage-record.ts} +42 -22
- package/src/{meta-request.js → meta-request.ts} +17 -14
- package/src/{migrate.js → migrate.ts} +9 -9
- package/src/{model-property.js → model-property.ts} +12 -6
- package/src/{model.js → model.ts} +5 -4
- package/src/mysql/{connection.js → connection.ts} +43 -28
- package/src/mysql/{migration-generator.js → migration-generator.ts} +332 -286
- package/src/mysql/{migration-runner.js → migration-runner.ts} +116 -110
- package/src/mysql/{mysql-db.js → mysql-db.ts} +537 -473
- package/src/mysql/{query-builder.js → query-builder.ts} +69 -64
- package/src/mysql/{schema-introspector.js → schema-introspector.ts} +355 -325
- package/src/mysql/{type-map.js → type-map.ts} +42 -37
- package/src/{orm-request.js → orm-request.ts} +169 -97
- package/src/plural-registry.ts +12 -0
- package/src/postgres/{connection.js → connection.ts} +14 -5
- package/src/postgres/{migration-generator.js → migration-generator.ts} +82 -38
- package/src/postgres/{migration-runner.js → migration-runner.ts} +11 -10
- package/src/postgres/{postgres-db.js → postgres-db.ts} +198 -114
- package/src/postgres/{query-builder.js → query-builder.ts} +27 -28
- package/src/postgres/{schema-introspector.js → schema-introspector.ts} +87 -58
- package/src/postgres/{type-map.js → type-map.ts} +10 -6
- package/src/{record.js → record.ts} +73 -34
- package/src/relationships.ts +53 -0
- package/src/{serializer.js → serializer.ts} +52 -36
- package/src/{setup-rest-server.js → setup-rest-server.ts} +18 -13
- package/src/{standalone-db.js → standalone-db.ts} +33 -24
- package/src/{store.js → store.ts} +90 -68
- package/src/timescale/{query-builder.js → query-builder.ts} +33 -38
- package/src/timescale/timescale-db.ts +119 -0
- package/src/transforms.ts +20 -0
- package/src/types/mysql2.d.ts +30 -0
- package/src/types/orm-types.ts +146 -0
- package/src/types/pg.d.ts +28 -0
- package/src/types/stonyx-cron.d.ts +5 -0
- package/src/types/stonyx-events.d.ts +4 -0
- package/src/types/stonyx-rest-server.d.ts +11 -0
- package/src/types/stonyx-utils.d.ts +33 -0
- package/src/types/stonyx.d.ts +21 -0
- package/src/utils.ts +16 -0
- package/src/{view-resolver.js → view-resolver.ts} +51 -24
- package/src/view.ts +22 -0
- package/src/belongs-to.js +0 -70
- package/src/has-many.js +0 -68
- package/src/relationships.js +0 -43
- package/src/timescale/timescale-db.js +0 -111
- package/src/transforms.js +0 -20
- package/src/utils.js +0 -12
- package/src/view.js +0 -21
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import type { AggregateProperty } from '../aggregates.js';
|
|
2
|
+
|
|
3
|
+
export interface OrmDbConfig {
|
|
4
|
+
file: string;
|
|
5
|
+
schema: string;
|
|
6
|
+
mode: string;
|
|
7
|
+
directory: string;
|
|
8
|
+
autosave: string;
|
|
9
|
+
saveInterval: unknown;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface OrmMysqlConfig {
|
|
13
|
+
host: string;
|
|
14
|
+
port?: number;
|
|
15
|
+
user: string;
|
|
16
|
+
password: string;
|
|
17
|
+
database: string;
|
|
18
|
+
connectionLimit?: number;
|
|
19
|
+
migrationsDir?: string;
|
|
20
|
+
migrationsTable?: string;
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface OrmPostgresConfig {
|
|
25
|
+
host: string;
|
|
26
|
+
port: number;
|
|
27
|
+
user: string;
|
|
28
|
+
password: string;
|
|
29
|
+
database: string;
|
|
30
|
+
connectionLimit?: number;
|
|
31
|
+
migrationsDir?: string;
|
|
32
|
+
migrationsTable?: string;
|
|
33
|
+
[key: string]: unknown;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface OrmPaths {
|
|
37
|
+
model: string;
|
|
38
|
+
serializer: string;
|
|
39
|
+
transform: string;
|
|
40
|
+
view?: string;
|
|
41
|
+
access?: string;
|
|
42
|
+
[key: string]: string | undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface OrmRestServerConfig {
|
|
46
|
+
enabled: string;
|
|
47
|
+
route: string;
|
|
48
|
+
metaRoute: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface OrmSection {
|
|
52
|
+
db: OrmDbConfig;
|
|
53
|
+
paths: OrmPaths;
|
|
54
|
+
restServer: OrmRestServerConfig;
|
|
55
|
+
mysql?: OrmMysqlConfig;
|
|
56
|
+
postgres?: OrmPostgresConfig;
|
|
57
|
+
timescale?: OrmPostgresConfig;
|
|
58
|
+
[key: string]: unknown;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface OrmConfig {
|
|
62
|
+
rootPath: string;
|
|
63
|
+
orm: OrmSection;
|
|
64
|
+
[key: string]: unknown;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface SourceRecord {
|
|
68
|
+
__model: { __name: string; [key: string]: unknown };
|
|
69
|
+
__data?: Record<string, unknown>;
|
|
70
|
+
__relationships?: Record<string, unknown>;
|
|
71
|
+
id: unknown;
|
|
72
|
+
[key: string]: unknown;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface OrmRecord {
|
|
76
|
+
id: string | number | unknown;
|
|
77
|
+
__model?: { __name: string };
|
|
78
|
+
__data: Record<string, unknown> & { id?: unknown; __pendingSqlId?: boolean };
|
|
79
|
+
__relationships: Record<string, unknown>;
|
|
80
|
+
toJSON?(options?: { fields?: Set<string>; baseUrl?: string }): unknown;
|
|
81
|
+
[key: string]: unknown;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface ForeignKeyDef {
|
|
85
|
+
references: string;
|
|
86
|
+
column: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface ModelSchema {
|
|
90
|
+
table: string;
|
|
91
|
+
idType: string;
|
|
92
|
+
columns: Record<string, string>;
|
|
93
|
+
foreignKeys: Record<string, ForeignKeyDef>;
|
|
94
|
+
relationships: {
|
|
95
|
+
belongsTo: Record<string, string | null>;
|
|
96
|
+
hasMany: Record<string, string | null>;
|
|
97
|
+
};
|
|
98
|
+
vectorColumns?: Record<string, number>;
|
|
99
|
+
memory: boolean;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface ViewSchema {
|
|
103
|
+
viewName: string;
|
|
104
|
+
source: string;
|
|
105
|
+
groupBy?: string;
|
|
106
|
+
columns: Record<string, string>;
|
|
107
|
+
foreignKeys: Record<string, ForeignKeyDef>;
|
|
108
|
+
aggregates: Record<string, AggregateProperty>;
|
|
109
|
+
relationships: {
|
|
110
|
+
belongsTo: Record<string, string | null>;
|
|
111
|
+
hasMany: Record<string, string | null>;
|
|
112
|
+
};
|
|
113
|
+
isView: boolean;
|
|
114
|
+
memory: boolean;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Typed relationship registry maps.
|
|
119
|
+
* Each key in Orm.relationships stores a different nested Map structure.
|
|
120
|
+
*/
|
|
121
|
+
/** Relationship registry map types — source → target → recordId → value */
|
|
122
|
+
export type HasManyMap = Map<string, Map<string, Map<unknown, unknown[]>>>;
|
|
123
|
+
export type BelongsToMap = Map<string, Map<string, Map<unknown, unknown>>>;
|
|
124
|
+
export type GlobalMap = Map<string, unknown[][]>;
|
|
125
|
+
export type PendingMap = Map<string, Map<unknown, unknown[][]>>;
|
|
126
|
+
export type PendingBelongsToMap = Map<string, Map<unknown, unknown[]>>;
|
|
127
|
+
|
|
128
|
+
export interface RelationshipMaps {
|
|
129
|
+
hasMany: HasManyMap;
|
|
130
|
+
belongsTo: BelongsToMap;
|
|
131
|
+
global: GlobalMap;
|
|
132
|
+
pending: PendingMap;
|
|
133
|
+
pendingBelongsTo: PendingBelongsToMap;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export interface SnapshotEntry {
|
|
137
|
+
table?: string;
|
|
138
|
+
idType?: string;
|
|
139
|
+
columns?: Record<string, string>;
|
|
140
|
+
foreignKeys?: Record<string, ForeignKeyDef>;
|
|
141
|
+
vectorColumns?: Record<string, number>;
|
|
142
|
+
isView?: boolean;
|
|
143
|
+
viewName?: string;
|
|
144
|
+
source?: string;
|
|
145
|
+
viewQuery?: string;
|
|
146
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
declare module 'pg' {
|
|
2
|
+
interface PoolConfig {
|
|
3
|
+
host?: string;
|
|
4
|
+
user?: string;
|
|
5
|
+
password?: string;
|
|
6
|
+
database?: string;
|
|
7
|
+
port?: number;
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface QueryResult {
|
|
12
|
+
rows: Record<string, unknown>[];
|
|
13
|
+
rowCount: number;
|
|
14
|
+
fields?: { name: string }[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class Pool {
|
|
18
|
+
constructor(config?: PoolConfig);
|
|
19
|
+
query(sql: string, params?: unknown[]): Promise<QueryResult>;
|
|
20
|
+
connect(): Promise<PoolClient>;
|
|
21
|
+
end(): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class PoolClient {
|
|
25
|
+
query(sql: string, params?: unknown[]): Promise<QueryResult>;
|
|
26
|
+
release(): void;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
declare module '@stonyx/rest-server' {
|
|
2
|
+
export class Request {
|
|
3
|
+
constructor(...args: unknown[]);
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export default class RestServer {
|
|
7
|
+
static instance: RestServer;
|
|
8
|
+
static close(): void;
|
|
9
|
+
mountRoute(RequestClass: unknown, options: { name: string; options?: unknown }): void;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
declare module '@stonyx/utils/file' {
|
|
2
|
+
export function createFile(path: string, data: unknown, options?: { json?: boolean }): Promise<void>;
|
|
3
|
+
export function createDirectory(path: string): Promise<void>;
|
|
4
|
+
export function updateFile(path: string, data: unknown, options?: { json?: boolean }): Promise<void>;
|
|
5
|
+
export function readFile(path: string, options?: { json?: boolean; missingFileCallback?: () => Promise<unknown> }): Promise<unknown>;
|
|
6
|
+
export function fileExists(path: string): Promise<boolean>;
|
|
7
|
+
export function deleteDirectory(path: string): Promise<void>;
|
|
8
|
+
export function forEachFileImport(
|
|
9
|
+
path: string,
|
|
10
|
+
callback: (exported: unknown, meta: { name: string }) => unknown,
|
|
11
|
+
options?: { ignoreAccessFailure?: boolean; rawName?: boolean; recursive?: boolean; recursiveNaming?: boolean }
|
|
12
|
+
): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
declare module '@stonyx/utils/string' {
|
|
16
|
+
export function pluralize(word: string): string;
|
|
17
|
+
export function kebabCaseToPascalCase(str: string): string;
|
|
18
|
+
export function camelCaseToKebabCase(str: string): string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
declare module '@stonyx/utils/object' {
|
|
22
|
+
export function get(obj: unknown, path: string): unknown;
|
|
23
|
+
export function getOrSet<T>(map: Map<unknown, T>, key: unknown, defaultValue: T): T;
|
|
24
|
+
export function makeArray<T>(value: T | T[]): T[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
declare module '@stonyx/utils/date' {
|
|
28
|
+
export function getTimestamp(value?: unknown): number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
declare module '@stonyx/utils/prompt' {
|
|
32
|
+
export function confirm(message: string): Promise<boolean>;
|
|
33
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
declare module 'stonyx/config' {
|
|
2
|
+
import type { OrmConfig } from './orm-types.js';
|
|
3
|
+
const config: OrmConfig;
|
|
4
|
+
export default config;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
declare module 'stonyx/log' {
|
|
8
|
+
const log: Record<string, ((...args: unknown[]) => void) | undefined>;
|
|
9
|
+
export default log;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
declare module 'stonyx' {
|
|
13
|
+
export function waitForModule(name: string): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
declare module 'stonyx/test-helpers' {
|
|
17
|
+
export function setupIntegrationTests(hooks: {
|
|
18
|
+
before(fn: () => void | Promise<void>): void;
|
|
19
|
+
after(fn: () => void | Promise<void>): void;
|
|
20
|
+
}): void;
|
|
21
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { pluralize as basePluralize } from '@stonyx/utils/string';
|
|
2
|
+
|
|
3
|
+
export function isDbError(error: unknown): error is { code: string; message: string } {
|
|
4
|
+
return typeof error === 'object' && error !== null && 'code' in error && typeof (error as Record<string, unknown>).code === 'string' && 'message' in error && typeof (error as Record<string, unknown>).message === 'string';
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
// Wrapper to handle dasherized model names (e.g., "access-link" → "access-links")
|
|
8
|
+
export function pluralize(word: string): string {
|
|
9
|
+
if (word.includes('-')) {
|
|
10
|
+
const parts = word.split('-');
|
|
11
|
+
const pluralizedLast = basePluralize(parts.pop()!);
|
|
12
|
+
return [...parts, pluralizedLast].join('-');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return basePluralize(word);
|
|
16
|
+
}
|
|
@@ -1,30 +1,41 @@
|
|
|
1
|
-
import Orm, {
|
|
1
|
+
import Orm, { store } from '@stonyx/orm';
|
|
2
|
+
import { createRecord } from './manage-record.js';
|
|
2
3
|
import { AggregateProperty } from './aggregates.js';
|
|
3
4
|
import { get } from '@stonyx/utils/object';
|
|
5
|
+
import type { SourceRecord } from './types/orm-types.js';
|
|
6
|
+
|
|
7
|
+
interface ViewClass {
|
|
8
|
+
source?: string;
|
|
9
|
+
resolve?: Record<string, unknown>;
|
|
10
|
+
groupBy?: string;
|
|
11
|
+
new (viewName: string): Record<string, unknown>;
|
|
12
|
+
}
|
|
4
13
|
|
|
5
14
|
export default class ViewResolver {
|
|
6
|
-
|
|
15
|
+
viewName: string;
|
|
16
|
+
|
|
17
|
+
constructor(viewName: string) {
|
|
7
18
|
this.viewName = viewName;
|
|
8
19
|
}
|
|
9
20
|
|
|
10
|
-
async resolveAll() {
|
|
21
|
+
async resolveAll(): Promise<unknown[]> {
|
|
11
22
|
const orm = Orm.instance;
|
|
12
|
-
const { modelClass: viewClass } = orm.getRecordClasses(this.viewName);
|
|
23
|
+
const { modelClass: viewClass } = orm.getRecordClasses(this.viewName) as { modelClass: ViewClass | undefined; serializerClass: unknown };
|
|
13
24
|
|
|
14
25
|
if (!viewClass) return [];
|
|
15
26
|
|
|
16
27
|
const source = viewClass.source;
|
|
17
28
|
if (!source) return [];
|
|
18
29
|
|
|
19
|
-
const sourceRecords = await store.findAll(source);
|
|
30
|
+
const sourceRecords = await store.findAll(source) as SourceRecord[];
|
|
20
31
|
if (!sourceRecords || sourceRecords.length === 0) {
|
|
21
32
|
return [];
|
|
22
33
|
}
|
|
23
34
|
|
|
24
35
|
const resolveMap = viewClass.resolve || {};
|
|
25
36
|
const viewInstance = new viewClass(this.viewName);
|
|
26
|
-
const aggregateFields = {};
|
|
27
|
-
const regularFields = {};
|
|
37
|
+
const aggregateFields: Record<string, AggregateProperty> = {};
|
|
38
|
+
const regularFields: Record<string, unknown> = {};
|
|
28
39
|
|
|
29
40
|
// Categorize fields on the view instance
|
|
30
41
|
for (const [key, value] of Object.entries(viewInstance)) {
|
|
@@ -48,14 +59,21 @@ export default class ViewResolver {
|
|
|
48
59
|
return this._resolvePerRecord(sourceRecords, aggregateFields, regularFields, resolveMap, viewClass);
|
|
49
60
|
}
|
|
50
61
|
|
|
51
|
-
_resolvePerRecord(
|
|
52
|
-
|
|
62
|
+
private _resolvePerRecord(
|
|
63
|
+
sourceRecords: SourceRecord[],
|
|
64
|
+
aggregateFields: Record<string, AggregateProperty>,
|
|
65
|
+
regularFields: Record<string, unknown>,
|
|
66
|
+
resolveMap: Record<string, unknown>,
|
|
67
|
+
viewClass: ViewClass
|
|
68
|
+
): unknown[] {
|
|
69
|
+
const results: unknown[] = [];
|
|
53
70
|
|
|
54
71
|
for (const sourceRecord of sourceRecords) {
|
|
55
|
-
const rawData = { id: sourceRecord.id };
|
|
72
|
+
const rawData: Record<string, unknown> = { id: sourceRecord.id };
|
|
56
73
|
|
|
57
74
|
// Compute aggregate fields from source record's relationships
|
|
58
75
|
for (const [key, aggProp] of Object.entries(aggregateFields)) {
|
|
76
|
+
if (!aggProp.relationship) continue;
|
|
59
77
|
const relatedRecords = sourceRecord.__relationships?.[aggProp.relationship]
|
|
60
78
|
|| sourceRecord[aggProp.relationship];
|
|
61
79
|
const relArray = Array.isArray(relatedRecords) ? relatedRecords : [];
|
|
@@ -93,8 +111,8 @@ export default class ViewResolver {
|
|
|
93
111
|
|
|
94
112
|
// Clear existing record from store to allow re-resolution
|
|
95
113
|
const viewStore = store.get(this.viewName);
|
|
96
|
-
if (viewStore?.has(rawData.id)) {
|
|
97
|
-
viewStore.delete(rawData.id);
|
|
114
|
+
if (viewStore?.has(rawData.id as number | string)) {
|
|
115
|
+
viewStore.delete(rawData.id as number | string);
|
|
98
116
|
}
|
|
99
117
|
|
|
100
118
|
const record = createRecord(this.viewName, rawData, { isDbRecord: true });
|
|
@@ -104,21 +122,28 @@ export default class ViewResolver {
|
|
|
104
122
|
return results;
|
|
105
123
|
}
|
|
106
124
|
|
|
107
|
-
_resolveGroupBy(
|
|
125
|
+
private _resolveGroupBy(
|
|
126
|
+
sourceRecords: SourceRecord[],
|
|
127
|
+
groupByField: string,
|
|
128
|
+
aggregateFields: Record<string, AggregateProperty>,
|
|
129
|
+
regularFields: Record<string, unknown>,
|
|
130
|
+
resolveMap: Record<string, unknown>,
|
|
131
|
+
viewClass: ViewClass
|
|
132
|
+
): unknown[] {
|
|
108
133
|
// Group source records by the groupBy field value
|
|
109
|
-
const groups = new Map();
|
|
134
|
+
const groups = new Map<unknown, SourceRecord[]>();
|
|
110
135
|
for (const record of sourceRecords) {
|
|
111
136
|
const key = record.__data?.[groupByField] ?? record[groupByField];
|
|
112
137
|
if (!groups.has(key)) {
|
|
113
138
|
groups.set(key, []);
|
|
114
139
|
}
|
|
115
|
-
groups.get(key)
|
|
140
|
+
groups.get(key)!.push(record);
|
|
116
141
|
}
|
|
117
142
|
|
|
118
|
-
const results = [];
|
|
143
|
+
const results: unknown[] = [];
|
|
119
144
|
|
|
120
145
|
for (const [groupKey, groupRecords] of groups) {
|
|
121
|
-
const rawData = { id: groupKey };
|
|
146
|
+
const rawData: Record<string, unknown> = { id: groupKey };
|
|
122
147
|
|
|
123
148
|
// Compute aggregate fields
|
|
124
149
|
for (const [key, aggProp] of Object.entries(aggregateFields)) {
|
|
@@ -127,7 +152,8 @@ export default class ViewResolver {
|
|
|
127
152
|
rawData[key] = aggProp.compute(groupRecords);
|
|
128
153
|
} else {
|
|
129
154
|
// Relationship aggregate — flatten related records across all group members
|
|
130
|
-
|
|
155
|
+
if (!aggProp.relationship) continue;
|
|
156
|
+
const allRelated: unknown[] = [];
|
|
131
157
|
for (const record of groupRecords) {
|
|
132
158
|
const relatedRecords = record.__relationships?.[aggProp.relationship]
|
|
133
159
|
|| record[aggProp.relationship];
|
|
@@ -135,7 +161,7 @@ export default class ViewResolver {
|
|
|
135
161
|
allRelated.push(...relatedRecords);
|
|
136
162
|
}
|
|
137
163
|
}
|
|
138
|
-
rawData[key] = aggProp.compute(allRelated);
|
|
164
|
+
rawData[key] = aggProp.compute(allRelated as { __data?: Record<string, unknown>; [key: string]: unknown }[]);
|
|
139
165
|
}
|
|
140
166
|
}
|
|
141
167
|
|
|
@@ -163,8 +189,8 @@ export default class ViewResolver {
|
|
|
163
189
|
|
|
164
190
|
// Clear existing record from store to allow re-resolution
|
|
165
191
|
const viewStore = store.get(this.viewName);
|
|
166
|
-
if (viewStore?.has(rawData.id)) {
|
|
167
|
-
viewStore.delete(rawData.id);
|
|
192
|
+
if (viewStore?.has(rawData.id as number | string)) {
|
|
193
|
+
viewStore.delete(rawData.id as number | string);
|
|
168
194
|
}
|
|
169
195
|
|
|
170
196
|
const record = createRecord(this.viewName, rawData, { isDbRecord: true });
|
|
@@ -174,10 +200,11 @@ export default class ViewResolver {
|
|
|
174
200
|
return results;
|
|
175
201
|
}
|
|
176
202
|
|
|
177
|
-
async resolveOne(id) {
|
|
203
|
+
async resolveOne(id: unknown): Promise<unknown> {
|
|
178
204
|
const all = await this.resolveAll();
|
|
179
|
-
return all.find(record => {
|
|
180
|
-
|
|
205
|
+
return all.find((record: unknown) => {
|
|
206
|
+
const r = record as SourceRecord;
|
|
207
|
+
return r.id === id || r.id == id;
|
|
181
208
|
});
|
|
182
209
|
}
|
|
183
210
|
}
|
package/src/view.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import attr from './attr.js';
|
|
2
|
+
|
|
3
|
+
export default class View {
|
|
4
|
+
static memory: boolean = false;
|
|
5
|
+
static readOnly: boolean = true;
|
|
6
|
+
static pluralName: string | undefined = undefined;
|
|
7
|
+
static source: string | undefined = undefined;
|
|
8
|
+
static groupBy: string | undefined = undefined;
|
|
9
|
+
static resolve: ((record: unknown) => unknown) | undefined = undefined;
|
|
10
|
+
|
|
11
|
+
id = attr('number');
|
|
12
|
+
__name: string;
|
|
13
|
+
|
|
14
|
+
constructor(name: string) {
|
|
15
|
+
this.__name = name;
|
|
16
|
+
|
|
17
|
+
// Enforce readOnly — cannot be overridden to false
|
|
18
|
+
if ((this.constructor as typeof View).readOnly !== true) {
|
|
19
|
+
throw new Error(`View '${name}' cannot override readOnly to false`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
package/src/belongs-to.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { createRecord, relationships, store } from '@stonyx/orm';
|
|
2
|
-
import { getRelationships } from './relationships.js';
|
|
3
|
-
|
|
4
|
-
function getOrSet(map, key, defaultValue) {
|
|
5
|
-
if (!map.has(key)) map.set(key, defaultValue);
|
|
6
|
-
return map.get(key);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export default function belongsTo(modelName) {
|
|
10
|
-
const hasManyRelationships = relationships.get('hasMany');
|
|
11
|
-
const pendingHasManyQueue = relationships.get('pending');
|
|
12
|
-
const pendingBelongsToQueue = relationships.get('pendingBelongsTo');
|
|
13
|
-
|
|
14
|
-
const fn = (sourceRecord, rawData, options) => {
|
|
15
|
-
if (!rawData) return null;
|
|
16
|
-
|
|
17
|
-
const { __name: sourceModelName } = sourceRecord.__model;
|
|
18
|
-
const relationshipId = sourceRecord.id;
|
|
19
|
-
const relationshipKey = options._relationshipKey;
|
|
20
|
-
const relationship = getRelationships('belongsTo', sourceModelName, modelName, relationshipId);
|
|
21
|
-
const modelStore = store.get(modelName);
|
|
22
|
-
|
|
23
|
-
// Try to get existing record
|
|
24
|
-
let output;
|
|
25
|
-
|
|
26
|
-
if (typeof rawData === 'object') {
|
|
27
|
-
output = createRecord(modelName, rawData, options);
|
|
28
|
-
} else if (modelStore) {
|
|
29
|
-
output = modelStore.get(rawData);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// If not found and is a string ID, register as pending
|
|
33
|
-
if (!output && typeof rawData !== 'object') {
|
|
34
|
-
const targetId = rawData;
|
|
35
|
-
|
|
36
|
-
// Register pending belongsTo
|
|
37
|
-
const modelPendingMap = getOrSet(pendingBelongsToQueue, modelName, new Map());
|
|
38
|
-
const targetPendingArray = getOrSet(modelPendingMap, targetId, []);
|
|
39
|
-
|
|
40
|
-
targetPendingArray.push({
|
|
41
|
-
sourceRecord,
|
|
42
|
-
sourceModelName,
|
|
43
|
-
relationshipKey,
|
|
44
|
-
relationshipId
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
relationship.set(relationshipId, null);
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
relationship.set(relationshipId, output || {});
|
|
52
|
-
|
|
53
|
-
// Populate hasMany side if the relationship is defined
|
|
54
|
-
const otherSide = hasManyRelationships.get(modelName)?.get(sourceModelName)?.get(output?.id);
|
|
55
|
-
|
|
56
|
-
if (otherSide) {
|
|
57
|
-
otherSide.push(sourceRecord);
|
|
58
|
-
|
|
59
|
-
// Remove pending queue if it was just fulfilled
|
|
60
|
-
const pendingModelRelationships = pendingHasManyQueue.get(sourceModelName);
|
|
61
|
-
|
|
62
|
-
if (pendingModelRelationships) pendingModelRelationships.delete(relationshipId);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return output;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
Object.defineProperty(fn, '__relatedModelName', { value: modelName });
|
|
69
|
-
return fn;
|
|
70
|
-
}
|
package/src/has-many.js
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { createRecord, relationships, store } from '@stonyx/orm';
|
|
2
|
-
import { getRelationships } from './relationships.js';
|
|
3
|
-
import { getOrSet, makeArray } from '@stonyx/utils/object';
|
|
4
|
-
import { dbKey } from './db.js';
|
|
5
|
-
|
|
6
|
-
function queuePendingRelationship(pendingRelationshipQueue, pendingRelationships, modelName, id) {
|
|
7
|
-
pendingRelationshipQueue.push({
|
|
8
|
-
pendingRelationship: getOrSet(pendingRelationships, modelName, new Map()),
|
|
9
|
-
id
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
return null;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export default function hasMany(modelName) {
|
|
16
|
-
const globalRelationships = relationships.get('global');
|
|
17
|
-
const pendingRelationships = relationships.get('pending');
|
|
18
|
-
|
|
19
|
-
const fn = (sourceRecord, rawData, options) => {
|
|
20
|
-
const { __name: sourceModelName } = sourceRecord.__model;
|
|
21
|
-
const relationshipId = sourceRecord.id;
|
|
22
|
-
const relationship = getRelationships('hasMany', sourceModelName, modelName, relationshipId);
|
|
23
|
-
const modelStore = store.get(modelName);
|
|
24
|
-
const pendingRelationshipQueue = [];
|
|
25
|
-
|
|
26
|
-
const output = !rawData ? [] : makeArray(rawData).map(elementData => {
|
|
27
|
-
let record;
|
|
28
|
-
|
|
29
|
-
if (typeof elementData !== 'object') {
|
|
30
|
-
if (!modelStore) {
|
|
31
|
-
return queuePendingRelationship(pendingRelationshipQueue, pendingRelationships, modelName, elementData);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
record = modelStore.get(elementData);
|
|
35
|
-
|
|
36
|
-
if (!record) {
|
|
37
|
-
return queuePendingRelationship(pendingRelationshipQueue, pendingRelationships, modelName, elementData);
|
|
38
|
-
}
|
|
39
|
-
} else {
|
|
40
|
-
if (elementData !== Object(elementData)) {
|
|
41
|
-
return queuePendingRelationship(pendingRelationshipQueue, pendingRelationships, modelName, elementData);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
record = createRecord(modelName, elementData, options);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Populate belongTo side if the relationship is defined
|
|
48
|
-
const otherSide = relationships.get('belongsTo').get(modelName)?.get(sourceModelName)?.get(record.id);
|
|
49
|
-
|
|
50
|
-
if (otherSide) Object.assign(otherSide, sourceRecord);
|
|
51
|
-
|
|
52
|
-
return record;
|
|
53
|
-
}).filter(value => value);
|
|
54
|
-
|
|
55
|
-
relationship.set(relationshipId, output);
|
|
56
|
-
|
|
57
|
-
// Assign global relationship
|
|
58
|
-
if (options.global || sourceModelName === dbKey) getOrSet(globalRelationships, modelName, []).push(output);
|
|
59
|
-
|
|
60
|
-
// Assign pending relationships
|
|
61
|
-
for (const { pendingRelationship, id } of pendingRelationshipQueue) getOrSet(pendingRelationship, id, []).push(output);
|
|
62
|
-
|
|
63
|
-
return output;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
Object.defineProperty(fn, '__relatedModelName', { value: modelName });
|
|
67
|
-
return fn;
|
|
68
|
-
}
|
package/src/relationships.js
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { relationships } from "@stonyx/orm";
|
|
2
|
-
|
|
3
|
-
export default class Relationships {
|
|
4
|
-
constructor() {
|
|
5
|
-
if (Relationships.instance) return Relationships.instance;
|
|
6
|
-
Relationships.instance = this;
|
|
7
|
-
|
|
8
|
-
this.data = new Map();
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
get(key) {
|
|
12
|
-
return this.data.get(key);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
set(key, value) {
|
|
16
|
-
this.data.set(key, value);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// TODO: Refactor mapping to remove a level of iteration
|
|
21
|
-
export function getRelationships(type, sourceModel, targetModel, relationshipId) {
|
|
22
|
-
const allRelationships = relationships.get(type);
|
|
23
|
-
|
|
24
|
-
// create relationship map for this type of it doesn't already exist
|
|
25
|
-
if (!allRelationships.has(sourceModel)) allRelationships.set(sourceModel, new Map());
|
|
26
|
-
|
|
27
|
-
const modelRelationship = allRelationships.get(sourceModel);
|
|
28
|
-
|
|
29
|
-
if (!modelRelationship.has(targetModel)) modelRelationship.set(targetModel, new Map());
|
|
30
|
-
|
|
31
|
-
const relationship = modelRelationship.get(targetModel);
|
|
32
|
-
|
|
33
|
-
// TODO: Determine whether already having id should be handled differently
|
|
34
|
-
//if (relationship.has(relationshipId)) return;
|
|
35
|
-
|
|
36
|
-
return relationship;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function getHasManyRelationships(sourceModel, targetModel) {
|
|
40
|
-
return relationships.get('hasMany').get(sourceModel)?.get(targetModel);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export const TYPES = ['global', 'hasMany', 'belongsTo', 'pending'];
|