@reventlessdev/reventless-core 3.0.0-alpha.166 → 3.0.0-alpha.168
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/CHANGELOG.md +20 -0
- package/package.json +3 -3
- package/src/Projection.res +11 -6
- package/src/Projection.res.mjs +27 -25
- package/src/admin/AdminApi.res +9 -62
- package/src/admin/AdminApi.res.mjs +6 -28
- package/src/components/Api/GraphQL_Stitcher.res +4 -112
- package/src/components/Api/GraphQL_Stitcher.res.mjs +0 -90
- package/src/components/Dcb/Dcb_Builder.res +38 -21
- package/src/components/Dcb/Dcb_Builder.res.mjs +13 -4
- package/src/components/ReadModel/ReadModel_Callback.res +1 -1
- package/src/components/ReadModel/ReadModel_Callback.res.mjs +1 -1
- package/src/components/StateViewSlice/StateViewSlice_Builder.res +1 -1
- package/src/components/StateViewSlice/StateViewSlice_Builder.res.mjs +1 -1
- package/src/components/StateViewSlice/StateViewSlice_Callback.res +1 -1
- package/src/components/StateViewSlice/StateViewSlice_Callback.res.mjs +1 -1
- package/src/plugin/component/Plugin_Builder.res +5 -7
- package/src/plugin/component/Plugin_Builder.res.mjs +3 -6
- package/src/util/EffectLogger.res +5 -3
- package/src/util/EffectLogger.res.mjs +3 -10
- package/src/util/Logger.res +41 -11
- package/src/util/Logger.res.mjs +36 -10
- package/tests/api/GraphQL_StitcherTest.res +0 -198
- package/tests/api/GraphQL_StitcherTest.res.mjs +0 -168
- package/src/admin/ApiFragmentRegistry/ApiFragmentRegistryBehavior.res +0 -108
- package/src/admin/ApiFragmentRegistry/ApiFragmentRegistryBehavior.res.mjs +0 -190
- package/src/admin/ApiFragmentRegistry/ApiFragmentRegistrySpec.res +0 -71
- package/src/admin/ApiFragmentRegistry/ApiFragmentRegistrySpec.res.mjs +0 -92
- package/src/admin/ApiFragmentRegistry/ApiFragmentsProjection.res +0 -63
- package/src/admin/ApiFragmentRegistry/ApiFragmentsProjection.res.mjs +0 -108
- package/src/admin/ApiFragmentRegistry/ApiFragmentsReadModelSpec.res +0 -23
- package/src/admin/ApiFragmentRegistry/ApiFragmentsReadModelSpec.res.mjs +0 -43
- package/src/admin/Platform_ApiFragmentsApi.res +0 -32
- package/src/admin/Platform_ApiFragmentsApi.res.mjs +0 -52
- package/src/components/Api/Api_Builder.res +0 -40
- package/src/components/Api/Api_Builder.res.mjs +0 -33
- package/src/components/Api/Api_Operations.res +0 -13
- package/src/components/Api/Api_Operations.res.mjs +0 -12
- package/src/components/Api/GraphQL_PushPlanner.res +0 -65
- package/src/components/Api/GraphQL_PushPlanner.res.mjs +0 -46
- package/tests/admin/Platform_ApiFragmentsApiTest.res +0 -66
- package/tests/admin/Platform_ApiFragmentsApiTest.res.mjs +0 -68
- package/tests/api/GraphQL_PushPlannerTest.res +0 -62
- package/tests/api/GraphQL_PushPlannerTest.res.mjs +0 -80
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
// ApiFragmentRegistry aggregate — the platform API-schema fragment registry, replacing the
|
|
2
|
-
// raw `deploy-schema:*` keyspace (see docs/plans/event-sourced-fragment-registries.md).
|
|
3
|
-
//
|
|
4
|
-
// Modelled as a SINGLETON aggregate (fixed id, e.g. "registry"): the whole registry lives in
|
|
5
|
-
// one consistency boundary / one event stream, so a fragment change reads the whole registry
|
|
6
|
-
// with a single strongly-consistent partition read + snapshot — fixing the lagging-read
|
|
7
|
-
// consistency AND the unbounded-scan cost of the earlier per-plugin DCB-slice design (deploy
|
|
8
|
-
// validation #4). `pluginId` is a payload field here, not the aggregate id.
|
|
9
|
-
//
|
|
10
|
-
// Transitions are driven by the DEPLOY / DESTROY of a plugin stack: the deploy registers its
|
|
11
|
-
// fragment as a SigV4 system caller against the Platform API, and only final retirement
|
|
12
|
-
// (`pulumi destroy`, resolvers gone) deregisters. Version supersession must NOT deregister —
|
|
13
|
-
// the registry is keyed by plugin NAME, so a successor's deploy overwrites its predecessor's
|
|
14
|
-
// fragment in place. The registry is idempotent: re-registering an identical fragment is a
|
|
15
|
-
// no-op, deregistering an absent fragment is a no-op — it never rejects a command.
|
|
16
|
-
//
|
|
17
|
-
// `RecordApiFragmentPush` is the write-back of the reactive schema-push SideEffect: after
|
|
18
|
-
// stitching and pushing the composed schema it records the outcome (ok / error + message) on
|
|
19
|
-
// the row of the plugin whose fragment change triggered the push, so the deploy waiter can
|
|
20
|
-
// poll the push status instead of timing out on introspection. It is `@noApi` — dispatched
|
|
21
|
-
// platform-side, never by a GraphQL caller.
|
|
22
|
-
@@reventless.spec("ApiFragmentRegistry")
|
|
23
|
-
|
|
24
|
-
open Reventless.Plugin
|
|
25
|
-
|
|
26
|
-
// One entry of the consistent registry snapshot carried on `ApiSchemaComputed` — the whole
|
|
27
|
-
// per-plugin fragment set AFTER the triggering change, folded from the aggregate's own
|
|
28
|
-
// consistent state. The push SideEffect stitches per target from this snapshot (splitApi +
|
|
29
|
-
// admin base are its deploy concerns), so consistency is guaranteed without any
|
|
30
|
-
// eventually-consistent read.
|
|
31
|
-
@schema
|
|
32
|
-
type fragmentSnapshotEntry = {
|
|
33
|
-
pluginId: string,
|
|
34
|
-
encoded: string,
|
|
35
|
-
protocol: string,
|
|
36
|
-
apiTarget: apiTarget,
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// `RegisterApiFragment` / `DeregisterApiFragment` ARE the GraphQL surface — the deploy calls
|
|
40
|
-
// them as a SigV4 system caller against the Platform API (exposed via the standard admin-aggregate
|
|
41
|
-
// path as `Platform_RegisterApiFragment` / `Platform_DeregisterApiFragment`; IAM via
|
|
42
|
-
// `systemCallerFieldNames`). `at` is the registration timestamp, threaded so the read-model row
|
|
43
|
-
// can surface registered/updated times.
|
|
44
|
-
@schema
|
|
45
|
-
type command =
|
|
46
|
-
| RegisterApiFragment({pluginId: string, fragment: apiSchemaFragment, apiTarget: apiTarget, at: string})
|
|
47
|
-
| DeregisterApiFragment({pluginId: string})
|
|
48
|
-
| @noApi RecordApiFragmentPush({pluginId: string, ok: bool, message: string, at: string})
|
|
49
|
-
|
|
50
|
-
// The registry never rejects (idempotent register/deregister/record); this variant is never
|
|
51
|
-
// returned.
|
|
52
|
-
@schema
|
|
53
|
-
type error = | RegistryError
|
|
54
|
-
|
|
55
|
-
@schema
|
|
56
|
-
type event =
|
|
57
|
-
| ApiFragmentRegistered({pluginId: string, fragment: apiSchemaFragment, apiTarget: apiTarget, at: string})
|
|
58
|
-
| ApiFragmentUpdated({
|
|
59
|
-
pluginId: string,
|
|
60
|
-
previousFragment: apiSchemaFragment,
|
|
61
|
-
newFragment: apiSchemaFragment,
|
|
62
|
-
apiTarget: apiTarget,
|
|
63
|
-
at: string,
|
|
64
|
-
})
|
|
65
|
-
| ApiFragmentDeregistered({pluginId: string})
|
|
66
|
-
| ApiFragmentPushRecorded({pluginId: string, ok: bool, message: string, at: string})
|
|
67
|
-
// Derived echo of the consistent registry contents after a fragment change — the reactive
|
|
68
|
-
// push SideEffect's trigger. Carries the full per-plugin snapshot so the SideEffect stitches
|
|
69
|
-
// from consistent state without a read. Does NOT change fold state (evolve ignores it), and
|
|
70
|
-
// is NOT projected by the ApiFragments read model.
|
|
71
|
-
| ApiSchemaComputed({snapshot: array<fragmentSnapshotEntry>})
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
-
|
|
3
|
-
import * as S from "sury/src/S.res.mjs";
|
|
4
|
-
import * as Plugin$Reventless from "@reventlessdev/reventless-spec/src/components/Plugin.res.mjs";
|
|
5
|
-
import * as Api$ReventlessInfra from "@reventlessdev/reventless-infra/src/components/Api.res.mjs";
|
|
6
|
-
|
|
7
|
-
let fragmentSnapshotEntrySchema = S.schema(s => ({
|
|
8
|
-
pluginId: s.m(S.string),
|
|
9
|
-
encoded: s.m(S.string),
|
|
10
|
-
protocol: s.m(S.string),
|
|
11
|
-
apiTarget: s.m(Plugin$Reventless.apiTargetSchema)
|
|
12
|
-
}));
|
|
13
|
-
|
|
14
|
-
let commandSchema = S.union([
|
|
15
|
-
S.schema(s => ({
|
|
16
|
-
TAG: "RegisterApiFragment",
|
|
17
|
-
pluginId: s.m(S.string),
|
|
18
|
-
fragment: s.m(Plugin$Reventless.apiSchemaFragmentSchema),
|
|
19
|
-
apiTarget: s.m(Plugin$Reventless.apiTargetSchema),
|
|
20
|
-
at: s.m(S.string)
|
|
21
|
-
})),
|
|
22
|
-
S.schema(s => ({
|
|
23
|
-
TAG: "DeregisterApiFragment",
|
|
24
|
-
pluginId: s.m(S.string)
|
|
25
|
-
})),
|
|
26
|
-
S.schema(s => ({
|
|
27
|
-
TAG: "RecordApiFragmentPush",
|
|
28
|
-
pluginId: s.m(S.string),
|
|
29
|
-
ok: s.m(S.bool),
|
|
30
|
-
message: s.m(S.string),
|
|
31
|
-
at: s.m(S.string)
|
|
32
|
-
}))
|
|
33
|
-
]);
|
|
34
|
-
|
|
35
|
-
let errorSchema = S.literal("RegistryError");
|
|
36
|
-
|
|
37
|
-
let eventSchema = S.union([
|
|
38
|
-
S.schema(s => ({
|
|
39
|
-
TAG: "ApiFragmentRegistered",
|
|
40
|
-
pluginId: s.m(S.string),
|
|
41
|
-
fragment: s.m(Plugin$Reventless.apiSchemaFragmentSchema),
|
|
42
|
-
apiTarget: s.m(Plugin$Reventless.apiTargetSchema),
|
|
43
|
-
at: s.m(S.string)
|
|
44
|
-
})),
|
|
45
|
-
S.schema(s => ({
|
|
46
|
-
TAG: "ApiFragmentUpdated",
|
|
47
|
-
pluginId: s.m(S.string),
|
|
48
|
-
previousFragment: s.m(Plugin$Reventless.apiSchemaFragmentSchema),
|
|
49
|
-
newFragment: s.m(Plugin$Reventless.apiSchemaFragmentSchema),
|
|
50
|
-
apiTarget: s.m(Plugin$Reventless.apiTargetSchema),
|
|
51
|
-
at: s.m(S.string)
|
|
52
|
-
})),
|
|
53
|
-
S.schema(s => ({
|
|
54
|
-
TAG: "ApiFragmentDeregistered",
|
|
55
|
-
pluginId: s.m(S.string)
|
|
56
|
-
})),
|
|
57
|
-
S.schema(s => ({
|
|
58
|
-
TAG: "ApiFragmentPushRecorded",
|
|
59
|
-
pluginId: s.m(S.string),
|
|
60
|
-
ok: s.m(S.bool),
|
|
61
|
-
message: s.m(S.string),
|
|
62
|
-
at: s.m(S.string)
|
|
63
|
-
})),
|
|
64
|
-
S.schema(s => ({
|
|
65
|
-
TAG: "ApiSchemaComputed",
|
|
66
|
-
snapshot: s.m(S.array(fragmentSnapshotEntrySchema))
|
|
67
|
-
}))
|
|
68
|
-
]);
|
|
69
|
-
|
|
70
|
-
let commandSchema$1 = Api$ReventlessInfra.markNoApiVariants(commandSchema, ["RecordApiFragmentPush"]);
|
|
71
|
-
|
|
72
|
-
function commandAuthorization(param) {
|
|
73
|
-
return "AllowAuthenticated";
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
let name = "ApiFragmentRegistry";
|
|
77
|
-
|
|
78
|
-
let Id;
|
|
79
|
-
|
|
80
|
-
let moduleUrl = "@reventlessdev/reventless-core/src/admin/ApiFragmentRegistry/ApiFragmentRegistrySpec.res.mjs";
|
|
81
|
-
|
|
82
|
-
export {
|
|
83
|
-
name,
|
|
84
|
-
Id,
|
|
85
|
-
fragmentSnapshotEntrySchema,
|
|
86
|
-
errorSchema,
|
|
87
|
-
eventSchema,
|
|
88
|
-
commandSchema$1 as commandSchema,
|
|
89
|
-
moduleUrl,
|
|
90
|
-
commandAuthorization,
|
|
91
|
-
}
|
|
92
|
-
/* fragmentSnapshotEntrySchema Not a pure module */
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
// ApiFragments projection — maps ApiFragmentRegistry aggregate events to per-plugin rows of
|
|
2
|
-
// the ApiFragments read model. Manual Mapping.Make form (mirrors PluginsProjection; no ppx —
|
|
3
|
-
// this file lives outside a ReadModel/ folder). Rows are keyed by the payload `pluginId`, not
|
|
4
|
-
// the (singleton) aggregate id. `ApiSchemaComputed` is the reactive push's trigger only — it
|
|
5
|
-
// carries no per-plugin row change, so it is Ignored here.
|
|
6
|
-
//
|
|
7
|
-
// A fragment change resets the row to "pending"; the push outcome arrives as a subsequent
|
|
8
|
-
// ApiFragmentPushRecorded. An Update on a deleted row (a push recorded after deregistration) is
|
|
9
|
-
// a no-op by Update semantics.
|
|
10
|
-
|
|
11
|
-
open Reventless.Projection
|
|
12
|
-
open Reventless.Message // brings the projection input record ({event, id, meta}) labels into scope
|
|
13
|
-
|
|
14
|
-
let moduleUrl: string = %raw(`import.meta.url`)
|
|
15
|
-
|
|
16
|
-
module ApiFragmentsMapping = Reventless.Projection.Mapping.Make(
|
|
17
|
-
ApiFragmentRegistrySpec,
|
|
18
|
-
ApiFragmentsReadModelSpec,
|
|
19
|
-
{
|
|
20
|
-
let project = ({event, _}) =>
|
|
21
|
-
switch event {
|
|
22
|
-
| ApiFragmentRegistrySpec.ApiFragmentRegistered({pluginId, fragment, apiTarget, at}) =>
|
|
23
|
-
Set(
|
|
24
|
-
pluginId,
|
|
25
|
-
{
|
|
26
|
-
ApiFragmentsReadModelSpec.pluginId,
|
|
27
|
-
encoded: fragment.encoded,
|
|
28
|
-
protocol: fragment.protocol,
|
|
29
|
-
apiTarget,
|
|
30
|
-
registeredAt: at,
|
|
31
|
-
updatedAt: at,
|
|
32
|
-
pushStatus: "pending",
|
|
33
|
-
pushMessage: "",
|
|
34
|
-
pushedAt: "",
|
|
35
|
-
},
|
|
36
|
-
)
|
|
37
|
-
| ApiFragmentUpdated({pluginId, newFragment, apiTarget, at}) =>
|
|
38
|
-
Update(pluginId, state => {
|
|
39
|
-
...state,
|
|
40
|
-
encoded: newFragment.encoded,
|
|
41
|
-
protocol: newFragment.protocol,
|
|
42
|
-
apiTarget,
|
|
43
|
-
updatedAt: at,
|
|
44
|
-
pushStatus: "pending",
|
|
45
|
-
pushMessage: "",
|
|
46
|
-
})
|
|
47
|
-
| ApiFragmentDeregistered({pluginId}) => Delete(pluginId)
|
|
48
|
-
| ApiFragmentPushRecorded({pluginId, ok, message, at}) =>
|
|
49
|
-
Update(pluginId, state => {
|
|
50
|
-
...state,
|
|
51
|
-
pushStatus: ok ? "ok" : "error",
|
|
52
|
-
pushMessage: message,
|
|
53
|
-
pushedAt: at,
|
|
54
|
-
})
|
|
55
|
-
// Reactive push trigger only — no row change.
|
|
56
|
-
| ApiSchemaComputed(_) => Ignore
|
|
57
|
-
}
|
|
58
|
-
},
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
module Mappings = Reventless.Projection.Mappings.Make(ApiFragmentsReadModelSpec)
|
|
62
|
-
|
|
63
|
-
let mappings: array<module(Mappings.Mapping)> = [module(ApiFragmentsMapping)]
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
-
|
|
3
|
-
import * as Id$Reventless from "@reventlessdev/reventless-spec/src/types/Id.res.mjs";
|
|
4
|
-
import * as Projection$Reventless from "@reventlessdev/reventless-spec/src/types/Projection.res.mjs";
|
|
5
|
-
import * as ApiFragmentRegistrySpec$ReventlessCore from "./ApiFragmentRegistrySpec.res.mjs";
|
|
6
|
-
import * as ApiFragmentsReadModelSpec$ReventlessCore from "./ApiFragmentsReadModelSpec.res.mjs";
|
|
7
|
-
|
|
8
|
-
let moduleUrl = import.meta.url;
|
|
9
|
-
|
|
10
|
-
function project(param) {
|
|
11
|
-
let event = param.event;
|
|
12
|
-
switch (event.TAG) {
|
|
13
|
-
case "ApiFragmentRegistered" :
|
|
14
|
-
let at = event.at;
|
|
15
|
-
let fragment = event.fragment;
|
|
16
|
-
let pluginId = event.pluginId;
|
|
17
|
-
return {
|
|
18
|
-
TAG: "Set",
|
|
19
|
-
_0: pluginId,
|
|
20
|
-
_1: {
|
|
21
|
-
pluginId: pluginId,
|
|
22
|
-
encoded: fragment.encoded,
|
|
23
|
-
protocol: fragment.protocol,
|
|
24
|
-
apiTarget: event.apiTarget,
|
|
25
|
-
registeredAt: at,
|
|
26
|
-
updatedAt: at,
|
|
27
|
-
pushStatus: "pending",
|
|
28
|
-
pushMessage: "",
|
|
29
|
-
pushedAt: ""
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
case "ApiFragmentUpdated" :
|
|
33
|
-
let at$1 = event.at;
|
|
34
|
-
let apiTarget = event.apiTarget;
|
|
35
|
-
let newFragment = event.newFragment;
|
|
36
|
-
return {
|
|
37
|
-
TAG: "Update",
|
|
38
|
-
_0: event.pluginId,
|
|
39
|
-
_1: state => ({
|
|
40
|
-
pluginId: state.pluginId,
|
|
41
|
-
encoded: newFragment.encoded,
|
|
42
|
-
protocol: newFragment.protocol,
|
|
43
|
-
apiTarget: apiTarget,
|
|
44
|
-
registeredAt: state.registeredAt,
|
|
45
|
-
updatedAt: at$1,
|
|
46
|
-
pushStatus: "pending",
|
|
47
|
-
pushMessage: "",
|
|
48
|
-
pushedAt: state.pushedAt
|
|
49
|
-
})
|
|
50
|
-
};
|
|
51
|
-
case "ApiFragmentDeregistered" :
|
|
52
|
-
return {
|
|
53
|
-
TAG: "Delete",
|
|
54
|
-
_0: event.pluginId
|
|
55
|
-
};
|
|
56
|
-
case "ApiFragmentPushRecorded" :
|
|
57
|
-
let at$2 = event.at;
|
|
58
|
-
let message = event.message;
|
|
59
|
-
let ok = event.ok;
|
|
60
|
-
return {
|
|
61
|
-
TAG: "Update",
|
|
62
|
-
_0: event.pluginId,
|
|
63
|
-
_1: state => ({
|
|
64
|
-
pluginId: state.pluginId,
|
|
65
|
-
encoded: state.encoded,
|
|
66
|
-
protocol: state.protocol,
|
|
67
|
-
apiTarget: state.apiTarget,
|
|
68
|
-
registeredAt: state.registeredAt,
|
|
69
|
-
updatedAt: state.updatedAt,
|
|
70
|
-
pushStatus: ok ? "ok" : "error",
|
|
71
|
-
pushMessage: message,
|
|
72
|
-
pushedAt: at$2
|
|
73
|
-
})
|
|
74
|
-
};
|
|
75
|
-
case "ApiSchemaComputed" :
|
|
76
|
-
return "Ignore";
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
let ApiFragmentsMapping = Projection$Reventless.Mapping.Make({
|
|
81
|
-
Id: Id$Reventless.$$String,
|
|
82
|
-
name: ApiFragmentRegistrySpec$ReventlessCore.name,
|
|
83
|
-
eventSchema: ApiFragmentRegistrySpec$ReventlessCore.eventSchema
|
|
84
|
-
})({
|
|
85
|
-
Id: Id$Reventless.$$String,
|
|
86
|
-
name: ApiFragmentsReadModelSpec$ReventlessCore.name,
|
|
87
|
-
stateSchema: ApiFragmentsReadModelSpec$ReventlessCore.stateSchema,
|
|
88
|
-
subIdConfig: undefined
|
|
89
|
-
})({
|
|
90
|
-
project: project
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
let Mappings = Projection$Reventless.Mappings.Make({
|
|
94
|
-
Id: Id$Reventless.$$String,
|
|
95
|
-
name: ApiFragmentsReadModelSpec$ReventlessCore.name,
|
|
96
|
-
stateSchema: ApiFragmentsReadModelSpec$ReventlessCore.stateSchema,
|
|
97
|
-
subIdConfig: undefined
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
let mappings = [ApiFragmentsMapping];
|
|
101
|
-
|
|
102
|
-
export {
|
|
103
|
-
moduleUrl,
|
|
104
|
-
ApiFragmentsMapping,
|
|
105
|
-
Mappings,
|
|
106
|
-
mappings,
|
|
107
|
-
}
|
|
108
|
-
/* moduleUrl Not a pure module */
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
// ApiFragments read model — the read side of the platform API-schema fragment registry:
|
|
2
|
-
// one row per plugin name holding its current fragment plus the outcome of the last schema
|
|
3
|
-
// push that included it. Projected off the ApiFragmentRegistry singleton aggregate (keyed by
|
|
4
|
-
// pluginId from the event payload, NOT by the aggregate id — the aggregate is a singleton).
|
|
5
|
-
// Backs the `Platform_ApiFragments` status query the deploy waiter polls.
|
|
6
|
-
//
|
|
7
|
-
// `pushStatus` is "pending" from registration/update until the reactive push SideEffect records
|
|
8
|
-
// the outcome ("ok" / "error"); `pushMessage`/`pushedAt` are "" until then (plain strings, no
|
|
9
|
-
// option fields — matches the aggregate's wire contract).
|
|
10
|
-
@@reventless.spec("ApiFragments")
|
|
11
|
-
|
|
12
|
-
@schema
|
|
13
|
-
type state = {
|
|
14
|
-
pluginId: string,
|
|
15
|
-
encoded: string,
|
|
16
|
-
protocol: string,
|
|
17
|
-
apiTarget: Reventless.Plugin.apiTarget,
|
|
18
|
-
registeredAt: string,
|
|
19
|
-
updatedAt: string,
|
|
20
|
-
pushStatus: string,
|
|
21
|
-
pushMessage: string,
|
|
22
|
-
pushedAt: string,
|
|
23
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
-
|
|
3
|
-
import * as S from "sury/src/S.res.mjs";
|
|
4
|
-
import * as Plugin$Reventless from "@reventlessdev/reventless-spec/src/components/Plugin.res.mjs";
|
|
5
|
-
import * as ReadModel$Reventless from "@reventlessdev/reventless-spec/src/components/ReadModel.res.mjs";
|
|
6
|
-
|
|
7
|
-
let stateSchema = S.schema(s => ({
|
|
8
|
-
pluginId: s.m(S.string),
|
|
9
|
-
encoded: s.m(S.string),
|
|
10
|
-
protocol: s.m(S.string),
|
|
11
|
-
apiTarget: s.m(Plugin$Reventless.apiTargetSchema),
|
|
12
|
-
registeredAt: s.m(S.string),
|
|
13
|
-
updatedAt: s.m(S.string),
|
|
14
|
-
pushStatus: s.m(S.string),
|
|
15
|
-
pushMessage: s.m(S.string),
|
|
16
|
-
pushedAt: s.m(S.string)
|
|
17
|
-
}));
|
|
18
|
-
|
|
19
|
-
let config = ReadModel$Reventless.config(undefined, undefined, undefined);
|
|
20
|
-
|
|
21
|
-
let name = "ApiFragments";
|
|
22
|
-
|
|
23
|
-
let Id;
|
|
24
|
-
|
|
25
|
-
let subIdConfig;
|
|
26
|
-
|
|
27
|
-
let moduleUrl = "@reventlessdev/reventless-core/src/admin/ApiFragmentRegistry/ApiFragmentsReadModelSpec.res.mjs";
|
|
28
|
-
|
|
29
|
-
let authorization = "AllowAuthenticated";
|
|
30
|
-
|
|
31
|
-
let visibility = "Public";
|
|
32
|
-
|
|
33
|
-
export {
|
|
34
|
-
name,
|
|
35
|
-
Id,
|
|
36
|
-
stateSchema,
|
|
37
|
-
config,
|
|
38
|
-
subIdConfig,
|
|
39
|
-
moduleUrl,
|
|
40
|
-
authorization,
|
|
41
|
-
visibility,
|
|
42
|
-
}
|
|
43
|
-
/* stateSchema Not a pure module */
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
// SDL type and JSON encoder for the Platform_ApiFragments GraphQL query — the
|
|
2
|
-
// deploy-facing status surface of the API-schema fragment registry. Shared
|
|
3
|
-
// between the in-memory adapter (which resolves from the in-process ApiFragments
|
|
4
|
-
// read-model QueryDb) and the AWS adapter (which scans the ApiFragments
|
|
5
|
-
// DynamoDB table), so the wire shape is byte-identical and one client query
|
|
6
|
-
// string works against either.
|
|
7
|
-
//
|
|
8
|
-
// The deploy waiter polls this to confirm its plugin's fragment reached ACTIVE
|
|
9
|
-
// (pushStatus "ok") or to surface a stitch error (pushStatus "error" +
|
|
10
|
-
// pushMessage). It exposes status only — NOT the encoded SDL, which the waiter
|
|
11
|
-
// does not need.
|
|
12
|
-
|
|
13
|
-
let sdlTypes: array<string> = [
|
|
14
|
-
`type Platform_ApiFragmentEntry {\n pluginId: String!\n apiTarget: String!\n pushStatus: String!\n pushMessage: String!\n pushedAt: String!\n registeredAt: String!\n updatedAt: String!\n}`,
|
|
15
|
-
]
|
|
16
|
-
|
|
17
|
-
let queryFieldName: string = "Platform_ApiFragments"
|
|
18
|
-
|
|
19
|
-
let sdlQueryField: string = ` ${queryFieldName}: [Platform_ApiFragmentEntry!]!`
|
|
20
|
-
|
|
21
|
-
let encodeApiFragmentEntry = (entry: ApiFragmentsReadModelSpec.state): JSON.t =>
|
|
22
|
-
Dict.fromArray([
|
|
23
|
-
("pluginId", JSON.Encode.string(Plugin.name(entry.pluginId))),
|
|
24
|
-
// apiTarget is a payload-less variant — its schema serializes to the bare
|
|
25
|
-
// JSON string "Domain" / "Platform".
|
|
26
|
-
("apiTarget", entry.apiTarget->S.reverseConvertToJsonOrThrow(Reventless.Plugin.apiTargetSchema)),
|
|
27
|
-
("pushStatus", JSON.Encode.string(entry.pushStatus)),
|
|
28
|
-
("pushMessage", JSON.Encode.string(entry.pushMessage)),
|
|
29
|
-
("pushedAt", JSON.Encode.string(entry.pushedAt)),
|
|
30
|
-
("registeredAt", JSON.Encode.string(entry.registeredAt)),
|
|
31
|
-
("updatedAt", JSON.Encode.string(entry.updatedAt)),
|
|
32
|
-
])->JSON.Encode.object
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
-
|
|
3
|
-
import * as S from "sury/src/S.res.mjs";
|
|
4
|
-
import * as Plugin$Reventless from "@reventlessdev/reventless-spec/src/components/Plugin.res.mjs";
|
|
5
|
-
import * as Plugin$ReventlessCore from "../plugin/component/Plugin.res.mjs";
|
|
6
|
-
|
|
7
|
-
let sdlTypes = [`type Platform_ApiFragmentEntry {\n pluginId: String!\n apiTarget: String!\n pushStatus: String!\n pushMessage: String!\n pushedAt: String!\n registeredAt: String!\n updatedAt: String!\n}`];
|
|
8
|
-
|
|
9
|
-
let queryFieldName = "Platform_ApiFragments";
|
|
10
|
-
|
|
11
|
-
let sdlQueryField = ` ` + queryFieldName + `: [Platform_ApiFragmentEntry!]!`;
|
|
12
|
-
|
|
13
|
-
function encodeApiFragmentEntry(entry) {
|
|
14
|
-
return Object.fromEntries([
|
|
15
|
-
[
|
|
16
|
-
"pluginId",
|
|
17
|
-
Plugin$ReventlessCore.name(entry.pluginId)
|
|
18
|
-
],
|
|
19
|
-
[
|
|
20
|
-
"apiTarget",
|
|
21
|
-
S.reverseConvertToJsonOrThrow(entry.apiTarget, Plugin$Reventless.apiTargetSchema)
|
|
22
|
-
],
|
|
23
|
-
[
|
|
24
|
-
"pushStatus",
|
|
25
|
-
entry.pushStatus
|
|
26
|
-
],
|
|
27
|
-
[
|
|
28
|
-
"pushMessage",
|
|
29
|
-
entry.pushMessage
|
|
30
|
-
],
|
|
31
|
-
[
|
|
32
|
-
"pushedAt",
|
|
33
|
-
entry.pushedAt
|
|
34
|
-
],
|
|
35
|
-
[
|
|
36
|
-
"registeredAt",
|
|
37
|
-
entry.registeredAt
|
|
38
|
-
],
|
|
39
|
-
[
|
|
40
|
-
"updatedAt",
|
|
41
|
-
entry.updatedAt
|
|
42
|
-
]
|
|
43
|
-
]);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export {
|
|
47
|
-
sdlTypes,
|
|
48
|
-
queryFieldName,
|
|
49
|
-
sdlQueryField,
|
|
50
|
-
encodeApiFragmentEntry,
|
|
51
|
-
}
|
|
52
|
-
/* S Not a pure module */
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
// Api_Builder — creates the Api component backed by a Provider (AppSync / in-memory yoga).
|
|
2
|
-
//
|
|
3
|
-
// Usage:
|
|
4
|
-
// module AwsApi = Api_Builder.Make(AppSync_Adapter)
|
|
5
|
-
// let api = AwsApi.make(~name="MyApi", ~baseFragment, ~opts)
|
|
6
|
-
|
|
7
|
-
module Make = (Provider: ReventlessInfra.Api_Adapter.Provider) => {
|
|
8
|
-
let construct = (~name, ~baseFragment: Reventless.Plugin.apiSchemaFragment, self) => {
|
|
9
|
-
let opts = {Pulumi.ComponentResource.parent: self->Component.toPulumiResource}
|
|
10
|
-
let (api, _role) = Provider.makeApiResource(~name, ~opts)
|
|
11
|
-
|
|
12
|
-
// Outputs — the cloud API ID
|
|
13
|
-
let apiId = api->Pulumi.Output.apply(a => (a->Obj.magic)["id"]->Option.getOr(name))
|
|
14
|
-
let _ = self->Component.setOutputs({ReventlessInfra.Api.apiId: apiId})
|
|
15
|
-
|
|
16
|
-
// Operations — updateSchema rebuilds the stitched SDL and publishes it
|
|
17
|
-
self->Component.setOperations(
|
|
18
|
-
api->Pulumi.Output.apply(apiValue => {
|
|
19
|
-
ReventlessInfra.Api.updateSchema: pluginFragments =>
|
|
20
|
-
Provider.updateSchema(
|
|
21
|
-
~api=Pulumi.Output.make(apiValue),
|
|
22
|
-
~baseFragment,
|
|
23
|
-
~pluginFragments,
|
|
24
|
-
),
|
|
25
|
-
}),
|
|
26
|
-
)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
let make = (
|
|
30
|
-
~name: string,
|
|
31
|
-
~baseFragment: Reventless.Plugin.apiSchemaFragment,
|
|
32
|
-
~opts: option<Pulumi.ComponentResource.options>=?,
|
|
33
|
-
): ReventlessInfra.Api.component =>
|
|
34
|
-
Component.make(
|
|
35
|
-
~componentType="reventless:index:Api",
|
|
36
|
-
~name,
|
|
37
|
-
~construct=construct(~name, ~baseFragment, ...),
|
|
38
|
-
~opts,
|
|
39
|
-
)
|
|
40
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
-
|
|
3
|
-
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
4
|
-
import * as Pulumi from "@pulumi/pulumi";
|
|
5
|
-
import * as Component$ReventlessCore from "../Component.res.mjs";
|
|
6
|
-
|
|
7
|
-
function Make(Provider) {
|
|
8
|
-
let construct = (name, baseFragment, self) => {
|
|
9
|
-
let opts_parent = Component$ReventlessCore.toPulumiResource(self);
|
|
10
|
-
let opts = {
|
|
11
|
-
parent: opts_parent
|
|
12
|
-
};
|
|
13
|
-
let match = Provider.makeApiResource(name, opts);
|
|
14
|
-
let api = match[0];
|
|
15
|
-
let apiId = api.apply(a => Stdlib_Option.getOr(a.id, name));
|
|
16
|
-
Component$ReventlessCore.setOutputs(self, {
|
|
17
|
-
apiId: apiId
|
|
18
|
-
});
|
|
19
|
-
Component$ReventlessCore.setOperations(self, api.apply(apiValue => ({
|
|
20
|
-
updateSchema: pluginFragments => Provider.updateSchema(Pulumi.output(apiValue), baseFragment, pluginFragments)
|
|
21
|
-
})));
|
|
22
|
-
};
|
|
23
|
-
let make = (name, baseFragment, opts) => Component$ReventlessCore.make("reventless:index:Api", name, extra => construct(name, baseFragment, extra), opts);
|
|
24
|
-
return {
|
|
25
|
-
construct: construct,
|
|
26
|
-
make: make
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export {
|
|
31
|
-
Make,
|
|
32
|
-
}
|
|
33
|
-
/* @pulumi/pulumi Not a pure module */
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
// Api_Operations — thin runtime wrapper for Api component operations.
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
Trigger a schema rebuild on the given Api component with the supplied plugin fragments.
|
|
5
|
-
Returns a promise that resolves once the provider has published the new schema.
|
|
6
|
-
*/
|
|
7
|
-
let updateSchema = (
|
|
8
|
-
api: ReventlessInfra.Api.component,
|
|
9
|
-
fragments: array<Reventless.Plugin.apiSchemaFragment>,
|
|
10
|
-
): Pulumi.Output.t<promise<unit>> =>
|
|
11
|
-
api
|
|
12
|
-
->Component.operations
|
|
13
|
-
->Pulumi.Output.apply(ops => ops.updateSchema(fragments))
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
-
|
|
3
|
-
import * as Component$ReventlessCore from "../Component.res.mjs";
|
|
4
|
-
|
|
5
|
-
function updateSchema(api, fragments) {
|
|
6
|
-
return Component$ReventlessCore.operations(api).apply(ops => ops.updateSchema(fragments));
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export {
|
|
10
|
-
updateSchema,
|
|
11
|
-
}
|
|
12
|
-
/* Component-ReventlessCore Not a pure module */
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
// Generic, provider-neutral push planner for the event-sourced API-schema fragment registry.
|
|
2
|
-
//
|
|
3
|
-
// Given the fragments currently in the ApiFragmentRegistry (each tagged with the API it belongs to)
|
|
4
|
-
// it groups them by target and produces one stitched-SDL push plan per API, honouring split vs.
|
|
5
|
-
// unified mode. It encodes the SAME base-selection rules the deploy-time push
|
|
6
|
-
// (`reventless-aws Platform.preResolversSchemaHook`) applies, so the reactive single writer stays
|
|
7
|
-
// consistent with the deploy path:
|
|
8
|
-
// - split mode: the Platform API carries the admin base + Platform-target fragments; the Domain
|
|
9
|
-
// API carries an EMPTY base + Domain-target fragments (admin lives only on the Platform API).
|
|
10
|
-
// - unified mode: a single API carries the admin base + ALL fragments regardless of target.
|
|
11
|
-
//
|
|
12
|
-
// Neutral SDL only — the AWS adapter injects @aws_subscribe per plan and pushes each to the
|
|
13
|
-
// matching AppSync API id; the local platform (scope 2) will consume the same plans in-process.
|
|
14
|
-
|
|
15
|
-
open Reventless.Plugin
|
|
16
|
-
|
|
17
|
-
type targetedFragment = {
|
|
18
|
-
fragment: apiSchemaFragment,
|
|
19
|
-
target: apiTarget,
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// The destination API of a push plan. In unified mode every fragment collapses onto `DomainApi`
|
|
23
|
-
// (the single shared API); the AWS adapter maps `PlatformApi` → the Platform AppSync id and
|
|
24
|
-
// `DomainApi` → the Domain AppSync id.
|
|
25
|
-
@schema
|
|
26
|
-
type planTarget = DomainApi | PlatformApi
|
|
27
|
-
|
|
28
|
-
type pushPlan = {
|
|
29
|
-
api: planTarget,
|
|
30
|
-
// Stitched neutral SDL for that API (admin/empty base + the target's fragments).
|
|
31
|
-
sdl: string,
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// An empty base fragment — the Domain API's base in split mode (admin is on the Platform API).
|
|
35
|
-
let emptyBase: apiSchemaFragment = GraphQL_Stitcher.encode({
|
|
36
|
-
types: [],
|
|
37
|
-
queries: [],
|
|
38
|
-
mutations: [],
|
|
39
|
-
subscriptions: [],
|
|
40
|
-
subscriptionSources: [],
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
let planPushes = (
|
|
44
|
-
~adminBase: apiSchemaFragment,
|
|
45
|
-
~fragments: array<targetedFragment>,
|
|
46
|
-
~splitApi: bool,
|
|
47
|
-
): array<pushPlan> =>
|
|
48
|
-
if splitApi {
|
|
49
|
-
let platformFrags =
|
|
50
|
-
fragments->Array.filter(f => f.target == Platform)->Array.map(f => f.fragment)
|
|
51
|
-
let domainFrags = fragments->Array.filter(f => f.target == Domain)->Array.map(f => f.fragment)
|
|
52
|
-
[
|
|
53
|
-
{
|
|
54
|
-
api: PlatformApi,
|
|
55
|
-
sdl: GraphQL_Stitcher.stitch(~baseFragment=adminBase, ~pluginFragments=platformFrags),
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
api: DomainApi,
|
|
59
|
-
sdl: GraphQL_Stitcher.stitch(~baseFragment=emptyBase, ~pluginFragments=domainFrags),
|
|
60
|
-
},
|
|
61
|
-
]
|
|
62
|
-
} else {
|
|
63
|
-
let allFrags = fragments->Array.map(f => f.fragment)
|
|
64
|
-
[{api: DomainApi, sdl: GraphQL_Stitcher.stitch(~baseFragment=adminBase, ~pluginFragments=allFrags)}]
|
|
65
|
-
}
|