@mayhem93/nexxus-core-lib 0.0.1 → 0.0.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.
Files changed (87) hide show
  1. package/dist-cjs/common/BuiltinSchemas.js +24 -5
  2. package/dist-cjs/common/BuiltinSchemas.js.map +1 -1
  3. package/dist-cjs/common/FilterQuery.js +5 -18
  4. package/dist-cjs/common/FilterQuery.js.map +1 -1
  5. package/dist-cjs/common/InferModel.js +3 -0
  6. package/dist-cjs/common/InferModel.js.map +1 -0
  7. package/dist-cjs/common/JsonPatch.js +42 -225
  8. package/dist-cjs/common/JsonPatch.js.map +1 -1
  9. package/dist-cjs/common/SchemaValidator.js +100 -29
  10. package/dist-cjs/common/SchemaValidator.js.map +1 -1
  11. package/dist-cjs/index.js +4 -0
  12. package/dist-cjs/index.js.map +1 -1
  13. package/dist-cjs/lib/BaseService.js +6 -6
  14. package/dist-cjs/lib/BaseService.js.map +1 -1
  15. package/dist-cjs/lib/ConfigManager.js +56 -25
  16. package/dist-cjs/lib/ConfigManager.js.map +1 -1
  17. package/dist-cjs/lib/ConfigProvider.js +14 -1
  18. package/dist-cjs/lib/ConfigProvider.js.map +1 -1
  19. package/dist-cjs/lib/Exceptions.js +22 -4
  20. package/dist-cjs/lib/Exceptions.js.map +1 -1
  21. package/dist-cjs/lib/Logger.js +138 -65
  22. package/dist-cjs/lib/Logger.js.map +1 -1
  23. package/dist-cjs/lib/ServiceResolver.js +57 -0
  24. package/dist-cjs/lib/ServiceResolver.js.map +1 -0
  25. package/dist-cjs/models/AppModel.js +17 -3
  26. package/dist-cjs/models/AppModel.js.map +1 -1
  27. package/dist-cjs/models/Application.js +51 -12
  28. package/dist-cjs/models/Application.js.map +1 -1
  29. package/dist-cjs/models/BaseModel.js.map +1 -1
  30. package/dist-cjs/models/BuiltinModel.js +8 -0
  31. package/dist-cjs/models/BuiltinModel.js.map +1 -0
  32. package/dist-cjs/models/User.js +12 -3
  33. package/dist-cjs/models/User.js.map +1 -1
  34. package/dist-esm/common/BuiltinSchemas.js +23 -4
  35. package/dist-esm/common/BuiltinSchemas.js.map +1 -1
  36. package/dist-esm/common/FilterQuery.js +6 -19
  37. package/dist-esm/common/FilterQuery.js.map +1 -1
  38. package/dist-esm/common/InferModel.js +2 -0
  39. package/dist-esm/common/InferModel.js.map +1 -0
  40. package/dist-esm/common/JsonPatch.js +44 -227
  41. package/dist-esm/common/JsonPatch.js.map +1 -1
  42. package/dist-esm/common/SchemaValidator.js +100 -29
  43. package/dist-esm/common/SchemaValidator.js.map +1 -1
  44. package/dist-esm/index.js +4 -0
  45. package/dist-esm/index.js.map +1 -1
  46. package/dist-esm/lib/BaseService.js +8 -8
  47. package/dist-esm/lib/BaseService.js.map +1 -1
  48. package/dist-esm/lib/ConfigManager.js +56 -25
  49. package/dist-esm/lib/ConfigManager.js.map +1 -1
  50. package/dist-esm/lib/ConfigProvider.js +14 -1
  51. package/dist-esm/lib/ConfigProvider.js.map +1 -1
  52. package/dist-esm/lib/Exceptions.js +20 -3
  53. package/dist-esm/lib/Exceptions.js.map +1 -1
  54. package/dist-esm/lib/Logger.js +139 -66
  55. package/dist-esm/lib/Logger.js.map +1 -1
  56. package/dist-esm/lib/ServiceResolver.js +52 -0
  57. package/dist-esm/lib/ServiceResolver.js.map +1 -0
  58. package/dist-esm/models/AppModel.js +17 -3
  59. package/dist-esm/models/AppModel.js.map +1 -1
  60. package/dist-esm/models/Application.js +52 -13
  61. package/dist-esm/models/Application.js.map +1 -1
  62. package/dist-esm/models/BaseModel.js.map +1 -1
  63. package/dist-esm/models/BuiltinModel.js +4 -0
  64. package/dist-esm/models/BuiltinModel.js.map +1 -0
  65. package/dist-esm/models/User.js +11 -2
  66. package/dist-esm/models/User.js.map +1 -1
  67. package/dist-types/common/BuiltinSchemas.d.ts +59 -5
  68. package/dist-types/common/FilterQuery.d.ts +3 -11
  69. package/dist-types/common/InferModel.d.ts +29 -0
  70. package/dist-types/common/JsonPatch.d.ts +4 -45
  71. package/dist-types/common/ModelTypes.d.ts +1 -0
  72. package/dist-types/common/QueuePayloads.d.ts +48 -26
  73. package/dist-types/common/SchemaValidator.d.ts +42 -2
  74. package/dist-types/index.d.ts +4 -0
  75. package/dist-types/lib/BaseService.d.ts +51 -1
  76. package/dist-types/lib/ConfigManager.d.ts +50 -13
  77. package/dist-types/lib/ConfigProvider.d.ts +6 -0
  78. package/dist-types/lib/Exceptions.d.ts +15 -2
  79. package/dist-types/lib/Logger.d.ts +84 -14
  80. package/dist-types/lib/ServiceResolver.d.ts +23 -0
  81. package/dist-types/models/AppModel.d.ts +31 -5
  82. package/dist-types/models/Application.d.ts +36 -8
  83. package/dist-types/models/BaseModel.d.ts +14 -12
  84. package/dist-types/models/BuiltinModel.d.ts +3 -0
  85. package/dist-types/models/User.d.ts +15 -13
  86. package/package.json +4 -4
  87. package/src/schemas/winston-logger.schema.json +51 -6
@@ -1,4 +1,4 @@
1
- import { NexxusAppModelType } from '../models/AppModel';
1
+ import { INexxusAppModel } from '../models/AppModel';
2
2
  import { NexxusJsonPatchInternal, NexxusJsonPatchMetadata } from '../common/JsonPatch';
3
3
  export interface NexxusBaseQueuePayload {
4
4
  event: string;
@@ -6,65 +6,87 @@ export interface NexxusBaseQueuePayload {
6
6
  }
7
7
  export type NexxusModelCreatedPayload = {
8
8
  event: 'model_created';
9
- data: NexxusAppModelType;
9
+ data: INexxusAppModel;
10
10
  };
11
11
  export type NexxusModelUpdatedPayload = {
12
12
  event: 'model_updated';
13
13
  data: Array<NexxusJsonPatchInternal>;
14
14
  };
15
- export type NexxusModelDeletedData = Pick<NexxusAppModelType, 'id' | 'type' | 'appId' | 'userId'>;
15
+ export type NexxusModelDeletedData = Pick<INexxusAppModel, 'id' | 'type' | 'appId' | 'userId'>;
16
16
  export type NexxusModelDeletedPayload = {
17
17
  event: 'model_deleted';
18
18
  data: NexxusModelDeletedData;
19
19
  };
20
20
  export type NexxusWriterPayload = NexxusModelCreatedPayload | NexxusModelUpdatedPayload | NexxusModelDeletedPayload;
21
- export type NexxusTransportManagetJsonPatch = Omit<NexxusJsonPatchInternal, 'metadata'> & {
21
+ export type NexxusTransportManagerJsonPatch = Omit<NexxusJsonPatchInternal, 'metadata'> & {
22
22
  metadata: NexxusJsonPatchMetadata & {
23
- partialModel: Partial<NexxusAppModelType>;
23
+ partialModel: Partial<INexxusAppModel>;
24
24
  };
25
25
  };
26
26
  export type NexxusTransportManagerModelUpdatedPayload = {
27
27
  event: 'model_updated';
28
- data: Array<NexxusTransportManagetJsonPatch>;
28
+ data: Array<NexxusTransportManagerJsonPatch>;
29
29
  };
30
30
  export type NexxusTransportManagerPayload = NexxusModelCreatedPayload | NexxusTransportManagerModelUpdatedPayload | NexxusModelDeletedPayload;
31
- export interface NexxusWebSocketJsonPatchMetadata {
32
- id: string;
31
+ /**
32
+ * Metadata for transport-bound model events (create/update/delete alike).
33
+ * Carries the channels that matched the subscription router for this device,
34
+ * so the client can correlate the event with its local subscription containers.
35
+ */
36
+ export interface NexxusTransportMetadata {
33
37
  channels: Array<string>;
34
38
  }
35
39
  /**
36
- * JsonPatch type for WebSocket transport workers
40
+ * Payload for any transport worker - model created
41
+ */
42
+ export type NexxusTransportModelCreatedPayload = {
43
+ event: 'model_created';
44
+ model: INexxusAppModel;
45
+ metadata: NexxusTransportMetadata;
46
+ };
47
+ /**
48
+ * Payload for any transport worker - model deleted
37
49
  */
38
- export type NexxusWebSocketJsonPatch = Omit<NexxusJsonPatchInternal, 'metadata'> & {
39
- metadata: NexxusWebSocketJsonPatchMetadata;
50
+ export type NexxusTransportModelDeletedPayload = {
51
+ event: 'model_deleted';
52
+ model: NexxusModelDeletedData;
53
+ metadata: NexxusTransportMetadata;
40
54
  };
41
55
  /**
42
- * Payload for WebSocket Transport - slim metadata with just channel
56
+ * Payload for any transport worker - model updated.
57
+ * All patches in a single event target the same model — its identity is hoisted
58
+ * into `model`, and the matched channels into top-level `metadata`. Per-patch
59
+ * metadata is intentionally absent (it was redundant across patches of the same
60
+ * update event under the previous shape).
43
61
  */
44
- export type NexxusWebSocketModelUpdatedPayload = {
62
+ export type NexxusTransportModelUpdatedPayload = {
45
63
  event: 'model_updated';
46
- data: Array<NexxusWebSocketJsonPatch>;
64
+ /**
65
+ * Includes `version` — the post-update version stamp assigned by the adapter.
66
+ * Clients use it for gap detection: apply if `model.version === local.version + 1`,
67
+ * request a fresh GET if there's a gap, ignore if it's already been applied.
68
+ */
69
+ model: Pick<INexxusAppModel, 'id' | 'type' | 'appId' | 'userId' | 'version'>;
70
+ patches: Array<Omit<NexxusJsonPatchInternal, 'metadata'>>;
71
+ metadata: NexxusTransportMetadata;
47
72
  };
48
- export type NexxusWebsocketPayload = {
73
+ /**
74
+ * Canonical payload consumed by ALL transport workers (volatile and persistent alike).
75
+ * The shape is identical across transports; each transport translates the inner `data`
76
+ * into its own wire format inside its `sendToDevice` implementation.
77
+ */
78
+ export type NexxusTransportWorkerPayload = {
49
79
  event: 'device_message';
50
80
  deviceIds: Array<string>;
51
- data: NexxusModelCreatedPayload | NexxusWebSocketModelUpdatedPayload | NexxusModelDeletedPayload;
52
- };
53
- export type NexxusMqttPayload = {
54
- event: 'mqtt_publish';
55
- topic: string;
56
- payload: Buffer;
57
- } | {
58
- event: 'mqtt_subscribe';
59
- topic: string;
81
+ data: NexxusTransportModelCreatedPayload | NexxusTransportModelUpdatedPayload | NexxusTransportModelDeletedPayload;
60
82
  };
61
83
  export interface NexxusBuiltInQueuePayloadMap {
62
84
  'writer': NexxusWriterPayload;
63
85
  'transport-manager': NexxusTransportManagerPayload;
64
86
  }
65
87
  export interface NexxusDynamicQueuePayloadMap {
66
- 'websockets-transport': NexxusWebsocketPayload;
67
- 'mqtt-transport': NexxusMqttPayload;
88
+ 'websockets-transport': NexxusTransportWorkerPayload;
89
+ 'mqtt-transport': NexxusTransportWorkerPayload;
68
90
  }
69
91
  export type NexxusBuiltInQueueName = keyof NexxusBuiltInQueuePayloadMap;
70
92
  export type NexxusDynamicQueueType = keyof NexxusDynamicQueuePayloadMap;
@@ -1,4 +1,44 @@
1
- import { NexxusUserDetailSchema } from "../models/User";
1
+ import type { NexxusFieldDef, NexxusModelDef } from './ModelTypes';
2
+ /**
3
+ * Pure schema-aware validation + normalization for a single value against a
4
+ * field definition, or for whole-model data against a model definition.
5
+ *
6
+ * Returns a normalized copy of the input value (e.g. date strings become
7
+ * integer timestamps). Throws InvalidSchemaDataException on the first violation.
8
+ *
9
+ * Used by:
10
+ * - NexxusJsonPatch (per-path validation on patch operations)
11
+ * - NexxusAppModel (whole-model validation on construction)
12
+ *
13
+ * Required-field checking is intentionally NOT performed here yet; only fields
14
+ * present in the input are validated.
15
+ */
2
16
  export declare class NexxusSchemaValidator {
3
- static validateUserDetails(partialDetails: Record<string, any>, userDetailSchema: NexxusUserDetailSchema): boolean;
17
+ /**
18
+ * Validate every present field of `data` against the model definition.
19
+ * Returns a shallow-merged copy of `data` with each value normalized.
20
+ * Unknown fields (present in data but not declared in modelDef) are passed
21
+ * through unchanged.
22
+ */
23
+ static validateAgainstSchema(data: Record<string, unknown>, modelDef: NexxusModelDef): Record<string, unknown>;
24
+ /**
25
+ * Validate a single value against a field definition. Returns the normalized
26
+ * value. Throws InvalidSchemaDataException on failure.
27
+ *
28
+ * `path` is used only for error messages.
29
+ */
30
+ static validateValue(value: unknown, fieldDef: NexxusFieldDef, path: string): unknown;
31
+ private static validateString;
32
+ private static validateNumber;
33
+ private static validateBoolean;
34
+ /**
35
+ * Accepts: number (ms timestamp), ISO date string, or numeric string.
36
+ * Returns: integer ms timestamp.
37
+ *
38
+ * This is the single canonical date parser for the system; the previous
39
+ * inconsistency between `Date.parse()` and `new Date(s).getTime()` is gone.
40
+ */
41
+ private static validateDate;
42
+ private static validateObject;
43
+ private static validateArray;
4
44
  }
@@ -3,10 +3,14 @@ export * from "./lib/ConfigProvider";
3
3
  export * from "./lib/ConfigManager";
4
4
  export * from "./lib/BaseService";
5
5
  export * from "./lib/Logger";
6
+ export * from "./lib/ServiceResolver";
6
7
  export * from "./common/QueuePayloads";
7
8
  export * from "./common/JsonPatch";
8
9
  export * from "./common/FilterQuery";
10
+ export * from "./common/InferModel";
11
+ export * from "./common/SchemaValidator";
9
12
  export * from "./models/BaseModel";
13
+ export * from "./models/BuiltinModel";
10
14
  export * from './common/BuiltinSchemas';
11
15
  export * from "./models/AppModel";
12
16
  export * from "./models/Application";
@@ -1,11 +1,54 @@
1
1
  import type { ConfigEnvVars, ConfigCliArgs, AddJsonSchemaDefFuncArg, NexxusConfigManager } from './ConfigManager';
2
- import type { NexxusBaseLogger } from "./Logger";
2
+ import type { NexxusBaseLogger } from './Logger';
3
3
  import { NexxusConfig } from './ConfigProvider';
4
4
  export type EventMap = Record<string | symbol, any[]>;
5
5
  export interface INexxusBaseServices {
6
6
  configManager: NexxusConfigManager;
7
7
  logger: NexxusBaseLogger<NexxusConfig>;
8
8
  }
9
+ /**
10
+ * Static-only shape of a Nexxus service class. Anything ConfigManager needs to
11
+ * register a service's config lives here — `name` (for diagnostics), plus the
12
+ * three static-config accessors. Constructor visibility is intentionally
13
+ * absent, so classes that expose an async factory instead of a public
14
+ * constructor (e.g. `WinstonNexxusLogger.create(...)`) still satisfy the type.
15
+ *
16
+ * All `NexxusBaseService` subclasses satisfy this structurally. Downstream code
17
+ * writing custom services should type against this interface when passing
18
+ * classes to `ConfigManager.validateServices(...)`.
19
+ */
20
+ export interface NexxusServiceClass {
21
+ readonly name: string;
22
+ schema(): AddJsonSchemaDefFuncArg;
23
+ envVarConfig(): ConfigEnvVars;
24
+ cliArgConfig(): ConfigCliArgs;
25
+ }
26
+ /**
27
+ * `NexxusServiceClass` + a public constructor. This is the default shape for
28
+ * pluggable adapters — DB, MQ, most anything without async init. Consumers
29
+ * `new Cls(services)` directly at the bootstrap site.
30
+ *
31
+ * If your adapter needs to await something before it's ready (dynamic
32
+ * imports, remote fetches, warm-up connections), use
33
+ * `NexxusFactoryServiceClass` instead — that's what `WinstonNexxusLogger`
34
+ * does for dynamic transports.
35
+ */
36
+ export interface NexxusConstructableServiceClass extends NexxusServiceClass {
37
+ new (services: INexxusBaseServices): NexxusBaseService<any, any>;
38
+ }
39
+ /**
40
+ * `NexxusServiceClass` + a static async factory. For adapters whose
41
+ * initialization has to await something — dynamic imports, remote handshakes,
42
+ * schema fetches. The class typically hides its constructor (private) so
43
+ * consumers must go through `Cls.create(services)`.
44
+ *
45
+ * Currently used only for `WinstonNexxusLogger`. Most adapters are fine as
46
+ * `NexxusConstructableServiceClass`; only reach for this when async init is
47
+ * actually needed.
48
+ */
49
+ export interface NexxusFactoryServiceClass extends NexxusServiceClass {
50
+ create(services: INexxusBaseServices): Promise<NexxusBaseService<any, any>>;
51
+ }
9
52
  declare class TypedEventEmitter<E> {
10
53
  private emitter;
11
54
  constructor();
@@ -18,6 +61,13 @@ export declare abstract class NexxusBaseService<T extends NexxusConfig, Ev exten
18
61
  protected config: Readonly<T>;
19
62
  protected static envVars: ConfigEnvVars;
20
63
  protected static cliArgs: ConfigCliArgs;
64
+ /**
65
+ * The top-level key under which this service's configuration sits in the merged
66
+ * root config (e.g. "logger", "database", "redis", "app"). Required for every
67
+ * concrete service — ConfigManager uses it as the property name when grafting
68
+ * the service's schema definition into the root JSON Schema.
69
+ */
70
+ protected static configRootKey: string;
21
71
  protected static schemaPath: string;
22
72
  private static schemaContents;
23
73
  constructor(config: Readonly<T>);
@@ -1,4 +1,4 @@
1
- import { NexxusBaseService } from './BaseService';
1
+ import { NexxusServiceClass } from './BaseService';
2
2
  import { CliArgType, NexxusConfig, INexxusConfigProvider } from "./ConfigProvider";
3
3
  import type { JSONSchema7Definition } from "json-schema";
4
4
  export type AddJsonSchemaDefFuncArg = {
@@ -9,35 +9,72 @@ export type AddJsonSchemaDefFuncArg = {
9
9
  };
10
10
  type EnvVarsSpec = {
11
11
  name: string;
12
+ /** Dot-notation path relative to the service's configRootKey (e.g. "host", "auth.jwtSecret"). */
12
13
  location: string;
13
14
  };
14
- export type ConfigEnvVars = {
15
- source: string;
16
- specs: Array<EnvVarsSpec>;
17
- };
15
+ export type ConfigEnvVars = Array<EnvVarsSpec>;
18
16
  type CliArgsSpec = {
19
17
  name: string;
18
+ /** Dot-notation path relative to the service's configRootKey. */
20
19
  location: string;
21
20
  type: CliArgType;
22
21
  };
23
- export type ConfigCliArgs = {
24
- source: string;
25
- specs: Array<CliArgsSpec>;
26
- };
22
+ export type ConfigCliArgs = Array<CliArgsSpec>;
27
23
  export declare class NexxusConfigManager {
28
24
  private static CONF_FILE_NAME;
25
+ private static DEFAULT_CONF_PATH;
29
26
  private jsonSchema;
30
27
  private envVarsSpecs;
31
28
  private cliArgsSpecs;
32
29
  private data;
33
30
  private configProviders;
34
31
  private customProviders;
35
- constructor(confFile?: string);
32
+ /**
33
+ * Names (class.name) of services already registered. Guards against
34
+ * double-registration when the same class is passed to `registerService`
35
+ * or `validateServices` more than once — schema, env, and CLI specs each
36
+ * carry a uniqueness invariant that would otherwise throw on a second pass.
37
+ */
38
+ private registeredServices;
39
+ /**
40
+ * True until the file + custom providers have been loaded into `this.data`
41
+ * (i.e., first successful validate()). Subsequent validate() calls skip
42
+ * re-reading the config file — the file is treated as immutable for the
43
+ * lifetime of this ConfigManager.
44
+ */
45
+ private dataInitialized;
46
+ /**
47
+ * True when at least one service has been registered since the last
48
+ * successful validate(). Lets `validateServices()` no-op cleanly when
49
+ * called after everything's already been validated.
50
+ */
51
+ private hasNewRegistrations;
52
+ constructor(configFilePath?: string);
36
53
  addCustomProvider(provider: INexxusConfigProvider): void;
37
54
  private addJsonSchemaDef;
38
- validateServices(svcs: Array<typeof NexxusBaseService>): void;
39
- private addCliArgsToSpec;
40
- private addEnvVarsToSpec;
55
+ /**
56
+ * Register a service's config schema, env-var spec, and CLI-arg spec so
57
+ * they're included in the next `validateServices()` pass. Idempotent per
58
+ * class name — repeat calls with the same class are silent no-ops.
59
+ *
60
+ * Intended usage: call once per service class ahead of time (in any order),
61
+ * then call `validateServices()` once to actually run validation. For the
62
+ * shorthand where you know all services up front, `validateServices([...])`
63
+ * still accepts an array and registers each entry before validating.
64
+ */
65
+ registerService(svc: NexxusServiceClass): void;
66
+ /**
67
+ * Runs config validation against every registered service's schema. Safe
68
+ * to call multiple times — the config file is read only on the first call,
69
+ * and repeat calls with no new registrations short-circuit as a no-op.
70
+ *
71
+ * The optional `svcs` array is a shorthand: each entry is passed through
72
+ * `registerService()` before validation runs. Existing callers passing all
73
+ * services up front (`validateServices([A, B, C])`) continue to work.
74
+ * Later calls can register additional services (e.g. dynamically-resolved
75
+ * logger/db/mq implementations) and re-validate.
76
+ */
77
+ validateServices(svcs?: Array<NexxusServiceClass>): Promise<void>;
41
78
  private populateFromCliArgs;
42
79
  private populateFromEnvVars;
43
80
  private populateFromCustomProviders;
@@ -21,7 +21,13 @@ export declare class NexxusEnvVarsConfigProvider extends NexxusConfigProvider {
21
21
  export declare class NexxusCliArgConfigProvider extends NexxusConfigProvider {
22
22
  private argParser;
23
23
  private originalExit;
24
+ private addedArgs;
24
25
  constructor();
26
+ /**
27
+ * Idempotent per argument name — safe to call multiple times across repeat
28
+ * `ConfigManager.validateServices()` invocations. Argparse would otherwise
29
+ * throw on a second `add_argument` with the same name.
30
+ */
25
31
  addArgument(name: string, type: CliArgType): void;
26
32
  getConfig(): NexxusConfig;
27
33
  }
@@ -5,13 +5,22 @@ export declare enum NexxusExceptions {
5
5
  INVALID_CONFIG = "InvalidConfigException",
6
6
  INVALID_JSON_PATCH = "InvalidJsonPatchException",
7
7
  INVALID_QUERY_FILTER = "InvalidQueryFilterException",
8
+ INVALID_SCHEMA_DATA = "InvalidSchemaDataException",
8
9
  INVALID_USER_MODEL = "InvalidUserModelException"
9
10
  }
10
11
  export declare class NexxusException extends Error {
11
- constructor(type: string, message: string);
12
+ readonly subcode?: string;
13
+ constructor(type: string, message: string, subcode?: string);
14
+ }
15
+ declare enum FatalErrorSubcodes {
16
+ CONFIG_FILE_NOT_FOUND = "CONFIG_FILE_NOT_FOUND",
17
+ CONFIG_FILE_INVALID_JSON = "CONFIG_FILE_INVALID_JSON",
18
+ CONFIG_FILE_INVALID_SCHEMA = "CONFIG_FILE_INVALID_SCHEMA",
19
+ CONFIG_FILE_UNREADABLE = "CONFIG_FILE_UNREADABLE"
12
20
  }
13
21
  export declare class FatalErrorException extends NexxusException {
14
- constructor(message: string);
22
+ static SUBCODES: Readonly<typeof FatalErrorSubcodes>;
23
+ constructor(message: string, subcode?: FatalErrorSubcodes);
15
24
  }
16
25
  export declare class BadRequestException extends NexxusException {
17
26
  constructor(message: string);
@@ -28,6 +37,10 @@ export declare class InvalidJsonPatchException extends NexxusException {
28
37
  export declare class InvalidQueryFilterException extends NexxusException {
29
38
  constructor(message: string);
30
39
  }
40
+ export declare class InvalidSchemaDataException extends NexxusException {
41
+ constructor(message: string);
42
+ }
31
43
  export declare class InvalidUserModelException extends NexxusException {
32
44
  constructor(message: string);
33
45
  }
46
+ export {};
@@ -1,7 +1,7 @@
1
1
  import { ConfigEnvVars, ConfigCliArgs } from './ConfigManager';
2
2
  import { NexxusBaseService, INexxusBaseServices } from './BaseService';
3
3
  import { NexxusConfig } from './ConfigProvider';
4
- type LoggableType = string | object | number | boolean | null | undefined;
4
+ export type LogAttributes = Record<string, unknown>;
5
5
  export declare const enum NexxusLoggerLevels {
6
6
  EMERGENCY = "emerg",
7
7
  ALERT = "alert",
@@ -12,37 +12,107 @@ export declare const enum NexxusLoggerLevels {
12
12
  INFO = "info",
13
13
  DEBUG = "debug"
14
14
  }
15
+ export type StdoutTransportConfig = {
16
+ type: 'stdout';
17
+ };
18
+ export type FileTransportConfig = {
19
+ type: 'file';
20
+ filename: string;
21
+ maxSize?: number;
22
+ maxFiles?: number;
23
+ };
24
+ /**
25
+ * Catch-all for transports loaded dynamically from npm packages. The `type`
26
+ * field is the package name (e.g. "winston-papertrail"); `export` optionally
27
+ * names the class export within the package; `options` is the passthrough
28
+ * config object handed to the transport's constructor.
29
+ *
30
+ * The schema's `oneOf` discriminator excludes built-in type strings from this
31
+ * variant, so the TS-side `type: string` doesn't cause ambiguity at runtime.
32
+ */
33
+ export type CustomTransportConfig = {
34
+ type: string;
35
+ export?: string;
36
+ options?: Record<string, unknown>;
37
+ };
38
+ export type WinstonNexxusTransportConfig = StdoutTransportConfig | FileTransportConfig | CustomTransportConfig;
15
39
  type WinstonNexxusLoggerConfig = {
16
40
  level: NexxusLoggerLevels;
17
41
  logType: "json" | "text";
18
42
  timestamps: boolean;
19
43
  colors: boolean;
44
+ transports?: Array<WinstonNexxusTransportConfig>;
20
45
  } & NexxusConfig;
21
46
  export interface INexxusLogger {
22
- log(level: NexxusLoggerLevels, message: LoggableType, label?: string): void;
47
+ log(level: NexxusLoggerLevels, message: string, attributes?: LogAttributes, label?: string): void;
23
48
  }
24
- export interface INexxusAsyncLogger extends INexxusLogger {
25
- log(level: NexxusLoggerLevels, message: LoggableType, label?: string): Promise<void>;
49
+ export interface INexxusAsyncLogger {
50
+ log(level: NexxusLoggerLevels, message: string, attributes?: LogAttributes, label?: string): Promise<void>;
26
51
  }
27
52
  interface NexxusLoggerServices extends Omit<INexxusBaseServices, 'logger'> {
28
53
  }
29
54
  export declare abstract class NexxusBaseLogger<T extends NexxusConfig> extends NexxusBaseService<T> implements INexxusLogger {
55
+ protected static configRootKey: string;
30
56
  constructor(services: NexxusLoggerServices);
31
- abstract log(level: NexxusLoggerLevels, message: LoggableType, label?: string): void;
32
- debug(message: LoggableType, label?: string): void;
33
- info(message: LoggableType, label?: string): void;
34
- warn(message: LoggableType, label?: string): void;
35
- error(message: LoggableType, label?: string): void;
36
- critical(message: LoggableType, label?: string): void;
37
- alert(message: LoggableType, label?: string): void;
38
- emerg(message: LoggableType, label?: string): void;
57
+ abstract log(level: NexxusLoggerLevels, message: string, attributes?: LogAttributes, label?: string): void;
58
+ protected static serializeError(err: Error): Record<string, unknown>;
59
+ protected static makeSafeReplacer(): (key: string, value: unknown) => unknown;
60
+ protected static safeStringify(value: unknown): string;
61
+ debug(message: string, label?: string): void;
62
+ debug(message: string, attributes: LogAttributes, label?: string): void;
63
+ info(message: string, label?: string): void;
64
+ info(message: string, attributes: LogAttributes, label?: string): void;
65
+ warn(message: string, label?: string): void;
66
+ warn(message: string, attributes: LogAttributes, label?: string): void;
67
+ error(message: string, label?: string): void;
68
+ error(message: string, attributes: LogAttributes, label?: string): void;
69
+ critical(message: string, label?: string): void;
70
+ critical(message: string, attributes: LogAttributes, label?: string): void;
71
+ alert(message: string, label?: string): void;
72
+ alert(message: string, attributes: LogAttributes, label?: string): void;
73
+ emerg(message: string, label?: string): void;
74
+ emerg(message: string, attributes: LogAttributes, label?: string): void;
75
+ private dispatch;
39
76
  }
40
77
  export declare class WinstonNexxusLogger extends NexxusBaseLogger<WinstonNexxusLoggerConfig> {
41
78
  private winston;
42
79
  protected static schemaPath: string;
43
80
  protected static envVars: ConfigEnvVars;
44
81
  protected static cliArgs: ConfigCliArgs;
45
- constructor(services: NexxusLoggerServices);
46
- log(level: NexxusLoggerLevels, message: LoggableType, label?: string): void;
82
+ /**
83
+ * Private use `WinstonNexxusLogger.create(...)` instead. Transport instances
84
+ * must be resolved (async, since custom ones are dynamically imported) before
85
+ * the Winston logger can be constructed; the factory owns that step.
86
+ */
87
+ private constructor();
88
+ /**
89
+ * Async factory. Custom transports are loaded via dynamic `import()` so
90
+ * initialization is unavoidably async. API/Worker bootstrap code should
91
+ * `await WinstonNexxusLogger.create(...)` in place of `new WinstonNexxusLogger(...)`.
92
+ */
93
+ static create(services: NexxusLoggerServices): Promise<WinstonNexxusLogger>;
94
+ /**
95
+ * Maps each transport config entry to a constructed Winston transport.
96
+ * Built-in types are instantiated synchronously; everything else falls
97
+ * through to `loadCustomTransport`, which dynamically imports the npm
98
+ * package named by `type`.
99
+ *
100
+ * If the config has no transports (undefined or empty array), defaults to
101
+ * a single Console transport so the logger is never silent.
102
+ */
103
+ private static resolveTransports;
104
+ /**
105
+ * Dynamically imports the npm package named by `transport.type` and instantiates
106
+ * its transport class. The class is resolved in priority order:
107
+ * 1. `mod[transport.export]` if `transport.export` is set in config
108
+ * 2. `mod.default` if it's a function/class (ESM default export)
109
+ * 3. The module itself if it's a function/class (CJS `module.exports = Cls`)
110
+ *
111
+ * On any failure (package missing, no resolvable class, constructor throws)
112
+ * we re-wrap as `InvalidConfigException` so the operator sees an actionable
113
+ * message rather than a raw module-resolution stack trace.
114
+ */
115
+ private static loadCustomTransport;
116
+ log(level: NexxusLoggerLevels, message: string, attributes?: LogAttributes, label?: string): void;
47
117
  }
48
118
  export {};
@@ -0,0 +1,23 @@
1
+ import { NexxusConstructableServiceClass, NexxusFactoryServiceClass } from './BaseService';
2
+ /**
3
+ * Resolves a config-declared service to a **constructable** class — one the
4
+ * bootstrap will instantiate via `new Cls(services)`.
5
+ *
6
+ * 1. If `configuredName` is in `builtins`, that class is returned directly
7
+ * (fast path — no I/O, no dynamic import). Callers pass their known
8
+ * static-imported builtins here.
9
+ * 2. Otherwise the name is treated as an npm package specifier and loaded
10
+ * via dynamic import against the app's install tree.
11
+ *
12
+ * Used by `NexxusApi.resolveConstructableService` and
13
+ * `NexxusBaseWorker.resolveConstructableService` — the host classes provide
14
+ * their own builtin maps.
15
+ */
16
+ export declare function resolveConstructableServiceClass(configuredName: string, builtins: Record<string, NexxusConstructableServiceClass>): Promise<NexxusConstructableServiceClass>;
17
+ /**
18
+ * Same as `resolveConstructableServiceClass` but for services that expose an
19
+ * async `static create(services)` factory. Runtime-checks the class actually
20
+ * has `create` before returning, so misconfigurations surface here instead
21
+ * of blowing up at bootstrap time.
22
+ */
23
+ export declare function resolveFactoryServiceClass(configuredName: string, builtins: Record<string, NexxusFactoryServiceClass>): Promise<NexxusFactoryServiceClass>;
@@ -1,9 +1,35 @@
1
+ import { NexxusApplicationSchema } from "./Application";
1
2
  import { NexxusBaseModel, INexxusBaseModel } from "./BaseModel";
2
- export type NexxusAppModelType = INexxusBaseModel & {
3
+ export interface INexxusAppModel extends INexxusBaseModel {
3
4
  appId: string;
4
5
  userId?: string;
5
- [key: string]: any;
6
- };
7
- export declare class NexxusAppModel extends NexxusBaseModel<NexxusAppModelType> {
8
- constructor(props: NexxusAppModelType);
6
+ /**
7
+ * Monotonically-increasing per-document version counter, assigned by the
8
+ * database adapter on every write. Used for client-side gap detection and
9
+ * dedup when applying real-time update events. App models flow through the
10
+ * async Writer → Transport Manager → client pipeline; built-in models
11
+ * (Application, User) do not and therefore do not carry a version.
12
+ * Never set by user input — the validator rejects user-supplied values.
13
+ */
14
+ version?: number;
15
+ [key: string]: unknown;
16
+ }
17
+ export declare class NexxusAppModel extends NexxusBaseModel<INexxusAppModel> {
18
+ /**
19
+ * Construct an AppModel from user-supplied or pre-validated data.
20
+ *
21
+ * Pass the application's schema as the second argument to validate + normalize
22
+ * the input (e.g. date strings → integer timestamps). Pass `null` to skip
23
+ * validation — only do that when the data is already trusted (e.g. coming
24
+ * back from storage). The `fromStorage` static factory below is the preferred
25
+ * way to express that intent.
26
+ */
27
+ constructor(props: INexxusAppModel, appSchema: NexxusApplicationSchema | null);
28
+ /**
29
+ * Hydrate an AppModel from already-validated storage. Use when reading back
30
+ * from the database — the data was validated when it was written, so we don't
31
+ * re-validate (saves work and avoids false-positives on legacy/edge-case docs
32
+ * that pre-date a later schema change).
33
+ */
34
+ static fromStorage(props: INexxusAppModel): NexxusAppModel;
9
35
  }
@@ -1,5 +1,8 @@
1
- import { NexxusBaseModel, INexxusBaseModel } from "./BaseModel";
1
+ import { INexxusBaseModel } from "./BaseModel";
2
+ import { NexxusBuiltinModel } from "./BuiltinModel";
2
3
  import { NexxusModelDef } from "../common/ModelTypes";
4
+ import { InferModel } from "../common/InferModel";
5
+ import { NEXXUS_BUILTIN_MODEL_SCHEMAS } from "../common/BuiltinSchemas";
3
6
  import { NexxusUserDetailSchema } from "./User";
4
7
  export interface NexxusApplicationSchema {
5
8
  [modelName: string]: NexxusModelDef;
@@ -7,23 +10,48 @@ export interface NexxusApplicationSchema {
7
10
  export interface NexxusUserTypeConfig {
8
11
  private?: boolean;
9
12
  }
10
- export type NexxusApplicationModelType = INexxusBaseModel<'application'> & {
11
- name: string;
12
- description?: string;
13
+ /**
14
+ * Per-application auth configuration. Lives on the Application document so
15
+ * that each tenant carries its own JWT secret and per-strategy settings.
16
+ *
17
+ * `strategies` is a map keyed by strategy name (must be a subset of the
18
+ * deployment's `api.auth.availableStrategies`). Each value's shape is
19
+ * whatever the strategy's own JSON Schema defines — validated by the
20
+ * corresponding `NexxusAuthStrategy` subclass at instantiation time.
21
+ */
22
+ export interface NexxusApplicationAuthConfig {
23
+ jwtSecret: string;
24
+ jwtExpiresIn?: string;
25
+ strategies: Record<string, unknown>;
26
+ }
27
+ export type INexxusApplication = INexxusBaseModel<'application'> & InferModel<typeof NEXXUS_BUILTIN_MODEL_SCHEMAS.application> & {
13
28
  schema: NexxusApplicationSchema;
14
- authEnabled: boolean;
15
- allowMultipleLogin: boolean | null;
16
29
  userTypes?: {
17
30
  [userType: string]: NexxusUserTypeConfig;
18
31
  };
19
32
  userDetailSchema?: {
20
33
  [userType: string]: NexxusUserDetailSchema;
21
34
  };
35
+ auth?: NexxusApplicationAuthConfig;
36
+ defaultLimit?: number;
37
+ maxLimit?: number;
22
38
  };
23
- export declare class NexxusApplication extends NexxusBaseModel<NexxusApplicationModelType> {
24
- constructor(data: NexxusApplicationModelType);
39
+ export declare class NexxusApplication extends NexxusBuiltinModel<INexxusApplication> {
40
+ constructor(data: INexxusApplication);
25
41
  getSchema(): NexxusApplicationSchema;
26
42
  getUserDetailSchema(userType?: string): NexxusUserDetailSchema | null;
43
+ hasAuthEnabled(): boolean;
44
+ /**
45
+ * Runtime field schema for one of the developer-declared models in this
46
+ * application's `schema` field. Built-in models (user, application) have
47
+ * their own static `getModelSchema` and are not resolved here.
48
+ */
49
+ getAppModelSchema(modelType: string): NexxusModelDef;
50
+ /**
51
+ * Runtime field schema for Application records. Static because the schema
52
+ * is global — not per-app.
53
+ */
54
+ static getModelSchema(): NexxusModelDef;
27
55
  getAppModelFieldType(modelType: string, fieldPath: string): string | undefined;
28
56
  getModelFilterableFields(modelType: string): Set<string>;
29
57
  }