encore.dev 0.0.0-devel.202311141645
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/LICENSE +362 -0
- package/README.md +4 -0
- package/api/mod.ts +47 -0
- package/config/mod.ts +2 -0
- package/config/secrets.ts +27 -0
- package/cron/mod.ts +17 -0
- package/dist/api/mod.d.ts +7 -0
- package/dist/api/mod.js +5 -0
- package/dist/api/mod.js.map +1 -0
- package/dist/config/mod.d.ts +2 -0
- package/dist/config/mod.js +2 -0
- package/dist/config/mod.js.map +1 -0
- package/dist/config/secrets.d.ts +11 -0
- package/dist/config/secrets.js +15 -0
- package/dist/config/secrets.js.map +1 -0
- package/dist/cron/mod.d.ts +14 -0
- package/dist/cron/mod.js +10 -0
- package/dist/cron/mod.js.map +1 -0
- package/dist/internal/types/mod.d.ts +7 -0
- package/dist/internal/types/mod.js +2 -0
- package/dist/internal/types/mod.js.map +1 -0
- package/dist/log/mod.d.ts +3 -0
- package/dist/log/mod.js +4 -0
- package/dist/log/mod.js.map +1 -0
- package/dist/mod.d.ts +0 -0
- package/dist/mod.js +2 -0
- package/dist/mod.js.map +1 -0
- package/dist/pubsub/acker.d.ts +26 -0
- package/dist/pubsub/acker.js +73 -0
- package/dist/pubsub/acker.js.map +1 -0
- package/dist/pubsub/attributes.d.ts +59 -0
- package/dist/pubsub/attributes.js +2 -0
- package/dist/pubsub/attributes.js.map +1 -0
- package/dist/pubsub/mod.d.ts +5 -0
- package/dist/pubsub/mod.js +3 -0
- package/dist/pubsub/mod.js.map +1 -0
- package/dist/pubsub/subscription.d.ts +100 -0
- package/dist/pubsub/subscription.js +164 -0
- package/dist/pubsub/subscription.js.map +1 -0
- package/dist/pubsub/topic.d.ts +105 -0
- package/dist/pubsub/topic.js +62 -0
- package/dist/pubsub/topic.js.map +1 -0
- package/dist/storage/sqldb/database.d.ts +51 -0
- package/dist/storage/sqldb/database.js +78 -0
- package/dist/storage/sqldb/database.js.map +1 -0
- package/dist/storage/sqldb/mod.d.ts +2 -0
- package/dist/storage/sqldb/mod.js +2 -0
- package/dist/storage/sqldb/mod.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/internal/types/mod.ts +10 -0
- package/log/mod.ts +4 -0
- package/mod.ts +0 -0
- package/package.json +77 -0
- package/pubsub/acker.ts +79 -0
- package/pubsub/attributes.ts +66 -0
- package/pubsub/mod.ts +7 -0
- package/pubsub/subscription.ts +302 -0
- package/pubsub/topic.ts +132 -0
- package/storage/sqldb/database.ts +105 -0
- package/storage/sqldb/mod.ts +2 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Acker is used to ack or nack a message.
|
|
3
|
+
*/
|
|
4
|
+
export class Acker {
|
|
5
|
+
#acked;
|
|
6
|
+
#nacked;
|
|
7
|
+
#succesfulResult = false;
|
|
8
|
+
#ack;
|
|
9
|
+
#nack;
|
|
10
|
+
constructor(ack, nack) {
|
|
11
|
+
this.#ack = ack;
|
|
12
|
+
this.#nack = nack;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Acknowledges the message.
|
|
16
|
+
*
|
|
17
|
+
* If the message has been nacked, an error is thrown.
|
|
18
|
+
*
|
|
19
|
+
* Multiple calls to ack() will return the same promise, unless the
|
|
20
|
+
* promise has been rejected, in which case a new promise will be returned.
|
|
21
|
+
*/
|
|
22
|
+
async ack() {
|
|
23
|
+
if (this.#nacked) {
|
|
24
|
+
throw new Error("message already nacked and cannot be acked");
|
|
25
|
+
}
|
|
26
|
+
if (this.#acked) {
|
|
27
|
+
return this.#acked;
|
|
28
|
+
}
|
|
29
|
+
this.#acked = this.#ack();
|
|
30
|
+
this.#acked
|
|
31
|
+
.then(() => {
|
|
32
|
+
this.#succesfulResult = true;
|
|
33
|
+
})
|
|
34
|
+
.catch((e) => {
|
|
35
|
+
this.#acked = undefined;
|
|
36
|
+
throw e;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Negatively acknowledges the message.
|
|
41
|
+
*
|
|
42
|
+
* If the message has been acked, an error is thrown.
|
|
43
|
+
*
|
|
44
|
+
* Multiple calls to nack() will return the same promise, unless the
|
|
45
|
+
* promise has been rejected, in which case a new promise will be returned.
|
|
46
|
+
*/
|
|
47
|
+
async nack() {
|
|
48
|
+
if (this.#acked) {
|
|
49
|
+
throw new Error("message already acked and cannot be nacked");
|
|
50
|
+
}
|
|
51
|
+
if (this.#nacked) {
|
|
52
|
+
return this.#nacked;
|
|
53
|
+
}
|
|
54
|
+
this.#nacked = this.#nack();
|
|
55
|
+
this.#nacked
|
|
56
|
+
.then(() => {
|
|
57
|
+
this.#succesfulResult = true;
|
|
58
|
+
})
|
|
59
|
+
.catch((e) => {
|
|
60
|
+
this.#nacked = undefined;
|
|
61
|
+
throw e;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
currentResult() {
|
|
65
|
+
if (this.#succesfulResult) {
|
|
66
|
+
return Promise.resolve(true);
|
|
67
|
+
}
|
|
68
|
+
return (this.#acked ?? this.#nacked ?? Promise.reject())
|
|
69
|
+
.then(() => true)
|
|
70
|
+
.catch(() => false);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=acker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"acker.js","sourceRoot":"","sources":["../../pubsub/acker.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,OAAO,KAAK;IAChB,MAAM,CAAiB;IACvB,OAAO,CAAiB;IACxB,gBAAgB,GAAG,KAAK,CAAC;IAChB,IAAI,CAAsB;IAC1B,KAAK,CAAsB;IAEpC,YAAY,GAAwB,EAAE,IAAyB;QAC7D,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC;QAChB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;IACpB,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,GAAG;QACP,IAAI,IAAI,CAAC,OAAO,EAAE;YAChB,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;SAC/D;QACD,IAAI,IAAI,CAAC,MAAM,EAAE;YACf,OAAO,IAAI,CAAC,MAAM,CAAC;SACpB;QAED,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC1B,IAAI,CAAC,MAAM;aACR,IAAI,CAAC,GAAG,EAAE;YACT,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC;QAC/B,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,CAAU,EAAE,EAAE;YACpB,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;YACxB,MAAM,CAAC,CAAC;QACV,CAAC,CAAC,CAAC;IACP,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,IAAI;QACR,IAAI,IAAI,CAAC,MAAM,EAAE;YACf,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;SAC/D;QACD,IAAI,IAAI,CAAC,OAAO,EAAE;YAChB,OAAO,IAAI,CAAC,OAAO,CAAC;SACrB;QAED,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO;aACT,IAAI,CAAC,GAAG,EAAE;YACT,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC;QAC/B,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,CAAU,EAAE,EAAE;YACpB,IAAI,CAAC,OAAO,GAAG,SAAS,CAAC;YACzB,MAAM,CAAC,CAAC;QACV,CAAC,CAAC,CAAC;IACP,CAAC;IAED,aAAa;QACX,IAAI,IAAI,CAAC,gBAAgB,EAAE;YACzB,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;SAC9B;QAED,OAAO,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;aACrD,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC;aAChB,KAAK,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC;IACxB,CAAC;CACF"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attribute represents a field on a message that should be sent
|
|
3
|
+
* as an attribute in a PubSub message, rather than in the message
|
|
4
|
+
* body.
|
|
5
|
+
*
|
|
6
|
+
* This is useful for ordering messages, or for filtering messages
|
|
7
|
+
* on a subscription - otherwise you should not use this.
|
|
8
|
+
*
|
|
9
|
+
* To create attributes on a message, use the `Attribute` type:
|
|
10
|
+
* type Message = {
|
|
11
|
+
* user_id: Attribute<number>;
|
|
12
|
+
* name: string;
|
|
13
|
+
* };
|
|
14
|
+
*
|
|
15
|
+
* const msg: Message = {
|
|
16
|
+
* user_id: 123,
|
|
17
|
+
* name: "John Doe",
|
|
18
|
+
* };
|
|
19
|
+
*
|
|
20
|
+
* The union of brandedAttribute is simply used to help the TypeScript compiler
|
|
21
|
+
* understand that the type is an attribute and allow the AttributesOf type
|
|
22
|
+
* to extract the keys of said type.
|
|
23
|
+
*/
|
|
24
|
+
export type Attribute<T extends string | number | boolean> = T | brandedAttribute<T>;
|
|
25
|
+
/**
|
|
26
|
+
* AttributesOf is a helper type to extract all keys from an object
|
|
27
|
+
* who's type is an Attribute type.
|
|
28
|
+
*
|
|
29
|
+
* For example:
|
|
30
|
+
* type Message = {
|
|
31
|
+
* user_id: Attribute<number>;
|
|
32
|
+
* name: string;
|
|
33
|
+
* age: Attribute<number>;
|
|
34
|
+
* };
|
|
35
|
+
*
|
|
36
|
+
* type MessageAttributes = AttributesOf<Message>; // "user_id" | "age"
|
|
37
|
+
*/
|
|
38
|
+
export type AttributesOf<T extends object> = keyof {
|
|
39
|
+
[Key in keyof T as Extract<T[Key], allBrandedTypes> extends never ? never : Key]: never;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* supportedAttributeTypes is a union of all primitive types that are supported as attributes
|
|
43
|
+
*/
|
|
44
|
+
type supportedAttributeTypes = string | number | boolean;
|
|
45
|
+
/**
|
|
46
|
+
* brandedAttribute is a helper type to brand a type as an attribute
|
|
47
|
+
* which is distinct from the base type. It is a compile time only
|
|
48
|
+
* type and has no runtime representation.
|
|
49
|
+
*/
|
|
50
|
+
type brandedAttribute<T> = T & {
|
|
51
|
+
readonly __attributeBrand: unique symbol;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* allBrandedTypes is a helper type to create a union of all branded supported attribute types
|
|
55
|
+
*
|
|
56
|
+
* The result of this is: brandedAttribute<string> | brandedAttribute<number> | brandedAttribute<boolean>
|
|
57
|
+
*/
|
|
58
|
+
type allBrandedTypes<Union = supportedAttributeTypes> = Union extends supportedAttributeTypes ? brandedAttribute<Union> : never;
|
|
59
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attributes.js","sourceRoot":"","sources":["../../pubsub/attributes.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mod.js","sourceRoot":"","sources":["../../pubsub/mod.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAGhC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { DurationString } from "../internal/types/mod.js";
|
|
2
|
+
import { Topic } from "./topic.js";
|
|
3
|
+
export declare class Subscription<Msg extends object> {
|
|
4
|
+
#private;
|
|
5
|
+
private readonly client_id;
|
|
6
|
+
private readonly topic;
|
|
7
|
+
private readonly name;
|
|
8
|
+
private readonly handler;
|
|
9
|
+
private readonly config;
|
|
10
|
+
private readonly logger;
|
|
11
|
+
private failedState;
|
|
12
|
+
private activeDeliveries;
|
|
13
|
+
constructor(topic: Topic<Msg>, name: string, cfg: SubscriptionConfig<Msg>);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* SubscriptionConfig is used when creating a subscription
|
|
17
|
+
*
|
|
18
|
+
* The values given here may be clamped to the supported values by
|
|
19
|
+
* the target cloud. (i.e. ack deadline may be brought within the supported range
|
|
20
|
+
* by the target cloud pubsub implementation).
|
|
21
|
+
*/
|
|
22
|
+
export interface SubscriptionConfig<Msg> {
|
|
23
|
+
/**
|
|
24
|
+
* Handler is the function which will be called to process a message
|
|
25
|
+
* sent on the topic.
|
|
26
|
+
*
|
|
27
|
+
* When this function returns an error the message will be
|
|
28
|
+
* negatively acknowledged (nacked), which will cause a redelivery
|
|
29
|
+
* attempt to be made (unless the retry policy's MaxRetries has been reached).
|
|
30
|
+
*/
|
|
31
|
+
handler: (msg: Msg) => Promise<unknown>;
|
|
32
|
+
/**
|
|
33
|
+
* MaxConcurrency is the maximum number of messages which will be processed
|
|
34
|
+
* simultaneously per instance of the service for this subscription.
|
|
35
|
+
*
|
|
36
|
+
* Note that this is per instance of the service, so if your service has
|
|
37
|
+
* scaled to 10 instances and this is set to 10, then 100 messages could be
|
|
38
|
+
* processed simultaneously.
|
|
39
|
+
*
|
|
40
|
+
* If the value is negative, then there will be no limit on the number
|
|
41
|
+
* of messages processed simultaneously.
|
|
42
|
+
*
|
|
43
|
+
* Note: This is not supported by all cloud providers; specifically on GCP
|
|
44
|
+
* when using Cloud Run instances on an unordered topic the subscription will
|
|
45
|
+
* be configured as a Push Subscription and will have an adaptive concurrency
|
|
46
|
+
* See [GCP Push Delivery Rate](https://cloud.google.com/pubsub/docs/push#push_delivery_rate).
|
|
47
|
+
*
|
|
48
|
+
* This setting also has no effect on Encore Cloud environments.
|
|
49
|
+
* If not set, it uses a reasonable default based on the cloud provider.
|
|
50
|
+
*/
|
|
51
|
+
maxConcurrency?: number;
|
|
52
|
+
/**
|
|
53
|
+
* AckDeadline is the time a consumer has to process a message
|
|
54
|
+
* before it's returned to the subscription
|
|
55
|
+
*
|
|
56
|
+
* Default is 30 seconds, however the ack deadline must be at least
|
|
57
|
+
* 1 second.
|
|
58
|
+
*/
|
|
59
|
+
ackDeadline?: DurationString;
|
|
60
|
+
/**
|
|
61
|
+
* MessageRetention is how long an undelivered message is kept
|
|
62
|
+
* on the topic before it's purged.
|
|
63
|
+
*
|
|
64
|
+
* Default is 7 days.
|
|
65
|
+
*/
|
|
66
|
+
messageRetention?: DurationString;
|
|
67
|
+
/**
|
|
68
|
+
* RetryPolicy defines how a message should be retried when
|
|
69
|
+
* the subscriber returns an error
|
|
70
|
+
*/
|
|
71
|
+
retryPolicy?: RetryPolicy;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* RetryPolicy defines how a subscription should handle retries
|
|
75
|
+
* after errors either delivering the message or processing the message.
|
|
76
|
+
*
|
|
77
|
+
* The values given to this structure are parsed at compile time, such that
|
|
78
|
+
* the correct Cloud resources can be provisioned to support the queue.
|
|
79
|
+
*
|
|
80
|
+
* As such the values given here may be clamped to the supported values by
|
|
81
|
+
* the target cloud. (i.e. min/max values brought within the supported range
|
|
82
|
+
* by the target cloud).
|
|
83
|
+
*/
|
|
84
|
+
export interface RetryPolicy {
|
|
85
|
+
/**
|
|
86
|
+
* The minimum time to wait between retries. Defaults to 10 seconds.
|
|
87
|
+
*/
|
|
88
|
+
minBackoff?: DurationString;
|
|
89
|
+
/**
|
|
90
|
+
* The maximum time to wait between retries. Defaults to 10 minutes.
|
|
91
|
+
*/
|
|
92
|
+
maxBackoff?: DurationString;
|
|
93
|
+
/**
|
|
94
|
+
* MaxRetries is used to control deadletter queuing logic, when:
|
|
95
|
+
* n == 0: A default value of 100 retries will be used
|
|
96
|
+
* n > 0: Encore will forward a message to a dead letter queue after n retries
|
|
97
|
+
* n == pubsub.InfiniteRetries: Messages will not be forwarded to the dead letter queue by the Encore framework
|
|
98
|
+
*/
|
|
99
|
+
maxRetries?: number;
|
|
100
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { api } from "@encore.dev/internal-runtime";
|
|
2
|
+
import reqtrack from "@encore.dev/internal-runtime/reqtrack/index";
|
|
3
|
+
import { newSpanID, newTraceID, } from "@encore.dev/internal-runtime/reqtrack/tracecontext";
|
|
4
|
+
import { withLogCtx, } from "@encore.dev/internal-runtime/reqtrack/logging";
|
|
5
|
+
import { durationToProto, now, parseDuration, since, toDurationStr, } from "@encore.dev/internal-runtime/utils/timers";
|
|
6
|
+
import { runtime } from "@encore.dev/internal-runtime/jsruntime";
|
|
7
|
+
import log from "../log/mod.js";
|
|
8
|
+
import { PubSub } from "@encore.dev/sidecar-api";
|
|
9
|
+
import { Acker } from "./acker.js";
|
|
10
|
+
export class Subscription {
|
|
11
|
+
client_id;
|
|
12
|
+
topic;
|
|
13
|
+
name;
|
|
14
|
+
handler;
|
|
15
|
+
config;
|
|
16
|
+
logger;
|
|
17
|
+
failedState = false;
|
|
18
|
+
activeDeliveries = [];
|
|
19
|
+
constructor(topic, name, cfg) {
|
|
20
|
+
// Generate a unique client ID for this instance
|
|
21
|
+
const id = new Uint8Array(16);
|
|
22
|
+
runtime().getRandomValues(id);
|
|
23
|
+
this.client_id = Buffer.from(id).toString("hex");
|
|
24
|
+
this.topic = topic;
|
|
25
|
+
this.name = name;
|
|
26
|
+
this.handler = cfg.handler;
|
|
27
|
+
this.logger = log.with({ topic: topic.name, subscription: name });
|
|
28
|
+
const ackDeadline = parseDuration(cfg.ackDeadline ?? "30s");
|
|
29
|
+
const minBackoff = parseDuration(cfg.retryPolicy?.minBackoff ?? "10s");
|
|
30
|
+
const maxBackoff = parseDuration(cfg.retryPolicy?.maxBackoff ?? "10m");
|
|
31
|
+
this.config = new PubSub.SubscriptionConfig({
|
|
32
|
+
maxConcurrency: cfg.maxConcurrency ?? -1,
|
|
33
|
+
ackDeadline: durationToProto(ackDeadline),
|
|
34
|
+
retryPolicy: new PubSub.RetryPolicy({
|
|
35
|
+
minBackoff: durationToProto(minBackoff),
|
|
36
|
+
maxBackoff: durationToProto(maxBackoff),
|
|
37
|
+
maxRetries: cfg.retryPolicy?.maxRetries ?? 100,
|
|
38
|
+
}),
|
|
39
|
+
});
|
|
40
|
+
let backoff = 250; // Start at 250ms for exponential backoff
|
|
41
|
+
const start = () => {
|
|
42
|
+
this.logger.trace("starting subscription");
|
|
43
|
+
this.#startSubscription()
|
|
44
|
+
.then(() => this.logger.warn("subscription ended"))
|
|
45
|
+
.catch((e) => {
|
|
46
|
+
this.failedState = true;
|
|
47
|
+
backoff *= 2;
|
|
48
|
+
if (backoff > 60 * 1000) {
|
|
49
|
+
// cap the backoff at 1 minute
|
|
50
|
+
backoff = 60 * 1000;
|
|
51
|
+
}
|
|
52
|
+
this.logger.error(e, "subscription failed with error");
|
|
53
|
+
setTimeout(start, backoff);
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
this.logger.info("registered subscription");
|
|
57
|
+
// Start the subscription
|
|
58
|
+
process.nextTick(start);
|
|
59
|
+
}
|
|
60
|
+
async #startSubscription() {
|
|
61
|
+
const resp = api().pubsub.subscribe(new PubSub.SubscribeRequest({
|
|
62
|
+
clientId: this.client_id,
|
|
63
|
+
topic: this.topic.name,
|
|
64
|
+
subscription: this.name,
|
|
65
|
+
config: this.config,
|
|
66
|
+
}));
|
|
67
|
+
// Now we have an active subscription, we touch messages if we have active messages
|
|
68
|
+
const interval = setInterval(() => {
|
|
69
|
+
this.#touchActiveMessages();
|
|
70
|
+
}, 30 * 1000);
|
|
71
|
+
try {
|
|
72
|
+
for await (const msg of resp) {
|
|
73
|
+
if (this.failedState) {
|
|
74
|
+
this.logger.info("subscription recovered");
|
|
75
|
+
this.failedState = false;
|
|
76
|
+
}
|
|
77
|
+
process.nextTick(() => this.#handleMessage(msg));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
clearInterval(interval);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
#touchActiveMessages() {
|
|
85
|
+
if (this.activeDeliveries.length === 0) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const acks = this.activeDeliveries;
|
|
89
|
+
api()
|
|
90
|
+
.pubsub.touch({
|
|
91
|
+
clientId: this.client_id,
|
|
92
|
+
ackIds: acks,
|
|
93
|
+
})
|
|
94
|
+
.then(() => {
|
|
95
|
+
this.logger.trace("touched active messages", { ack_ids: acks });
|
|
96
|
+
})
|
|
97
|
+
.catch((e) => {
|
|
98
|
+
this.logger.error(e, "failed to touch messages", { ack_ids: acks });
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
async #handleMessage(event) {
|
|
102
|
+
if (event.event.case !== "messageDelivery") {
|
|
103
|
+
// If it's a keep alive or some other unknown message
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const msg = event.event.value;
|
|
107
|
+
const logCtx = {
|
|
108
|
+
topic: this.topic.name,
|
|
109
|
+
subscription: this.name,
|
|
110
|
+
msg_id: msg.msgId,
|
|
111
|
+
delivery_attempt: msg.deliveryAttempt,
|
|
112
|
+
};
|
|
113
|
+
const traceID = newTraceID();
|
|
114
|
+
const spanID = newSpanID();
|
|
115
|
+
const ctx = {
|
|
116
|
+
traceID: traceID,
|
|
117
|
+
spanID: spanID,
|
|
118
|
+
};
|
|
119
|
+
// Record this ack ID as being active
|
|
120
|
+
this.activeDeliveries.push(msg.ackId);
|
|
121
|
+
return reqtrack.run(ctx, async () => {
|
|
122
|
+
return withLogCtx(logCtx, async () => {
|
|
123
|
+
const acker = new Acker(() => this.#acknowledge(msg.ackId), () => this.#negativeAcknowledge(msg.ackId));
|
|
124
|
+
try {
|
|
125
|
+
this.logger.info("starting request");
|
|
126
|
+
const data = Buffer.from(msg.message?.data ?? Uint8Array.of()).toString();
|
|
127
|
+
const start = now();
|
|
128
|
+
await this.handler(JSON.parse(data), acker);
|
|
129
|
+
this.logger.info("request completed", {
|
|
130
|
+
duration: toDurationStr(since(start)),
|
|
131
|
+
});
|
|
132
|
+
const hasAcked = await acker.currentResult();
|
|
133
|
+
if (!hasAcked) {
|
|
134
|
+
await acker.ack();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
catch (e) {
|
|
138
|
+
this.logger.error(e, "request failed");
|
|
139
|
+
const hasAcked = await acker.currentResult();
|
|
140
|
+
if (!hasAcked) {
|
|
141
|
+
await acker.nack();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
async #acknowledge(ackID) {
|
|
148
|
+
await api().pubsub.acknowledge({
|
|
149
|
+
clientId: this.client_id,
|
|
150
|
+
ackId: ackID,
|
|
151
|
+
});
|
|
152
|
+
this.activeDeliveries = this.activeDeliveries.filter((id) => id !== ackID);
|
|
153
|
+
this.logger.trace("acknowledged message", { ack_id: ackID });
|
|
154
|
+
}
|
|
155
|
+
async #negativeAcknowledge(ackID) {
|
|
156
|
+
await api().pubsub.negativeAcknowledge({
|
|
157
|
+
clientId: this.client_id,
|
|
158
|
+
ackId: ackID,
|
|
159
|
+
});
|
|
160
|
+
this.activeDeliveries = this.activeDeliveries.filter((id) => id !== ackID);
|
|
161
|
+
this.logger.trace("negatively acknowledged message", { ack_id: ackID });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=subscription.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscription.js","sourceRoot":"","sources":["../../pubsub/subscription.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { AttributesOf } from "./attributes.js";
|
|
2
|
+
/**
|
|
3
|
+
* A topic is a resource to which you can publish messages
|
|
4
|
+
* to be delivered to subscribers of that topic.
|
|
5
|
+
*/
|
|
6
|
+
export declare class Topic<Msg extends object> {
|
|
7
|
+
readonly name: string;
|
|
8
|
+
readonly cfg: TopicConfig<Msg>;
|
|
9
|
+
constructor(name: string, cfg: TopicConfig<Msg>);
|
|
10
|
+
publish(msg: Msg): Promise<string>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* DeliveryGuarantee is used to configure the delivery contract for a topic.
|
|
14
|
+
*/
|
|
15
|
+
export type DeliveryGuarantee = "at-least-once" | "exactly-once";
|
|
16
|
+
/**
|
|
17
|
+
* At Least Once delivery guarantees that a message for a subscription is delivered to
|
|
18
|
+
* a consumer at least once.
|
|
19
|
+
*
|
|
20
|
+
* On AWS and GCP there is no limit to the throughput for a topic.
|
|
21
|
+
*/
|
|
22
|
+
export declare const atLeastOnce: DeliveryGuarantee;
|
|
23
|
+
/**
|
|
24
|
+
* ExactlyOnce guarantees that a message for a subscription is delivered to
|
|
25
|
+
* a consumer exactly once, to the best of the system's ability.
|
|
26
|
+
*
|
|
27
|
+
* However, there are edge cases when a message might be redelivered.
|
|
28
|
+
* For example, if a networking issue causes the acknowledgement of success
|
|
29
|
+
* processing the message to be lost before the cloud provider receives it.
|
|
30
|
+
*
|
|
31
|
+
* It is also important to note that the ExactlyOnce delivery guarantee only
|
|
32
|
+
* applies to the delivery of the message to the consumer, and not to the
|
|
33
|
+
* original publishing of the message, such that if a message is published twice,
|
|
34
|
+
* such as due to an retry within the application logic, it will be delivered twice.
|
|
35
|
+
* (i.e. ExactlyOnce delivery does not imply message deduplication on publish)
|
|
36
|
+
*
|
|
37
|
+
* As such it's recommended that the subscription handler function is idempotent
|
|
38
|
+
* and is able to handle duplicate messages.
|
|
39
|
+
*
|
|
40
|
+
* Subscriptions attached to ExactlyOnce topics have higher message delivery latency compared to AtLeastOnce.
|
|
41
|
+
*
|
|
42
|
+
* By using ExactlyOnce semantics on a topic, the throughput will be limited depending on the cloud provider:
|
|
43
|
+
* - AWS: 300 messages per second for the topic (see [AWS SQS Quotas]).
|
|
44
|
+
* - GCP: At least 3,000 messages per second across all topics in the region
|
|
45
|
+
* (can be higher on the region see [GCP PubSub Quotas]).
|
|
46
|
+
*
|
|
47
|
+
* [AWS SQS Quotas]: https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/quotas-messages.html
|
|
48
|
+
* [GCP PubSub Quotas]: https://cloud.google.com/pubsub/quotas#quotas
|
|
49
|
+
*/
|
|
50
|
+
export declare const exactlyOnce: DeliveryGuarantee;
|
|
51
|
+
/**
|
|
52
|
+
* TopicConfig is used when creating a Topic
|
|
53
|
+
*/
|
|
54
|
+
export interface TopicConfig<Msg extends object> {
|
|
55
|
+
/**
|
|
56
|
+
* DeliveryGuarantee is used to configure the delivery guarantee of a Topic
|
|
57
|
+
*/
|
|
58
|
+
deliveryGuarantee: DeliveryGuarantee;
|
|
59
|
+
/**
|
|
60
|
+
* OrderingAttribute is the message attribute to use as a ordering key for
|
|
61
|
+
* messages and delivery will ensure that messages with the same value will
|
|
62
|
+
* be delivered in the order they where published.
|
|
63
|
+
*
|
|
64
|
+
* If OrderingAttribute is not set, messages can be delivered in any order.
|
|
65
|
+
*
|
|
66
|
+
* It is important to note, that in the case of an error being returned by a
|
|
67
|
+
* subscription handler, the message will be retried before any subsequent
|
|
68
|
+
* messages for that ordering key are delivered. This means depending on the
|
|
69
|
+
* retry configuration, a large backlog of messages for a given ordering key
|
|
70
|
+
* may build up. When using OrderingAttribute, it is recommended to use reason
|
|
71
|
+
* about your failure modes and set the retry configuration appropriately.
|
|
72
|
+
*
|
|
73
|
+
* Once the maximum number of retries has been reached, the message will be
|
|
74
|
+
* forwarded to the dead letter queue, and the next message for that ordering
|
|
75
|
+
* key will be delivered.
|
|
76
|
+
*
|
|
77
|
+
* To create attributes on a message, use the `Attribute` type:
|
|
78
|
+
*
|
|
79
|
+
* type UserEvent = {
|
|
80
|
+
* user_id Attribute<string>;
|
|
81
|
+
* action string;
|
|
82
|
+
* }
|
|
83
|
+
*
|
|
84
|
+
* const topic = new Topic<UserEvent>("user-events", {
|
|
85
|
+
* deliveryGuarantee: DeliveryGuarantee.AtLeastOnce,
|
|
86
|
+
* orderingAttribute: "user_id", // Messages with the same user-id will be delivered in the order they where
|
|
87
|
+
* published
|
|
88
|
+
* })
|
|
89
|
+
*
|
|
90
|
+
* topic.publish(ctx, {user_id: "1", action: "login"}) // This message will be delivered before the logout
|
|
91
|
+
* topic.publish(ctx, {user_id: "2", action: "login"}) // This could be delivered at any time because it has a different user id
|
|
92
|
+
* topic.publish(ctx, {user_id: "1", action: "logout"}) // This message will be delivered after the first message
|
|
93
|
+
*
|
|
94
|
+
* By using OrderingAttribute, the throughput will be limited depending on the cloud provider:
|
|
95
|
+
*
|
|
96
|
+
* - AWS: 300 messages per second for the topic (see [AWS SQS Quotas]).
|
|
97
|
+
* - GCP: 1MB/s for each ordering key (see [GCP PubSub Quotas]).
|
|
98
|
+
*
|
|
99
|
+
* Note: OrderingAttribute currently has no effect during local development.
|
|
100
|
+
*
|
|
101
|
+
* [AWS SQS Quotas]: https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/quotas-messages.html
|
|
102
|
+
* [GCP PubSub Quotas]: https://cloud.google.com/pubsub/quotas#resource_limits
|
|
103
|
+
*/
|
|
104
|
+
orderingAttribute?: AttributesOf<Msg>;
|
|
105
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { api } from "@encore.dev/internal-runtime";
|
|
2
|
+
import reqtrack from "@encore.dev/internal-runtime/reqtrack/index";
|
|
3
|
+
import { PubSub } from "@encore.dev/sidecar-api";
|
|
4
|
+
/**
|
|
5
|
+
* A topic is a resource to which you can publish messages
|
|
6
|
+
* to be delivered to subscribers of that topic.
|
|
7
|
+
*/
|
|
8
|
+
export class Topic {
|
|
9
|
+
name;
|
|
10
|
+
cfg;
|
|
11
|
+
constructor(name, cfg) {
|
|
12
|
+
this.name = name;
|
|
13
|
+
this.cfg = cfg;
|
|
14
|
+
}
|
|
15
|
+
async publish(msg) {
|
|
16
|
+
const res = await api().pubsub.publish(new PubSub.PublishRequest({
|
|
17
|
+
topic: this.name,
|
|
18
|
+
message: new PubSub.Message({
|
|
19
|
+
data: Buffer.from(JSON.stringify(msg)),
|
|
20
|
+
attributes: {},
|
|
21
|
+
traceParent: reqtrack.currentTraceParent(),
|
|
22
|
+
}),
|
|
23
|
+
}));
|
|
24
|
+
return res.id;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* At Least Once delivery guarantees that a message for a subscription is delivered to
|
|
29
|
+
* a consumer at least once.
|
|
30
|
+
*
|
|
31
|
+
* On AWS and GCP there is no limit to the throughput for a topic.
|
|
32
|
+
*/
|
|
33
|
+
export const atLeastOnce = "at-least-once";
|
|
34
|
+
/**
|
|
35
|
+
* ExactlyOnce guarantees that a message for a subscription is delivered to
|
|
36
|
+
* a consumer exactly once, to the best of the system's ability.
|
|
37
|
+
*
|
|
38
|
+
* However, there are edge cases when a message might be redelivered.
|
|
39
|
+
* For example, if a networking issue causes the acknowledgement of success
|
|
40
|
+
* processing the message to be lost before the cloud provider receives it.
|
|
41
|
+
*
|
|
42
|
+
* It is also important to note that the ExactlyOnce delivery guarantee only
|
|
43
|
+
* applies to the delivery of the message to the consumer, and not to the
|
|
44
|
+
* original publishing of the message, such that if a message is published twice,
|
|
45
|
+
* such as due to an retry within the application logic, it will be delivered twice.
|
|
46
|
+
* (i.e. ExactlyOnce delivery does not imply message deduplication on publish)
|
|
47
|
+
*
|
|
48
|
+
* As such it's recommended that the subscription handler function is idempotent
|
|
49
|
+
* and is able to handle duplicate messages.
|
|
50
|
+
*
|
|
51
|
+
* Subscriptions attached to ExactlyOnce topics have higher message delivery latency compared to AtLeastOnce.
|
|
52
|
+
*
|
|
53
|
+
* By using ExactlyOnce semantics on a topic, the throughput will be limited depending on the cloud provider:
|
|
54
|
+
* - AWS: 300 messages per second for the topic (see [AWS SQS Quotas]).
|
|
55
|
+
* - GCP: At least 3,000 messages per second across all topics in the region
|
|
56
|
+
* (can be higher on the region see [GCP PubSub Quotas]).
|
|
57
|
+
*
|
|
58
|
+
* [AWS SQS Quotas]: https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/quotas-messages.html
|
|
59
|
+
* [GCP PubSub Quotas]: https://cloud.google.com/pubsub/quotas#quotas
|
|
60
|
+
*/
|
|
61
|
+
export const exactlyOnce = "exactly-once";
|
|
62
|
+
//# sourceMappingURL=topic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"topic.js","sourceRoot":"","sources":["../../pubsub/topic.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,8BAA8B,CAAC;AACnD,OAAO,QAAQ,MAAM,6CAA6C,CAAC;AACnE,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAGjD;;;GAGG;AACH,MAAM,OAAO,KAAK;IACA,IAAI,CAAS;IACb,GAAG,CAAmB;IAEtC,YAAY,IAAY,EAAE,GAAqB;QAC7C,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;IACjB,CAAC;IAEM,KAAK,CAAC,OAAO,CAAC,GAAQ;QAC3B,MAAM,GAAG,GAAG,MAAM,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CACpC,IAAI,MAAM,CAAC,cAAc,CAAC;YACxB,KAAK,EAAE,IAAI,CAAC,IAAI;YAChB,OAAO,EAAE,IAAI,MAAM,CAAC,OAAO,CAAC;gBAC1B,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;gBACtC,UAAU,EAAE,EAAE;gBACd,WAAW,EAAE,QAAQ,CAAC,kBAAkB,EAAE;aAC3C,CAAC;SACH,CAAC,CACH,CAAC;QAEF,OAAO,GAAG,CAAC,EAAE,CAAC;IAChB,CAAC;CACF;AAOD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAsB,eAAe,CAAC;AAE9D;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,CAAC,MAAM,WAAW,GAAsB,cAAc,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { StringLiteral } from "@encore.dev/internal-runtime/utils/constraints";
|
|
2
|
+
export interface SQLDatabaseConfig {
|
|
3
|
+
migrations?: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Represents a single row from a query result
|
|
7
|
+
*/
|
|
8
|
+
export type Row = Record<string, any>;
|
|
9
|
+
/** Represents a type that can be used in query template literals */
|
|
10
|
+
export type Primitive = string | number | boolean | null;
|
|
11
|
+
/**
|
|
12
|
+
* Constructing a new database object will result in Encore provisioning a database with
|
|
13
|
+
* that name and returning this object to represent it.
|
|
14
|
+
*
|
|
15
|
+
* If you want to reference an existing database, use `Database.Named(name)` as it is a
|
|
16
|
+
* compile error to create duplicate databases.
|
|
17
|
+
*/
|
|
18
|
+
export declare class SQLDatabase {
|
|
19
|
+
private readonly runtimeCfg;
|
|
20
|
+
private readonly pool;
|
|
21
|
+
/**
|
|
22
|
+
* Creates a new database with the given name and configuration
|
|
23
|
+
*/
|
|
24
|
+
constructor(name: string, cfg?: SQLDatabaseConfig);
|
|
25
|
+
/**
|
|
26
|
+
* Reference an existing database by name, if the database doesn't
|
|
27
|
+
* exist yet, use `new Database(name)` instead.
|
|
28
|
+
*/
|
|
29
|
+
static named<name extends string>(name: StringLiteral<name>): SQLDatabase;
|
|
30
|
+
/**
|
|
31
|
+
* Returns the connection string for the database
|
|
32
|
+
*/
|
|
33
|
+
get connectionString(): string;
|
|
34
|
+
/**
|
|
35
|
+
* q allows you to query the database using a template string, replacing your placeholders in the template
|
|
36
|
+
* with parametrised values. It returns an async generator that you can iterate over to get the results, in a
|
|
37
|
+
* streaming fashion.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
*
|
|
41
|
+
* const email = "foo@example.com";
|
|
42
|
+
* const result = database.q`SELECT id FROM users WHERE email=${email}` // produces a prepared query of
|
|
43
|
+
* // "SELECT id FROM users WHERE email=$1"
|
|
44
|
+
* // with an arg array of [email]
|
|
45
|
+
*
|
|
46
|
+
* for await (const row of result) {
|
|
47
|
+
* console.log(row.id);
|
|
48
|
+
* }
|
|
49
|
+
*/
|
|
50
|
+
q<T extends Row = Record<string, any>>(strings: TemplateStringsArray, ...expr: Primitive[]): AsyncGenerator<T>;
|
|
51
|
+
}
|