@genesislcap/mock-server 15.51.1 → 15.53.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 (70) hide show
  1. package/README.md +408 -39
  2. package/dist/db/criteria.d.ts +13 -0
  3. package/dist/db/criteria.js +60 -21
  4. package/dist/db/identity.d.ts +11 -0
  5. package/dist/db/identity.js +46 -0
  6. package/dist/db/ordering.d.ts +8 -0
  7. package/dist/db/ordering.js +49 -0
  8. package/dist/db/schema.d.ts +62 -0
  9. package/dist/db/schema.js +490 -0
  10. package/dist/db/store.d.ts +20 -3
  11. package/dist/db/store.js +300 -50
  12. package/dist/handlers/commitEvent.js +47 -9
  13. package/dist/handlers/crudEvents.d.ts +2 -0
  14. package/dist/handlers/crudEvents.js +138 -0
  15. package/dist/handlers/dataLogon.js +105 -6
  16. package/dist/handlers/eventValidation.d.ts +5 -0
  17. package/dist/handlers/eventValidation.js +29 -0
  18. package/dist/handlers/jsonSchema.d.ts +4 -2
  19. package/dist/handlers/jsonSchema.js +222 -61
  20. package/dist/handlers/meta.d.ts +6 -1
  21. package/dist/handlers/meta.js +168 -21
  22. package/dist/handlers/requestReply.js +519 -30
  23. package/dist/handlers/resourceAuth.d.ts +1 -0
  24. package/dist/handlers/resourceAuth.js +14 -0
  25. package/dist/handlers/resources.js +3 -0
  26. package/dist/index.d.ts +3 -2
  27. package/dist/index.js +2 -1
  28. package/dist/protocol/connection.d.ts +2 -0
  29. package/dist/protocol/errors.d.ts +100 -2
  30. package/dist/protocol/errors.js +300 -12
  31. package/dist/protocol/fieldTypes.d.ts +10 -0
  32. package/dist/protocol/fieldTypes.js +107 -0
  33. package/dist/protocol/gsfSchemas.d.ts +9 -0
  34. package/dist/protocol/gsfSchemas.js +423 -0
  35. package/dist/protocol/messageTypes.d.ts +1 -0
  36. package/dist/protocol/messageTypes.js +1 -0
  37. package/dist/protocol/msgNack.d.ts +2 -0
  38. package/dist/protocol/msgNack.js +10 -0
  39. package/dist/protocol/rowUpdate.d.ts +5 -3
  40. package/dist/protocol/rowUpdate.js +33 -15
  41. package/dist/protocol/schemaValidation.d.ts +11 -0
  42. package/dist/protocol/schemaValidation.js +186 -0
  43. package/dist/server.js +95 -29
  44. package/dist/types.d.ts +45 -0
  45. package/package.json +1 -1
  46. package/src/db/criteria.ts +72 -21
  47. package/src/db/identity.ts +53 -0
  48. package/src/db/ordering.ts +56 -0
  49. package/src/db/schema.ts +672 -0
  50. package/src/db/store.ts +343 -50
  51. package/src/handlers/commitEvent.ts +54 -10
  52. package/src/handlers/crudEvents.ts +181 -0
  53. package/src/handlers/dataLogon.ts +127 -7
  54. package/src/handlers/eventValidation.ts +43 -0
  55. package/src/handlers/jsonSchema.ts +302 -69
  56. package/src/handlers/meta.ts +227 -23
  57. package/src/handlers/requestReply.ts +626 -33
  58. package/src/handlers/resourceAuth.ts +18 -0
  59. package/src/handlers/resources.ts +3 -0
  60. package/src/index.ts +17 -1
  61. package/src/protocol/connection.ts +10 -2
  62. package/src/protocol/errors.ts +361 -13
  63. package/src/protocol/fieldTypes.ts +115 -0
  64. package/src/protocol/gsfSchemas.ts +505 -0
  65. package/src/protocol/messageTypes.ts +1 -0
  66. package/src/protocol/msgNack.ts +12 -0
  67. package/src/protocol/rowUpdate.ts +40 -20
  68. package/src/protocol/schemaValidation.ts +208 -0
  69. package/src/server.ts +106 -28
  70. package/src/types.ts +187 -5
package/src/types.ts CHANGED
@@ -4,6 +4,7 @@
4
4
 
5
5
  import type { IncomingMessage, ServerResponse } from 'node:http';
6
6
  import type { Store } from './db/store.ts';
7
+ import type { NackErrorInput } from './protocol/errors.ts';
7
8
 
8
9
  export type Row = Record<string, any>;
9
10
 
@@ -14,6 +15,81 @@ export interface GenesisMessage {
14
15
  [key: string]: any;
15
16
  }
16
17
 
18
+ // The Genesis dictionary field types (global.genesis.dictionary.Field.Type).
19
+ export type GenesisFieldType =
20
+ | 'STRING'
21
+ | 'ENUM'
22
+ | 'INT'
23
+ | 'SHORT'
24
+ | 'LONG'
25
+ | 'DOUBLE'
26
+ | 'BIGDECIMAL'
27
+ | 'BOOLEAN'
28
+ | 'DATE'
29
+ | 'DATETIME'
30
+ | 'NANO_TIMESTAMP'
31
+ | 'RAW';
32
+
33
+ // One column of a table, as the project's tables dictionary declares it
34
+ // (`field("NAME", STRING(100)).notNull()`). META_REQUEST and
35
+ // JSON_SCHEMA_REQUEST are built from these when a table declares them; see the
36
+ // README's "Declared schema".
37
+ export interface FieldDef {
38
+ name: string;
39
+ type: GenesisFieldType;
40
+ // Default true, as in the dictionary (`.notNull()` makes it false). Primary
41
+ // key and generated fields default to false.
42
+ nullable?: boolean;
43
+ // ENUM values, in dictionary order.
44
+ values?: string[];
45
+ // The dictionary `.default(...)`. A non-null field with a default is
46
+ // optional in event schemas.
47
+ default?: unknown;
48
+ // STRING column size (`STRING(100)`): MAX_LENGTH / maxLength.
49
+ maxSize?: number;
50
+ // `metadata { minLength = 0 }`: MIN_LENGTH / minLength.
51
+ minLength?: number;
52
+ // Replaces the DESCRIPTION / description the real server fills with the
53
+ // field's JVM class (kotlin.String, org.joda.time.DateTime, ...). Leave it
54
+ // unset for DATE/DATETIME: foundation-forms recognises dates partly by that
55
+ // class name.
56
+ description?: string;
57
+ // Defaults to the name in title case ('COUNTERPARTY_ID' -> 'Counterparty Id').
58
+ title?: string;
59
+ // Whether an event payload may leave it out (OPTIONAL, and not in the JSON
60
+ // schema's `required`). The real server makes a field optional only when
61
+ // its class property has a default. Unset: a table field is optional when
62
+ // it is nullable, generated or has a default (the generated DAO defaults
63
+ // all of those); a custom DTO field only when it has a `default` — set
64
+ // `optional: true` for a Kotlin default the metadata can't show, such as
65
+ // `val enabled: Boolean? = true`.
66
+ optional?: boolean;
67
+ }
68
+
69
+ // A field the database fills on insert when the record leaves it out (a
70
+ // supplied value is kept). Metadata marks it NULLABLE:false, OPTIONAL:true
71
+ // and leaves it out of `required`. A SEQUENCE mints values like the key's
72
+ // sequence (`prefix`, else the table's sequencePrefix, in its
73
+ // sequenceFormat); an AUTO_INCREMENT counts up from the highest seeded value
74
+ // (1 when there is none). A table with `sequencePrefix` gets its key
75
+ // generated as a SEQUENCE without declaring it here.
76
+ export interface GeneratedFieldDef {
77
+ field: string;
78
+ kind: 'SEQUENCE' | 'AUTO_INCREMENT';
79
+ prefix?: string;
80
+ }
81
+
82
+ // The verbs of the generic CRUD events (see TableDef.events).
83
+ export type CrudEventVerb = 'INSERT' | 'MODIFY' | 'DELETE';
84
+
85
+ // A table or dataserver-query index (`indices { unique("A") }`). FIELDS is
86
+ // sent space-separated (`'A B'`).
87
+ export interface IndexDef {
88
+ name: string;
89
+ fields: string[];
90
+ unique?: boolean;
91
+ }
92
+
17
93
  export interface TableDef {
18
94
  // A single field, or several for composite-keyed tables (e.g. PRICE keyed
19
95
  // by [RFQ_ID, SIDE]). Composite rows are identified by the fields' values
@@ -23,9 +99,40 @@ export interface TableDef {
23
99
  pkField: string | string[];
24
100
  sequencePrefix?: string;
25
101
  sequenceWidth?: number;
102
+ // How generated pk values look. See SequenceFormat.
103
+ sequenceFormat?: SequenceFormat;
26
104
  rows?: Row[];
105
+ // Declared columns, in dictionary order. When present they are the schema:
106
+ // metadata, JSON schemas and dataserver rows follow them (missing values go
107
+ // out as null), even with no rows at all. When absent, field metadata is
108
+ // inferred from the rows (src/db/metadata.ts).
109
+ fields?: FieldDef[];
110
+ generated?: GeneratedFieldDef[];
111
+ // Table indices. The generic CRUD events NACK DUPLICATE_KEY on a unique
112
+ // one, and a unique index over exactly the key fields names the primary
113
+ // key (else '<TABLE>_BY_<fields>'). A dataserver query only reports the
114
+ // indices on its own QueryDef.indexes, as the real dataserver does.
115
+ indexes?: IndexDef[];
116
+ // Registers generic CRUD event handlers for this table —
117
+ // EVENT_<TABLE>_INSERT, EVENT_<TABLE>_MODIFY, EVENT_<TABLE>_DELETE — the
118
+ // ones Genesis Create generates (entityDb.insert / modify / delete, no
119
+ // onValidate): insert the record (generating sequence and auto-increment
120
+ // fields, GENERATED listing them all; DUPLICATE_KEY on a taken key), modify
121
+ // or delete it by its primary key (RECORD_NOT_FOUND), ack VALIDATE without
122
+ // writing, push the change live. They are listed in RESOURCES and described
123
+ // by META_REQUEST / JSON_SCHEMA_REQUEST like any registered event. An
124
+ // eventHandlers entry of the same name wins. See the README's "Generic CRUD
125
+ // events".
126
+ events?: CrudEventVerb[];
27
127
  }
28
128
 
129
+ // 'prefix' (the default): sequencePrefix + a counter padded to sequenceWidth
130
+ // ('TR001'). 'genesis': the platform's id format (genesis-db IdStrategy.java)
131
+ // — the counter padded to 15 digits, the sequence id (sequencePrefix, two
132
+ // characters in a GSF dictionary), then location 'LO' and system '1':
133
+ // '000000000000001TRLO1'.
134
+ export type SequenceFormat = 'prefix' | 'genesis';
135
+
29
136
  export interface JoinDef {
30
137
  table: string;
31
138
  on: [string, string];
@@ -53,6 +160,16 @@ export interface ViewDef {
53
160
  joins?: JoinDef[];
54
161
  antiJoins?: AntiJoinDef[];
55
162
  derived?: Record<string, (row: Row) => any>;
163
+ // Types of the `derived` fields (`derivedField("TOTAL", DOUBLE)`): a type,
164
+ // or a full field definition without its name. Columns from the base table
165
+ // and from `joins[].select` take their declared types from their source
166
+ // tables; a derived field with no entry here is inferred from the rows.
167
+ derivedTypes?: Record<string, GenesisFieldType | Omit<FieldDef, 'name'>>;
168
+ // The view's fields { } block: the only columns it exposes, in this order
169
+ // (base, joined and derived alike). Unset: every base column, then each
170
+ // join's select, then the derived fields. Keep the key fields in it — they
171
+ // are the ROW_REF.
172
+ fields?: string[];
56
173
  }
57
174
 
58
175
  // The authenticated identity a resource-level auth hook sees. userName is
@@ -76,7 +193,17 @@ export interface ResourceAuthDef {
76
193
 
77
194
  export interface QueryDef {
78
195
  source: string;
196
+ // Overrides the TYPE of named fields, declared or inferred.
79
197
  fieldTypes?: Record<string, string>;
198
+ // The query's `fields { }` block: the only columns it exposes, in this
199
+ // order, in rows and in META_REQUEST. Names the source doesn't have are
200
+ // dropped from metadata. RECORD_ID and TIMESTAMP reach dataserver rows only
201
+ // when named here.
202
+
203
+ fields?: string[];
204
+ // The query's `indices { }` block, reported as META_REQUEST INDEXES. With
205
+ // none, INDEXES is the default [{NAME: null, FIELDS: 'RECORD_ID'}].
206
+ indexes?: IndexDef[];
80
207
  // Server-side where-clause equivalent, applied before any client
81
208
  // CRITERIA_MATCH — to both the DATA_LOGON snapshot and live pushes. Use it
82
209
  // for things the real query's filter/where clause does (e.g. keep template
@@ -100,22 +227,60 @@ export interface RequestReplyDef {
100
227
  // (`resolver`) — for request servers that calculate rather than read (date
101
228
  // calculations, holiday validate/roll, premium schedules, composite
102
229
  // lookups). A resolver receives the REQUEST fields and returns the REPLY
103
- // rows verbatim; throw NackError for a protocol-correct error reply. When
104
- // both are set, resolver wins. With resolver only, set `metadataRows` (a
105
- // representative sample reply) or `fieldTypes` so META_REQUEST can still
106
- // derive REPLY_FIELD metadata.
230
+ // rows verbatim; throw NackError for a protocol-correct error reply (a
231
+ // MSG_NACK). When both are set, resolver wins. With resolver only, set
232
+ // `metadataRows` (a representative sample reply) or `fieldTypes` so
233
+ // META_REQUEST can still derive REPLY_FIELD metadata.
107
234
  source?: string;
108
235
  resolver?: (ctx: RequestReplyResolverCtx) => Row[];
109
236
  metadataRows?: Row[];
110
237
  fieldTypes?: Record<string, string>;
238
+ // The request server's `filter { }` / `where { }` clause. With `filter` or
239
+ // `auth`, a criteria-only server reads past the rows they drop, and
240
+ // NEXT_OFFSET says where it stopped (README: Request servers).
111
241
  filter?: (row: Row) => boolean;
112
242
  auth?: ResourceAuthDef;
243
+ // `criteriaOnlyRequest = true` in the request server's config. REQUEST must
244
+ // then be present (an object; its fields are ignored), DETAILS.OFFSET pages,
245
+ // DETAILS.ORDER_BY sorts by fields, and every REP_ carries MORE_ROWS (plus
246
+ // NEXT_OFFSET while there are more). META_REQUEST reports REQUEST_FIELD: [],
247
+ // CRITERIA_ONLY_REQUEST: true, SORTABLE_FIELDS and CRITERIA_FIELDS. Without
248
+ // it, REQUEST's fields select rows (exact values, `*` wildcards,
249
+ // FIELD_FROM/FIELD_TO ranges; an array runs each) and OFFSET is ignored.
250
+ // See the README's "Request servers".
251
+ criteriaOnly?: boolean;
252
+ // The request block's fields (REQUEST_FIELD, and the JSON schema's REQUEST).
253
+ // Defaults to the source's primary key, as for a GPAL requestReply(TABLE).
254
+ requestFields?: string[];
255
+ // The reply block (`reply { }` in GPAL): REP_ rows carry exactly these
256
+ // columns, in this order, null when unset, and so do REPLY_FIELD and the JSON
257
+ // schema's REPLY. Names a declared source doesn't have are dropped.
258
+ // RECORD_ID and TIMESTAMP are sent only when named here. Unset: every column
259
+ // of the source followed by RECORD_ID and TIMESTAMP, as GSF sends a table
260
+ // entity. Ignored with a resolver.
261
+ fields?: string[];
262
+ // `config { maxRows = N }`: the most rows a REP_ carries when the request
263
+ // names no DETAILS.MAX_ROWS. Default 10000 (RequestReply.DEFAULT_MAXIMUM_ROWS).
264
+ rowLimit?: number;
113
265
  }
114
266
 
267
+ // How META_REQUEST / JSON_SCHEMA_REQUEST describe an event whose name doesn't
268
+ // follow EVENT_<TABLE>_<VERB>: the table (or view) its DETAILS take, and
269
+ // whether only the key fields are sent (a delete).
115
270
  export interface EventSchemaOverride {
116
271
  table?: string;
117
272
  pkOnly?: boolean;
118
273
  fieldTypes?: Record<string, string>;
274
+ // The event's own DETAILS class, for an event that takes a custom DTO
275
+ // (`eventHandler<MyInput>`) rather than a table row. Described as the real
276
+ // server describes a non-table class: no titles, lengths, ranges or
277
+ // genesis types; `nullable`, `default` and `optional` as declared. A
278
+ // nullable field is still mandatory unless it has a default or
279
+ // `optional: true` (Kotlin `val x: T?` must be sent; `val x: T? = null`
280
+ // needn't be). `table` then only names where its ENUM columns come from.
281
+ fields?: FieldDef[];
282
+ // The DTO's class name, reported as the event's DESCRIPTION.
283
+ className?: string;
119
284
  }
120
285
 
121
286
  export interface ResourceItem {
@@ -179,9 +344,19 @@ export interface EventHandlerCtx {
179
344
  message: GenesisMessage;
180
345
  broadcast: BroadcastFn;
181
346
  broadcastTableChange: BroadcastTableChangeFn;
347
+ // `VALIDATE: true` on the message: the client asks whether the event would
348
+ // succeed. GSF then runs only the handler's onValidate and answers its NACK,
349
+ // or an EVENT_ACK with GENERATED: [] — nothing is written. A handler that
350
+ // writes should run its checks and return before writing when this is set;
351
+ // the generic CRUD events do.
352
+ validate: boolean;
182
353
  }
183
354
 
184
- export type EventHandlerResult = { generated?: Row[] } | void;
355
+ // `warnings` turns the reply into an EVENT_NACK carrying them in WARNING
356
+ // (with an empty ERROR), as a GSF handler's warning-only EventNack does.
357
+ // Return them from a handler that has not written anything yet: the mock has
358
+ // no transaction to roll back, and doesn't honour IGNORE_WARNINGS yet.
359
+ export type EventHandlerResult = { generated?: Row[]; warnings?: NackErrorInput[] } | void;
185
360
 
186
361
  export type EventHandler = (details: Row, ctx: EventHandlerCtx) => EventHandlerResult;
187
362
 
@@ -245,11 +420,18 @@ export interface MockServerConfig {
245
420
  httpRoutes?: HttpRouteDef[];
246
421
  onMessage?: OnMessageHook;
247
422
  productName?: string;
423
+ // The Genesis application name, as in the generated enum classes the real
424
+ // server names in event metadata
425
+ // (global.genesis.gen.dao.enums.<appName>.<table>.<Field>). Unset: the
426
+ // segment is left out. Nothing a client does depends on it.
427
+ appName?: string;
248
428
  }
249
429
 
430
+ // A META_REQUEST FIELD / REQUEST_FIELD / REPLY_FIELD entry.
250
431
  export interface FieldMetadata {
251
432
  NAME: string;
252
433
  TYPE: string;
434
+ VALID_VALUES?: string[];
253
435
  }
254
436
 
255
437
  // Minimal shape both `ws`'s WebSocket and Bun's ServerWebSocket satisfy —