@btravstack/prisma 0.5.0 → 0.7.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/README.md CHANGED
@@ -32,6 +32,7 @@ import { PrismaPg } from "@prisma/adapter-pg";
32
32
  declare class PrismaClient {
33
33
  constructor(options: { readonly adapter: PrismaPg });
34
34
  $disconnect(): Promise<void>;
35
+ $queryRaw(query: TemplateStringsArray, ...values: unknown[]): Promise<unknown>;
35
36
  $extends(extension: unknown): this;
36
37
  }
37
38
  declare const unthrownPrisma: unknown;
package/dist/index.cjs CHANGED
@@ -60,7 +60,12 @@ const instrument = (client, logger, meter) => {
60
60
  /** Injected so a spec can drive the arm where the optional peer is absent. */
61
61
  const defaultLoader = async () => (await import("@prisma/instrumentation")).PrismaInstrumentation;
62
62
  /**
63
- * Prisma's own OpenTelemetry instrumentation, turned on if it is installed.
63
+ * Prisma's own OpenTelemetry instrumentation, loaded if it is installed.
64
+ *
65
+ * Handed to `@btravstack/observability/otel` through the `Instrumentations`
66
+ * set port rather than registered here: a graph with no SDK collects nothing
67
+ * and this is never called, so engine tracing follows from composing `otel()`
68
+ * instead of from importing this package.
64
69
  *
65
70
  * **A dynamic import, and it has to be**: `@prisma/instrumentation` is an
66
71
  * OPTIONAL peer, so a static import would make every consumer install it. The
@@ -79,7 +84,7 @@ const defaultLoader = async () => (await import("@prisma/instrumentation")).Pris
79
84
  * is worse than none, so the one path that quietly produces no spans says so at
80
85
  * `debug` — the level for something that is a choice rather than a fault.
81
86
  */
82
- const enableTracing = async (logger, load = defaultLoader) => {
87
+ const loadPrismaInstrumentation = async (logger, load = defaultLoader) => {
83
88
  try {
84
89
  const instrumentation = new (await (load()))();
85
90
  instrumentation.enable();
@@ -117,11 +122,10 @@ const prismaDatabase = (name) => ({ client, instrumented }) => {
117
122
  const instrumentedProvider = (0, _btravstack_di.Provider)(port)({
118
123
  settings: config.port,
119
124
  logger: _btravstack_core.Logger,
120
- tracer: _btravstack_core.Tracer,
121
125
  meter: _btravstack_core.Meter
122
126
  }, {
123
- acquire: ({ settings, logger, tracer, meter }) => {
124
- return (0, unthrown.fromSafePromise)(enableTracing(logger).then(() => instrument(open(settings.url), logger, meter)));
127
+ acquire: ({ settings, logger, meter }) => {
128
+ return (0, unthrown.fromSafePromise)(Promise.resolve(instrument(open(settings.url), logger, meter)));
125
129
  },
126
130
  release: (db) => db.$disconnect()
127
131
  });
@@ -129,20 +133,37 @@ const prismaDatabase = (name) => ({ client, instrumented }) => {
129
133
  acquire: ({ settings }) => (0, unthrown.OkAsync)(open(settings.url)),
130
134
  release: (db) => db.$disconnect()
131
135
  });
136
+ const healthCheck = _btravstack_di.Provider.member(_btravstack_core.HealthChecks)({ db: port }, { sync: ({ db }) => ({
137
+ name,
138
+ check: () => (0, unthrown.fromPromise)(db.$queryRaw`SELECT 1`, (cause) => new _btravstack_core.HealthCheckFailed({ reason: cause instanceof Error ? cause.message : "database unreachable" })).map(() => void 0)
139
+ }) });
140
+ const instrumentation = _btravstack_di.Provider.member(_btravstack_core.Instrumentations)({ logger: _btravstack_core.Logger }, { sync: ({ logger }) => () => loadPrismaInstrumentation(logger) });
132
141
  const instrumentedModule = (0, _btravstack_di.Module)(name)({
133
142
  needs: [
134
143
  _btravstack_config.Env,
135
144
  _btravstack_core.Logger,
136
- _btravstack_core.Meter,
137
- _btravstack_core.Tracer
145
+ _btravstack_core.Meter
146
+ ],
147
+ provides: [
148
+ config,
149
+ instrumentedProvider,
150
+ healthCheck,
151
+ instrumentation
138
152
  ],
139
- provides: [config, instrumentedProvider],
140
- exports: [DatabasePort]
153
+ exports: [
154
+ DatabasePort,
155
+ _btravstack_core.HealthChecks,
156
+ _btravstack_core.Instrumentations
157
+ ]
141
158
  });
142
159
  const plainModule = (0, _btravstack_di.Module)(name)({
143
160
  needs: [_btravstack_config.Env],
144
- provides: [config, plainProvider],
145
- exports: [DatabasePort]
161
+ provides: [
162
+ config,
163
+ plainProvider,
164
+ healthCheck
165
+ ],
166
+ exports: [DatabasePort, _btravstack_core.HealthChecks]
146
167
  });
147
168
  return Object.assign(instrumented !== false ? instrumentedModule : plainModule, { port: DatabasePort });
148
169
  };
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Env } from "@btravstack/config";
2
- import { Logger, Meter, Tracer } from "@btravstack/core";
2
+ import { HealthChecks, Instrumentations, Logger, Meter } from "@btravstack/core";
3
3
  import { Module, PortClassOf } from "@btravstack/di";
4
4
  import { PrismaPg } from "@prisma/adapter-pg";
5
5
  //#region src/prisma.d.ts
@@ -11,6 +11,13 @@ import { PrismaPg } from "@prisma/adapter-pg";
11
11
  */
12
12
  type PrismaLike = {
13
13
  readonly $disconnect: () => Promise<void>;
14
+ /**
15
+ * What the health check asks. Part of the contract rather than optional
16
+ * because every generated Prisma client has it, and a probe that has to
17
+ * feature-detect its own client cannot report the difference between "the
18
+ * database is down" and "this client cannot be asked".
19
+ */
20
+ readonly $queryRaw: (query: TemplateStringsArray, ...values: unknown[]) => Promise<unknown>;
14
21
  };
15
22
  /** What {@link prismaDatabase} is handed. */
16
23
  type PrismaOptions<C extends PrismaLike, Instrumented extends boolean> = {
@@ -56,15 +63,13 @@ type PrismaOptions<C extends PrismaLike, Instrumented extends boolean> = {
56
63
  * against this same URL *before the process starts*; an application that
57
64
  * migrates itself at boot races every other replica.
58
65
  */
59
- declare const prismaDatabase: <const N extends string>(name: N) => <C extends PrismaLike, Instrumented extends boolean = true>({ client, instrumented }: PrismaOptions<C, Instrumented>) => (Instrumented extends true ? Module<PortClassOf<N, C> extends (infer T) ? T extends PortClassOf<N, C> ? T extends import("@btravstack/di").AnyPort ? InstanceType<T> : T extends import("@btravstack/di").AnyProvider ? InstanceType<T["port"]> : never : never : never, import("@btravstack/config").ConfigInvalid, Exclude<Env, import("@btravstack/di").PortInstance<string, C> | import("@btravstack/di").PortInstance<`${N}Config`, {
60
- readonly url: string;
61
- }>> | Exclude<Logger, import("@btravstack/di").PortInstance<string, C> | import("@btravstack/di").PortInstance<`${N}Config`, {
66
+ declare const prismaDatabase: <const N extends string>(name: N) => <C extends PrismaLike, Instrumented extends boolean = true>({ client, instrumented }: PrismaOptions<C, Instrumented>) => (Instrumented extends true ? Module<HealthChecks | Instrumentations | (PortClassOf<N, C> extends (infer T) ? T extends PortClassOf<N, C> ? T extends import("@btravstack/di").AnyPort ? InstanceType<T> : T extends import("@btravstack/di").AnyProvider ? InstanceType<T["port"]> : never : never : never), import("@btravstack/config").ConfigInvalid, Exclude<Env, HealthChecks | Instrumentations | import("@btravstack/di").PortInstance<string, C> | import("@btravstack/di").PortInstance<`${N}Config`, {
62
67
  readonly url: string;
63
- }>> | Exclude<Meter, import("@btravstack/di").PortInstance<string, C> | import("@btravstack/di").PortInstance<`${N}Config`, {
68
+ }>> | Exclude<Logger, HealthChecks | Instrumentations | import("@btravstack/di").PortInstance<string, C> | import("@btravstack/di").PortInstance<`${N}Config`, {
64
69
  readonly url: string;
65
- }>> | Exclude<Tracer, import("@btravstack/di").PortInstance<string, C> | import("@btravstack/di").PortInstance<`${N}Config`, {
70
+ }>> | Exclude<Meter, HealthChecks | Instrumentations | import("@btravstack/di").PortInstance<string, C> | import("@btravstack/di").PortInstance<`${N}Config`, {
66
71
  readonly url: string;
67
- }>>> : Module<PortClassOf<N, C> extends (infer T) ? T extends PortClassOf<N, C> ? T extends import("@btravstack/di").AnyPort ? InstanceType<T> : T extends import("@btravstack/di").AnyProvider ? InstanceType<T["port"]> : never : never : never, import("@btravstack/config").ConfigInvalid, Exclude<Env, import("@btravstack/di").PortInstance<string, C> | import("@btravstack/di").PortInstance<`${N}Config`, {
72
+ }>>> : Module<HealthChecks | (PortClassOf<N, C> extends (infer T) ? T extends PortClassOf<N, C> ? T extends import("@btravstack/di").AnyPort ? InstanceType<T> : T extends import("@btravstack/di").AnyProvider ? InstanceType<T["port"]> : never : never : never), import("@btravstack/config").ConfigInvalid, Exclude<Env, HealthChecks | import("@btravstack/di").PortInstance<string, C> | import("@btravstack/di").PortInstance<`${N}Config`, {
68
73
  readonly url: string;
69
74
  }>>>) & {
70
75
  readonly port: PortClassOf<N, C>;
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Env } from "@btravstack/config";
2
- import { Logger, Meter, Tracer } from "@btravstack/core";
2
+ import { HealthChecks, Instrumentations, Logger, Meter } from "@btravstack/core";
3
3
  import { Module, PortClassOf } from "@btravstack/di";
4
4
  import { PrismaPg } from "@prisma/adapter-pg";
5
5
  //#region src/prisma.d.ts
@@ -11,6 +11,13 @@ import { PrismaPg } from "@prisma/adapter-pg";
11
11
  */
12
12
  type PrismaLike = {
13
13
  readonly $disconnect: () => Promise<void>;
14
+ /**
15
+ * What the health check asks. Part of the contract rather than optional
16
+ * because every generated Prisma client has it, and a probe that has to
17
+ * feature-detect its own client cannot report the difference between "the
18
+ * database is down" and "this client cannot be asked".
19
+ */
20
+ readonly $queryRaw: (query: TemplateStringsArray, ...values: unknown[]) => Promise<unknown>;
14
21
  };
15
22
  /** What {@link prismaDatabase} is handed. */
16
23
  type PrismaOptions<C extends PrismaLike, Instrumented extends boolean> = {
@@ -56,15 +63,13 @@ type PrismaOptions<C extends PrismaLike, Instrumented extends boolean> = {
56
63
  * against this same URL *before the process starts*; an application that
57
64
  * migrates itself at boot races every other replica.
58
65
  */
59
- declare const prismaDatabase: <const N extends string>(name: N) => <C extends PrismaLike, Instrumented extends boolean = true>({ client, instrumented }: PrismaOptions<C, Instrumented>) => (Instrumented extends true ? Module<PortClassOf<N, C> extends (infer T) ? T extends PortClassOf<N, C> ? T extends import("@btravstack/di").AnyPort ? InstanceType<T> : T extends import("@btravstack/di").AnyProvider ? InstanceType<T["port"]> : never : never : never, import("@btravstack/config").ConfigInvalid, Exclude<Env, import("@btravstack/di").PortInstance<string, C> | import("@btravstack/di").PortInstance<`${N}Config`, {
60
- readonly url: string;
61
- }>> | Exclude<Logger, import("@btravstack/di").PortInstance<string, C> | import("@btravstack/di").PortInstance<`${N}Config`, {
66
+ declare const prismaDatabase: <const N extends string>(name: N) => <C extends PrismaLike, Instrumented extends boolean = true>({ client, instrumented }: PrismaOptions<C, Instrumented>) => (Instrumented extends true ? Module<HealthChecks | Instrumentations | (PortClassOf<N, C> extends (infer T) ? T extends PortClassOf<N, C> ? T extends import("@btravstack/di").AnyPort ? InstanceType<T> : T extends import("@btravstack/di").AnyProvider ? InstanceType<T["port"]> : never : never : never), import("@btravstack/config").ConfigInvalid, Exclude<Env, HealthChecks | Instrumentations | import("@btravstack/di").PortInstance<string, C> | import("@btravstack/di").PortInstance<`${N}Config`, {
62
67
  readonly url: string;
63
- }>> | Exclude<Meter, import("@btravstack/di").PortInstance<string, C> | import("@btravstack/di").PortInstance<`${N}Config`, {
68
+ }>> | Exclude<Logger, HealthChecks | Instrumentations | import("@btravstack/di").PortInstance<string, C> | import("@btravstack/di").PortInstance<`${N}Config`, {
64
69
  readonly url: string;
65
- }>> | Exclude<Tracer, import("@btravstack/di").PortInstance<string, C> | import("@btravstack/di").PortInstance<`${N}Config`, {
70
+ }>> | Exclude<Meter, HealthChecks | Instrumentations | import("@btravstack/di").PortInstance<string, C> | import("@btravstack/di").PortInstance<`${N}Config`, {
66
71
  readonly url: string;
67
- }>>> : Module<PortClassOf<N, C> extends (infer T) ? T extends PortClassOf<N, C> ? T extends import("@btravstack/di").AnyPort ? InstanceType<T> : T extends import("@btravstack/di").AnyProvider ? InstanceType<T["port"]> : never : never : never, import("@btravstack/config").ConfigInvalid, Exclude<Env, import("@btravstack/di").PortInstance<string, C> | import("@btravstack/di").PortInstance<`${N}Config`, {
72
+ }>>> : Module<HealthChecks | (PortClassOf<N, C> extends (infer T) ? T extends PortClassOf<N, C> ? T extends import("@btravstack/di").AnyPort ? InstanceType<T> : T extends import("@btravstack/di").AnyProvider ? InstanceType<T["port"]> : never : never : never), import("@btravstack/config").ConfigInvalid, Exclude<Env, HealthChecks | import("@btravstack/di").PortInstance<string, C> | import("@btravstack/di").PortInstance<`${N}Config`, {
68
73
  readonly url: string;
69
74
  }>>>) & {
70
75
  readonly port: PortClassOf<N, C>;
package/dist/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  import { Config, Env } from "@btravstack/config";
2
- import { Logger, Meter, Tracer } from "@btravstack/core";
2
+ import { HealthCheckFailed, HealthChecks, Instrumentations, Logger, Meter } from "@btravstack/core";
3
3
  import { Module, Port, Provider } from "@btravstack/di";
4
4
  import { PrismaPg } from "@prisma/adapter-pg";
5
- import { OkAsync, fromSafePromise } from "unthrown";
5
+ import { OkAsync, fromPromise, fromSafePromise } from "unthrown";
6
6
  //#region src/instrument.ts
7
7
  /**
8
8
  * Every query, wrapped: a span around it, a count of how it came out, and a log
@@ -59,7 +59,12 @@ const instrument = (client, logger, meter) => {
59
59
  /** Injected so a spec can drive the arm where the optional peer is absent. */
60
60
  const defaultLoader = async () => (await import("@prisma/instrumentation")).PrismaInstrumentation;
61
61
  /**
62
- * Prisma's own OpenTelemetry instrumentation, turned on if it is installed.
62
+ * Prisma's own OpenTelemetry instrumentation, loaded if it is installed.
63
+ *
64
+ * Handed to `@btravstack/observability/otel` through the `Instrumentations`
65
+ * set port rather than registered here: a graph with no SDK collects nothing
66
+ * and this is never called, so engine tracing follows from composing `otel()`
67
+ * instead of from importing this package.
63
68
  *
64
69
  * **A dynamic import, and it has to be**: `@prisma/instrumentation` is an
65
70
  * OPTIONAL peer, so a static import would make every consumer install it. The
@@ -78,7 +83,7 @@ const defaultLoader = async () => (await import("@prisma/instrumentation")).Pris
78
83
  * is worse than none, so the one path that quietly produces no spans says so at
79
84
  * `debug` — the level for something that is a choice rather than a fault.
80
85
  */
81
- const enableTracing = async (logger, load = defaultLoader) => {
86
+ const loadPrismaInstrumentation = async (logger, load = defaultLoader) => {
82
87
  try {
83
88
  const instrumentation = new (await (load()))();
84
89
  instrumentation.enable();
@@ -116,11 +121,10 @@ const prismaDatabase = (name) => ({ client, instrumented }) => {
116
121
  const instrumentedProvider = Provider(port)({
117
122
  settings: config.port,
118
123
  logger: Logger,
119
- tracer: Tracer,
120
124
  meter: Meter
121
125
  }, {
122
- acquire: ({ settings, logger, tracer, meter }) => {
123
- return fromSafePromise(enableTracing(logger).then(() => instrument(open(settings.url), logger, meter)));
126
+ acquire: ({ settings, logger, meter }) => {
127
+ return fromSafePromise(Promise.resolve(instrument(open(settings.url), logger, meter)));
124
128
  },
125
129
  release: (db) => db.$disconnect()
126
130
  });
@@ -128,20 +132,37 @@ const prismaDatabase = (name) => ({ client, instrumented }) => {
128
132
  acquire: ({ settings }) => OkAsync(open(settings.url)),
129
133
  release: (db) => db.$disconnect()
130
134
  });
135
+ const healthCheck = Provider.member(HealthChecks)({ db: port }, { sync: ({ db }) => ({
136
+ name,
137
+ check: () => fromPromise(db.$queryRaw`SELECT 1`, (cause) => new HealthCheckFailed({ reason: cause instanceof Error ? cause.message : "database unreachable" })).map(() => void 0)
138
+ }) });
139
+ const instrumentation = Provider.member(Instrumentations)({ logger: Logger }, { sync: ({ logger }) => () => loadPrismaInstrumentation(logger) });
131
140
  const instrumentedModule = Module(name)({
132
141
  needs: [
133
142
  Env,
134
143
  Logger,
135
- Meter,
136
- Tracer
144
+ Meter
145
+ ],
146
+ provides: [
147
+ config,
148
+ instrumentedProvider,
149
+ healthCheck,
150
+ instrumentation
137
151
  ],
138
- provides: [config, instrumentedProvider],
139
- exports: [DatabasePort]
152
+ exports: [
153
+ DatabasePort,
154
+ HealthChecks,
155
+ Instrumentations
156
+ ]
140
157
  });
141
158
  const plainModule = Module(name)({
142
159
  needs: [Env],
143
- provides: [config, plainProvider],
144
- exports: [DatabasePort]
160
+ provides: [
161
+ config,
162
+ plainProvider,
163
+ healthCheck
164
+ ],
165
+ exports: [DatabasePort, HealthChecks]
145
166
  });
146
167
  return Object.assign(instrumented !== false ? instrumentedModule : plainModule, { port: DatabasePort });
147
168
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@btravstack/prisma",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "The Prisma starter for @btravstack/core: DATABASE_URL through Config, the Postgres driver adapter, and a client whose pool is the application scope's",
5
5
  "keywords": [
6
6
  "database",
@@ -53,17 +53,17 @@
53
53
  "typescript": "7.0.2",
54
54
  "unthrown": "5.7.0",
55
55
  "vitest": "4.1.11",
56
- "@btravstack/config": "0.5.0",
57
- "@btravstack/core": "0.5.0",
58
- "@btravstack/di": "0.5.0"
56
+ "@btravstack/config": "0.7.0",
57
+ "@btravstack/core": "0.7.0",
58
+ "@btravstack/di": "0.7.0"
59
59
  },
60
60
  "peerDependencies": {
61
61
  "@prisma/adapter-pg": "^7.0.0",
62
62
  "@prisma/instrumentation": "^7.0.0",
63
63
  "unthrown": "^5.0.0",
64
- "@btravstack/config": "^0.5.0",
65
- "@btravstack/core": "^0.5.0",
66
- "@btravstack/di": "^0.5.0"
64
+ "@btravstack/config": "^0.7.0",
65
+ "@btravstack/core": "^0.7.0",
66
+ "@btravstack/di": "^0.7.0"
67
67
  },
68
68
  "peerDependenciesMeta": {
69
69
  "@prisma/instrumentation": {