@reventlessdev/reventless-spec 3.0.0-alpha.100

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.
Files changed (139) hide show
  1. package/CHANGELOG.md +931 -0
  2. package/LICENSE +202 -0
  3. package/README.md +109 -0
  4. package/package.json +49 -0
  5. package/rescript.json +32 -0
  6. package/run-generator.mjs +2 -0
  7. package/run-platform-generator.mjs +2 -0
  8. package/scripts/generate-currency.mjs +215 -0
  9. package/scripts/iso-4217-list-one.xml +1956 -0
  10. package/src/AnsiStyle.res +40 -0
  11. package/src/AnsiStyle.res.mjs +54 -0
  12. package/src/LogPrefix.res +192 -0
  13. package/src/LogPrefix.res.mjs +159 -0
  14. package/src/PackageVersion.res +67 -0
  15. package/src/PackageVersion.res.mjs +81 -0
  16. package/src/components/Aggregate.res +64 -0
  17. package/src/components/Aggregate.res.mjs +2 -0
  18. package/src/components/AutomationSlice.res +279 -0
  19. package/src/components/AutomationSlice.res.mjs +30 -0
  20. package/src/components/CapabilityManifest.res +74 -0
  21. package/src/components/CapabilityManifest.res.mjs +61 -0
  22. package/src/components/ComponentKind.res +99 -0
  23. package/src/components/ComponentKind.res.mjs +125 -0
  24. package/src/components/Counter.res +24 -0
  25. package/src/components/Counter.res.mjs +2 -0
  26. package/src/components/DcbDecode.res +118 -0
  27. package/src/components/DcbDecode.res.mjs +100 -0
  28. package/src/components/DcbScopeInference.res +244 -0
  29. package/src/components/DcbScopeInference.res.mjs +177 -0
  30. package/src/components/DcbTag.res +1335 -0
  31. package/src/components/DcbTag.res.mjs +898 -0
  32. package/src/components/DcbValidation.res +427 -0
  33. package/src/components/DcbValidation.res.mjs +423 -0
  34. package/src/components/DisplayName.res +40 -0
  35. package/src/components/DisplayName.res.mjs +26 -0
  36. package/src/components/ExtensionPoint.res +27 -0
  37. package/src/components/ExtensionPoint.res.mjs +2 -0
  38. package/src/components/InboundTranslationSlice.res +85 -0
  39. package/src/components/InboundTranslationSlice.res.mjs +2 -0
  40. package/src/components/OutboundTranslationSlice.res +153 -0
  41. package/src/components/OutboundTranslationSlice.res.mjs +2 -0
  42. package/src/components/Plugin.res +538 -0
  43. package/src/components/Plugin.res.mjs +264 -0
  44. package/src/components/PluginName.res +39 -0
  45. package/src/components/PluginName.res.mjs +45 -0
  46. package/src/components/ReadModel.res +199 -0
  47. package/src/components/ReadModel.res.mjs +18 -0
  48. package/src/components/Reference.res +55 -0
  49. package/src/components/Reference.res.mjs +50 -0
  50. package/src/components/Snapshot.res +26 -0
  51. package/src/components/Snapshot.res.mjs +2 -0
  52. package/src/components/StateAnnotations.res +97 -0
  53. package/src/components/StateAnnotations.res.mjs +15 -0
  54. package/src/components/StateChangeSlice.res +131 -0
  55. package/src/components/StateChangeSlice.res.mjs +2 -0
  56. package/src/components/StateViewSlice.res +123 -0
  57. package/src/components/StateViewSlice.res.mjs +2 -0
  58. package/src/components/Task.res +62 -0
  59. package/src/components/Task.res.mjs +2 -0
  60. package/src/generator/Codegen.res +842 -0
  61. package/src/generator/Codegen.res.mjs +565 -0
  62. package/src/generator/Config.res +106 -0
  63. package/src/generator/Config.res.mjs +69 -0
  64. package/src/generator/Discovery.res +230 -0
  65. package/src/generator/Discovery.res.mjs +198 -0
  66. package/src/generator/Generator_Node.res +14 -0
  67. package/src/generator/Generator_Node.res.mjs +18 -0
  68. package/src/generator/Pairing.res +460 -0
  69. package/src/generator/Pairing.res.mjs +415 -0
  70. package/src/generator/PlatformCodegen.res +207 -0
  71. package/src/generator/PlatformCodegen.res.mjs +154 -0
  72. package/src/generator/PlatformGenerator.res +126 -0
  73. package/src/generator/PlatformGenerator.res.mjs +114 -0
  74. package/src/generator/PlatformManifests.res +203 -0
  75. package/src/generator/PlatformManifests.res.mjs +212 -0
  76. package/src/generator/PluginGenerator.res +57 -0
  77. package/src/generator/PluginGenerator.res.mjs +73 -0
  78. package/src/semantic/Bytes.res +54 -0
  79. package/src/semantic/Bytes.res.mjs +38 -0
  80. package/src/semantic/Capabilities.res +43 -0
  81. package/src/semantic/Capabilities.res.mjs +17 -0
  82. package/src/semantic/Color.res +51 -0
  83. package/src/semantic/Color.res.mjs +29 -0
  84. package/src/semantic/Currency.res +598 -0
  85. package/src/semantic/Currency.res.mjs +743 -0
  86. package/src/semantic/DateRange.res +148 -0
  87. package/src/semantic/DateRange.res.mjs +74 -0
  88. package/src/semantic/Duration.res +53 -0
  89. package/src/semantic/Duration.res.mjs +26 -0
  90. package/src/semantic/Email.res +51 -0
  91. package/src/semantic/Email.res.mjs +31 -0
  92. package/src/semantic/GeoPoint.res +226 -0
  93. package/src/semantic/GeoPoint.res.mjs +190 -0
  94. package/src/semantic/Geocoding.res +127 -0
  95. package/src/semantic/Geocoding.res.mjs +36 -0
  96. package/src/semantic/Money.res +196 -0
  97. package/src/semantic/Money.res.mjs +138 -0
  98. package/src/semantic/Offload.res +294 -0
  99. package/src/semantic/Offload.res.mjs +191 -0
  100. package/src/semantic/Percent.res +53 -0
  101. package/src/semantic/Percent.res.mjs +33 -0
  102. package/src/semantic/Phone.res +55 -0
  103. package/src/semantic/Phone.res.mjs +29 -0
  104. package/src/semantic/Semantic.res +162 -0
  105. package/src/semantic/Semantic.res.mjs +95 -0
  106. package/src/semantic/StorageRef.res +164 -0
  107. package/src/semantic/StorageRef.res.mjs +111 -0
  108. package/src/semantic/Url.res +66 -0
  109. package/src/semantic/Url.res.mjs +48 -0
  110. package/src/types/Authorization.res +23 -0
  111. package/src/types/Authorization.res.mjs +33 -0
  112. package/src/types/Behavior.res +86 -0
  113. package/src/types/Behavior.res.mjs +2 -0
  114. package/src/types/DateTime.res +29 -0
  115. package/src/types/DateTime.res.mjs +16 -0
  116. package/src/types/EventMapping.res +100 -0
  117. package/src/types/EventMapping.res.mjs +15 -0
  118. package/src/types/Handler.res +30 -0
  119. package/src/types/Handler.res.mjs +2 -0
  120. package/src/types/Id.res +75 -0
  121. package/src/types/Id.res.mjs +37 -0
  122. package/src/types/Identity.res +46 -0
  123. package/src/types/Identity.res.mjs +51 -0
  124. package/src/types/Message.res +326 -0
  125. package/src/types/Message.res.mjs +186 -0
  126. package/src/types/Projection.res +220 -0
  127. package/src/types/Projection.res.mjs +44 -0
  128. package/src/types/QueryEngine.res +123 -0
  129. package/src/types/QueryEngine.res.mjs +12 -0
  130. package/src/types/ReadConsistency.res +38 -0
  131. package/src/types/ReadConsistency.res.mjs +29 -0
  132. package/src/types/Schedule.res +65 -0
  133. package/src/types/Schedule.res.mjs +68 -0
  134. package/src/types/SideEffect.res +45 -0
  135. package/src/types/SideEffect.res.mjs +2 -0
  136. package/src/types/StoredEvent.res +46 -0
  137. package/src/types/StoredEvent.res.mjs +32 -0
  138. package/src/types/Visibility.res +24 -0
  139. package/src/types/Visibility.res.mjs +25 -0
@@ -0,0 +1,212 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Nodefs from "node:fs";
4
+ import * as Nodepath from "node:path";
5
+ import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
6
+ import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
7
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
8
+
9
+ function nodeFileSystem_exists(prim) {
10
+ return Nodefs.existsSync(prim);
11
+ }
12
+
13
+ function nodeFileSystem_readJson(path) {
14
+ try {
15
+ return JSON.parse(Nodefs.readFileSync(path, "utf8"));
16
+ } catch (exn) {
17
+ return;
18
+ }
19
+ }
20
+
21
+ let nodeFileSystem = {
22
+ exists: nodeFileSystem_exists,
23
+ readJson: nodeFileSystem_readJson
24
+ };
25
+
26
+ function describeVia(via) {
27
+ if (typeof via !== "object") {
28
+ return "its own src/";
29
+ } else if (via.TAG === "Generated") {
30
+ return `its \`generate\` script's ` + via._0;
31
+ } else {
32
+ return `dependency ` + via._0;
33
+ }
34
+ }
35
+
36
+ function asObject(json) {
37
+ return Stdlib_JSON.Decode.object(json);
38
+ }
39
+
40
+ function packageJsonAt(fs, dir) {
41
+ return Stdlib_Option.flatMap(fs.readJson(Nodepath.join(dir, "package.json")), asObject);
42
+ }
43
+
44
+ function scriptsOf(pkg) {
45
+ return Stdlib_Option.mapOr(Stdlib_Option.flatMap(pkg["scripts"], asObject), [], scripts => Stdlib_Array.filterMap(Object.values(scripts), Stdlib_JSON.Decode.string));
46
+ }
47
+
48
+ function generatedSrcDir(pkg) {
49
+ return Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_Option.flatMap(pkg["scripts"], asObject), scripts => scripts["generate"]), Stdlib_JSON.Decode.string), script => {
50
+ let tokens = script.split(" ").filter(t => t !== "");
51
+ if (tokens.length !== 4) {
52
+ return;
53
+ }
54
+ let match = tokens[0];
55
+ if (match !== "generate-plugin") {
56
+ return;
57
+ }
58
+ let match$1 = tokens[1];
59
+ if (match$1 === "--aws") {
60
+ return tokens[3];
61
+ }
62
+ });
63
+ }
64
+
65
+ function emitsCapabilities(pkg) {
66
+ return scriptsOf(pkg).some(script => script.includes("emit-capabilities"));
67
+ }
68
+
69
+ function dependencyNames(pkg) {
70
+ let seen = {};
71
+ return [
72
+ "dependencies",
73
+ "devDependencies"
74
+ ].flatMap(field => Stdlib_Option.mapOr(Stdlib_Option.flatMap(pkg[field], asObject), [], prim => Object.keys(prim))).filter(name => {
75
+ let match = seen[name];
76
+ if (match !== undefined) {
77
+ return false;
78
+ } else {
79
+ seen[name] = true;
80
+ return true;
81
+ }
82
+ });
83
+ }
84
+
85
+ function resolvePackageDir(fs, fromDir, name) {
86
+ let _dir = fromDir;
87
+ while (true) {
88
+ let dir = _dir;
89
+ let candidate = Nodepath.join(dir, "node_modules", name);
90
+ if (fs.exists(Nodepath.join(candidate, "package.json"))) {
91
+ return candidate;
92
+ }
93
+ let parent = Nodepath.dirname(dir);
94
+ if (parent === dir) {
95
+ return;
96
+ }
97
+ _dir = parent;
98
+ continue;
99
+ };
100
+ }
101
+
102
+ function fromDependencies(fs, pluginDir, pkg) {
103
+ let found = [];
104
+ let unbuilt = {
105
+ contents: undefined
106
+ };
107
+ dependencyNames(pkg).forEach(name => {
108
+ let packageDir = resolvePackageDir(fs, pluginDir, name);
109
+ if (packageDir === undefined) {
110
+ return;
111
+ }
112
+ let candidate = Nodepath.join(packageDir, "src", "capabilities.json");
113
+ if (fs.exists(candidate)) {
114
+ found.push({
115
+ path: candidate,
116
+ via: {
117
+ TAG: "Dependency",
118
+ _0: name
119
+ }
120
+ });
121
+ return;
122
+ }
123
+ if (!Stdlib_Option.mapOr(packageJsonAt(fs, packageDir), false, emitsCapabilities)) {
124
+ return;
125
+ }
126
+ let match = unbuilt.contents;
127
+ if (match !== undefined) {
128
+ return;
129
+ } else {
130
+ unbuilt.contents = {
131
+ evidence: `dependency ` + name + ` emits a capability manifest`,
132
+ expected: candidate
133
+ };
134
+ return;
135
+ }
136
+ });
137
+ let match = unbuilt.contents;
138
+ if (match !== undefined) {
139
+ return {
140
+ TAG: "Unbuilt",
141
+ _0: {
142
+ evidence: match.evidence,
143
+ expected: match.expected
144
+ }
145
+ };
146
+ } else if (found.length !== 0) {
147
+ return {
148
+ TAG: "Manifests",
149
+ _0: found
150
+ };
151
+ } else {
152
+ return "NotAPlugin";
153
+ }
154
+ }
155
+
156
+ function resolve(fsOpt, pluginDir) {
157
+ let fs = fsOpt !== undefined ? fsOpt : nodeFileSystem;
158
+ let direct = Nodepath.join(pluginDir, "src", "capabilities.json");
159
+ if (fs.exists(direct)) {
160
+ return {
161
+ TAG: "Manifests",
162
+ _0: [{
163
+ path: direct,
164
+ via: "Composition"
165
+ }]
166
+ };
167
+ }
168
+ let pkg = packageJsonAt(fs, pluginDir);
169
+ if (pkg === undefined) {
170
+ return "NotAPlugin";
171
+ }
172
+ let srcDir = generatedSrcDir(pkg);
173
+ if (srcDir === undefined) {
174
+ return fromDependencies(fs, pluginDir, pkg);
175
+ }
176
+ let expected = Nodepath.resolve(pluginDir, srcDir, "capabilities.json");
177
+ if (fs.exists(expected)) {
178
+ return {
179
+ TAG: "Manifests",
180
+ _0: [{
181
+ path: expected,
182
+ via: {
183
+ TAG: "Generated",
184
+ _0: srcDir
185
+ }
186
+ }]
187
+ };
188
+ } else {
189
+ return {
190
+ TAG: "Unbuilt",
191
+ _0: {
192
+ evidence: `its \`generate\` script composes ` + srcDir,
193
+ expected: expected
194
+ }
195
+ };
196
+ }
197
+ }
198
+
199
+ export {
200
+ nodeFileSystem,
201
+ describeVia,
202
+ asObject,
203
+ packageJsonAt,
204
+ scriptsOf,
205
+ generatedSrcDir,
206
+ emitsCapabilities,
207
+ dependencyNames,
208
+ resolvePackageDir,
209
+ fromDependencies,
210
+ resolve,
211
+ }
212
+ /* node:fs Not a pure module */
@@ -0,0 +1,57 @@
1
+ // Entry point for the plugin generator.
2
+ // Usage: generate-plugin <srcDir>
3
+ // generate-plugin --aws <Namespace> <srcDir>
4
+
5
+
6
+ let () = {
7
+ let argv2 = NodeProcess.argv->Array.get(2)->Option.getOr("")
8
+ let argv3 = NodeProcess.argv->Array.get(3)->Option.getOr("")
9
+ let argv4 = NodeProcess.argv->Array.get(4)->Option.getOr("")
10
+
11
+ let (variant, srcDirArg) = if argv2 === "--aws" {
12
+ if argv3 !== "" && argv4 !== "" {
13
+ (Config.Aws({compositionNamespace: argv3}), argv4)
14
+ } else {
15
+ Console.error("Usage: generate-plugin --aws <Namespace> <srcDir>")
16
+ (Config.Composition, "")
17
+ }
18
+ } else {
19
+ (Config.Composition, argv2)
20
+ }
21
+
22
+ if srcDirArg === "" {
23
+ if argv2 !== "--aws" {
24
+ Console.error("Usage: generate-plugin <srcDir>")
25
+ Console.error(" generate-plugin --aws <Namespace> <srcDir>")
26
+ }
27
+ // Exit non-zero on a usage error so `prebuild` (and CI) actually fail
28
+ // instead of continuing green with no Plugin.res generated.
29
+ NodeProcess.exit(1)
30
+ } else {
31
+ // Resolve to absolute path (handles relative paths and trailing slashes)
32
+ let srcDir = NodePath.resolve([srcDirArg])
33
+
34
+ let config = {...Config.read(~srcDir), variant}
35
+ let discovered = Discovery.scan(~srcDir, ~exclude=config.exclude)
36
+ let resolved = Pairing.resolve(discovered, ~srcDir)
37
+ let source = Codegen.render(~config, ~resolved, ~discovered)
38
+
39
+ let outputDir = switch variant {
40
+ | Config.Composition => srcDir
41
+ | Config.Aws(_) => NodePath.join([NodeProcess.cwd(), "src"])
42
+ }
43
+
44
+ let pluginPath = NodePath.join([outputDir, "Plugin.res"])
45
+ NodeFs.writeFileSync(pluginPath, source)
46
+ Console.log("Generated: " ++ pluginPath)
47
+
48
+ switch variant {
49
+ | Config.Composition => ()
50
+ | Config.Aws(_) =>
51
+ let mainSource = Codegen.renderMain(~config)
52
+ let mainPath = NodePath.join([outputDir, "Main.res"])
53
+ NodeFs.writeFileSync(mainPath, mainSource)
54
+ Console.log("Generated: " ++ mainPath)
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,73 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Nodefs from "node:fs";
4
+ import * as Nodepath from "node:path";
5
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
6
+ import * as Config$Reventless from "./Config.res.mjs";
7
+ import * as Codegen$Reventless from "./Codegen.res.mjs";
8
+ import * as Pairing$Reventless from "./Pairing.res.mjs";
9
+ import * as Discovery$Reventless from "./Discovery.res.mjs";
10
+
11
+ let argv2 = Stdlib_Option.getOr(process.argv[2], "");
12
+
13
+ let argv3 = Stdlib_Option.getOr(process.argv[3], "");
14
+
15
+ let argv4 = Stdlib_Option.getOr(process.argv[4], "");
16
+
17
+ let match = argv2 === "--aws" ? (
18
+ argv3 !== "" && argv4 !== "" ? [
19
+ {
20
+ TAG: "Aws",
21
+ compositionNamespace: argv3
22
+ },
23
+ argv4
24
+ ] : (console.error("Usage: generate-plugin --aws <Namespace> <srcDir>"), [
25
+ "Composition",
26
+ ""
27
+ ])
28
+ ) : [
29
+ "Composition",
30
+ argv2
31
+ ];
32
+
33
+ let srcDirArg = match[1];
34
+
35
+ let variant = match[0];
36
+
37
+ if (srcDirArg === "") {
38
+ if (argv2 !== "--aws") {
39
+ console.error("Usage: generate-plugin <srcDir>");
40
+ console.error(" generate-plugin --aws <Namespace> <srcDir>");
41
+ }
42
+ process.exit(1);
43
+ } else {
44
+ let srcDir = Nodepath.resolve(srcDirArg);
45
+ let init = Config$Reventless.read(srcDir);
46
+ let config_name = init.name;
47
+ let config_heartbeatInterval = init.heartbeatInterval;
48
+ let config_exclude = init.exclude;
49
+ let config_componentRuntime = init.componentRuntime;
50
+ let config = {
51
+ name: config_name,
52
+ heartbeatInterval: config_heartbeatInterval,
53
+ exclude: config_exclude,
54
+ componentRuntime: config_componentRuntime,
55
+ variant: variant
56
+ };
57
+ let discovered = Discovery$Reventless.scan(srcDir, config_exclude);
58
+ let resolved = Pairing$Reventless.resolve(discovered, srcDir);
59
+ let source = Codegen$Reventless.render(config, resolved, discovered);
60
+ let outputDir;
61
+ outputDir = typeof variant !== "object" ? srcDir : Nodepath.join(process.cwd(), "src");
62
+ let pluginPath = Nodepath.join(outputDir, "Plugin.res");
63
+ Nodefs.writeFileSync(pluginPath, source, "utf8");
64
+ console.log("Generated: " + pluginPath);
65
+ if (typeof variant === "object") {
66
+ let mainSource = Codegen$Reventless.renderMain(config);
67
+ let mainPath = Nodepath.join(outputDir, "Main.res");
68
+ Nodefs.writeFileSync(mainPath, mainSource, "utf8");
69
+ console.log("Generated: " + mainPath);
70
+ }
71
+ }
72
+
73
+ /* argv2 Not a pure module */
@@ -0,0 +1,54 @@
1
+ /**
2
+ Marks a numeric field as a count of bytes.
3
+
4
+ ## Why `float` and not `int`
5
+
6
+ A byte count is a whole number and cannot be negative, so `int` is the type it
7
+ wants to be. It is the wrong one here anyway: ReScript's `int` is int32, and
8
+ sury enforces that, so an `int` byte count silently caps at 2,147,483,647 — just
9
+ under 2 GiB. A type whose stated job is file sizes cannot stop at 2 GB.
10
+
11
+ `float` is a JS number, exact for every integer up to 2^53 — nine petabytes,
12
+ which is enough. The discreteness `int` would have given for free is recovered
13
+ by checking it: the grammar below rejects a fractional byte count, so the type
14
+ still means what `int` meant, minus the ceiling.
15
+
16
+ The wire is unaffected either way — both are JSON numbers — so this is a source
17
+ choice, not a format one. A field genuinely bounded below 2 GiB can still be
18
+ declared `int` and left unmarked; this type is for the ones that are not.
19
+
20
+ ## The grammar
21
+
22
+ A finite, whole number, zero or greater. Zero is a real byte count — an empty
23
+ object — so it is accepted.
24
+
25
+ @example
26
+ ```rescript
27
+ @schema type state = {
28
+ documentId: string,
29
+ size: @s.matches(Reventless.Bytes.schema) float,
30
+ }
31
+ ```
32
+ */
33
+
34
+ /** The count's representation. Transparent `float`; see the note above on why
35
+ it is not `int`. */
36
+ type t = float
37
+
38
+ external unsafe: float => t = "%identity"
39
+ external toFloat: t => float = "%identity"
40
+
41
+ /** Validate a number as a byte count, saying why when it is not one. */
42
+ let fromFloat = (raw: float): result<t, string> =>
43
+ if !Float.isFinite(raw) {
44
+ Error(`a byte count must be a finite number, got ${Float.toString(raw)}`)
45
+ } else if raw < 0.0 {
46
+ Error(`a byte count cannot be negative, got ${Float.toString(raw)}`)
47
+ } else if Math.floor(raw) !== raw {
48
+ Error(`a byte count is a whole number of bytes, got ${Float.toString(raw)}`)
49
+ } else {
50
+ Ok(raw)
51
+ }
52
+
53
+ /** The sury schema for a byte-count field. Use with `@s.matches(Reventless.Bytes.schema)`. */
54
+ let schema: S.t<t> = S.float->Semantic.refined(~id=Semantic.Id.bytes, ~check=fromFloat)
@@ -0,0 +1,38 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as S from "sury/src/S.res.mjs";
4
+ import * as Semantic$Reventless from "./Semantic.res.mjs";
5
+
6
+ function fromFloat(raw) {
7
+ if (isFinite(raw)) {
8
+ if (raw < 0.0) {
9
+ return {
10
+ TAG: "Error",
11
+ _0: `a byte count cannot be negative, got ` + raw.toString()
12
+ };
13
+ } else if (Math.floor(raw) !== raw) {
14
+ return {
15
+ TAG: "Error",
16
+ _0: `a byte count is a whole number of bytes, got ` + raw.toString()
17
+ };
18
+ } else {
19
+ return {
20
+ TAG: "Ok",
21
+ _0: raw
22
+ };
23
+ }
24
+ } else {
25
+ return {
26
+ TAG: "Error",
27
+ _0: `a byte count must be a finite number, got ` + raw.toString()
28
+ };
29
+ }
30
+ }
31
+
32
+ let schema = Semantic$Reventless.refined(S.float, Semantic$Reventless.Id.bytes, fromFloat);
33
+
34
+ export {
35
+ fromFloat,
36
+ schema,
37
+ }
38
+ /* schema Not a pure module */
@@ -0,0 +1,43 @@
1
+ /**
2
+ The platform capabilities a slice's `translate` is handed.
3
+
4
+ A plugin is provider-agnostic: it depends on `reventless-spec`, never on
5
+ `reventless-aws`, so it cannot name Amazon Location — and it should not want to.
6
+ What it can do is receive a function that geocodes, and let whoever assembled the
7
+ runtime decide what is on the other end. The same capability a client reaches
8
+ through a GraphQL field, plugin code reaches through here; the object store
9
+ already works this way, with `Upload_Presign` on one side and an injected
10
+ `Offload.resolve(~fetch)` on the other.
11
+
12
+ Injected rather than looked up, and required rather than optional, because the
13
+ alternative is a slot filled at cold start that nothing enforces: ES modules
14
+ evaluate imports before the importing module's body, so "the entry point runs
15
+ first" is an assumption a bundling change can quietly break. A missing argument
16
+ does not compile; an unfilled slot fails on the first real address, in
17
+ production.
18
+
19
+ **A record, not a widening argument list.** Adding a capability later changes
20
+ this type, which breaks the handful of places that *construct* it — the
21
+ platforms, which is exactly where a new capability has to be wired anyway — and
22
+ leaves every `translate` reading `capabilities.geocode` untouched.
23
+ */
24
+ type t = {
25
+ /** Turn an address into ranked candidates. See `Geocoding.search`. */
26
+ geocode: Geocoding.search,
27
+ }
28
+
29
+ /**
30
+ The capability set of a platform that provisions nothing.
31
+
32
+ Every capability here answers `Unavailable`, which is a *modelled* outcome:
33
+ `translate` maps it to `Error`, the item is retried and the sweep surfaces it.
34
+ That is the behaviour wanted for a deployment that simply has no geocoder — the
35
+ work stays queued and visible rather than being written off as a verdict on the
36
+ address, which is what `NoMatch` would mean.
37
+
38
+ Named so that a platform passing it is making a statement rather than filling in
39
+ a blank.
40
+ */
41
+ let none: t = {
42
+ geocode: async (~text as _) => Error(Unavailable("no geocoder is configured for this platform")),
43
+ }
@@ -0,0 +1,17 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+
4
+ let none = {
5
+ geocode: async param => ({
6
+ TAG: "Error",
7
+ _0: {
8
+ TAG: "Unavailable",
9
+ _0: "no geocoder is configured for this platform"
10
+ }
11
+ })
12
+ };
13
+
14
+ export {
15
+ none,
16
+ }
17
+ /* No side effect */
@@ -0,0 +1,51 @@
1
+ /**
2
+ Marks a `string` field as a colour, written as a hex triplet.
3
+
4
+ ## Why hex only
5
+
6
+ A field carrying this semantic is rendered as a swatch by setting the value as a
7
+ CSS `background`. CSS accepts far more than a colour there — `url(…)`, gradients,
8
+ `var(…)` — so "any CSS colour" would make a colour field a way to write arbitrary
9
+ CSS into a log that cannot be edited afterwards. Hex is the form that is a
10
+ colour and nothing else, and it is what a colour picker emits anyway.
11
+
12
+ Named colours (`rebeccapurple`) and functional forms (`rgb(…)`, `oklch(…)`) are
13
+ rejected for the same reason, not because they are worse notation.
14
+
15
+ ## The grammar
16
+
17
+ `#` then 3, 4, 6 or 8 hex digits — the shorthand, the shorthand with alpha, the
18
+ full triplet, and the triplet with alpha. Case is not significant.
19
+
20
+ @example
21
+ ```rescript
22
+ @schema type command =
23
+ | SetLabelColour({
24
+ labelId: @s.matches(DcbTag.string) string,
25
+ colour: @s.matches(Reventless.Color.schema) string,
26
+ })
27
+ ```
28
+ */
29
+
30
+ /** The colour's representation. Transparent `string`; see `Email.t`. */
31
+ type t = string
32
+
33
+ external unsafe: string => t = "%identity"
34
+ external toString: t => string = "%identity"
35
+
36
+ let hex = /^#([0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/
37
+
38
+ /** Validate a raw string as a hex colour, saying why when it is not one. */
39
+ let fromString = (raw: string): result<t, string> =>
40
+ if hex->RegExp.test(raw) {
41
+ Ok(raw)
42
+ } else {
43
+ Error(
44
+ `expected a hex colour such as "#1e90ff" or "#1e90ffcc", got ${Semantic.showString(
45
+ raw,
46
+ )}. Named colours and rgb()/oklch() forms are not accepted.`,
47
+ )
48
+ }
49
+
50
+ /** The sury schema for a colour field. Use with `@s.matches(Reventless.Color.schema)`. */
51
+ let schema: S.t<t> = S.string->Semantic.refined(~id=Semantic.Id.color, ~check=fromString)
@@ -0,0 +1,29 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as S from "sury/src/S.res.mjs";
4
+ import * as Semantic$Reventless from "./Semantic.res.mjs";
5
+
6
+ let hex = /^#([0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
7
+
8
+ function fromString(raw) {
9
+ if (hex.test(raw)) {
10
+ return {
11
+ TAG: "Ok",
12
+ _0: raw
13
+ };
14
+ } else {
15
+ return {
16
+ TAG: "Error",
17
+ _0: `expected a hex colour such as "#1e90ff" or "#1e90ffcc", got ` + Semantic$Reventless.showString(raw) + `. Named colours and rgb()/oklch() forms are not accepted.`
18
+ };
19
+ }
20
+ }
21
+
22
+ let schema = Semantic$Reventless.refined(S.string, Semantic$Reventless.Id.color, fromString);
23
+
24
+ export {
25
+ hex,
26
+ fromString,
27
+ schema,
28
+ }
29
+ /* schema Not a pure module */