@feasibleone/blong-gogo 1.26.0 → 1.28.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.
- package/CHANGELOG.md +32 -0
- package/bin/blong-watch.ts +6 -0
- package/bin/blong.ts +52 -3
- package/package.json +9 -6
- package/src/ApiGateway.test.ts +190 -0
- package/src/ApiGateway.ts +167 -0
- package/src/BrowserLog.ts +5 -1
- package/src/Gateway.ts +17 -1
- package/src/Log.ts +33 -12
- package/src/Registry.ts +87 -7
- package/src/Remote.ts +12 -0
- package/src/Watch.ts +55 -26
- package/src/adapter/browser/http.ts +4 -2
- package/src/adapter/schema/knex/binary.ts +8 -5
- package/src/adapter/schema/knex/database.test.ts +87 -0
- package/src/adapter/schema/knex/database.ts +67 -0
- package/src/adapter/schema/knex/json.test.ts +243 -0
- package/src/adapter/schema/knex/json.ts +213 -0
- package/src/adapter/schema/knex/schemaMysql.ts +0 -26
- package/src/adapter/schema/knex/schemaTable.ts +4 -138
- package/src/adapter/schema/knex/types.ts +46 -2
- package/src/adapter/schema/knex/utils.ts +3 -1
- package/src/adapter/server/knex-deadlock.test.ts +114 -0
- package/src/adapter/server/knex.ts +425 -102
- package/src/adapter/server/redis.ts +236 -0
- package/src/adapter/server.ts +2 -0
- package/src/chain.ts +12 -1
- package/src/cliHelp.test.ts +31 -0
- package/src/cliHelp.ts +45 -0
- package/src/codec/adapter/mle/ready.ts +22 -12
- package/src/favicon.ico +0 -0
- package/src/graceful-shutdown.spawn.test.ts +68 -0
- package/src/graceful-shutdown.test.ts +109 -0
- package/src/jose.ts +7 -293
- package/src/jwt.ts +2 -1
- package/src/kopi.ts +77 -12
- package/src/lib.ts +40 -4
- package/src/load.ts +108 -65
- package/src/log.progress.test.ts +35 -0
- package/src/mle.ts +4 -4
- package/src/pino-cacache.mjs +7 -0
- package/src/pino-cacache.ts +13 -2
- package/src/pino-pretty.mjs +8 -0
- package/src/public.ts +9 -0
- package/src/registry.validation.override.test.ts +203 -0
- package/src/runServer.ts +114 -2
- package/src/static.ts +35 -0
- package/src/template-files.ts +52 -0
- package/template/browser/orchestrator/subject/init.ts +15 -0
- package/template/browser/test/test/test$Object.flow.ts +100 -0
- package/template/browser-test.ts +87 -0
- package/template/browser.ts +29 -0
- package/template/error/error.ts +11 -0
- package/template/eslint.config.mjs +2 -0
- package/template/index.browser.ts +36 -0
- package/template/index.html.ts +7 -0
- package/template/index.test.ts +32 -0
- package/template/index.ts +52 -0
- package/template/meta/db/db.ts +21 -0
- package/template/meta/dbTest/$subject$ObjectMerge.yaml +8 -0
- package/template/meta/dbTest/accessAuthorizationMerge.yaml +21 -0
- package/template/meta/model/$subject$ObjectModel.ts +134 -0
- package/template/meta/type/schema.ts +55 -0
- package/template/orchestrator/subject/init.ts +16 -0
- package/template/playwright.config.ts +7 -0
- package/template/server/test/test/test$Object.ts +83 -0
- package/template/server.ts +12 -0
- package/template/test/$subject.play.ts +64 -0
- package/template/tsconfig.json +28 -0
- package/template/vite.config.ts +5 -0
|
@@ -41,8 +41,27 @@ export interface ITableConstraints {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
export interface ISchemaTable {
|
|
44
|
-
|
|
44
|
+
/**
|
|
45
|
+
* The TypeBox `TObject` for the table. Optional when the definition is
|
|
46
|
+
* available from the realm's `schema` (resolved via `objectSchema[subject][object]`),
|
|
47
|
+
* in which case the spec only overrides `order` / `dropdown`.
|
|
48
|
+
*/
|
|
49
|
+
definition?: TObject;
|
|
45
50
|
order?: number;
|
|
51
|
+
/**
|
|
52
|
+
* Optional per-table dropdown binding override used by the auto-bound
|
|
53
|
+
* `{subject}.dropdown.list` handler (see the knex adapter `exec()`).
|
|
54
|
+
*/
|
|
55
|
+
dropdown?: {
|
|
56
|
+
/** `core_type.typeAlias` to resolve entries from (defaults to `${subject}.${object}`). */
|
|
57
|
+
typeAlias?: string;
|
|
58
|
+
/** Entity table to join on `${joinColumn} = core_resource.resourceId` to restrict to real rows. */
|
|
59
|
+
joinTable?: string;
|
|
60
|
+
/** Join column on `joinTable` (defaults to `${object}Id`). */
|
|
61
|
+
joinColumn?: string;
|
|
62
|
+
/** Column to use as the label (defaults to `resourceName`). */
|
|
63
|
+
labelColumn?: string;
|
|
64
|
+
};
|
|
46
65
|
}
|
|
47
66
|
|
|
48
67
|
export interface IColumnSchema {
|
|
@@ -53,8 +72,32 @@ export interface IColumnSchema {
|
|
|
53
72
|
readonly?: boolean;
|
|
54
73
|
}
|
|
55
74
|
|
|
75
|
+
/** Knex connection settings accepted by a DB adapter. */
|
|
76
|
+
export interface IKnexConnection {
|
|
77
|
+
host?: string;
|
|
78
|
+
port?: number;
|
|
79
|
+
user?: string;
|
|
80
|
+
password?: string;
|
|
81
|
+
database?: string;
|
|
82
|
+
[key: string]: unknown;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** The `knex` config block of a DB adapter (adapter.knex / the shared `srv.db`). */
|
|
86
|
+
export interface IKnexConfig {
|
|
87
|
+
client?: string;
|
|
88
|
+
connection?: IKnexConnection;
|
|
89
|
+
/**
|
|
90
|
+
* When `true`, the adapter tries to `CREATE DATABASE` if it does not exist
|
|
91
|
+
* before connecting. Default on in the `dev` intent via the shared `srv.db`
|
|
92
|
+
* adapter (`core/blong-server/adapter/db.ts`); opt out with `false`. Off in
|
|
93
|
+
* `default` / `ci` / `prod`, where the database is provisioned externally.
|
|
94
|
+
*/
|
|
95
|
+
createDatabase?: boolean;
|
|
96
|
+
[key: string]: unknown;
|
|
97
|
+
}
|
|
98
|
+
|
|
56
99
|
export interface IConfig {
|
|
57
|
-
knex:
|
|
100
|
+
knex: IKnexConfig;
|
|
58
101
|
context: {
|
|
59
102
|
queryBuilder?: Knex;
|
|
60
103
|
};
|
|
@@ -89,6 +132,7 @@ export interface IConfig {
|
|
|
89
132
|
* By convention use a sub-folder named `schema`.
|
|
90
133
|
*/
|
|
91
134
|
procedurePaths?: string[];
|
|
135
|
+
accessPathRefresh?: boolean; // When `true`, calls `access_pathRefresh()` after schema sync/seed (default: `false`).
|
|
92
136
|
};
|
|
93
137
|
/**
|
|
94
138
|
* When `true`, mocks some handlers
|
|
@@ -147,7 +147,9 @@ export function snakeToCamel(str: string): string {
|
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
export function capitalize(str: string): string {
|
|
150
|
-
|
|
150
|
+
// `$`-aware: a leading `$` placeholder (e.g. `$object`) must not prevent
|
|
151
|
+
// the first letter being capitalised — `$object` → `$Object`.
|
|
152
|
+
return str.replace(/^(\$*)([a-z])/, (_m, pre: string, c: string) => pre + c.toUpperCase());
|
|
151
153
|
}
|
|
152
154
|
|
|
153
155
|
/** Normalise a handler name for registry look-up: remove dots, lowercase. */
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the knex adapter's MySQL deadlock logging (`logKnexDeadlock`),
|
|
3
|
+
* including the `wrapKnex`/`onDeadlock` wiring used by the adapter's `start()`.
|
|
4
|
+
*
|
|
5
|
+
* Deadlock *detection* (`isDeadlock`) and the `wrapJsonBuilder` `onDeadlock`
|
|
6
|
+
* hook itself are covered in `adapter/schema/knex/json.test.ts`. These tests
|
|
7
|
+
* cover the logging that turns a detected deadlock into a 'knex deadlock' log
|
|
8
|
+
* entry (and the debug/logLevel gating), plus the end-to-end wiring a real
|
|
9
|
+
* deadlock rejection travels through.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {test} from 'tap';
|
|
13
|
+
|
|
14
|
+
import {wrapKnex} from '../schema/knex/json.ts';
|
|
15
|
+
import {logKnexDeadlock} from './knex.ts';
|
|
16
|
+
|
|
17
|
+
function makeLog(): {log: {error: (...a: unknown[]) => void}; calls: Array<{args: unknown[]}>} {
|
|
18
|
+
const calls: Array<{args: unknown[]}> = [];
|
|
19
|
+
return {
|
|
20
|
+
log: {
|
|
21
|
+
error: (...args: unknown[]) => calls.push({args}),
|
|
22
|
+
},
|
|
23
|
+
calls,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Shaped like a real mysql2 deadlock error: `message` feeds the logged `err`,
|
|
28
|
+
// `sql`/`sqlMessage` are the actual detail the adapter surfaces.
|
|
29
|
+
const DEADLOCK = {
|
|
30
|
+
errno: 1213,
|
|
31
|
+
code: 'ER_LOCK_DEADLOCK',
|
|
32
|
+
sql: 'UPDATE `deadlock_demo` set `deadlockValue` = 3 where `deadlockId` = 2',
|
|
33
|
+
sqlMessage: 'Deadlock found when trying to get lock; try restarting transaction',
|
|
34
|
+
message: 'Deadlock found when trying to get lock; try restarting transaction',
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const EXPECTED_ENTRY = {
|
|
38
|
+
err: DEADLOCK.message,
|
|
39
|
+
code: 'ER_LOCK_DEADLOCK',
|
|
40
|
+
errno: 1213,
|
|
41
|
+
sql: DEADLOCK.sql,
|
|
42
|
+
sqlMessage: DEADLOCK.sqlMessage,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
test('logKnexDeadlock logs full deadlock details when config.debug is set', t => {
|
|
46
|
+
const {log, calls} = makeLog();
|
|
47
|
+
logKnexDeadlock({debug: true}, log, DEADLOCK);
|
|
48
|
+
|
|
49
|
+
t.equal(calls.length, 1, 'logged exactly once');
|
|
50
|
+
t.same(calls[0].args[0], EXPECTED_ENTRY, 'entry carries err, code, errno, sql, sqlMessage');
|
|
51
|
+
t.equal(calls[0].args[1], 'knex deadlock', 'message is "knex deadlock"');
|
|
52
|
+
t.end();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('logKnexDeadlock logs when logLevel is debug', t => {
|
|
56
|
+
const {log, calls} = makeLog();
|
|
57
|
+
logKnexDeadlock({logLevel: 'debug'}, log, DEADLOCK);
|
|
58
|
+
t.equal(calls.length, 1, 'logged once');
|
|
59
|
+
t.end();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('logKnexDeadlock stays silent unless debug or logLevel debug', t => {
|
|
63
|
+
const {log, calls} = makeLog();
|
|
64
|
+
logKnexDeadlock({logLevel: 'info'}, log, DEADLOCK);
|
|
65
|
+
logKnexDeadlock({}, log, DEADLOCK);
|
|
66
|
+
t.same(calls, [], 'no log entries when gating is off');
|
|
67
|
+
t.end();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('logKnexDeadlock maps a plain Error to its message', t => {
|
|
71
|
+
const {log, calls} = makeLog();
|
|
72
|
+
logKnexDeadlock({debug: true}, log, new Error('boom'));
|
|
73
|
+
t.equal(calls.length, 1, 'logged once');
|
|
74
|
+
t.equal((calls[0].args[0] as {err?: unknown}).err, 'boom', 'err is the error message');
|
|
75
|
+
t.end();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('logKnexDeadlock tolerates a missing log method', t => {
|
|
79
|
+
t.doesNotThrow(() => logKnexDeadlock({debug: true}, undefined, DEADLOCK), 'no log at all');
|
|
80
|
+
t.doesNotThrow(() => logKnexDeadlock({debug: true}, {}, DEADLOCK), 'log without error method');
|
|
81
|
+
t.end();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('wrapKnex onDeadlock wiring logs the deadlock (as wired in adapter start())', async t => {
|
|
85
|
+
const {log, calls} = makeLog();
|
|
86
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
87
|
+
const fakeKnex = (() => makeRejectingBuilder(DEADLOCK)) as any;
|
|
88
|
+
const wrapped = wrapKnex(fakeKnex, {
|
|
89
|
+
onDeadlock: error => logKnexDeadlock({debug: true}, log, error),
|
|
90
|
+
});
|
|
91
|
+
const builder = wrapped('deadlock_demo');
|
|
92
|
+
|
|
93
|
+
await t.rejects(Promise.resolve(builder.then(null, null)), DEADLOCK, 'query still rejects');
|
|
94
|
+
t.equal(calls.length, 1, 'deadlock logged once');
|
|
95
|
+
t.equal(calls[0].args[1], 'knex deadlock', 'logged with the deadlock message');
|
|
96
|
+
t.same(calls[0].args[0], EXPECTED_ENTRY, 'full deadlock details logged');
|
|
97
|
+
t.end();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
/** A minimal knex-like builder whose `then` rejects with `error`. */
|
|
101
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
102
|
+
function makeRejectingBuilder(error: unknown): any {
|
|
103
|
+
return {
|
|
104
|
+
insert(..._args: unknown[]) {
|
|
105
|
+
return this;
|
|
106
|
+
},
|
|
107
|
+
update(..._args: unknown[]) {
|
|
108
|
+
return this;
|
|
109
|
+
},
|
|
110
|
+
then(onFulfilled?: (value: unknown) => unknown, onRejected?: (reason: unknown) => unknown) {
|
|
111
|
+
return Promise.reject(error).then(onFulfilled, onRejected);
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|