@echovisionlab/geul-event 0.1.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.
@@ -0,0 +1,36 @@
1
+ import { create, fromBinary, toBinary } from "@bufbuild/protobuf";
2
+ import { describe, expect, it } from "vitest";
3
+ import { CreateCampaignRequestSchema } from "@echovisionlab/geul-proto/secure/campaign_pb.ts";
4
+
5
+ describe("CreateCampaignRequest target wire contract", () => {
6
+ it.each([
7
+ {
8
+ name: "all recipients",
9
+ target: { case: "all" as const, value: {} },
10
+ },
11
+ {
12
+ name: "one exact audience segment",
13
+ target: { case: "segmentId" as const, value: "segment-1" },
14
+ },
15
+ ])("round-trips the explicit $name target", ({ target }) => {
16
+ const request = create(CreateCampaignRequestSchema, {
17
+ subject: "Subject",
18
+ name: "Campaign",
19
+ target,
20
+ });
21
+ const decoded = fromBinary(
22
+ CreateCampaignRequestSchema,
23
+ toBinary(CreateCampaignRequestSchema, request),
24
+ );
25
+
26
+ expect(decoded.target.case).toBe(target.case);
27
+ if (target.case === "all") {
28
+ expect(decoded.target).toMatchObject({
29
+ case: "all",
30
+ value: { $typeName: "google.protobuf.Empty" },
31
+ });
32
+ } else {
33
+ expect(decoded.target).toEqual(target);
34
+ }
35
+ });
36
+ });
@@ -0,0 +1,51 @@
1
+ import { create, fromBinary, toBinary } from "@bufbuild/protobuf";
2
+ import { describe, expect, it } from "vitest";
3
+ import {
4
+ AIDocumentFieldPathSegmentSchema,
5
+ AIDocumentFieldTargetSchema,
6
+ } from "@echovisionlab/geul-proto/secure/ai_pb.ts";
7
+ import { ApplyPageBlockBatchRequestSchema } from "@echovisionlab/geul-proto/intra/page_pb.ts";
8
+
9
+ function fieldPath(fieldHandle: string) {
10
+ return create(AIDocumentFieldPathSegmentSchema, {
11
+ selector: { case: "fieldHandle", value: fieldHandle },
12
+ });
13
+ }
14
+
15
+ function itemPath(itemHandle: string) {
16
+ return create(AIDocumentFieldPathSegmentSchema, {
17
+ selector: { case: "itemHandle", value: itemHandle },
18
+ });
19
+ }
20
+
21
+ describe("resident collaboration affected locale values", () => {
22
+ it("round-trips explicit-empty and exact table-cell leaf targets", () => {
23
+ const explicitEmpty = create(AIDocumentFieldTargetSchema, {
24
+ owner: { case: "blockHandle", value: "paragraph-1" },
25
+ fieldHandle: "content",
26
+ });
27
+ const tableCell = create(AIDocumentFieldTargetSchema, {
28
+ owner: { case: "blockHandle", value: "table-1" },
29
+ fieldHandle: "tableContent",
30
+ path: [
31
+ fieldPath("rows"),
32
+ itemPath("row-1"),
33
+ fieldPath("cells"),
34
+ itemPath("cell-1"),
35
+ fieldPath("content"),
36
+ ],
37
+ });
38
+ const request = create(ApplyPageBlockBatchRequestSchema, {
39
+ pageId: "page-1",
40
+ locale: "en",
41
+ affectedLocaleValues: [explicitEmpty, tableCell],
42
+ });
43
+
44
+ const decoded = fromBinary(
45
+ ApplyPageBlockBatchRequestSchema,
46
+ toBinary(ApplyPageBlockBatchRequestSchema, request),
47
+ );
48
+
49
+ expect(decoded.affectedLocaleValues).toEqual([explicitEmpty, tableCell]);
50
+ });
51
+ });
@@ -0,0 +1,74 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { AIDocumentDomain } from "@echovisionlab/geul-proto/secure/ai_pb.ts";
3
+ import { ContentEntityType } from "@echovisionlab/geul-proto/secure/events_pb.ts";
4
+
5
+ describe("ContentEntityType interactive document parity", () => {
6
+ it("covers every registered AI document domain with one exact typed value", () => {
7
+ const pairs = [
8
+ [AIDocumentDomain.AI_DOCUMENT_DOMAIN_POST, ContentEntityType.POST],
9
+ [AIDocumentDomain.AI_DOCUMENT_DOMAIN_PAGE, ContentEntityType.PAGE],
10
+ [AIDocumentDomain.AI_DOCUMENT_DOMAIN_WORK, ContentEntityType.WORK],
11
+ [
12
+ AIDocumentDomain.AI_DOCUMENT_DOMAIN_PROGRAM_EVENT,
13
+ ContentEntityType.PROGRAM_EVENT,
14
+ ],
15
+ [AIDocumentDomain.AI_DOCUMENT_DOMAIN_RELEASE, ContentEntityType.RELEASE],
16
+ [AIDocumentDomain.AI_DOCUMENT_DOMAIN_ARTIST, ContentEntityType.ARTIST],
17
+ [AIDocumentDomain.AI_DOCUMENT_DOMAIN_LABEL, ContentEntityType.LABEL],
18
+ [AIDocumentDomain.AI_DOCUMENT_DOMAIN_MENU, ContentEntityType.MENU],
19
+ [
20
+ AIDocumentDomain.AI_DOCUMENT_DOMAIN_EMAIL_TEMPLATE,
21
+ ContentEntityType.EMAIL_TEMPLATE,
22
+ ],
23
+ [
24
+ AIDocumentDomain.AI_DOCUMENT_DOMAIN_EMAIL_LAYOUT,
25
+ ContentEntityType.EMAIL_LAYOUT,
26
+ ],
27
+ [
28
+ AIDocumentDomain.AI_DOCUMENT_DOMAIN_CAMPAIGN,
29
+ ContentEntityType.CAMPAIGN,
30
+ ],
31
+ [AIDocumentDomain.AI_DOCUMENT_DOMAIN_FORM, ContentEntityType.FORM],
32
+ [AIDocumentDomain.AI_DOCUMENT_DOMAIN_PRIVACY, ContentEntityType.PRIVACY],
33
+ [AIDocumentDomain.AI_DOCUMENT_DOMAIN_TERMS, ContentEntityType.TERMS],
34
+ [
35
+ AIDocumentDomain.AI_DOCUMENT_DOMAIN_POST_SERIES,
36
+ ContentEntityType.POST_SERIES,
37
+ ],
38
+ ] as const;
39
+
40
+ expect(new Set(pairs.map(([domain]) => domain)).size).toBe(15);
41
+ expect(new Set(pairs.map(([, content]) => content)).size).toBe(15);
42
+ expect(
43
+ Object.values(AIDocumentDomain).filter(
44
+ (value): value is number =>
45
+ typeof value === "number" &&
46
+ value !== AIDocumentDomain.AI_DOCUMENT_DOMAIN_UNSPECIFIED,
47
+ ),
48
+ ).toHaveLength(15);
49
+ expect(
50
+ Object.values(ContentEntityType).filter(
51
+ (value): value is number =>
52
+ typeof value === "number" && value !== ContentEntityType.UNSPECIFIED,
53
+ ),
54
+ ).toHaveLength(15);
55
+ });
56
+
57
+ it("preserves existing numbers and allocates the six additive values", () => {
58
+ expect(ContentEntityType.POST).toBe(1);
59
+ expect(ContentEntityType.PAGE).toBe(2);
60
+ expect(ContentEntityType.WORK).toBe(3);
61
+ expect(ContentEntityType.ARTIST).toBe(4);
62
+ expect(ContentEntityType.LABEL).toBe(5);
63
+ expect(ContentEntityType.RELEASE).toBe(6);
64
+ expect(ContentEntityType.FORM).toBe(7);
65
+ expect(ContentEntityType.PROGRAM_EVENT).toBe(8);
66
+ expect(ContentEntityType.POST_SERIES).toBe(9);
67
+ expect(ContentEntityType.MENU).toBe(10);
68
+ expect(ContentEntityType.EMAIL_TEMPLATE).toBe(11);
69
+ expect(ContentEntityType.EMAIL_LAYOUT).toBe(12);
70
+ expect(ContentEntityType.CAMPAIGN).toBe(13);
71
+ expect(ContentEntityType.PRIVACY).toBe(14);
72
+ expect(ContentEntityType.TERMS).toBe(15);
73
+ });
74
+ });
@@ -0,0 +1,125 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { parse } from "yaml";
4
+ import { describe, expect, it } from "vitest";
5
+ import { Queues } from "./index.ts";
6
+
7
+ type AsyncAPIChannel = {
8
+ address?: string;
9
+ messages?: Record<string, { $ref?: string }>;
10
+ "x-channel-kind"?: "durable_queue" | "signal";
11
+ "x-signal-types"?: string[];
12
+ };
13
+
14
+ const spec = parse(
15
+ readFileSync(resolve(process.cwd(), "asyncapi/asyncapi.yaml"), "utf8"),
16
+ ) as {
17
+ channels: Record<string, AsyncAPIChannel>;
18
+ components: { messages: Record<string, unknown> };
19
+ };
20
+
21
+ describe("durable asynchronous event model", () => {
22
+ it("exports the authoritative result and projection queues", () => {
23
+ expect(Queues).toMatchObject({
24
+ transcodeResult: "transcode.result",
25
+ waveformResult: "waveform.result",
26
+ meshOptimizationResult: "mesh.optimization.result",
27
+ releaseTrackOriginalAudioProjection:
28
+ "release.track_original_audio.projection",
29
+ emailCampaign: "email.campaign",
30
+ userDeleteIdentity: "user.delete.identity",
31
+ userDeleteAvatar: "user.delete.avatar",
32
+ });
33
+
34
+ for (const [channelName, expected] of Object.entries({
35
+ transcodeResult: {
36
+ queueName: "transcode.result",
37
+ messageName: "transcodeCompleteEvent",
38
+ messageRef: "#/components/messages/TranscodeCompleteEvent",
39
+ },
40
+ waveformResult: {
41
+ queueName: "waveform.result",
42
+ messageName: "waveformResultEvent",
43
+ messageRef: "#/components/messages/WaveformResultEvent",
44
+ },
45
+ meshOptimizationResult: {
46
+ queueName: "mesh.optimization.result",
47
+ messageName: "meshOptimizationResultEvent",
48
+ messageRef: "#/components/messages/MeshOptimizationResultEvent",
49
+ },
50
+ releaseTrackOriginalAudioProjection: {
51
+ queueName: "release.track_original_audio.projection",
52
+ messageName: "fileIngestAttachedEvent",
53
+ messageRef: "#/components/messages/FileIngestAttachedEvent",
54
+ },
55
+ emailCampaign: {
56
+ queueName: "email.campaign",
57
+ messageName: "sendBulkEmailBatchEvent",
58
+ messageRef: "#/components/messages/SendBulkEmailBatchEvent",
59
+ },
60
+ userDeleteIdentity: {
61
+ queueName: "user.delete.identity",
62
+ messageName: "userDeleteIdentityCommand",
63
+ messageRef: "#/components/messages/UserDeleteIdentityCommand",
64
+ },
65
+ userDeleteAvatar: {
66
+ queueName: "user.delete.avatar",
67
+ messageName: "userDeleteAvatarCommand",
68
+ messageRef: "#/components/messages/UserDeleteAvatarCommand",
69
+ },
70
+ })) {
71
+ const channel = spec.channels[channelName];
72
+ expect(channel?.address).toBe(expected.queueName);
73
+ expect(channel?.messages).toEqual({
74
+ [expected.messageName]: { $ref: expected.messageRef },
75
+ });
76
+ expect(channel?.["x-channel-kind"]).toBe("durable_queue");
77
+ }
78
+ });
79
+
80
+ it("keeps file-ingest signals reconstructable and projection work durable", () => {
81
+ expect(spec.channels.releaseTrackOriginalAudioProjection).toMatchObject({
82
+ "x-channel-kind": "durable_queue",
83
+ });
84
+ expect(spec.channels.fileIngest).toMatchObject({
85
+ "x-channel-kind": "signal",
86
+ "x-signal-types": [
87
+ "upload",
88
+ "download",
89
+ "finalized",
90
+ "attached",
91
+ "failed",
92
+ ],
93
+ });
94
+ });
95
+
96
+ it("keeps the retired generic render queues and messages absent", () => {
97
+ for (const queue of [
98
+ "post.render",
99
+ "work.render",
100
+ "program_event.render",
101
+ "privacy.render",
102
+ "terms.render",
103
+ ]) {
104
+ expect(Object.values(Queues)).not.toContain(queue);
105
+ }
106
+ for (const channel of [
107
+ "postRender",
108
+ "workRender",
109
+ "programEventRender",
110
+ "privacyRender",
111
+ "termsRender",
112
+ ]) {
113
+ expect(Object.keys(spec.channels)).not.toContain(channel);
114
+ }
115
+ for (const message of [
116
+ "PostRenderRequest",
117
+ "WorkRenderRequest",
118
+ "ProgramEventRenderRequest",
119
+ "PrivacyRenderRequest",
120
+ "TermsRenderRequest",
121
+ ]) {
122
+ expect(Object.keys(spec.components.messages)).not.toContain(message);
123
+ }
124
+ });
125
+ });
@@ -0,0 +1,67 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { describe, expect, it } from "vitest";
3
+ import {
4
+ assertFormLocaleStableID,
5
+ formFieldBlockHandle,
6
+ formFieldCheckboxLabelTarget,
7
+ formOptionBlockHandle,
8
+ formOptionLabelTarget,
9
+ formRootBlockHandle,
10
+ formRootTitleTarget,
11
+ formStepBlockHandle,
12
+ formStepDescriptionTarget,
13
+ formValidatorBlockHandle,
14
+ formValidatorMessageTarget,
15
+ } from "@echovisionlab/geul-proto/intra/form_locale_catalog.ts";
16
+
17
+ interface FormLocaleParityFixture {
18
+ readonly valid: Record<string, string>;
19
+ readonly ids: Record<string, string>;
20
+ readonly invalidIds: readonly string[];
21
+ }
22
+
23
+ const fixture = JSON.parse(
24
+ readFileSync(
25
+ new URL(
26
+ "../../proto/gen/api/intra/v1/form_locale_catalog_parity.json",
27
+ import.meta.url,
28
+ ),
29
+ "utf8",
30
+ ),
31
+ ) as FormLocaleParityFixture;
32
+
33
+ describe("generated Form locale catalog", () => {
34
+ it("matches the shared cross-language block-handle fixtures", () => {
35
+ expect({
36
+ root: formRootBlockHandle,
37
+ step: formStepBlockHandle(fixture.ids.step),
38
+ field: formFieldBlockHandle(fixture.ids.field),
39
+ option: formOptionBlockHandle(fixture.ids.option),
40
+ validator: formValidatorBlockHandle(fixture.ids.validator),
41
+ }).toEqual(fixture.valid);
42
+ });
43
+
44
+ it("rejects non-canonical and all-digit stable IDs", () => {
45
+ for (const id of fixture.invalidIds) {
46
+ expect(() => assertFormLocaleStableID(id)).toThrow();
47
+ }
48
+ });
49
+
50
+ it("constructs only block-owned leaf targets without paths", () => {
51
+ const targets = [
52
+ formRootTitleTarget(),
53
+ formStepDescriptionTarget(fixture.ids.step),
54
+ formFieldCheckboxLabelTarget(fixture.ids.field),
55
+ formOptionLabelTarget(fixture.ids.option),
56
+ formValidatorMessageTarget(fixture.ids.validator),
57
+ ];
58
+ for (const target of targets) {
59
+ expect(target.owner.case).toBe("blockHandle");
60
+ expect(target.owner.value).toMatch(
61
+ /^(document|form:(step|field|option|validator):)/,
62
+ );
63
+ expect(target.fieldHandle).not.toBe("");
64
+ expect(target.path).toEqual([]);
65
+ }
66
+ });
67
+ });
@@ -0,0 +1,59 @@
1
+ // Code generated by AsyncAPI. DO NOT EDIT.
2
+ // Source: asyncapi/asyncapi.yaml
3
+
4
+ /**
5
+ * PostgreSQL NOTIFY names for reconstructable realtime signals
6
+ */
7
+ export const Signals = {
8
+ transcodeProgress: "transcode.progress",
9
+ transcodeCancel: "transcode.cancel",
10
+ waveformProgress: "waveform.progress",
11
+ waveformCancel: "waveform.cancel",
12
+ meshOptimizationProgress: "mesh.optimization.progress",
13
+ mediaProcessingLifecycle: "media.processing.lifecycle",
14
+ ogLifecycle: "og.lifecycle",
15
+ mailAdapterChanged: "mail_adapter.changed",
16
+ contentUpdated: "content.updated",
17
+ fileIngest: "file.ingest",
18
+ translationLifecycle: "translation.lifecycle",
19
+ } as const;
20
+
21
+ /**
22
+ * Queue names for direct work and durable consumer queues
23
+ */
24
+ export const Queues = {
25
+ ogGenerate: "og.generate",
26
+ transcoderAudio: "transcoder.audio",
27
+ transcoderVideo: "transcoder.video",
28
+ waveformGenerate: "waveform.generate",
29
+ assetOptimizerMesh: "asset_optimizer.mesh",
30
+ fileDelete: "file.delete",
31
+ emailSend: "email.send",
32
+ emailAuth: "email.auth",
33
+ emailCampaign: "email.campaign",
34
+ userDeleteIdentity: "user.delete.identity",
35
+ userDeleteAvatar: "user.delete.avatar",
36
+ aiMetadataGenerate: "ai.metadata.generate",
37
+ translationGenerate: "translation.generate",
38
+ transcodeResult: "transcode.result",
39
+ waveformResult: "waveform.result",
40
+ meshOptimizationResult: "mesh.optimization.result",
41
+ releaseTrackOriginalAudioProjection:
42
+ "release.track_original_audio.projection",
43
+ } as const;
44
+
45
+ export const FileIngestSignalTypes = {
46
+ upload: "upload",
47
+ download: "download",
48
+ finalized: "finalized",
49
+ attached: "attached",
50
+ failed: "failed",
51
+ } as const;
52
+
53
+ /**
54
+ * Content type constants
55
+ */
56
+ export const ContentType = {
57
+ protobuf: "application/x-protobuf",
58
+ json: "application/json",
59
+ } as const;
package/src/index.ts ADDED
@@ -0,0 +1,86 @@
1
+ export {
2
+ ContentType,
3
+ FileIngestSignalTypes,
4
+ Queues,
5
+ Signals,
6
+ } from "./generated.ts";
7
+
8
+ export {
9
+ assertPgmqEnvelope,
10
+ createPgmqEnvelope,
11
+ pgmqEnvelopePayload,
12
+ PGMQ_ENVELOPE_SCHEMA_VERSION,
13
+ PgmqClient,
14
+ } from "./pgmq.ts";
15
+ export type {
16
+ PgmqEnvelope,
17
+ PgmqExecutor,
18
+ PgmqHeaders,
19
+ PgmqMessage,
20
+ } from "./pgmq.ts";
21
+
22
+ export { assertRelayInteractiveAIDocumentMutationRequest } from "./interactive-mutation.ts";
23
+
24
+ export {
25
+ InternalOgService,
26
+ OgGenerationClaimResult,
27
+ } from "@echovisionlab/geul-proto/intra/og_pb.ts";
28
+ export type {
29
+ ClaimOgGenerationRequest,
30
+ ClaimOgGenerationResponse,
31
+ CompleteOgGenerationRequest,
32
+ CompleteOgGenerationResponse,
33
+ FailOgGenerationRequest,
34
+ FailOgGenerationResponse,
35
+ OgRenderConfigSnapshot,
36
+ } from "@echovisionlab/geul-proto/intra/og_pb.ts";
37
+
38
+ export {
39
+ AssetDisposition,
40
+ deserializeProto,
41
+ FileIngestAttachedEventSchema,
42
+ FileIngestDownloadEventSchema,
43
+ FileIngestFailedEventSchema,
44
+ FileIngestFailureReason,
45
+ FileIngestFinalizedEventSchema,
46
+ FileIngestIdentitySchema,
47
+ FileIngestMediaKind,
48
+ FileIngestSource,
49
+ FileIngestUploadEventSchema,
50
+ MediaProcessingLifecycleEventSchema,
51
+ MediaProcessingStatus,
52
+ MeshOptimizationResultEventSchema,
53
+ OgEntityType,
54
+ OgGenerationJobSchema,
55
+ OgGenerationLifecycleEventSchema,
56
+ OgGenerationStatus,
57
+ OgGenerationTargetSchema,
58
+ TranscodeCompleteEventSchema,
59
+ TranscodeEntityType,
60
+ TranslationLifecycleEventSchema,
61
+ WaveformResultEventSchema,
62
+ } from "./proto-helpers.ts";
63
+
64
+ export { assertTranslationJobRequestArtifact } from "./translation-source.ts";
65
+
66
+ export type {
67
+ AssetRef,
68
+ AssetWriteResult,
69
+ AssetWriteTarget,
70
+ FileIngestAttachedEvent,
71
+ FileIngestDownloadEvent,
72
+ FileIngestFailedEvent,
73
+ FileIngestFinalizedEvent,
74
+ FileIngestIdentity,
75
+ FileIngestProgress,
76
+ FileIngestUploadEvent,
77
+ MediaProcessingLifecycleEvent,
78
+ MediaProcessingLifecycleOutputs,
79
+ MeshOptimizationResultEvent,
80
+ OgGenerationJob,
81
+ OgGenerationLifecycleEvent,
82
+ OgGenerationTarget,
83
+ TranscodeCompleteEvent,
84
+ TranslationLifecycleEvent,
85
+ WaveformResultEvent,
86
+ } from "./proto-helpers.ts";