@webda/postgres 4.0.0-beta.1 → 4.0.0-beta.3

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,327 @@
1
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
2
+ var useValue = arguments.length > 2;
3
+ for (var i = 0; i < initializers.length; i++) {
4
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
5
+ }
6
+ return useValue ? value : void 0;
7
+ };
8
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
9
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
10
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
11
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
12
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
13
+ var _, done = false;
14
+ for (var i = decorators.length - 1; i >= 0; i--) {
15
+ var context = {};
16
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
17
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
18
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
19
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
20
+ if (kind === "accessor") {
21
+ if (result === void 0) continue;
22
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
23
+ if (_ = accept(result.get)) descriptor.get = _;
24
+ if (_ = accept(result.set)) descriptor.set = _;
25
+ if (_ = accept(result.init)) initializers.unshift(_);
26
+ }
27
+ else if (_ = accept(result)) {
28
+ if (kind === "field") initializers.unshift(_);
29
+ else descriptor[key] = _;
30
+ }
31
+ }
32
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
33
+ done = true;
34
+ };
35
+ import { EventRepository, InstanceCache, useApplication, useCore, useModel, useModelMetadata } from "@webda/core";
36
+ import pg from "pg";
37
+ import { acquirePool, releasePool } from "./pgpool.js";
38
+ import { PostgresRepository, SQLStore, SQLStoreParameters } from "./sqlstore.js";
39
+ import { useLog } from "@webda/workout";
40
+ /*
41
+ * Ideas:
42
+ * - Make views for each models to have a better query: https://dba.stackexchange.com/questions/151838/postgresql-json-column-to-view
43
+ * - CREATE VIEW my_view AS SELECT uuid,data->>'status' as status from table;
44
+ * - could auto resolve FK
45
+ * - Use STORED generated model to define FK: https://stackoverflow.com/questions/24489647/json-foreign-keys-in-postgresql
46
+ * - FKs should be defined based on relationship
47
+ * - Define colums dynamically based on the schema -> benchmark to see if it is useful
48
+ * - Allow to define indexes on fields
49
+ */
50
+ /**
51
+ *
52
+ */
53
+ export class PostgresParameters extends SQLStoreParameters {
54
+ /**
55
+ * @override
56
+ * @param params - raw parameters
57
+ * @returns this
58
+ */
59
+ load(params) {
60
+ super.load(params);
61
+ this.autoCreateTable ?? (this.autoCreateTable = true);
62
+ this.usePool ?? (this.usePool = false);
63
+ this.viewPrefix ?? (this.viewPrefix = "");
64
+ this.views ?? (this.views = [".*"]);
65
+ this.tables ?? (this.tables = {});
66
+ return this;
67
+ }
68
+ }
69
+ /**
70
+ * Store data within PostgreSQL with JSONB
71
+ *
72
+ * The table should be created before with
73
+ *
74
+ * ```sql
75
+ * CREATE TABLE IF NOT EXISTS ${tableName}
76
+ * (
77
+ * uuid uuid NOT NULL,
78
+ * data jsonb,
79
+ * CONSTRAINT ${tableName}_pkey PRIMARY KEY (uuid)
80
+ * );
81
+ * ```
82
+ *
83
+ * @WebdaModda
84
+ */
85
+ let PostgresStore = (() => {
86
+ var _a;
87
+ let _classSuper = SQLStore;
88
+ let _instanceExtraInitializers = [];
89
+ let _getRepository_decorators;
90
+ return _a = class PostgresStore extends _classSuper {
91
+ constructor() {
92
+ super(...arguments);
93
+ this.client = __runInitializers(this, _instanceExtraInitializers);
94
+ /**
95
+ * True when {@link client} is a shared pool acquired from the registry.
96
+ * Tracked so {@link stop} only releases pools it took a refcount on.
97
+ */
98
+ this.ownsPool = false;
99
+ }
100
+ /**
101
+ * @override
102
+ */
103
+ async init() {
104
+ if (this.parameters.table && (this.parameters.models?.length ?? 0) > 1) {
105
+ useLog("WARN", `PostgresStore "${this.getName()}": parameters.table is ignored when more than one model is configured. Use parameters.tables[id] instead.`);
106
+ }
107
+ if (this.parameters.usePool) {
108
+ this.client = acquirePool(this.parameters.postgresqlServer);
109
+ this.ownsPool = true;
110
+ }
111
+ else {
112
+ this.client = new pg.Client(this.parameters.postgresqlServer);
113
+ await this.client.connect();
114
+ }
115
+ await this.checkTable();
116
+ await super.init();
117
+ return this;
118
+ }
119
+ /**
120
+ * @override
121
+ */
122
+ async stop() {
123
+ if (this.ownsPool) {
124
+ this.ownsPool = false;
125
+ await releasePool(this.parameters.postgresqlServer);
126
+ }
127
+ else if (this.client && "end" in this.client) {
128
+ await this.client.end().catch(() => {
129
+ /* already ended */
130
+ });
131
+ }
132
+ this.client = undefined;
133
+ await super.stop();
134
+ }
135
+ /**
136
+ * Resolve the table name for a given model class.
137
+ *
138
+ * Resolution order:
139
+ * 1. `parameters.tables[meta.Identifier]` — explicit per-model override
140
+ * 2. Single-model back-compat: when only one model is configured, `parameters.table` maps to it
141
+ * 3. Default — model identifier lowercased with "/" replaced by "_"
142
+ *
143
+ * @param model - the model class to resolve the table for
144
+ * @returns the table name
145
+ */
146
+ resolveTable(model) {
147
+ const meta = useModelMetadata(model);
148
+ if (!meta) {
149
+ return this.parameters.table;
150
+ }
151
+ // Explicit per-model override
152
+ if (this.parameters.tables?.[meta.Identifier]) {
153
+ return this.parameters.tables[meta.Identifier];
154
+ }
155
+ // Single-model back-compat: parameters.table maps to the one model
156
+ if (this.parameters.models?.length === 1 && this.parameters.table) {
157
+ return this.parameters.table;
158
+ }
159
+ // Default derivation
160
+ return meta.Identifier.toLowerCase().replace(/\//g, "_");
161
+ }
162
+ /**
163
+ * Ensure all managed model tables exist (one per model in the hierarchy).
164
+ * When `autoCreateTable` is false, this is a no-op.
165
+ */
166
+ async checkTable() {
167
+ if (!this.parameters.autoCreateTable) {
168
+ return;
169
+ }
170
+ // Delegate the per-model DDL to each PostgresRepository.setupTable().
171
+ // Deduplicate by table name so shared-table models don't issue redundant DDL.
172
+ const done = new Set();
173
+ for (const repo of this.getRepositories()) {
174
+ const table = repo.getTable();
175
+ if (done.has(table)) {
176
+ continue;
177
+ }
178
+ done.add(table);
179
+ await repo.setupTable();
180
+ }
181
+ }
182
+ /**
183
+ * Return the postgresql client
184
+ * @returns the pg client or pool
185
+ */
186
+ getClient() {
187
+ return this.client;
188
+ }
189
+ /**
190
+ * Build and return a PostgresRepository for the given model, using the
191
+ * per-model table name resolved by `resolveTable`.
192
+ *
193
+ * The result is cached per model class via `@InstanceCache`.
194
+ * @param model - the model class
195
+ * @returns a repository backed by this store's pg connection
196
+ */
197
+ getRepository(model) {
198
+ const meta = useModelMetadata(model);
199
+ const table = this.resolveTable(model);
200
+ const inner = new PostgresRepository(model, meta.PrimaryKey, this.client, table);
201
+ // Wrap in EventRepository so typed CRUD events fire; consumers reach them
202
+ // via useRepository(model).on(...).
203
+ return new EventRepository(model, meta.PrimaryKey, inner);
204
+ }
205
+ /**
206
+ * Return the underlying PostgresRepository for each model this store manages,
207
+ * unwrapping the EventRepository produced by {@link getRepository}.
208
+ * @returns the per-model PostgresRepository instances
209
+ */
210
+ getRepositories() {
211
+ const repos = [];
212
+ for (const modelId of Object.keys(this._modelsHierarchy ?? {})) {
213
+ let model;
214
+ try {
215
+ model = useModel(modelId);
216
+ }
217
+ catch {
218
+ continue;
219
+ }
220
+ if (!model) {
221
+ continue;
222
+ }
223
+ const repo = this.getRepository(model);
224
+ repos.push((repo.repository ?? repo));
225
+ }
226
+ return repos;
227
+ }
228
+ /**
229
+ * Create views for each model that is stored in a PostgresStore.
230
+ *
231
+ * Creates one SQL VIEW per model (or model subclass) that maps the JSONB
232
+ * `data` column to typed columns based on the model's stored JSON schema.
233
+ */
234
+ async createViews() {
235
+ const app = useApplication();
236
+ const models = app.getModels();
237
+ const viewPatterns = (this.parameters.views ?? [".*"]).map(p => new RegExp(p));
238
+ const modelMatches = (identifier) => viewPatterns.some(re => re.test(identifier));
239
+ for (const model of Object.values(models)) {
240
+ if (!model)
241
+ continue;
242
+ const meta = useModelMetadata(model);
243
+ if (!meta || !meta.Identifier)
244
+ continue;
245
+ if (!modelMatches(meta.Identifier))
246
+ continue;
247
+ // Find the PostgresStore responsible for this model
248
+ const allServices = Object.values(useCore().getServices()).filter(s => s instanceof _a);
249
+ const store = allServices.find(s => s.handleModel(model) >= 0);
250
+ if (!store)
251
+ continue;
252
+ const schema = meta.Schemas?.Stored;
253
+ if (!schema || !schema.properties)
254
+ continue;
255
+ const plural = meta.Plural || meta.Identifier.split("/").pop().toLowerCase() + "s";
256
+ const viewName = `${this.parameters.viewPrefix}${plural}`;
257
+ const fields = ["uuid"];
258
+ for (const field of Object.keys(schema.properties)) {
259
+ if (field === "uuid" || !field.match(/^[0-9a-zA-Z-_$]+$/)) {
260
+ continue;
261
+ }
262
+ let cast = "";
263
+ const type = schema.properties[field].type;
264
+ if (type === "number") {
265
+ cast = "::bigint";
266
+ }
267
+ else if (type === "boolean") {
268
+ cast = "::boolean";
269
+ }
270
+ else if (type === "string") {
271
+ cast = "::text";
272
+ }
273
+ else if (type === "array") {
274
+ cast = "::jsonb";
275
+ }
276
+ else if (type === "object") {
277
+ cast = "::jsonb";
278
+ }
279
+ fields.push(`(data->>'${field}')${cast} as ${field}`);
280
+ }
281
+ let query = `CREATE OR REPLACE VIEW ${viewName} AS SELECT ${fields.join(",")} FROM ${store.resolveTable(model)}`;
282
+ if (store.handleModel(model) > 0) {
283
+ query += ` WHERE (data#>>'{__type}') = '${meta.ShortName || meta.Identifier}'`;
284
+ }
285
+ try {
286
+ this.log("INFO", `Dropping view ${viewName}`);
287
+ await store.getClient().query(`DROP VIEW IF EXISTS ${viewName}`);
288
+ this.log("INFO", query);
289
+ await store.getClient().query(query);
290
+ }
291
+ catch (err) {
292
+ this.log("ERROR", err);
293
+ }
294
+ }
295
+ }
296
+ /**
297
+ * Delete all rows from all managed tables (used in tests).
298
+ */
299
+ async __clean() {
300
+ const tables = new Set([this.parameters.table]);
301
+ for (const modelId of Object.keys(this._modelsHierarchy ?? {})) {
302
+ try {
303
+ const model = useModel(modelId);
304
+ if (model) {
305
+ tables.add(this.resolveTable(model));
306
+ }
307
+ }
308
+ catch {
309
+ // skip unreachable models
310
+ }
311
+ }
312
+ for (const table of tables) {
313
+ await this.client.query(`DELETE FROM ${table}`);
314
+ }
315
+ }
316
+ },
317
+ (() => {
318
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
319
+ _getRepository_decorators = [InstanceCache()];
320
+ __esDecorate(_a, null, _getRepository_decorators, { kind: "method", name: "getRepository", static: false, private: false, access: { has: obj => "getRepository" in obj, get: obj => obj.getRepository }, metadata: _metadata }, null, _instanceExtraInitializers);
321
+ if (_metadata) Object.defineProperty(_a, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
322
+ })(),
323
+ _a;
324
+ })();
325
+ export { PostgresStore };
326
+ export default PostgresStore;
327
+ //# sourceMappingURL=postgresstore.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgresstore.service.js","sourceRoot":"","sources":["../src/postgresstore.service.ts"],"names":[],"mappings":"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"}
package/lib/sqlstore.d.ts CHANGED
@@ -1,78 +1,160 @@
1
- import { CoreModel, ModelLink, Store, StoreParameters } from "@webda/core";
1
+ import { MemoryRepository, Store, StoreParameters } from "@webda/core";
2
+ import type { ModelClass, Repository } from "@webda/core";
2
3
  import * as WebdaQL from "@webda/ql";
4
+ /** Database connection metadata */
3
5
  export interface SQLDatabase {
4
6
  name: string;
5
7
  }
8
+ /** Base parameters for SQL-backed stores */
6
9
  export declare class SQLStoreParameters extends StoreParameters {
7
10
  table: string;
8
11
  database: SQLDatabase;
12
+ /**
13
+ * @override
14
+ * @param params - raw parameters
15
+ * @returns this
16
+ */
17
+ load(params: any): this;
9
18
  }
19
+ /** Minimal SQL client interface compatible with pg.Client and pg.Pool */
10
20
  export interface SQLClient {
11
- query: () => Promise<void>;
21
+ query: (q: string, values?: any[]) => Promise<{
22
+ rows: any[];
23
+ rowCount: number;
24
+ }>;
12
25
  }
26
+ /** Typed result wrapper for SQL queries */
13
27
  export interface SQLResult<T> {
14
28
  rows: T[];
15
29
  rowCount: number;
16
30
  }
31
+ /** Extends ComparisonExpression to emit SQL-compatible string literals for JSONB comparisons */
17
32
  export declare class SQLComparisonExpression extends WebdaQL.ComparisonExpression {
33
+ /**
34
+ * Single quotes are doubled so a value can never close the literal. Backslashes stay
35
+ * literal: with `standard_conforming_strings` (PostgreSQL default) `\\` is not an
36
+ * escape character in '...' strings — only LIKE gives it a meaning.
37
+ *
38
+ * @override
39
+ * @param value - the value to stringify
40
+ * @returns SQL-compatible string literal
41
+ */
18
42
  toStringValue(value: (string | number | boolean) | (string | number | boolean)[]): string;
43
+ /**
44
+ * @override
45
+ * @returns SQL attribute expression with type cast
46
+ */
19
47
  toStringAttribute(): string;
20
48
  }
21
- export declare abstract class SQLStore<T extends CoreModel, K extends SQLStoreParameters = SQLStoreParameters> extends Store<T, K> {
22
- sqlQuery(q: string, values?: any[]): Promise<SQLResult<T>>;
49
+ /**
50
+ * PostgreSQL-backed repository for a single model class.
51
+ *
52
+ * Stores every object as a JSONB `data` column alongside a `uuid` primary-key
53
+ * column. All CRUD operations hit the pg client; the inherited MemoryRepository
54
+ * serialize/deserialize helpers are reused for JSON ↔ model-instance
55
+ * conversion.
56
+ */
57
+ /**
58
+ * PostgreSQL-backed repository for a single model class.
59
+ *
60
+ * Stores every object as a JSONB `data` column alongside a `uuid` primary-key
61
+ * column. All CRUD operations hit the pg client; the inherited MemoryRepository
62
+ * serialize/deserialize helpers are reused for JSON to model-instance conversion.
63
+ */
64
+ export declare class PostgresRepository<T extends ModelClass> extends MemoryRepository<T> {
65
+ protected readonly client: SQLClient;
66
+ protected readonly table: string;
23
67
  /**
24
- * Execute a SQL query
25
- * @param q the query
26
- * @param values to be added to the query
68
+ * Create a new PostgresRepository.
69
+ * @param model - the model class
70
+ * @param pks - primary key field names
71
+ * @param client - the pg client or pool
72
+ * @param table - the table name
73
+ * @param separator - composite key separator
27
74
  */
28
- abstract executeQuery(q: string, values?: any[]): Promise<SQLResult<T>>;
75
+ constructor(model: T, pks: string[], client: SQLClient, table: string, separator?: string);
29
76
  /**
30
- * Add the SELECT * FROM table if the query is not a full query
31
- * @param q query to complete
32
- * @returns
77
+ * The backing table name for this repository's model.
78
+ * @returns the table name
79
+ * @internal Not part of the @webda/models Repository interface.
33
80
  */
34
- completeQuery(q: string): string;
81
+ getTable(): string;
35
82
  /**
36
- * @override
83
+ * Ensure the backing table for this repository's model exists.
84
+ * Owns the per-model DDL previously held by PostgresStore.checkTable().
85
+ * @internal Not part of the @webda/models Repository interface.
37
86
  */
38
- _delete(uid: string, writeCondition: any, writeConditionField: string): Promise<void>;
39
- abstract mapExpressionAttribute(attribute: string[]): string;
40
- duplicateExpression(expression: WebdaQL.Expression): WebdaQL.Expression;
87
+ setupTable(): Promise<void>;
41
88
  /**
42
- * @override
89
+ * Map an expression attribute path array to a JSONB path expression.
90
+ * @param attribute - the attribute path
91
+ * @returns the JSONB path expression
43
92
  */
44
- find(query: WebdaQL.Query): Promise<{
45
- results: T[];
46
- continuationToken: string;
47
- filter: WebdaQL.Expression;
48
- }>;
93
+ mapExpressionAttribute(attribute: string[]): string;
49
94
  /**
50
- * @override
95
+ * Build a SQL WHERE sub-expression from a write-condition.
96
+ * @param writeCondition - the expected value
97
+ * @param writeConditionField - the field to check
98
+ * @param params - the existing params array (will be extended in place)
99
+ * @returns the SQL AND clause
51
100
  */
52
- _exists(uid: string): Promise<boolean>;
101
+ getQueryCondition(writeCondition: any, writeConditionField: string, params: any[]): string;
53
102
  /**
54
- * @override
103
+ * Run a raw SQL query and return typed results.
104
+ * @param q - the SQL query (WHERE clause or full query)
105
+ * @param values - the query parameters
106
+ * @returns the raw pg query result
55
107
  */
56
- _get(uid: string, raiseIfNotFound?: boolean): Promise<T>;
108
+ protected sqlQuery(q: string, values?: any[]): Promise<{
109
+ rows: any[];
110
+ rowCount: number;
111
+ }>;
57
112
  /**
58
- * @override
113
+ * Deserialize a raw JSON object from the database into a model instance.
114
+ * @param data - the raw JSON object from the JSONB column
115
+ * @returns the model instance
59
116
  */
60
- getAll(list?: string[]): Promise<T[]>;
117
+ protected fromJSON(data: any): InstanceType<T>;
118
+ /** @override */
119
+ get(primaryKey: any): Promise<any>;
120
+ /** @override */
121
+ create(data: any, _save?: boolean): Promise<any>;
122
+ /** @override */
123
+ update(data: any, conditionField?: any, condition?: any): Promise<void>;
124
+ /** @override */
125
+ patch(primaryKey: any, data: any, conditionField?: any, condition?: any): Promise<void>;
126
+ /** @override */
127
+ delete(primaryKey: any, conditionField?: any, condition?: any): Promise<void>;
128
+ /** @override */
129
+ exists(primaryKey: any): Promise<boolean>;
130
+ /** @override */
131
+ removeAttribute(primaryKey: any, attribute: any, conditionField?: any, condition?: any): Promise<void>;
132
+ /** @override */
133
+ incrementAttributes(primaryKey: any, info: any, _conditionField?: any, _condition?: any): Promise<void>;
134
+ /** @override */
135
+ upsertItemToCollection(primaryKey: any, collection: any, item: any, index?: number, itemWriteConditionField?: any, itemWriteCondition?: any): Promise<void>;
136
+ /** @override */
137
+ deleteItemFromCollection(primaryKey: any, collection: any, index: number, itemWriteConditionField?: any, itemWriteCondition?: any): Promise<void>;
61
138
  /**
62
- *
63
- * @param itemWriteCondition
64
- * @param itemWriteConditionField
65
- * @param offset parameter offset
139
+ * Duplicate and translate a WebdaQL expression into SQL-friendly JSONB path expressions.
140
+ * @param expression - the WebdaQL expression
141
+ * @returns the translated expression
66
142
  */
67
- abstract getQueryCondition(itemWriteCondition: any, itemWriteConditionField: string, values: any[]): any;
68
- /**
69
- * @override
70
- */
71
- _update(object: any, uid: string, itemWriteCondition?: any, itemWriteConditionField?: string): Promise<any>;
72
- getUuid(object: T | string | ModelLink<T>): string;
143
+ duplicateExpression(expression: WebdaQL.Expression): WebdaQL.Expression;
144
+ /** @override — use SQL WHERE clause instead of in-memory scan */
145
+ query(queryStr: string): Promise<{
146
+ results: InstanceType<T>[];
147
+ continuationToken?: string;
148
+ }>;
149
+ /** @override — iterate via paginated SQL queries */
150
+ iterate(queryStr: string): AsyncGenerator<InstanceType<T>, any, any>;
73
151
  /**
74
- * @override
152
+ * Delete all rows from the table (used in tests).
75
153
  */
76
- _save(object: T): Promise<any>;
77
154
  __clean(): Promise<void>;
78
155
  }
156
+ /** Abstract base class for SQL-backed stores */
157
+ export declare abstract class SQLStore<K extends SQLStoreParameters = SQLStoreParameters> extends Store<K> {
158
+ abstract getRepository<T extends ModelClass>(model: T): Repository<T>;
159
+ }
160
+ //# sourceMappingURL=sqlstore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlstore.d.ts","sourceRoot":"","sources":["../src/sqlstore.ts"],"names":[],"mappings":"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"}