@btravstack/prisma 0.9.0 → 0.10.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/dist/index.cjs CHANGED
@@ -30,27 +30,25 @@ let unthrown = require("unthrown");
30
30
  * rejection propagates to `@unthrown/prisma`'s `try*` twin without this file
31
31
  * needing a `no-throw` exemption.
32
32
  */
33
- const instrument = (client, logger, meter) => {
34
- const operations = meter.createCounter("btravstack.database.operations", { description: "Database queries, by model, operation and outcome" });
33
+ const instrument = (client, observers) => {
35
34
  return client.$extends({ query: { $allModels: { $allOperations: ({ model, operation, args, query }) => {
36
- const label = model ?? "raw";
35
+ const settle = (0, _btravstack_core.observe)(observers, {
36
+ component: "database",
37
+ name: operation,
38
+ attributes: {
39
+ model: model ?? "raw",
40
+ operation
41
+ },
42
+ traced: false
43
+ });
37
44
  return query(args).then((value) => {
38
- operations.add(1, {
39
- model: label,
40
- operation,
41
- outcome: "ok"
42
- });
45
+ settle({ outcome: "ok" });
43
46
  return value;
44
47
  }, (cause) => {
45
- operations.add(1, {
46
- model: label,
47
- operation,
48
- outcome: "error"
48
+ settle({
49
+ outcome: "error",
50
+ cause
49
51
  });
50
- logger.error("a database query failed", {
51
- model: label,
52
- operation
53
- }, cause);
54
52
  return Promise.reject(cause);
55
53
  });
56
54
  } } } });
@@ -83,6 +81,11 @@ const defaultLoader = async () => (await import("@prisma/instrumentation")).Pris
83
81
  * **The skip is logged, not silent.** Telemetry you believe you have and do not
84
82
  * is worse than none, so the one path that quietly produces no spans says so at
85
83
  * `debug` — the level for something that is a choice rather than a fault.
84
+ *
85
+ * This is the one place in this package that still holds a `Logger`, and it is
86
+ * why the module needs one: it is a STARTUP fact rather than an operation, so
87
+ * the `Observers` seam has nothing to settle. An observer writes lines about
88
+ * operations that fail; a missing optional peer is neither.
86
89
  */
87
90
  const loadPrismaInstrumentation = async (logger, load = defaultLoader) => {
88
91
  try {
@@ -114,27 +117,21 @@ const loadPrismaInstrumentation = async (logger, load = defaultLoader) => {
114
117
  * against this same URL *before the process starts*; an application that
115
118
  * migrates itself at boot races every other replica.
116
119
  */
117
- const prismaDatabase = (name) => ({ client, instrumented }) => {
120
+ const prismaDatabase = (name) => ({ client }) => {
118
121
  const config = _btravstack_config.Config.provider(`${name}Config`)(_btravstack_config.Config.object({ url: _btravstack_config.Config.string("DATABASE_URL") }));
119
122
  const DatabasePort = (0, _btravstack_di.Port)(name);
120
123
  const open = (url) => client(new _prisma_adapter_pg.PrismaPg({ connectionString: url }));
121
124
  const port = DatabasePort;
122
- const instrumentedProvider = (0, _btravstack_di.Provider)(port)({
125
+ const clientProvider = (0, _btravstack_di.Provider)(port)({
123
126
  inject: {
124
127
  settings: config.port,
125
- logger: _btravstack_core.Logger,
126
- meter: _btravstack_core.Meter
128
+ observers: _btravstack_core.Observers
127
129
  },
128
- acquire: ({ settings, logger, meter }) => {
129
- return (0, unthrown.fromSafePromise)(Promise.resolve(instrument(open(settings.url), logger, meter)));
130
+ acquire: ({ settings, observers }) => {
131
+ return (0, unthrown.fromSafePromise)(Promise.resolve(instrument(open(settings.url), observers)));
130
132
  },
131
133
  release: (db) => db.$disconnect()
132
134
  });
133
- const plainProvider = (0, _btravstack_di.Provider)(port)({
134
- inject: { settings: config.port },
135
- acquire: ({ settings }) => (0, unthrown.OkAsync)(open(settings.url)),
136
- release: (db) => db.$disconnect()
137
- });
138
135
  const healthCheck = _btravstack_di.Provider.member(_btravstack_core.HealthChecks)({
139
136
  inject: { db: port },
140
137
  sync: ({ db }) => ({
@@ -146,15 +143,15 @@ const prismaDatabase = (name) => ({ client, instrumented }) => {
146
143
  inject: { logger: _btravstack_core.Logger },
147
144
  sync: ({ logger }) => () => loadPrismaInstrumentation(logger)
148
145
  });
149
- const instrumentedModule = (0, _btravstack_di.Module)(name)({
150
- needs: [
151
- _btravstack_config.Env,
152
- _btravstack_core.Logger,
153
- _btravstack_core.Meter
154
- ],
146
+ const chosen = (0, _btravstack_di.Module)(name)({
147
+ needs: [_btravstack_config.Env, _btravstack_core.Logger],
155
148
  provides: [
156
149
  config,
157
- instrumentedProvider,
150
+ _btravstack_di.Provider.member(_btravstack_core.Observers)({
151
+ inject: {},
152
+ value: _btravstack_core.noObserver
153
+ }),
154
+ clientProvider,
158
155
  healthCheck,
159
156
  instrumentation
160
157
  ],
@@ -164,16 +161,7 @@ const prismaDatabase = (name) => ({ client, instrumented }) => {
164
161
  _btravstack_core.Instrumentations
165
162
  ]
166
163
  });
167
- const plainModule = (0, _btravstack_di.Module)(name)({
168
- needs: [_btravstack_config.Env],
169
- provides: [
170
- config,
171
- plainProvider,
172
- healthCheck
173
- ],
174
- exports: [DatabasePort, _btravstack_core.HealthChecks]
175
- });
176
- return Object.assign(instrumented !== false ? instrumentedModule : plainModule, { port: DatabasePort });
164
+ return Object.assign(chosen, { port: DatabasePort });
177
165
  };
178
166
  //#endregion
179
167
  exports.prismaDatabase = prismaDatabase;
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Env } from "@btravstack/config";
2
- import { HealthChecks, Instrumentations, Logger, Meter } from "@btravstack/core";
2
+ import { HealthChecks, Instrumentations, Logger, Observers } 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
@@ -20,7 +20,7 @@ type PrismaLike = {
20
20
  readonly $queryRaw: (query: TemplateStringsArray, ...values: unknown[]) => Promise<unknown>;
21
21
  };
22
22
  /** What {@link prismaDatabase} is handed. */
23
- type PrismaOptions<C extends PrismaLike, Instrumented extends boolean> = {
23
+ type PrismaOptions<C extends PrismaLike> = {
24
24
  /**
25
25
  * Builds the client from the driver adapter, which is already constructed
26
26
  * from the environment's URL.
@@ -31,19 +31,6 @@ type PrismaOptions<C extends PrismaLike, Instrumented extends boolean> = {
31
31
  * the returned type is exactly the one the application will hold.
32
32
  */
33
33
  readonly client: (adapter: PrismaPg) => C;
34
- /**
35
- * Span, count and log every query. **Default `true`**, `false` opts out — the
36
- * same shape as `cache`, `mailer` and `storage`, and as `StartOptions`'
37
- * `signals` and `probes`.
38
- *
39
- * On by default because telemetry that is missing is discovered during an
40
- * incident. The cost is stated rather than hidden: instrumenting puts
41
- * `Logger`, `Meter` and `Tracer` in the provider's dependencies, so a root
42
- * without `observability()` and `otel()` gets a compile error naming all
43
- * three. It also turns on Prisma's own engine-level tracing when
44
- * `@prisma/instrumentation` is installed — no wiring at the root.
45
- */
46
- readonly instrumented?: Instrumented;
47
34
  };
48
35
  /**
49
36
  * A Prisma client whose pool is the application scope's.
@@ -63,15 +50,11 @@ type PrismaOptions<C extends PrismaLike, Instrumented extends boolean> = {
63
50
  * against this same URL *before the process starts*; an application that
64
51
  * migrates itself at boot races every other replica.
65
52
  */
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`, {
67
- readonly url: string;
68
- }>> | Exclude<Logger, HealthChecks | Instrumentations | import("@btravstack/di").PortInstance<string, C> | import("@btravstack/di").PortInstance<`${N}Config`, {
69
- readonly url: string;
70
- }>> | Exclude<Meter, HealthChecks | Instrumentations | import("@btravstack/di").PortInstance<string, C> | import("@btravstack/di").PortInstance<`${N}Config`, {
53
+ declare const prismaDatabase: <const N extends string>(name: N) => <C extends PrismaLike>({ client }: PrismaOptions<C>) => 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 | Observers | import("@btravstack/di").PortInstance<string, C> | import("@btravstack/di").PortInstance<`${N}Config`, {
71
54
  readonly url: string;
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`, {
55
+ }>> | Exclude<Logger, HealthChecks | Instrumentations | Observers | import("@btravstack/di").PortInstance<string, C> | import("@btravstack/di").PortInstance<`${N}Config`, {
73
56
  readonly url: string;
74
- }>>>) & {
57
+ }>>> & {
75
58
  readonly port: PortClassOf<N, C>;
76
59
  };
77
60
  //#endregion
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Env } from "@btravstack/config";
2
- import { HealthChecks, Instrumentations, Logger, Meter } from "@btravstack/core";
2
+ import { HealthChecks, Instrumentations, Logger, Observers } 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
@@ -20,7 +20,7 @@ type PrismaLike = {
20
20
  readonly $queryRaw: (query: TemplateStringsArray, ...values: unknown[]) => Promise<unknown>;
21
21
  };
22
22
  /** What {@link prismaDatabase} is handed. */
23
- type PrismaOptions<C extends PrismaLike, Instrumented extends boolean> = {
23
+ type PrismaOptions<C extends PrismaLike> = {
24
24
  /**
25
25
  * Builds the client from the driver adapter, which is already constructed
26
26
  * from the environment's URL.
@@ -31,19 +31,6 @@ type PrismaOptions<C extends PrismaLike, Instrumented extends boolean> = {
31
31
  * the returned type is exactly the one the application will hold.
32
32
  */
33
33
  readonly client: (adapter: PrismaPg) => C;
34
- /**
35
- * Span, count and log every query. **Default `true`**, `false` opts out — the
36
- * same shape as `cache`, `mailer` and `storage`, and as `StartOptions`'
37
- * `signals` and `probes`.
38
- *
39
- * On by default because telemetry that is missing is discovered during an
40
- * incident. The cost is stated rather than hidden: instrumenting puts
41
- * `Logger`, `Meter` and `Tracer` in the provider's dependencies, so a root
42
- * without `observability()` and `otel()` gets a compile error naming all
43
- * three. It also turns on Prisma's own engine-level tracing when
44
- * `@prisma/instrumentation` is installed — no wiring at the root.
45
- */
46
- readonly instrumented?: Instrumented;
47
34
  };
48
35
  /**
49
36
  * A Prisma client whose pool is the application scope's.
@@ -63,15 +50,11 @@ type PrismaOptions<C extends PrismaLike, Instrumented extends boolean> = {
63
50
  * against this same URL *before the process starts*; an application that
64
51
  * migrates itself at boot races every other replica.
65
52
  */
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`, {
67
- readonly url: string;
68
- }>> | Exclude<Logger, HealthChecks | Instrumentations | import("@btravstack/di").PortInstance<string, C> | import("@btravstack/di").PortInstance<`${N}Config`, {
69
- readonly url: string;
70
- }>> | Exclude<Meter, HealthChecks | Instrumentations | import("@btravstack/di").PortInstance<string, C> | import("@btravstack/di").PortInstance<`${N}Config`, {
53
+ declare const prismaDatabase: <const N extends string>(name: N) => <C extends PrismaLike>({ client }: PrismaOptions<C>) => 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 | Observers | import("@btravstack/di").PortInstance<string, C> | import("@btravstack/di").PortInstance<`${N}Config`, {
71
54
  readonly url: string;
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`, {
55
+ }>> | Exclude<Logger, HealthChecks | Instrumentations | Observers | import("@btravstack/di").PortInstance<string, C> | import("@btravstack/di").PortInstance<`${N}Config`, {
73
56
  readonly url: string;
74
- }>>>) & {
57
+ }>>> & {
75
58
  readonly port: PortClassOf<N, C>;
76
59
  };
77
60
  //#endregion
package/dist/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  import { Config, Env } from "@btravstack/config";
2
- import { HealthCheckFailed, HealthChecks, Instrumentations, Logger, Meter } from "@btravstack/core";
2
+ import { HealthCheckFailed, HealthChecks, Instrumentations, Logger, Observers, noObserver, observe } from "@btravstack/core";
3
3
  import { Module, Port, Provider } from "@btravstack/di";
4
4
  import { PrismaPg } from "@prisma/adapter-pg";
5
- import { OkAsync, fromPromise, fromSafePromise } from "unthrown";
5
+ import { 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
@@ -29,27 +29,25 @@ import { OkAsync, fromPromise, fromSafePromise } from "unthrown";
29
29
  * rejection propagates to `@unthrown/prisma`'s `try*` twin without this file
30
30
  * needing a `no-throw` exemption.
31
31
  */
32
- const instrument = (client, logger, meter) => {
33
- const operations = meter.createCounter("btravstack.database.operations", { description: "Database queries, by model, operation and outcome" });
32
+ const instrument = (client, observers) => {
34
33
  return client.$extends({ query: { $allModels: { $allOperations: ({ model, operation, args, query }) => {
35
- const label = model ?? "raw";
34
+ const settle = observe(observers, {
35
+ component: "database",
36
+ name: operation,
37
+ attributes: {
38
+ model: model ?? "raw",
39
+ operation
40
+ },
41
+ traced: false
42
+ });
36
43
  return query(args).then((value) => {
37
- operations.add(1, {
38
- model: label,
39
- operation,
40
- outcome: "ok"
41
- });
44
+ settle({ outcome: "ok" });
42
45
  return value;
43
46
  }, (cause) => {
44
- operations.add(1, {
45
- model: label,
46
- operation,
47
- outcome: "error"
47
+ settle({
48
+ outcome: "error",
49
+ cause
48
50
  });
49
- logger.error("a database query failed", {
50
- model: label,
51
- operation
52
- }, cause);
53
51
  return Promise.reject(cause);
54
52
  });
55
53
  } } } });
@@ -82,6 +80,11 @@ const defaultLoader = async () => (await import("@prisma/instrumentation")).Pris
82
80
  * **The skip is logged, not silent.** Telemetry you believe you have and do not
83
81
  * is worse than none, so the one path that quietly produces no spans says so at
84
82
  * `debug` — the level for something that is a choice rather than a fault.
83
+ *
84
+ * This is the one place in this package that still holds a `Logger`, and it is
85
+ * why the module needs one: it is a STARTUP fact rather than an operation, so
86
+ * the `Observers` seam has nothing to settle. An observer writes lines about
87
+ * operations that fail; a missing optional peer is neither.
85
88
  */
86
89
  const loadPrismaInstrumentation = async (logger, load = defaultLoader) => {
87
90
  try {
@@ -113,27 +116,21 @@ const loadPrismaInstrumentation = async (logger, load = defaultLoader) => {
113
116
  * against this same URL *before the process starts*; an application that
114
117
  * migrates itself at boot races every other replica.
115
118
  */
116
- const prismaDatabase = (name) => ({ client, instrumented }) => {
119
+ const prismaDatabase = (name) => ({ client }) => {
117
120
  const config = Config.provider(`${name}Config`)(Config.object({ url: Config.string("DATABASE_URL") }));
118
121
  const DatabasePort = Port(name);
119
122
  const open = (url) => client(new PrismaPg({ connectionString: url }));
120
123
  const port = DatabasePort;
121
- const instrumentedProvider = Provider(port)({
124
+ const clientProvider = Provider(port)({
122
125
  inject: {
123
126
  settings: config.port,
124
- logger: Logger,
125
- meter: Meter
127
+ observers: Observers
126
128
  },
127
- acquire: ({ settings, logger, meter }) => {
128
- return fromSafePromise(Promise.resolve(instrument(open(settings.url), logger, meter)));
129
+ acquire: ({ settings, observers }) => {
130
+ return fromSafePromise(Promise.resolve(instrument(open(settings.url), observers)));
129
131
  },
130
132
  release: (db) => db.$disconnect()
131
133
  });
132
- const plainProvider = Provider(port)({
133
- inject: { settings: config.port },
134
- acquire: ({ settings }) => OkAsync(open(settings.url)),
135
- release: (db) => db.$disconnect()
136
- });
137
134
  const healthCheck = Provider.member(HealthChecks)({
138
135
  inject: { db: port },
139
136
  sync: ({ db }) => ({
@@ -145,15 +142,15 @@ const prismaDatabase = (name) => ({ client, instrumented }) => {
145
142
  inject: { logger: Logger },
146
143
  sync: ({ logger }) => () => loadPrismaInstrumentation(logger)
147
144
  });
148
- const instrumentedModule = Module(name)({
149
- needs: [
150
- Env,
151
- Logger,
152
- Meter
153
- ],
145
+ const chosen = Module(name)({
146
+ needs: [Env, Logger],
154
147
  provides: [
155
148
  config,
156
- instrumentedProvider,
149
+ Provider.member(Observers)({
150
+ inject: {},
151
+ value: noObserver
152
+ }),
153
+ clientProvider,
157
154
  healthCheck,
158
155
  instrumentation
159
156
  ],
@@ -163,16 +160,7 @@ const prismaDatabase = (name) => ({ client, instrumented }) => {
163
160
  Instrumentations
164
161
  ]
165
162
  });
166
- const plainModule = Module(name)({
167
- needs: [Env],
168
- provides: [
169
- config,
170
- plainProvider,
171
- healthCheck
172
- ],
173
- exports: [DatabasePort, HealthChecks]
174
- });
175
- return Object.assign(instrumented !== false ? instrumentedModule : plainModule, { port: DatabasePort });
163
+ return Object.assign(chosen, { port: DatabasePort });
176
164
  };
177
165
  //#endregion
178
166
  export { prismaDatabase };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@btravstack/prisma",
3
- "version": "0.9.0",
3
+ "version": "0.10.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.9.0",
57
- "@btravstack/core": "0.9.0",
58
- "@btravstack/di": "0.9.0"
56
+ "@btravstack/config": "0.10.0",
57
+ "@btravstack/di": "0.10.0",
58
+ "@btravstack/core": "0.10.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.9.0",
65
- "@btravstack/core": "^0.9.0",
66
- "@btravstack/di": "^0.9.0"
64
+ "@btravstack/config": "^0.10.0",
65
+ "@btravstack/core": "^0.10.0",
66
+ "@btravstack/di": "^0.10.0"
67
67
  },
68
68
  "peerDependenciesMeta": {
69
69
  "@prisma/instrumentation": {