Skip to content

Commit ea4fafb

Browse files
committed
feat: follow the addon renames, and make the Datadog preload actually work
Three catalog packages were renamed on npm: @imqueue/opentelemetry-instrumentation-imqueue to @imqueue/opentelemetry, @imqueue/sequelize to @imqueue/pg-sequelize, and @imqueue/dd-trace to @imqueue/datadog. No shim was published, so the old names stay installable and frozen. Catalog ids are unchanged — `dd-trace` and `sequelize` stay as they are. They are what `--packages` takes and what ~/.imq/config.json and .imqrc.json hold, and validateSelection() rejects an id it does not know, so renaming them would turn every stored selection into a hard "Unknown package" error. The wiki now says plainly that two ids no longer match their npm names. Fleet probes accept BOTH names per member. usesMember() tested one exact string, so swapping in the new names alone would have made every unmigrated service invisible: a ten-service Sequelize fleet would read as "no ORM anywhere" and the prompt would recommend pg-prisma into it. No error, no failing test either — the existing assertions are all about ids. ProbeMember.dep now takes a string or a list, and four cases cover old-only and mixed fleets. Also fixes a bug the rename would otherwise have been blamed for: the dd-trace addon's preload was `import '@imqueue/dd-trace';`, which installs the tracing hooks but never calls tracer.init() — and init() is what enables the imq integration and starts reporting. Every service ever scaffolded with that addon has been sending nothing to Datadog. The preload now imports a generated src/tracer.ts that calls init(), mirroring how opentelemetry uses src/telemetry.ts. It has to be a separate module: %ADDON_PRELOAD sits at the top of src/index.ts and ESM evaluates all imports before any body statement, so an inline init() would run after the config and service-class modules. generateAddons() had no test coverage at all; it has three now, including one asserting the generated tracer calls init(). The generated import specifiers are string content, so tsc cannot check them — a stale one only shows up at the user's npm install. README's orm group listed `prisma`, which was replaced by `pg-prisma` in b27a4be.
1 parent 6902399 commit ea4fafb

8 files changed

Lines changed: 180 additions & 22 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ new addons can be published without a CLI release. Groups marked *exclusive*
158158
allow a single choice:
159159

160160
- **tracing** (exclusive): `dd-trace`, `opentelemetry`
161-
- **orm** (exclusive): `sequelize`, `prisma`
161+
- **orm** (exclusive): `sequelize`, `pg-prisma`
162162
- **features**: `pg-cache`, `pg-pubsub`, `tag-cache`, `job`, `net`,
163163
`http-protect`, `graphql-dependency`, `type-graphql-dependency`
164164

lib/catalog.json

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"title": "OpenTelemetry instrumentation",
2020
"pick": "Recommended: vendor-neutral, so the backend can change without touching service code.",
2121
"deps": {
22-
"@imqueue/opentelemetry-instrumentation-imqueue": "*",
22+
"@imqueue/opentelemetry": "*",
2323
"@opentelemetry/api": "*",
2424
"@opentelemetry/instrumentation": "*",
2525
"@opentelemetry/resources": "*",
@@ -44,22 +44,23 @@
4444
},
4545
"dd-trace": {
4646
"group": "tracing",
47-
"title": "Datadog APM (dd-trace)",
47+
"title": "Datadog APM",
4848
"hint": "already on Datadog",
4949
"pick": "For a fleet already standing on Datadog\u2019s own agent; otherwise take opentelemetry.",
50-
"deps": { "@imqueue/dd-trace": "*" },
51-
"snippets": { "preload": "import '@imqueue/dd-trace';" },
50+
"deps": { "@imqueue/datadog": "*" },
51+
"snippets": { "preload": "import './tracer.js';" },
5252
"env": ["DD_AGENT_HOST", "DD_TRACE_ENABLED"],
5353
"instructions": [
54+
"Datadog: tracing is initialized in src/tracer.ts (imported first at start-up).",
5455
"Datadog: ensure a Datadog agent is reachable via DD_AGENT_HOST."
5556
]
5657
},
5758
"sequelize": {
5859
"group": "orm",
59-
"title": "Sequelize ORM + @imqueue/sequelize toolkit",
60+
"title": "Sequelize ORM + @imqueue/pg-sequelize toolkit",
6061
"hint": "match a Sequelize fleet",
6162
"pick": "For a service joining a fleet already built on Sequelize, where matching the existing stack is worth more than the default. Migrating the fleet to pg-prisma is worth proposing, as separate work.",
62-
"deps": { "@imqueue/sequelize": "*", "sequelize": "*" },
63+
"deps": { "@imqueue/pg-sequelize": "*", "sequelize": "*" },
6364
"instructions": [
6465
"Sequelize: configure your database connection in config.ts."
6566
]

src/catalog/fleet.ts

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,16 @@ import { VAR_HOME, resolve } from '../../lib/index.js';
4242
interface ProbeMember {
4343
/** Catalog id, provider id — whatever the prompt for this group selects. */
4444
id: string;
45-
/** A dependency in the service's package.json. */
46-
dep?: string;
45+
/**
46+
* A dependency in the service's package.json, or several.
47+
*
48+
* A list is how a renamed package stays detectable: every name the package
49+
* has shipped under counts as evidence. Without that, the rename of an addon
50+
* would make every service that has not migrated yet invisible to the probe,
51+
* and the group would report nothing detected — no error, just a
52+
* recommendation that steers a fleet off its own stack.
53+
*/
54+
dep?: string | string[];
4755
/** A path inside the service, file or directory. */
4856
files?: string[];
4957
/** A substring of a remote URL in the service's .git/config. */
@@ -102,7 +110,10 @@ const PROBES: GroupProbe[] = [
102110
label: 'ORM',
103111
kind: 'catalog',
104112
members: [
105-
{ id: 'sequelize', dep: '@imqueue/sequelize' },
113+
{
114+
id: 'sequelize',
115+
dep: ['@imqueue/pg-sequelize', '@imqueue/sequelize'],
116+
},
106117
{ id: 'pg-prisma', dep: '@imqueue/pg-prisma' },
107118
],
108119
recommended: 'pg-prisma',
@@ -115,9 +126,15 @@ const PROBES: GroupProbe[] = [
115126
members: [
116127
{
117128
id: 'opentelemetry',
118-
dep: '@imqueue/opentelemetry-instrumentation-imqueue',
129+
dep: [
130+
'@imqueue/opentelemetry',
131+
'@imqueue/opentelemetry-instrumentation-imqueue',
132+
],
133+
},
134+
{
135+
id: 'dd-trace',
136+
dep: ['@imqueue/datadog', '@imqueue/dd-trace'],
119137
},
120-
{ id: 'dd-trace', dep: '@imqueue/dd-trace' },
121138
],
122139
recommended: 'opentelemetry',
123140
baseline: 'opentelemetry',
@@ -334,8 +351,12 @@ function usesMember(
334351
remotes: string,
335352
member: ProbeMember,
336353
): boolean {
337-
if (member.dep && deps.has(member.dep)) {
338-
return true;
354+
if (member.dep) {
355+
const names = Array.isArray(member.dep) ? member.dep : [member.dep];
356+
357+
if (names.some(name => deps.has(name))) {
358+
return true;
359+
}
339360
}
340361

341362
if (member.files?.some(file => existsSync(join(dir, file)))) {

src/service/create-scaffold.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,31 @@ function telemetryServiceName(
718718
};
719719
}
720720

721+
/**
722+
* Datadog APM setup.
723+
*
724+
* @remarks
725+
* A separate module rather than an inline statement in the preload slot, and that
726+
* is load-bearing. `%ADDON_PRELOAD` sits at the top of the generated
727+
* `src/index.ts`, and ESM evaluates every import of a module before any of its
728+
* body statements — so an inline `tracer.init()` there would run *after*
729+
* `./config.js` and the service class module had already been evaluated, which is
730+
* exactly what it needs to precede. The catalog's preload therefore imports this
731+
* file, and the `init()` call happens during that import.
732+
*/
733+
function tracerModule(header: string): string {
734+
return `${header}
735+
import tracer from '@imqueue/datadog';
736+
737+
// Importing the package installs the tracing hooks; init() is what enables the
738+
// imq integration and starts reporting. Every client and service constructed
739+
// after this point is traced, with no change to application code.
740+
tracer.init();
741+
742+
export default tracer;
743+
`;
744+
}
745+
721746
/** OpenTelemetry setup without a trace exporter (add one, e.g. the gcp pkg). */
722747
function telemetryBase(
723748
header: string,
@@ -730,7 +755,7 @@ function telemetryBase(
730755
${telemetryEnvDefaultsImport(isV2)}import {
731756
ImqueueInstrumentation,
732757
type RpcModule,
733-
} from '@imqueue/opentelemetry-instrumentation-imqueue';
758+
} from '@imqueue/opentelemetry';
734759
import { resourceFromAttributes } from '@opentelemetry/resources';
735760
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
736761
import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
@@ -769,7 +794,7 @@ ${telemetryEnvDefaultsImport(isV2)}import { TraceExporter } from '@google-cloud/
769794
import {
770795
ImqueueInstrumentation,
771796
type RpcModule,
772-
} from '@imqueue/opentelemetry-instrumentation-imqueue';
797+
} from '@imqueue/opentelemetry';
773798
import { resourceFromAttributes } from '@opentelemetry/resources';
774799
import {
775800
BatchSpanProcessor,
@@ -889,6 +914,11 @@ export function generateAddons(
889914
);
890915
}
891916

917+
if (has('dd-trace')) {
918+
console.log('Generating Datadog setup...');
919+
touch(resolve(path, 'src', 'tracer.ts'), tracerModule(header));
920+
}
921+
892922
if (hasPgPrisma) {
893923
console.log('Generating @imqueue/pg-prisma setup...');
894924

test/src/catalog/catalog.spec.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,12 @@ describe('package catalog', () => {
101101
it('should aggregate deps, snippets, env and instructions', () => {
102102
const addons = resolveAddons(['dd-trace', 'pg-cache'], catalog);
103103

104-
assert.equal(addons.deps['@imqueue/dd-trace'], '*');
104+
assert.equal(addons.deps['@imqueue/datadog'], '*');
105105
assert.equal(addons.deps['@imqueue/pg-cache'], '*');
106-
assert.match(addons.preload, /@imqueue\/dd-trace/);
106+
// The preload imports the generated ./tracer.js rather than the
107+
// package: a bare `import '@imqueue/datadog'` installs the hooks but
108+
// never calls tracer.init(), so nothing is reported.
109+
assert.match(addons.preload, /\.\/tracer\.js/);
107110
assert.ok(addons.hasSnippets);
108111
assert.ok(addons.env.includes('DD_AGENT_HOST'));
109112
assert.ok(addons.instructions.length > 0);

test/src/catalog/fleet.spec.ts

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,20 @@ function service(root: string, name: string, deps: string[]): void {
7878
}
7979

8080
const RPC = '@imqueue/rpc';
81-
const OTEL = '@imqueue/opentelemetry-instrumentation-imqueue';
82-
const DD = '@imqueue/dd-trace';
83-
const SEQ = '@imqueue/sequelize';
81+
const OTEL = '@imqueue/opentelemetry';
82+
const DD = '@imqueue/datadog';
83+
const SEQ = '@imqueue/pg-sequelize';
8484
const PRISMA = '@imqueue/pg-prisma';
8585

86+
// The names these three shipped under before the rename. Existing services still
87+
// declare them — deprecating a package does not uninstall it — so the probes must
88+
// keep recognising them. Without these cases a rename that missed the old names
89+
// would leave every unmigrated fleet reading as "nothing detected", and the suite
90+
// would stay green because the assertions elsewhere are all about catalog ids.
91+
const OTEL_OLD = '@imqueue/opentelemetry-instrumentation-imqueue';
92+
const DD_OLD = '@imqueue/dd-trace';
93+
const SEQ_OLD = '@imqueue/sequelize';
94+
8695
describe('analyseFleet()', () => {
8796
const fleets: string[] = [];
8897

@@ -124,6 +133,30 @@ describe('analyseFleet()', () => {
124133
);
125134
});
126135

136+
it('still reads a fleet on the pre-rename ORM name as sequelize', () => {
137+
const root = fleet();
138+
139+
service(root, 'auth', [RPC, SEQ_OLD]);
140+
service(root, 'billing', [RPC, SEQ_OLD]);
141+
142+
const analysis = analyseFleet(root);
143+
144+
assert.equal(orm(analysis).propose, 'sequelize');
145+
assert.equal(orm(analysis).counts.sequelize, 2);
146+
});
147+
148+
it('counts old and new ORM names as the same member', () => {
149+
const root = fleet();
150+
151+
service(root, 'auth', [RPC, SEQ]);
152+
service(root, 'billing', [RPC, SEQ_OLD]);
153+
154+
const analysis = analyseFleet(root);
155+
156+
assert.equal(orm(analysis).propose, 'sequelize');
157+
assert.equal(orm(analysis).counts.sequelize, 2);
158+
});
159+
127160
it('reads a Prisma fleet as pg-prisma', () => {
128161
const root = fleet();
129162

@@ -306,6 +339,30 @@ describe('analyseFleet() tracing', () => {
306339
assert.match(fleetNotes(analysis).tracing, /^Preselected dd-trace/);
307340
});
308341

342+
it('still reads a fleet on the pre-rename tracing names', () => {
343+
const root = fleet();
344+
345+
service(root, 'auth', [RPC, DD_OLD]);
346+
service(root, 'billing', [RPC, DD_OLD]);
347+
348+
const analysis = analyseFleet(root);
349+
350+
assert.equal(tracing(analysis).propose, 'dd-trace');
351+
assert.equal(tracing(analysis).counts['dd-trace'], 2);
352+
});
353+
354+
it('counts old and new OpenTelemetry names as the same member', () => {
355+
const root = fleet();
356+
357+
service(root, 'auth', [RPC, OTEL]);
358+
service(root, 'billing', [RPC, OTEL_OLD]);
359+
360+
const analysis = analyseFleet(root);
361+
362+
assert.equal(tracing(analysis).propose, 'opentelemetry');
363+
assert.equal(tracing(analysis).counts.opentelemetry, 2);
364+
});
365+
309366
it('proposes the recommended backend when a fleet uses both', () => {
310367
const root = fleet();
311368

test/src/service/create-scaffold.spec.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {
3838
buildServiceTokens,
3939
compileTemplate,
4040
ensureTemplate,
41+
generateAddons,
4142
isEsmService,
4243
loadTemplateManifest,
4344
overlayFragments,
@@ -227,4 +228,43 @@ describe('service create scaffolding', () => {
227228
);
228229
});
229230
});
231+
232+
describe('generateAddons()', () => {
233+
function addons(id: string): string {
234+
const sub = join(dir, `addons-${id}`);
235+
236+
mkdirSync(join(sub, 'src'), { recursive: true });
237+
generateAddons(sub, '// header', [id], true, 'svc');
238+
239+
return sub;
240+
}
241+
242+
it('should write a telemetry module for opentelemetry', () => {
243+
const sub = addons('opentelemetry');
244+
const src = readFileSync(join(sub, 'src', 'telemetry.ts'), 'utf8');
245+
246+
// the import specifier is generated source: tsc cannot check it,
247+
// and a stale one only fails at the user's npm install
248+
assert.match(src, /from '@imqueue\/opentelemetry'/);
249+
assert.doesNotMatch(src, /opentelemetry-instrumentation-imqueue/);
250+
});
251+
252+
it('should write a tracer module for dd-trace that calls init()', () => {
253+
const sub = addons('dd-trace');
254+
const src = readFileSync(join(sub, 'src', 'tracer.ts'), 'utf8');
255+
256+
assert.match(src, /from '@imqueue\/datadog'/);
257+
// init() is the whole point of the file. Importing the package alone
258+
// installs the hooks but reports nothing, which is what the previous
259+
// bare-import preload did.
260+
assert.match(src, /tracer\.init\(\);/);
261+
});
262+
263+
it('should write nothing for a deps-only addon', () => {
264+
const sub = addons('pg-cache');
265+
266+
assert.ok(!existsSync(join(sub, 'src', 'telemetry.ts')));
267+
assert.ok(!existsSync(join(sub, 'src', 'tracer.ts')));
268+
});
269+
});
230270
});

wiki/Package-Catalog.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ service that talks to no database it is the right one.
3636
| **ORM / database** | yes | `pg-prisma`, `sequelize` |
3737
| **Service features** | no | `pg-cache`, `pg-pubsub`, `tag-cache`, `job`, `net`, `http-protect`, `graphql-dependency`, `type-graphql-dependency` |
3838

39+
These are catalog **ids** — what `--packages` takes and what a saved config
40+
holds — not npm package names, and two of them no longer match. `dd-trace`
41+
installs `@imqueue/datadog` and `sequelize` installs `@imqueue/pg-sequelize`,
42+
both renamed while the ids stayed put so that existing configs and `.imqrc.json`
43+
files keep working.
44+
3945
## What each addon does when selected
4046

4147
For every selected package the scaffolder:
@@ -76,7 +82,7 @@ recommended one, with a line above the list saying why:
7682
pg-prisma is worth considering — as its own piece of work, not as part of this.
7783
(none)
7884
Prisma ORM + @imqueue/pg-prisma toolkit
79-
❯ Sequelize ORM + @imqueue/sequelize toolkit (recommended)
85+
❯ Sequelize ORM + @imqueue/pg-sequelize toolkit (recommended)
8086
```
8187

8288
A new service in an established fleet belongs on the fleet's stack: matching

0 commit comments

Comments
 (0)