@geekmidas/cloud 10.0.0-alpha.38 → 10.0.0-alpha.39
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geekmidas/cloud",
|
|
3
|
-
"version": "10.0.0-alpha.
|
|
3
|
+
"version": "10.0.0-alpha.39",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"lodash.snakecase": "~4.1.1",
|
|
44
|
-
"@geekmidas/envkit": "10.0.0-alpha.
|
|
45
|
-
"@geekmidas/manifest": "10.0.0-alpha.
|
|
44
|
+
"@geekmidas/envkit": "10.0.0-alpha.39",
|
|
45
|
+
"@geekmidas/manifest": "10.0.0-alpha.39"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/lodash.get": "~4.4.9",
|
|
@@ -58,10 +58,10 @@
|
|
|
58
58
|
"@pulumi/aws": ">=7.47.0",
|
|
59
59
|
"@pulumi/random": ">=4.21.2",
|
|
60
60
|
"@pulumi/pulumi": ">=3.263.0",
|
|
61
|
-
"@geekmidas/manifest": "^10.0.0-alpha.
|
|
62
|
-
"@geekmidas/storage": "^10.0.0-alpha.
|
|
63
|
-
"@geekmidas/events": "^10.0.0-alpha.
|
|
64
|
-
"@geekmidas/db": "^10.0.0-alpha.
|
|
61
|
+
"@geekmidas/manifest": "^10.0.0-alpha.39",
|
|
62
|
+
"@geekmidas/storage": "^10.0.0-alpha.39",
|
|
63
|
+
"@geekmidas/events": "^10.0.0-alpha.39",
|
|
64
|
+
"@geekmidas/db": "^10.0.0-alpha.39"
|
|
65
65
|
},
|
|
66
66
|
"peerDependenciesMeta": {
|
|
67
67
|
"@geekmidas/manifest": {
|
|
@@ -44,6 +44,12 @@ function manifest(): ConstructManifest {
|
|
|
44
44
|
id: 'Emails',
|
|
45
45
|
fifo: true,
|
|
46
46
|
provides: keys('Emails', 'queue', ['publisherConnectionString']),
|
|
47
|
+
// The queue's one consumer, as the build folds it in.
|
|
48
|
+
worker: {
|
|
49
|
+
id: 'EmailsWorker',
|
|
50
|
+
handler: '.gkm/aws-lambda/queues/emails.handler',
|
|
51
|
+
dependencies: [{ target: 'Uploads', kind: 'objects' }],
|
|
52
|
+
},
|
|
47
53
|
},
|
|
48
54
|
Users: {
|
|
49
55
|
kind: 'topic',
|
|
@@ -172,6 +178,27 @@ describe('fromManifest', () => {
|
|
|
172
178
|
expect(argsOf(provisioned.Orders)).toMatchObject({ vpc, version: '18' });
|
|
173
179
|
});
|
|
174
180
|
|
|
181
|
+
it('subscribes each queue’s one consumer, linked to the queue and what it reaches', () => {
|
|
182
|
+
vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
183
|
+
|
|
184
|
+
const provisioned = fromManifest(stack, manifest(), overrides, {
|
|
185
|
+
cache: 'db',
|
|
186
|
+
email: 'smtp',
|
|
187
|
+
});
|
|
188
|
+
const { subscribed } = provisioned.Emails as unknown as {
|
|
189
|
+
subscribed: { subscriber: { handler: string; link: unknown[] } }[];
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
expect(subscribed).toHaveLength(1);
|
|
193
|
+
expect(subscribed[0]?.subscriber.handler).toBe(
|
|
194
|
+
'.gkm/aws-lambda/queues/emails.handler',
|
|
195
|
+
);
|
|
196
|
+
expect(subscribed[0]?.subscriber.link).toEqual([
|
|
197
|
+
provisioned.Emails,
|
|
198
|
+
provisioned.Uploads,
|
|
199
|
+
]);
|
|
200
|
+
});
|
|
201
|
+
|
|
175
202
|
it('builds the site against the addresses its edges resolve to', () => {
|
|
176
203
|
vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
177
204
|
|
|
@@ -5,9 +5,11 @@ import { ObjectStorage } from '../aws/ObjectStorage';
|
|
|
5
5
|
import { type ProvidesMismatch, UnknownDeclarationKind } from '../errors';
|
|
6
6
|
import {
|
|
7
7
|
assertProvides,
|
|
8
|
+
PROVISIONED_ELSEWHERE,
|
|
8
9
|
type ProvisionedManifest,
|
|
9
10
|
provisionerFor,
|
|
10
11
|
resolveEdges,
|
|
12
|
+
subscribeConsumers,
|
|
11
13
|
} from '../fromManifest';
|
|
12
14
|
|
|
13
15
|
/**
|
|
@@ -337,3 +339,64 @@ describe('ExternalApi', () => {
|
|
|
337
339
|
).toEqual(['PAY_FAST_CREDENTIALS', 'PAY_FAST_URL']);
|
|
338
340
|
});
|
|
339
341
|
});
|
|
342
|
+
|
|
343
|
+
describe('a queue’s one consumer', () => {
|
|
344
|
+
it('is subscribed from the handler the build wrote, linked to what it depends on', () => {
|
|
345
|
+
const consumed: { handler: string; link?: unknown[] }[] = [];
|
|
346
|
+
const uploads = { _id: 'Uploads', _type: 'sst.aws.Bucket' };
|
|
347
|
+
const provisioned = {
|
|
348
|
+
Uploads: uploads,
|
|
349
|
+
Emails: {
|
|
350
|
+
_id: 'Emails',
|
|
351
|
+
_type: 'sst.aws.Queue',
|
|
352
|
+
consume: (consumer: { handler: string; link?: unknown[] }) =>
|
|
353
|
+
consumed.push(consumer),
|
|
354
|
+
},
|
|
355
|
+
} as unknown as ProvisionedManifest;
|
|
356
|
+
|
|
357
|
+
subscribeConsumers(
|
|
358
|
+
{
|
|
359
|
+
Uploads: { kind: 'objects', id: 'Uploads', provides: ['UPLOADS_URL'] },
|
|
360
|
+
Emails: {
|
|
361
|
+
kind: 'queue',
|
|
362
|
+
id: 'Emails',
|
|
363
|
+
provides: ['EMAILS_PUBLISHER_CONNECTION_STRING'],
|
|
364
|
+
worker: {
|
|
365
|
+
id: 'EmailsWorker',
|
|
366
|
+
handler: '.gkm/aws-lambda/queues/emails.handler',
|
|
367
|
+
dependencies: [{ target: 'Uploads', kind: 'objects' }],
|
|
368
|
+
},
|
|
369
|
+
},
|
|
370
|
+
} as never,
|
|
371
|
+
provisioned,
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
expect(consumed).toEqual([
|
|
375
|
+
{ handler: '.gkm/aws-lambda/queues/emails.handler', link: [uploads] },
|
|
376
|
+
]);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
it('subscribes through SST, with the queue itself linked', async () => {
|
|
380
|
+
const { Queue } = await import('../aws/Queue');
|
|
381
|
+
const queue = new Queue({} as never, 'Emails');
|
|
382
|
+
|
|
383
|
+
queue.consume({ handler: 'emails.handler', link: [], batchSize: 5 });
|
|
384
|
+
|
|
385
|
+
const [{ subscriber, args }] = (
|
|
386
|
+
queue as unknown as { subscribed: { subscriber: any; args: unknown }[] }
|
|
387
|
+
).subscribed;
|
|
388
|
+
expect(subscriber.handler).toBe('emails.handler');
|
|
389
|
+
expect(subscriber.link).toEqual([queue]);
|
|
390
|
+
expect(args).toEqual({ batch: { size: 5 } });
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
describe('kinds provisioned elsewhere', () => {
|
|
395
|
+
it('skips a worker, which is a process rather than a resource', () => {
|
|
396
|
+
// A worker in the manifest used to throw UnknownDeclarationKind at synth,
|
|
397
|
+
// so any app with background work could not be deployed to AWS at all.
|
|
398
|
+
expect(PROVISIONED_ELSEWHERE.has('worker')).toBe(true);
|
|
399
|
+
expect(PROVISIONED_ELSEWHERE.has('mobile-app')).toBe(true);
|
|
400
|
+
expect(PROVISIONED_ELSEWHERE.has('queue')).toBe(false);
|
|
401
|
+
});
|
|
402
|
+
});
|
package/src/sst/aws/Queue.ts
CHANGED
|
@@ -11,7 +11,8 @@ import type { StackType } from '../Stack';
|
|
|
11
11
|
* work queue. Link it to a producer and the runtime resolves `<NAME>_URL`,
|
|
12
12
|
* `<NAME>_ARN`, and a `<NAME>_PUBLISHER_CONNECTION_STRING` (`sqs://?queueUrl=…`)
|
|
13
13
|
* that `@geekmidas/events`'s `Publisher.fromConnectionString` consumes. Its
|
|
14
|
-
*
|
|
14
|
+
* one consumer — the handler the queue was declared with — is subscribed by
|
|
15
|
+
* {@link Queue.consume}.
|
|
15
16
|
*
|
|
16
17
|
* SST's native `Queue` link exposes only `url`, so `getSSTLink` is overridden to
|
|
17
18
|
* also expose `arn` (what the resolver needs). `QueueProps` extends
|
|
@@ -73,6 +74,25 @@ export class Queue<
|
|
|
73
74
|
};
|
|
74
75
|
}
|
|
75
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Subscribe the queue's one consumer: a Lambda running `handler`, linked to
|
|
79
|
+
* what the consumer depends on and to this queue itself.
|
|
80
|
+
*
|
|
81
|
+
* A queue has exactly one consumer, so this is called once per queue — by
|
|
82
|
+
* `fromManifest`, from the handler the build wrote for it.
|
|
83
|
+
*/
|
|
84
|
+
consume(consumer: QueueConsumer) {
|
|
85
|
+
return this.subscribe(
|
|
86
|
+
{
|
|
87
|
+
handler: consumer.handler,
|
|
88
|
+
link: [this, ...(consumer.link ?? [])],
|
|
89
|
+
runtime: 'nodejs24.x',
|
|
90
|
+
...(consumer.timeout ? { timeout: consumer.timeout } : {}),
|
|
91
|
+
},
|
|
92
|
+
consumer.batchSize ? { batch: { size: consumer.batchSize } } : {},
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
76
96
|
override getSSTLink() {
|
|
77
97
|
const link = super.getSSTLink();
|
|
78
98
|
return {
|
|
@@ -82,6 +102,16 @@ export class Queue<
|
|
|
82
102
|
}
|
|
83
103
|
}
|
|
84
104
|
|
|
105
|
+
/** The queue's one consumer, as {@link Queue.consume} subscribes it. */
|
|
106
|
+
export interface QueueConsumer {
|
|
107
|
+
/** The built handler — `.gkm/aws-lambda/queues/emails.handler`. */
|
|
108
|
+
handler: string;
|
|
109
|
+
/** What the consumer depends on; the queue itself is always linked. */
|
|
110
|
+
link?: unknown[];
|
|
111
|
+
timeout?: `${number} seconds` | `${number} minutes`;
|
|
112
|
+
batchSize?: number;
|
|
113
|
+
}
|
|
114
|
+
|
|
85
115
|
export interface QueueProps extends sst.aws.QueueArgs {
|
|
86
116
|
/**
|
|
87
117
|
* The physical queue name, where the auto-generated one will not do.
|
package/src/sst/fromManifest.ts
CHANGED
|
@@ -750,6 +750,46 @@ export function isServed(id: string, manifest: ConstructManifest): boolean {
|
|
|
750
750
|
);
|
|
751
751
|
}
|
|
752
752
|
|
|
753
|
+
/**
|
|
754
|
+
* Kinds this stack provisions nothing for of their own.
|
|
755
|
+
*
|
|
756
|
+
* - `mobile-app` ships through EAS and the stores. It is in the manifest for
|
|
757
|
+
* what it makes the surfaces trust, which `callersOf` reads off the graph.
|
|
758
|
+
* - `worker` is a process, not a resource: what it runs is provisioned as
|
|
759
|
+
* what it is — a queue's consumer by {@link subscribeConsumers}.
|
|
760
|
+
* - `cron` and `function` are Lambdas built from `manifest.crons` and
|
|
761
|
+
* `manifest.functions` (`Cron.fromManifest`, `Function.fromManifest`).
|
|
762
|
+
*/
|
|
763
|
+
export const PROVISIONED_ELSEWHERE: ReadonlySet<DeclarationKind> =
|
|
764
|
+
new Set<DeclarationKind>(['mobile-app', 'worker', 'cron', 'function']);
|
|
765
|
+
|
|
766
|
+
/** A provisioned queue: what {@link subscribeConsumers} subscribes through. */
|
|
767
|
+
interface Consumable {
|
|
768
|
+
consume(consumer: { handler: string; link?: unknown[] }): unknown;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* Subscribe every queue's one consumer — the handler the build wrote for it,
|
|
773
|
+
* linked to what that consumer depends on.
|
|
774
|
+
*
|
|
775
|
+
* After every construct is provisioned, because a consumer links to things
|
|
776
|
+
* that `provisionOrder` does not order before the queue.
|
|
777
|
+
*/
|
|
778
|
+
export function subscribeConsumers(
|
|
779
|
+
manifest: ConstructManifest,
|
|
780
|
+
provisioned: ProvisionedManifest,
|
|
781
|
+
): void {
|
|
782
|
+
for (const [id, declaration] of Object.entries(manifest)) {
|
|
783
|
+
if (declaration?.kind !== 'queue') continue;
|
|
784
|
+
|
|
785
|
+
const queue = provisioned[id] as unknown as Consumable | undefined;
|
|
786
|
+
if (!queue) continue;
|
|
787
|
+
|
|
788
|
+
const { link } = resolveEdges(declaration.worker.dependencies, provisioned);
|
|
789
|
+
queue.consume({ handler: declaration.worker.handler, link });
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
753
793
|
/** The provisioner for a kind. Pure — the lookup is testable without Pulumi. */
|
|
754
794
|
export function provisionerFor(kind: DeclarationKind): Provisioner {
|
|
755
795
|
const provisioner = PROVISIONERS[kind];
|
|
@@ -838,10 +878,7 @@ export function fromManifest(
|
|
|
838
878
|
for (const id of [...rest, ...sites]) {
|
|
839
879
|
const declaration = manifest[id];
|
|
840
880
|
if (!declaration) continue;
|
|
841
|
-
|
|
842
|
-
// in the manifest for what it makes the surfaces trust, which
|
|
843
|
-
// `callersOf` reads off the graph.
|
|
844
|
-
if (declaration.kind === 'mobile-app') continue;
|
|
881
|
+
if (PROVISIONED_ELSEWHERE.has(declaration.kind)) continue;
|
|
845
882
|
|
|
846
883
|
const component = provisionerFor(declaration.kind)(
|
|
847
884
|
stack,
|
|
@@ -865,6 +902,9 @@ export function fromManifest(
|
|
|
865
902
|
provisioned[id] = component;
|
|
866
903
|
}
|
|
867
904
|
|
|
905
|
+
// Each queue's one consumer, once everything it links to exists.
|
|
906
|
+
subscribeConsumers(manifest, provisioned);
|
|
907
|
+
|
|
868
908
|
for (const line of describeRoutes(manifest)) console.log(line);
|
|
869
909
|
|
|
870
910
|
// Now everything exists, so every surface's callers can be resolved — the
|