@stonyx/orm 0.2.1-beta.83 → 0.2.1-beta.84
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 +58 -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 +57 -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 +178 -0
- package/dist/manage-record.d.ts +13 -0
- package/dist/manage-record.js +113 -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 +411 -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 +453 -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 +473 -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 +35 -0
- package/dist/serializer.d.ts +17 -0
- package/dist/serializer.js +130 -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 +44 -0
- package/dist/timescale/timescale-db.js +81 -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 +165 -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.js → belongs-to.ts} +36 -17
- 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 +91 -0
- package/src/{hooks.js → hooks.ts} +23 -27
- package/src/{index.js → index.ts} +4 -4
- package/src/{main.js → main.ts} +59 -34
- package/src/{manage-record.js → manage-record.ts} +41 -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} +533 -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} +165 -95
- 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} +195 -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 +48 -0
- package/src/{serializer.js → serializer.ts} +44 -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 +107 -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} +53 -28
- package/src/view.ts +22 -0
- 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,16 +59,22 @@ 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)) {
|
|
59
|
-
const relatedRecords = sourceRecord.__relationships?.[aggProp.relationship]
|
|
60
|
-
|| sourceRecord[aggProp.relationship];
|
|
76
|
+
const relatedRecords = sourceRecord.__relationships?.[aggProp.relationship!]
|
|
77
|
+
|| sourceRecord[aggProp.relationship!];
|
|
61
78
|
const relArray = Array.isArray(relatedRecords) ? relatedRecords : [];
|
|
62
79
|
rawData[key] = aggProp.compute(relArray);
|
|
63
80
|
}
|
|
@@ -93,8 +110,8 @@ export default class ViewResolver {
|
|
|
93
110
|
|
|
94
111
|
// Clear existing record from store to allow re-resolution
|
|
95
112
|
const viewStore = store.get(this.viewName);
|
|
96
|
-
if (viewStore?.has(rawData.id)) {
|
|
97
|
-
viewStore.delete(rawData.id);
|
|
113
|
+
if (viewStore?.has(rawData.id as number | string)) {
|
|
114
|
+
viewStore.delete(rawData.id as number | string);
|
|
98
115
|
}
|
|
99
116
|
|
|
100
117
|
const record = createRecord(this.viewName, rawData, { isDbRecord: true });
|
|
@@ -104,21 +121,28 @@ export default class ViewResolver {
|
|
|
104
121
|
return results;
|
|
105
122
|
}
|
|
106
123
|
|
|
107
|
-
_resolveGroupBy(
|
|
124
|
+
private _resolveGroupBy(
|
|
125
|
+
sourceRecords: SourceRecord[],
|
|
126
|
+
groupByField: string,
|
|
127
|
+
aggregateFields: Record<string, AggregateProperty>,
|
|
128
|
+
regularFields: Record<string, unknown>,
|
|
129
|
+
resolveMap: Record<string, unknown>,
|
|
130
|
+
viewClass: ViewClass
|
|
131
|
+
): unknown[] {
|
|
108
132
|
// Group source records by the groupBy field value
|
|
109
|
-
const groups = new Map();
|
|
133
|
+
const groups = new Map<unknown, SourceRecord[]>();
|
|
110
134
|
for (const record of sourceRecords) {
|
|
111
135
|
const key = record.__data?.[groupByField] ?? record[groupByField];
|
|
112
136
|
if (!groups.has(key)) {
|
|
113
137
|
groups.set(key, []);
|
|
114
138
|
}
|
|
115
|
-
groups.get(key)
|
|
139
|
+
groups.get(key)!.push(record);
|
|
116
140
|
}
|
|
117
141
|
|
|
118
|
-
const results = [];
|
|
142
|
+
const results: unknown[] = [];
|
|
119
143
|
|
|
120
144
|
for (const [groupKey, groupRecords] of groups) {
|
|
121
|
-
const rawData = { id: groupKey };
|
|
145
|
+
const rawData: Record<string, unknown> = { id: groupKey };
|
|
122
146
|
|
|
123
147
|
// Compute aggregate fields
|
|
124
148
|
for (const [key, aggProp] of Object.entries(aggregateFields)) {
|
|
@@ -127,15 +151,15 @@ export default class ViewResolver {
|
|
|
127
151
|
rawData[key] = aggProp.compute(groupRecords);
|
|
128
152
|
} else {
|
|
129
153
|
// Relationship aggregate — flatten related records across all group members
|
|
130
|
-
const allRelated = [];
|
|
154
|
+
const allRelated: unknown[] = [];
|
|
131
155
|
for (const record of groupRecords) {
|
|
132
|
-
const relatedRecords = record.__relationships?.[aggProp.relationship]
|
|
133
|
-
|| record[aggProp.relationship];
|
|
156
|
+
const relatedRecords = record.__relationships?.[aggProp.relationship!]
|
|
157
|
+
|| record[aggProp.relationship!];
|
|
134
158
|
if (Array.isArray(relatedRecords)) {
|
|
135
159
|
allRelated.push(...relatedRecords);
|
|
136
160
|
}
|
|
137
161
|
}
|
|
138
|
-
rawData[key] = aggProp.compute(allRelated);
|
|
162
|
+
rawData[key] = aggProp.compute(allRelated as { __data?: Record<string, unknown>; [key: string]: unknown }[]);
|
|
139
163
|
}
|
|
140
164
|
}
|
|
141
165
|
|
|
@@ -163,8 +187,8 @@ export default class ViewResolver {
|
|
|
163
187
|
|
|
164
188
|
// Clear existing record from store to allow re-resolution
|
|
165
189
|
const viewStore = store.get(this.viewName);
|
|
166
|
-
if (viewStore?.has(rawData.id)) {
|
|
167
|
-
viewStore.delete(rawData.id);
|
|
190
|
+
if (viewStore?.has(rawData.id as number | string)) {
|
|
191
|
+
viewStore.delete(rawData.id as number | string);
|
|
168
192
|
}
|
|
169
193
|
|
|
170
194
|
const record = createRecord(this.viewName, rawData, { isDbRecord: true });
|
|
@@ -174,10 +198,11 @@ export default class ViewResolver {
|
|
|
174
198
|
return results;
|
|
175
199
|
}
|
|
176
200
|
|
|
177
|
-
async resolveOne(id) {
|
|
201
|
+
async resolveOne(id: unknown): Promise<unknown> {
|
|
178
202
|
const all = await this.resolveAll();
|
|
179
|
-
return all.find(record => {
|
|
180
|
-
|
|
203
|
+
return all.find((record: unknown) => {
|
|
204
|
+
const r = record as SourceRecord;
|
|
205
|
+
return r.id === id || r.id == id;
|
|
181
206
|
});
|
|
182
207
|
}
|
|
183
208
|
}
|
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/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'];
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import PostgresDB from '../postgres/postgres-db.js';
|
|
2
|
-
import { buildCreateHypertable, buildTimeBucket, buildContinuousAggregate, buildCompressionPolicy, buildEnableCompression } from './query-builder.js';
|
|
3
|
-
|
|
4
|
-
export default class TimescaleDB extends PostgresDB {
|
|
5
|
-
static extensions = ['timescaledb'];
|
|
6
|
-
static configKey = 'timescale';
|
|
7
|
-
|
|
8
|
-
constructor(deps = {}) {
|
|
9
|
-
super({
|
|
10
|
-
...deps,
|
|
11
|
-
buildCreateHypertable,
|
|
12
|
-
buildTimeBucket,
|
|
13
|
-
buildContinuousAggregate,
|
|
14
|
-
buildCompressionPolicy,
|
|
15
|
-
buildEnableCompression,
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Convert a table to a TimescaleDB hypertable.
|
|
21
|
-
* Should be called after the table is created (e.g. after initial migration).
|
|
22
|
-
* @param {string} modelName
|
|
23
|
-
* @param {string} timeColumn - The time-partitioning column
|
|
24
|
-
* @param {Object} [options]
|
|
25
|
-
* @param {string} [options.chunkInterval='7 days']
|
|
26
|
-
*/
|
|
27
|
-
async createHypertable(modelName, timeColumn, options = {}) {
|
|
28
|
-
const schemas = this.deps.introspectModels();
|
|
29
|
-
const schema = schemas[modelName];
|
|
30
|
-
if (!schema) throw new Error(`Model '${modelName}' not found`);
|
|
31
|
-
|
|
32
|
-
const { sql } = this.deps.buildCreateHypertable(schema.table, timeColumn, options);
|
|
33
|
-
await this.pool.query(sql);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Query time-bucketed aggregations on a hypertable.
|
|
38
|
-
* @param {string} modelName
|
|
39
|
-
* @param {string} timeColumn - Timestamp column to bucket
|
|
40
|
-
* @param {string} bucketSize - Bucket interval (e.g. '1 hour', '5 minutes')
|
|
41
|
-
* @param {Object} [options]
|
|
42
|
-
* @param {string[]} [options.aggregates] - Aggregate expressions
|
|
43
|
-
* @param {Object} [options.where] - WHERE conditions
|
|
44
|
-
* @param {number} [options.limit]
|
|
45
|
-
* @returns {Promise<Object[]>} Rows with bucket + aggregate columns
|
|
46
|
-
*/
|
|
47
|
-
async timeBucket(modelName, timeColumn, bucketSize, options = {}) {
|
|
48
|
-
const schemas = this.deps.introspectModels();
|
|
49
|
-
const schema = schemas[modelName];
|
|
50
|
-
if (!schema) return [];
|
|
51
|
-
|
|
52
|
-
const { sql, values } = this.deps.buildTimeBucket(schema.table, timeColumn, bucketSize, options);
|
|
53
|
-
|
|
54
|
-
try {
|
|
55
|
-
const result = await this.pool.query(sql, values);
|
|
56
|
-
return result.rows;
|
|
57
|
-
} catch (error) {
|
|
58
|
-
if (error.code === '42P01') return [];
|
|
59
|
-
throw error;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Create a continuous aggregate view on a hypertable.
|
|
65
|
-
* @param {string} viewName - Name for the materialized view
|
|
66
|
-
* @param {string} modelName - Source hypertable model
|
|
67
|
-
* @param {string} timeColumn - Timestamp column
|
|
68
|
-
* @param {string} bucketSize - Bucket interval
|
|
69
|
-
* @param {string[]} aggregates - Aggregate expressions
|
|
70
|
-
* @param {Object} [options]
|
|
71
|
-
* @param {boolean} [options.withNoData=false]
|
|
72
|
-
*/
|
|
73
|
-
async createContinuousAggregate(viewName, modelName, timeColumn, bucketSize, aggregates, options = {}) {
|
|
74
|
-
const schemas = this.deps.introspectModels();
|
|
75
|
-
const schema = schemas[modelName];
|
|
76
|
-
if (!schema) throw new Error(`Model '${modelName}' not found`);
|
|
77
|
-
|
|
78
|
-
const { sql } = this.deps.buildContinuousAggregate(viewName, schema.table, timeColumn, bucketSize, aggregates, options);
|
|
79
|
-
await this.pool.query(sql);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Enable compression on a hypertable.
|
|
84
|
-
* @param {string} modelName
|
|
85
|
-
* @param {Object} [options]
|
|
86
|
-
* @param {string} [options.segmentBy] - Column to segment by
|
|
87
|
-
* @param {string} [options.orderBy] - Column to order by
|
|
88
|
-
*/
|
|
89
|
-
async enableCompression(modelName, options = {}) {
|
|
90
|
-
const schemas = this.deps.introspectModels();
|
|
91
|
-
const schema = schemas[modelName];
|
|
92
|
-
if (!schema) throw new Error(`Model '${modelName}' not found`);
|
|
93
|
-
|
|
94
|
-
const { sql } = this.deps.buildEnableCompression(schema.table, options.segmentBy, options.orderBy);
|
|
95
|
-
await this.pool.query(sql);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Add a compression policy to a hypertable.
|
|
100
|
-
* @param {string} modelName
|
|
101
|
-
* @param {string} compressAfter - Interval after which to compress (e.g. '7 days')
|
|
102
|
-
*/
|
|
103
|
-
async addCompressionPolicy(modelName, compressAfter) {
|
|
104
|
-
const schemas = this.deps.introspectModels();
|
|
105
|
-
const schema = schemas[modelName];
|
|
106
|
-
if (!schema) throw new Error(`Model '${modelName}' not found`);
|
|
107
|
-
|
|
108
|
-
const { sql } = this.deps.buildCompressionPolicy(schema.table, compressAfter);
|
|
109
|
-
await this.pool.query(sql);
|
|
110
|
-
}
|
|
111
|
-
}
|