lupine.api 1.0.41

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 (137) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3 -0
  3. package/admin/admin-about.tsx +16 -0
  4. package/admin/admin-config.tsx +44 -0
  5. package/admin/admin-css.tsx +3 -0
  6. package/admin/admin-db.tsx +74 -0
  7. package/admin/admin-frame-props.tsx +9 -0
  8. package/admin/admin-frame.tsx +466 -0
  9. package/admin/admin-index.tsx +66 -0
  10. package/admin/admin-login.tsx +99 -0
  11. package/admin/admin-menu-edit.tsx +637 -0
  12. package/admin/admin-menu-list.tsx +87 -0
  13. package/admin/admin-page-edit.tsx +564 -0
  14. package/admin/admin-page-list.tsx +83 -0
  15. package/admin/admin-performance.tsx +28 -0
  16. package/admin/admin-release.tsx +320 -0
  17. package/admin/admin-resources.tsx +385 -0
  18. package/admin/admin-shell.tsx +89 -0
  19. package/admin/admin-table-data.tsx +146 -0
  20. package/admin/admin-table-list.tsx +231 -0
  21. package/admin/admin-test-animations.tsx +379 -0
  22. package/admin/admin-test-component.tsx +808 -0
  23. package/admin/admin-test-edit.tsx +319 -0
  24. package/admin/admin-test-themes.tsx +56 -0
  25. package/admin/admin-tokens.tsx +338 -0
  26. package/admin/design/admin-design.tsx +174 -0
  27. package/admin/design/block-grid.tsx +36 -0
  28. package/admin/design/block-grid1.tsx +21 -0
  29. package/admin/design/block-paragraph.tsx +19 -0
  30. package/admin/design/block-title.tsx +19 -0
  31. package/admin/design/design-block-box.tsx +140 -0
  32. package/admin/design/drag-data.tsx +24 -0
  33. package/admin/index.ts +6 -0
  34. package/admin/package.json +15 -0
  35. package/admin/tsconfig.json +127 -0
  36. package/dev/copy-folder.js +32 -0
  37. package/dev/cp-index-html.js +69 -0
  38. package/dev/file-utils.js +12 -0
  39. package/dev/index.js +19 -0
  40. package/dev/package.json +12 -0
  41. package/dev/plugin-gen-versions.js +20 -0
  42. package/dev/plugin-ifelse.js +155 -0
  43. package/dev/plugin-ifelse.test.js +37 -0
  44. package/dev/run-cmd.js +14 -0
  45. package/dev/send-request.js +12 -0
  46. package/package.json +55 -0
  47. package/src/admin-api/admin-api.ts +59 -0
  48. package/src/admin-api/admin-auth.ts +87 -0
  49. package/src/admin-api/admin-config.ts +93 -0
  50. package/src/admin-api/admin-csv.ts +81 -0
  51. package/src/admin-api/admin-db.ts +269 -0
  52. package/src/admin-api/admin-helper.ts +111 -0
  53. package/src/admin-api/admin-menu.ts +135 -0
  54. package/src/admin-api/admin-page.ts +135 -0
  55. package/src/admin-api/admin-performance.ts +128 -0
  56. package/src/admin-api/admin-release.ts +498 -0
  57. package/src/admin-api/admin-resources.ts +318 -0
  58. package/src/admin-api/admin-token-helper.ts +79 -0
  59. package/src/admin-api/admin-tokens.ts +90 -0
  60. package/src/admin-api/index.ts +2 -0
  61. package/src/api/api-cache.ts +103 -0
  62. package/src/api/api-helper.ts +44 -0
  63. package/src/api/api-module.ts +60 -0
  64. package/src/api/api-router.ts +177 -0
  65. package/src/api/api-shared-storage.ts +64 -0
  66. package/src/api/async-storage.ts +5 -0
  67. package/src/api/debug-service.ts +56 -0
  68. package/src/api/encode-html.ts +27 -0
  69. package/src/api/handle-status.ts +71 -0
  70. package/src/api/index.ts +16 -0
  71. package/src/api/mini-web-socket.ts +270 -0
  72. package/src/api/server-content-type.ts +82 -0
  73. package/src/api/server-render.ts +216 -0
  74. package/src/api/shell-service.ts +66 -0
  75. package/src/api/simple-storage.ts +80 -0
  76. package/src/api/static-server.ts +125 -0
  77. package/src/api/to-client-delivery.ts +26 -0
  78. package/src/app/app-cache.ts +55 -0
  79. package/src/app/app-loader.ts +62 -0
  80. package/src/app/app-message.ts +60 -0
  81. package/src/app/app-shared-storage.ts +317 -0
  82. package/src/app/app-start.ts +117 -0
  83. package/src/app/cleanup-exit.ts +12 -0
  84. package/src/app/host-to-path.ts +38 -0
  85. package/src/app/index.ts +11 -0
  86. package/src/app/process-dev-requests.ts +90 -0
  87. package/src/app/web-listener.ts +230 -0
  88. package/src/app/web-processor.ts +42 -0
  89. package/src/app/web-server.ts +86 -0
  90. package/src/common-js/web-env.js +104 -0
  91. package/src/index.ts +7 -0
  92. package/src/lang/api-lang-en.ts +27 -0
  93. package/src/lang/api-lang-zh-cn.ts +28 -0
  94. package/src/lang/index.ts +2 -0
  95. package/src/lang/lang-helper.ts +76 -0
  96. package/src/lang/lang-props.ts +6 -0
  97. package/src/lib/db/db-helper.ts +23 -0
  98. package/src/lib/db/db-mysql.ts +250 -0
  99. package/src/lib/db/db-sqlite.ts +101 -0
  100. package/src/lib/db/db.spec.ts +28 -0
  101. package/src/lib/db/db.ts +304 -0
  102. package/src/lib/db/index.ts +5 -0
  103. package/src/lib/index.ts +3 -0
  104. package/src/lib/logger.spec.ts +214 -0
  105. package/src/lib/logger.ts +274 -0
  106. package/src/lib/runtime-require.ts +37 -0
  107. package/src/lib/utils/cookie-util.ts +34 -0
  108. package/src/lib/utils/crypto.ts +58 -0
  109. package/src/lib/utils/date-utils.ts +317 -0
  110. package/src/lib/utils/deep-merge.ts +37 -0
  111. package/src/lib/utils/delay.ts +12 -0
  112. package/src/lib/utils/file-setting.ts +55 -0
  113. package/src/lib/utils/format-bytes.ts +11 -0
  114. package/src/lib/utils/fs-utils.ts +144 -0
  115. package/src/lib/utils/get-env.ts +27 -0
  116. package/src/lib/utils/index.ts +12 -0
  117. package/src/lib/utils/is-type.ts +48 -0
  118. package/src/lib/utils/load-env.ts +14 -0
  119. package/src/lib/utils/pad.ts +6 -0
  120. package/src/models/api-base.ts +5 -0
  121. package/src/models/api-module-props.ts +11 -0
  122. package/src/models/api-router-props.ts +26 -0
  123. package/src/models/app-cache-props.ts +33 -0
  124. package/src/models/app-data-props.ts +10 -0
  125. package/src/models/app-loader-props.ts +6 -0
  126. package/src/models/app-shared-storage-props.ts +37 -0
  127. package/src/models/app-start-props.ts +18 -0
  128. package/src/models/async-storage-props.ts +13 -0
  129. package/src/models/db-config.ts +30 -0
  130. package/src/models/host-to-path-props.ts +12 -0
  131. package/src/models/index.ts +16 -0
  132. package/src/models/json-object.ts +8 -0
  133. package/src/models/locals-props.ts +36 -0
  134. package/src/models/logger-props.ts +84 -0
  135. package/src/models/simple-storage-props.ts +14 -0
  136. package/src/models/to-client-delivery-props.ts +6 -0
  137. package/tsconfig.json +115 -0
@@ -0,0 +1,76 @@
1
+ import { deepMerge, Logger } from '../lib';
2
+ import { apiLangEn } from './api-lang-en';
3
+ import { LangProps, OneLangProps } from './lang-props';
4
+ import { apiCache } from '../api';
5
+ import { apiLangZhCn } from './api-lang-zh-cn';
6
+
7
+ /*
8
+ This module only provides server-side multi-language support.
9
+
10
+ language code: https://www.andiamo.co.uk/resources/iso-language-codes/
11
+ English: en, Chinese (PRC): zh-cn, Chinese (Hong Kong): zh-hk, Chinese (Taiwan): zh-tw, Japanese: ja, Korean: ko
12
+
13
+
14
+ Multi-language reason:
15
+ This framework may be used for Chinese or English projects, but to keep only one code, the underlying framework needs to return messages, pages, and other content when called.
16
+ Before the call, a message list needs to be set up. The underlying framework returns the message corresponding to the language based on the key.
17
+ */
18
+ export class LangHelper {
19
+ private static instance: LangHelper;
20
+ private logger = new Logger('lang-helper');
21
+ private langs: LangProps = {};
22
+
23
+ private constructor() {
24
+ this.addLang(apiLangEn);
25
+ this.addLang(apiLangZhCn);
26
+ }
27
+
28
+ public static getInstance(): LangHelper {
29
+ if (!LangHelper.instance) {
30
+ LangHelper.instance = new LangHelper();
31
+ }
32
+ return LangHelper.instance;
33
+ }
34
+
35
+ // 'My name is {name}' -> getLang('my_name_is', { name: 'John' })
36
+ public getLang(key: string, params?: { [key: string]: string | number | boolean }) {
37
+ return this.getLangWithLangName(key, apiCache.getLang(), '', params);
38
+ }
39
+
40
+ public getLangWithLangName(
41
+ key: string,
42
+ langName = '',
43
+ defaultLang = '',
44
+ params?: { [key: string]: string | number | boolean }
45
+ ) {
46
+ if (!langName) {
47
+ // if lang is not set, this should be called in a request
48
+ langName = apiCache.getLang();
49
+ }
50
+ const oneLang = this.langs[langName];
51
+ if (!oneLang || !oneLang.langs[key]) {
52
+ this.logger.warn(`Lang not found: ${langName}`);
53
+ return defaultLang || `${key} (Lang not found)`;
54
+ }
55
+ if (params) {
56
+ return oneLang.langs[key].replace(/\{(\w+)\}/g, (match, name) => {
57
+ return (params[name] || match).toString();
58
+ });
59
+ }
60
+ return oneLang.langs[key];
61
+ }
62
+
63
+ public getLangs() {
64
+ return this.langs;
65
+ }
66
+
67
+ // Sub projects can extend or override the language set
68
+ public addLang(lang: OneLangProps) {
69
+ if (!this.langs[lang.langName]) {
70
+ this.langs[lang.langName] = { langName: lang.langName, langs: {} };
71
+ }
72
+ deepMerge(this.langs[lang.langName], lang);
73
+ }
74
+ }
75
+
76
+ export const langHelper = /* @__PURE__ */ LangHelper.getInstance();
@@ -0,0 +1,6 @@
1
+ export type OneLangProps = {
2
+ langName: string;
3
+ langs: { [key: string]: string };
4
+ };
5
+
6
+ export type LangProps = { [key: string]: OneLangProps };
@@ -0,0 +1,23 @@
1
+ import { Logger } from '../logger';
2
+ import { Db } from './db';
3
+ import { DbConfig } from '../../models/db-config';
4
+ import { DbSqlite } from './db-sqlite';
5
+
6
+ const logger = new Logger('db-helper');
7
+ export class DbHelper {
8
+ static async createInstance(option: DbConfig): Promise<Db> {
9
+ if (!option || !option.type) {
10
+ throw new Error('Invalid configuration');
11
+ }
12
+
13
+ const type = option.type.toLowerCase();
14
+ switch (type) {
15
+ case 'sqlite':
16
+ const db = new DbSqlite(option);
17
+ await db.connect();
18
+ return db;
19
+ default:
20
+ throw new Error(`Unsupported database type: ${type}`);
21
+ }
22
+ }
23
+ }
@@ -0,0 +1,250 @@
1
+ /*
2
+ npm install mysql2
3
+ npm install --save-dev @types/mysql2
4
+
5
+ */
6
+ import { createPool, Pool, PoolConnection, RowDataPacket, OkPacket, ResultSetHeader } from 'mysql2/promise';
7
+ import { Logger } from '../logger';
8
+ import { Db } from './db';
9
+ import { DbConfig } from '../../models/db-config';
10
+
11
+ /**
12
+ connection = mysql.createConnection({
13
+ host : 'localhost',
14
+ user : '***',
15
+ password : '***',
16
+ port : 3306,
17
+ database: '***',
18
+ insecureAuth: true,
19
+ //debug: true,
20
+ });
21
+ connection.connect();
22
+
23
+ connection.end();
24
+
25
+ getConn: function (config) {
26
+ config.host = config.host || "localhost";
27
+ config.port = config.port || 3306;
28
+ connection = mysql.createConnection({
29
+ host: config.host,
30
+ user: config.user,
31
+ password: config.password,
32
+ port: config.port,
33
+ database: config.database,
34
+ insecureAuth: config.insecureAuth,
35
+ debug: config.debug,
36
+ });
37
+ connection.connect();
38
+
39
+ this.debug = config.debug || false;
40
+ var thisObj = this;
41
+ connection.on("error", function (err) {
42
+ if (!err.fatal) {
43
+ return;
44
+ }
45
+
46
+ if (err.code !== "PROTOCOL_CONNECTION_LOST") {
47
+ throw err;
48
+ }
49
+
50
+ //console.log('Re-connecting lost connection: ' + err.stack);
51
+ console.log("Re-connecting lost connection: " + err);
52
+ thisObj.connection = thisObj.getConn(config);
53
+ });
54
+ this.connection = connection;
55
+ return connection;
56
+ },
57
+
58
+ query: function (cb, sql) {
59
+ try {
60
+ var conn = this.connection;
61
+ sql = this.replacePrefix(sql);
62
+ console.log("query:" + sql);
63
+ conn.query(sql, cb);
64
+ } catch (err) {
65
+ console.log(err);
66
+ if (err.code == "PROTOCOL_CONNECTION_LOST") {
67
+ var conn = this.connection;
68
+ conn.query(sql, cb);
69
+ } else {
70
+ cb(err, false);
71
+ }
72
+ }
73
+ return true;
74
+ },
75
+
76
+ escape: function (val) {
77
+ return mysql.escape(val);
78
+ },
79
+
80
+ escapeId: function (id) {
81
+ return mysql.escapeId(id);
82
+ },
83
+
84
+ */
85
+
86
+
87
+ const logger = new Logger('db-mysql');
88
+
89
+ export class DbMysql extends Db {
90
+ private pool!: Pool;
91
+ private connection!: PoolConnection;
92
+
93
+ constructor(option: DbConfig) {
94
+ super(option);
95
+
96
+ this.pool = createPool({
97
+ host: option.host || 'localhost',
98
+ user: option.user,
99
+ password: option.password,
100
+ database: option.database,
101
+ port: option.port || 3306,
102
+ /* Math.max(2, Math.floor(10 / numCPUs)); */
103
+ connectionLimit: 2,
104
+ waitForConnections: true,
105
+ queueLimit: 0,
106
+ namedPlaceholders: true,
107
+ supportBigNumbers: true,
108
+ bigNumberStrings: true,
109
+ // Enable for debugging
110
+ // debug: logger.isDebug()
111
+ });
112
+
113
+ // Test the connection
114
+ if (logger.isDebug()) {
115
+ this.testConnection();
116
+ }
117
+ }
118
+
119
+ async close() {
120
+ if (this.pool) {
121
+ await this.pool.end();
122
+ }
123
+ }
124
+
125
+ async connect() {
126
+ try {
127
+ this.connection = await this.pool.getConnection();
128
+ return Promise.resolve();
129
+ } catch (error: any) {
130
+ logger.error('Failed to connect to MySQL:', error);
131
+ return Promise.reject(error);
132
+ }
133
+ }
134
+
135
+ public async nativeQuery(sql: string, params?: any, isSelect: boolean = true): Promise<any> {
136
+ let connection: PoolConnection | null = null;
137
+
138
+ try {
139
+ connection = await this.pool.getConnection();
140
+
141
+ if (logger.isDebug()) {
142
+ logger.debug('Executing query:', { sql, params });
143
+ }
144
+
145
+ let result: any;
146
+
147
+ if (isSelect) {
148
+ const [rows] = await connection.execute<RowDataPacket[]>(sql, params);
149
+ result = rows;
150
+ } else {
151
+ const [resultSet] = await connection.execute<ResultSetHeader>(sql, params);
152
+ // For INSERT, UPDATE, DELETE operations, return the result set
153
+ result = [resultSet];
154
+ }
155
+
156
+ if (logger.isDebug()) {
157
+ logger.debug('Query result:', { sql, rowCount: Array.isArray(result) ? result.length : 1 });
158
+ }
159
+
160
+ return result;
161
+ } catch (error) {
162
+ logger.error('Error executing query:', { sql, params, error });
163
+ throw error;
164
+ } finally {
165
+ if (connection) {
166
+ connection.release();
167
+ }
168
+ }
169
+ }
170
+
171
+ public async truncateTable(tableName: string): Promise<any> {
172
+ // MySQL uses TRUNCATE TABLE which is more efficient than DELETE FROM
173
+ return this.execute(`TRUNCATE TABLE ${tableName}`);
174
+ }
175
+
176
+ public async getTableCount(tableName: string): Promise<number> {
177
+ const result = await this.select(`SELECT COUNT(*) as c FROM ${tableName}`);
178
+ return result[0].c;
179
+ }
180
+
181
+ public async getAllTables(addCount: boolean = false): Promise<any[]> {
182
+ const query = `
183
+ SELECT
184
+ TABLE_NAME as name,
185
+ TABLE_SCHEMA as schema_name,
186
+ TABLE_TYPE as type,
187
+ ENGINE as engine,
188
+ TABLE_ROWS as row_count,
189
+ DATA_LENGTH as data_length,
190
+ INDEX_LENGTH as index_length,
191
+ CREATE_TIME as create_time,
192
+ UPDATE_TIME as update_time
193
+ FROM
194
+ information_schema.TABLES
195
+ WHERE
196
+ TABLE_SCHEMA = DATABASE()
197
+ `;
198
+
199
+ const result = await this.select(query);
200
+
201
+ if (result && addCount) {
202
+ for (const table of result) {
203
+ try {
204
+ table.count = await this.getTableCount(table.name);
205
+ } catch (error: any) {
206
+ logger.error(`Error getting count for table ${table.name}:`, error);
207
+ table.count = -1; // Indicate error
208
+ }
209
+ }
210
+ }
211
+
212
+ return result || [];
213
+ }
214
+
215
+ public async getTableInfo(table: string): Promise<any> {
216
+ const query = `
217
+ SELECT
218
+ COLUMN_NAME as name,
219
+ DATA_TYPE as type,
220
+ IS_NULLABLE as is_nullable,
221
+ COLUMN_DEFAULT as default_value,
222
+ COLUMN_KEY as key,
223
+ EXTRA as extra,
224
+ CHARACTER_MAXIMUM_LENGTH as max_length,
225
+ NUMERIC_PRECISION as numeric_precision,
226
+ NUMERIC_SCALE as numeric_scale,
227
+ COLUMN_COMMENT as comment
228
+ FROM
229
+ information_schema.COLUMNS
230
+ WHERE
231
+ TABLE_SCHEMA = DATABASE()
232
+ AND TABLE_NAME = ?
233
+ ORDER BY
234
+ ORDINAL_POSITION
235
+ `;
236
+
237
+ return await this.select(query, [table]);
238
+ }
239
+
240
+ public async testConnection() {
241
+ try {
242
+ const [result] = await this.pool.query('SELECT 1 as test');
243
+ logger.debug('MySQL connection test successful:', result);
244
+ return true;
245
+ } catch (error: any) {
246
+ logger.error('MySQL connection test failed:', error);
247
+ return false;
248
+ }
249
+ }
250
+ }
@@ -0,0 +1,101 @@
1
+ import Database from 'better-sqlite3';
2
+ import { Logger } from '../logger';
3
+ import { Db } from './db';
4
+ import { DbConfig } from '../../models/db-config';
5
+
6
+ const logger = new Logger('db-sqlite');
7
+ export class DbSqlite extends Db {
8
+ db!: Database.Database;
9
+
10
+ constructor(option: DbConfig) {
11
+ super(option);
12
+
13
+ this.db = new Database(option.filename!, {
14
+ nativeBinding: 'node_modules/better-sqlite3/build/Release/better_sqlite3.node',
15
+ });
16
+ this.db.pragma('journal_mode = WAL');
17
+
18
+ if (logger.isDebug()) {
19
+ this.testConnection();
20
+ }
21
+ }
22
+
23
+ close() {
24
+ this.db.close();
25
+ }
26
+
27
+ connect() {
28
+ return Promise.resolve();
29
+ }
30
+
31
+ // INSERT...RETURNING is also supported in MariaDB from 10.5.0
32
+ public nativeQuery(sql: string, params?: any, isSelect?: boolean): Promise<any> {
33
+ return new Promise((resolve, reject) => {
34
+ try {
35
+ let rows: any;
36
+ if (isSelect) {
37
+ rows = params ? this.db.prepare(sql).all(params) : this.db.prepare(sql).all();
38
+ } else {
39
+ const preSql = sql.trim().substring(0, 6).toLowerCase();
40
+ if (preSql.startsWith('insert') || preSql.startsWith('update') || preSql.startsWith('delete')) {
41
+ sql = sql + ' returning *';
42
+ }
43
+ rows = params ? this.db.prepare(sql).run(params) : this.db.prepare(sql).run();
44
+ if (rows && typeof rows.length === 'undefined') {
45
+ // sqlite3 returns id as a record
46
+ rows = [
47
+ {
48
+ ...rows,
49
+ id: rows.changes > 0 ? rows.lastInsertRowid : undefined,
50
+ },
51
+ ];
52
+ }
53
+ }
54
+
55
+ if (logger.isDebug()) {
56
+ console.log('query:', sql, ', params:', params, ', result:', rows && rows.length);
57
+ }
58
+ resolve(rows);
59
+ } catch (err) {
60
+ console.error('query:', sql, ', params:', params, ', error:', err);
61
+ reject(err);
62
+ }
63
+ });
64
+ }
65
+
66
+ public async truncateTable(tableName: string): Promise<any> {
67
+ // sqlite doesn't have DROP command
68
+ return this.execute(`DELETE FROM ${tableName}`);
69
+ }
70
+
71
+ // public async createTable(table: string, fields: string[]) {
72
+ // // table = this.replacePrefix(table);
73
+ // const query = 'CREATE TABLE ' + table + ' (' + fields.join(',') + ')';
74
+ // return await this.query(query);
75
+ // }
76
+
77
+ public async getTableCount(tableName: string) {
78
+ const result = await this.select(`SELECT COUNT(*) as c FROM ${tableName}`);
79
+ return result[0].c;
80
+ }
81
+
82
+ public async getAllTables(addCount = false) {
83
+ const query = `SELECT * FROM sqlite_master WHERE type ='table';`;
84
+ const result = await this.select(query);
85
+ if (result) {
86
+ if (addCount) {
87
+ for (let i in result) {
88
+ result[i].count = await this.getTableCount(result[i].tbl_name);
89
+ }
90
+ }
91
+ return result;
92
+ }
93
+ return false;
94
+ }
95
+
96
+ public async getTableInfo(table: string): Promise<any> {
97
+ const query = `PRAGMA table_info(${table});`;
98
+ const result = await this.execute(query);
99
+ return result;
100
+ }
101
+ }
@@ -0,0 +1,28 @@
1
+ import { Logger } from '../logger';
2
+
3
+ describe('Test logger', () => {
4
+ var db: any;
5
+ beforeEach(() => {
6
+ const sqlite3 = require('sqlite3').verbose();
7
+ db = new sqlite3.Database(':memory:');
8
+ });
9
+
10
+ it('test init without recreating a file', (done) => {
11
+ db.serialize(() => {
12
+ db.run('CREATE TABLE lorem (info TEXT)');
13
+
14
+ const stmt = db.prepare('INSERT INTO lorem VALUES (?)');
15
+ for (let i = 0; i < 10; i++) {
16
+ stmt.run('Ipsum ' + i);
17
+ }
18
+ stmt.finalize();
19
+
20
+ db.each('SELECT rowid AS id, info FROM lorem', (err: any, row: any) => {
21
+ console.log(row.id + ': ' + row.info);
22
+ });
23
+ });
24
+
25
+ db.wait(done);
26
+ db.close();
27
+ });
28
+ });