@idevconn/create-icore 0.10.1 → 0.11.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.
Files changed (27) hide show
  1. package/dist/cli.js +140 -9
  2. package/dist/index.cjs +140 -9
  3. package/dist/index.d.cts +1 -1
  4. package/dist/index.d.ts +1 -1
  5. package/dist/index.js +140 -9
  6. package/package.json +1 -1
  7. package/templates/apps/templates/client-antd/src/components/layout/LayoutHeader.tsx +2 -2
  8. package/templates/apps/templates/client-mui/src/components/layout/LayoutHeader.tsx +1 -1
  9. package/templates/apps/templates/client-shadcn/src/components/layout/LayoutHeader.tsx +2 -2
  10. package/templates/apps/templates/client-shadcn/src/components/layout/LayoutSider.tsx +7 -3
  11. package/templates/apps/templates/client-shadcn/src/routes/index.tsx +2 -2
  12. package/templates/apps/templates/client-shadcn/vite.config.mts +18 -1
  13. package/templates/docker-compose.yml +14 -0
  14. package/templates/libs/db-strategies/postgres/eslint.config.mjs +30 -0
  15. package/templates/libs/db-strategies/postgres/package.json +19 -0
  16. package/templates/libs/db-strategies/postgres/project.json +19 -0
  17. package/templates/libs/db-strategies/postgres/src/index.ts +3 -0
  18. package/templates/libs/db-strategies/postgres/src/lib/__tests__/postgres-db.contract.unit.test.ts +4 -0
  19. package/templates/libs/db-strategies/postgres/src/lib/__tests__/postgres-db.module.unit.test.ts +37 -0
  20. package/templates/libs/db-strategies/postgres/src/lib/postgres-db.module.ts +33 -0
  21. package/templates/libs/db-strategies/postgres/src/lib/postgres-db.strategy.ts +139 -0
  22. package/templates/libs/db-strategies/postgres/src/lib/testing/mock-postgres.ts +94 -0
  23. package/templates/libs/db-strategies/postgres/tsconfig.json +19 -0
  24. package/templates/libs/db-strategies/postgres/tsconfig.lib.json +24 -0
  25. package/templates/libs/db-strategies/postgres/tsconfig.spec.json +22 -0
  26. package/templates/libs/db-strategies/postgres/vitest.config.mts +22 -0
  27. package/templates/tsconfig.base.json +1 -0
@@ -0,0 +1,139 @@
1
+ import postgres from 'postgres';
2
+ import type { DBDocument, DBStrategy, QueryOptions } from '@icore/shared';
3
+
4
+ export class PostgresDBStrategy implements DBStrategy {
5
+ private readonly sql: postgres.Sql;
6
+ private readonly initializing = new Map<string, Promise<void>>();
7
+ private readonly initialized = new Set<string>();
8
+
9
+ constructor(url: string) {
10
+ this.sql = postgres(url);
11
+ }
12
+
13
+ private async ensureTable(collection: string): Promise<void> {
14
+ if (this.initialized.has(collection)) return;
15
+ let inflight = this.initializing.get(collection);
16
+ if (inflight) return inflight;
17
+ inflight = this._createTable(collection).catch((err) => {
18
+ this.initializing.delete(collection);
19
+ throw err;
20
+ });
21
+ this.initializing.set(collection, inflight);
22
+ return inflight;
23
+ }
24
+
25
+ private async _createTable(collection: string): Promise<void> {
26
+ await this.sql`
27
+ CREATE TABLE IF NOT EXISTS ${this.sql(collection)} (
28
+ id TEXT PRIMARY KEY,
29
+ data JSONB NOT NULL
30
+ )
31
+ `;
32
+ await this.sql`
33
+ CREATE INDEX IF NOT EXISTS ${this.sql(collection + '_data_gin')}
34
+ ON ${this.sql(collection)} USING GIN (data)
35
+ `;
36
+ this.initialized.add(collection);
37
+ this.initializing.delete(collection);
38
+ }
39
+
40
+ async get<T>(collection: string, id: string): Promise<DBDocument<T> | null> {
41
+ await this.ensureTable(collection);
42
+ const rows = await this.sql<{ id: string; data: T }[]>`
43
+ SELECT id, data FROM ${this.sql(collection)} WHERE id = ${id}
44
+ `;
45
+ const row = rows[0];
46
+ if (!row) return null;
47
+ return { id: row.id, data: row.data };
48
+ }
49
+
50
+ async set<T>(collection: string, id: string, data: T): Promise<void> {
51
+ await this.ensureTable(collection);
52
+ await this.sql`
53
+ INSERT INTO ${this.sql(collection)} (id, data)
54
+ VALUES (${id}, ${this.sql.json(data as unknown as postgres.JSONValue)})
55
+ ON CONFLICT (id) DO UPDATE SET data = EXCLUDED.data
56
+ `;
57
+ }
58
+
59
+ async update<T>(collection: string, id: string, patch: Partial<T>): Promise<void> {
60
+ await this.ensureTable(collection);
61
+ const rows = await this.sql`
62
+ UPDATE ${this.sql(collection)}
63
+ SET data = data || ${this.sql.json(patch as postgres.JSONValue)}
64
+ WHERE id = ${id}
65
+ RETURNING id
66
+ `;
67
+ if (rows.count === 0) throw new Error(`not_found: ${collection}/${id}`);
68
+ }
69
+
70
+ async delete(collection: string, id: string): Promise<void> {
71
+ await this.ensureTable(collection);
72
+ const rows = await this.sql`
73
+ DELETE FROM ${this.sql(collection)} WHERE id = ${id} RETURNING id
74
+ `;
75
+ if (rows.count === 0) throw new Error(`not_found: ${collection}/${id}`);
76
+ }
77
+
78
+ async list<T>(collection: string, opts?: QueryOptions): Promise<DBDocument<T>[]> {
79
+ await this.ensureTable(collection);
80
+
81
+ const conditions: postgres.Fragment[] = [];
82
+
83
+ if (opts?.where) {
84
+ for (const c of opts.where) {
85
+ const field = c.field;
86
+ if (c.op === '==' || c.op === '!=') {
87
+ const expr = field === 'id' ? this.sql`id` : this.sql`data->>${field}`;
88
+ conditions.push(
89
+ c.op === '=='
90
+ ? this.sql`${expr} = ${String(c.value)}`
91
+ : this.sql`${expr} != ${String(c.value)}`,
92
+ );
93
+ } else if (c.op === 'in') {
94
+ const vals = (c.value as unknown[]).map(String);
95
+ const expr = field === 'id' ? this.sql`id` : this.sql`data->>${field}`;
96
+ conditions.push(this.sql`${expr} = ANY(${vals})`);
97
+ } else {
98
+ // numeric ops: <, <=, >, >=
99
+ const expr =
100
+ field === 'id' ? this.sql`id::numeric` : this.sql`(data->>${field})::numeric`;
101
+ const val = Number(c.value);
102
+ if (c.op === '<') conditions.push(this.sql`${expr} < ${val}`);
103
+ else if (c.op === '<=') conditions.push(this.sql`${expr} <= ${val}`);
104
+ else if (c.op === '>') conditions.push(this.sql`${expr} > ${val}`);
105
+ else if (c.op === '>=') conditions.push(this.sql`${expr} >= ${val}`);
106
+ }
107
+ }
108
+ }
109
+
110
+ const whereClause =
111
+ conditions.length > 0
112
+ ? this.sql`WHERE ${conditions.reduce((acc, c) => this.sql`${acc} AND ${c}`)}`
113
+ : this.sql``;
114
+
115
+ const orderBy = opts?.orderBy;
116
+ const orderClause = orderBy
117
+ ? (() => {
118
+ const col = orderBy.field === 'id' ? this.sql`id` : this.sql`data->>${orderBy.field}`;
119
+ const dir = orderBy.direction === 'desc' ? this.sql`DESC` : this.sql`ASC`;
120
+ return this.sql`ORDER BY ${col} ${dir}`;
121
+ })()
122
+ : this.sql``;
123
+
124
+ const limitClause = opts?.limit != null ? this.sql`LIMIT ${opts.limit}` : this.sql``;
125
+
126
+ const rows = await this.sql<{ id: string; data: T }[]>`
127
+ SELECT id, data FROM ${this.sql(collection)}
128
+ ${whereClause}
129
+ ${orderClause}
130
+ ${limitClause}
131
+ `;
132
+
133
+ return rows.map((row) => ({ id: row.id, data: row.data }));
134
+ }
135
+
136
+ async end(): Promise<void> {
137
+ await this.sql.end();
138
+ }
139
+ }
@@ -0,0 +1,94 @@
1
+ import type { DBDocument, DBStrategy, QueryOptions } from '@icore/shared';
2
+
3
+ function applyOp(val: unknown, op: string, target: unknown): boolean {
4
+ switch (op) {
5
+ case '==':
6
+ return String(val) === String(target);
7
+ case '!=':
8
+ return String(val) !== String(target);
9
+ case '<':
10
+ return Number(val) < Number(target);
11
+ case '<=':
12
+ return Number(val) <= Number(target);
13
+ case '>':
14
+ return Number(val) > Number(target);
15
+ case '>=':
16
+ return Number(val) >= Number(target);
17
+ case 'in':
18
+ return Array.isArray(target) && (target as unknown[]).map(String).includes(String(val));
19
+ default:
20
+ return false;
21
+ }
22
+ }
23
+
24
+ function resolveField(data: Record<string, unknown>, id: string, field: string): unknown {
25
+ if (field === 'id') return id;
26
+ return data[field];
27
+ }
28
+
29
+ export function createMockPostgresDB(): DBStrategy {
30
+ const store = new Map<string, Map<string, Record<string, unknown>>>();
31
+
32
+ function getTable(collection: string): Map<string, Record<string, unknown>> {
33
+ let t = store.get(collection);
34
+ if (!t) {
35
+ t = new Map();
36
+ store.set(collection, t);
37
+ }
38
+ return t;
39
+ }
40
+
41
+ return {
42
+ async get<T>(collection: string, id: string): Promise<DBDocument<T> | null> {
43
+ const row = getTable(collection).get(id);
44
+ if (!row) return null;
45
+ return { id, data: row as T };
46
+ },
47
+
48
+ async set<T>(collection: string, id: string, data: T): Promise<void> {
49
+ getTable(collection).set(id, { ...(data as Record<string, unknown>) });
50
+ },
51
+
52
+ async update<T>(collection: string, id: string, patch: Partial<T>): Promise<void> {
53
+ const table = getTable(collection);
54
+ const existing = table.get(id);
55
+ if (!existing) throw new Error(`not_found: ${collection}/${id}`);
56
+ table.set(id, { ...existing, ...(patch as Record<string, unknown>) });
57
+ },
58
+
59
+ async delete(collection: string, id: string): Promise<void> {
60
+ const table = getTable(collection);
61
+ if (!table.has(id)) throw new Error(`not_found: ${collection}/${id}`);
62
+ table.delete(id);
63
+ },
64
+
65
+ async list<T>(collection: string, opts?: QueryOptions): Promise<DBDocument<T>[]> {
66
+ let entries = [...getTable(collection).entries()].map(([id, data]) => ({ id, data }));
67
+
68
+ if (opts?.where) {
69
+ for (const c of opts.where) {
70
+ entries = entries.filter((e) => {
71
+ const val = resolveField(e.data, e.id, c.field);
72
+ return applyOp(val, c.op, c.value);
73
+ });
74
+ }
75
+ }
76
+
77
+ if (opts?.orderBy) {
78
+ const { field, direction } = opts.orderBy;
79
+ entries.sort((a, b) => {
80
+ const av = resolveField(a.data, a.id, field);
81
+ const bv = resolveField(b.data, b.id, field);
82
+ const cmp = String(av) < String(bv) ? -1 : String(av) > String(bv) ? 1 : 0;
83
+ return direction === 'desc' ? -cmp : cmp;
84
+ });
85
+ }
86
+
87
+ if (opts?.limit != null) {
88
+ entries = entries.slice(0, opts.limit);
89
+ }
90
+
91
+ return entries.map((e) => ({ id: e.id, data: e.data as T }));
92
+ },
93
+ };
94
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "extends": "../../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "module": "node16",
5
+ "moduleResolution": "node16",
6
+ "experimentalDecorators": true,
7
+ "emitDecoratorMetadata": true,
8
+ "forceConsistentCasingInFileNames": true,
9
+ "strict": true,
10
+ "importHelpers": true,
11
+ "noImplicitOverride": true,
12
+ "noImplicitReturns": true,
13
+ "noFallthroughCasesInSwitch": true,
14
+ "noPropertyAccessFromIndexSignature": true
15
+ },
16
+ "files": [],
17
+ "include": [],
18
+ "references": [{ "path": "./tsconfig.lib.json" }, { "path": "./tsconfig.spec.json" }]
19
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "../../../dist/out-tsc",
5
+ "rootDir": "../../..",
6
+ "declaration": true,
7
+ "types": ["node"]
8
+ },
9
+ "include": ["src/**/*.ts"],
10
+ "exclude": [
11
+ "vite.config.ts",
12
+ "vite.config.mts",
13
+ "vitest.config.ts",
14
+ "vitest.config.mts",
15
+ "src/**/*.test.ts",
16
+ "src/**/*.spec.ts",
17
+ "src/**/*.test.tsx",
18
+ "src/**/*.spec.tsx",
19
+ "src/**/*.test.js",
20
+ "src/**/*.spec.js",
21
+ "src/**/*.test.jsx",
22
+ "src/**/*.spec.jsx"
23
+ ]
24
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "../../../dist/out-tsc",
5
+ "types": ["vitest/globals", "vitest/importMeta", "vite/client", "node", "vitest"]
6
+ },
7
+ "include": [
8
+ "vite.config.ts",
9
+ "vite.config.mts",
10
+ "vitest.config.ts",
11
+ "vitest.config.mts",
12
+ "src/**/*.test.ts",
13
+ "src/**/*.spec.ts",
14
+ "src/**/*.test.tsx",
15
+ "src/**/*.spec.tsx",
16
+ "src/**/*.test.js",
17
+ "src/**/*.spec.js",
18
+ "src/**/*.test.jsx",
19
+ "src/**/*.spec.jsx",
20
+ "src/**/*.d.ts"
21
+ ]
22
+ }
@@ -0,0 +1,22 @@
1
+ import { defineConfig } from 'vitest/config';
2
+ import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
3
+ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
4
+
5
+ export default defineConfig(() => ({
6
+ root: __dirname,
7
+ cacheDir: '../../../node_modules/.vite/libs/db-strategies/postgres',
8
+ plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
9
+ test: {
10
+ name: 'db-postgres',
11
+ watch: false,
12
+ globals: true,
13
+ environment: 'node',
14
+ include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
15
+ passWithNoTests: true,
16
+ reporters: ['default'],
17
+ coverage: {
18
+ reportsDirectory: '../../../coverage/libs/db-strategies/postgres',
19
+ provider: 'v8' as const,
20
+ },
21
+ },
22
+ }));
@@ -27,6 +27,7 @@
27
27
  "@idevconn/create-icore": ["./tools/create-icore/src/index.ts"],
28
28
  "@icore/db-supabase": ["./libs/db-strategies/supabase/src/index.ts"],
29
29
  "@icore/db-firestore": ["./libs/db-strategies/firestore/src/index.ts"],
30
+ "@icore/db-postgres": ["./libs/db-strategies/postgres/src/index.ts"],
30
31
  "@icore/payment-client": ["./libs/payment-client/src/index.ts"],
31
32
  "@icore/notes-client": ["./libs/notes-client/src/index.ts"],
32
33
  "@icore/jobs-client": ["./libs/jobs-client/src/index.ts"],