@shivaedev/server 0.0.0-stage → 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.
Files changed (148) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +323 -2
  3. package/dist/AppHostFactory.d.ts +15 -0
  4. package/dist/AppHostFactory.d.ts.map +1 -0
  5. package/dist/AppHostFactory.js +2 -0
  6. package/dist/AppHostFactory.js.map +1 -0
  7. package/dist/AppHostInput.d.ts +13 -0
  8. package/dist/AppHostInput.d.ts.map +1 -0
  9. package/dist/AppHostInput.js +2 -0
  10. package/dist/AppHostInput.js.map +1 -0
  11. package/dist/MigrationOptions.d.ts +15 -0
  12. package/dist/MigrationOptions.d.ts.map +1 -0
  13. package/dist/MigrationOptions.js +2 -0
  14. package/dist/MigrationOptions.js.map +1 -0
  15. package/dist/RedactionPolicy.d.ts +4 -0
  16. package/dist/RedactionPolicy.d.ts.map +1 -0
  17. package/dist/RedactionPolicy.js +4 -0
  18. package/dist/RedactionPolicy.js.map +1 -0
  19. package/dist/appHost.d.ts +47 -0
  20. package/dist/appHost.d.ts.map +1 -0
  21. package/dist/appHost.js +17 -0
  22. package/dist/appHost.js.map +1 -0
  23. package/dist/appHostHttp.d.ts +88 -0
  24. package/dist/appHostHttp.d.ts.map +1 -0
  25. package/dist/appHostHttp.js +67 -0
  26. package/dist/appHostHttp.js.map +1 -0
  27. package/dist/boundedRpcRequest.d.ts +3 -0
  28. package/dist/boundedRpcRequest.d.ts.map +1 -0
  29. package/dist/boundedRpcRequest.js +22 -0
  30. package/dist/boundedRpcRequest.js.map +1 -0
  31. package/dist/databaseAddress.d.ts +4 -0
  32. package/dist/databaseAddress.d.ts.map +1 -0
  33. package/dist/databaseAddress.js +25 -0
  34. package/dist/databaseAddress.js.map +1 -0
  35. package/dist/databaseLayer.d.ts +14 -0
  36. package/dist/databaseLayer.d.ts.map +1 -0
  37. package/dist/databaseLayer.js +100 -0
  38. package/dist/databaseLayer.js.map +1 -0
  39. package/dist/development.d.ts +10 -0
  40. package/dist/development.d.ts.map +1 -0
  41. package/dist/development.js +20 -0
  42. package/dist/development.js.map +1 -0
  43. package/dist/healthRoute.d.ts +3 -0
  44. package/dist/healthRoute.d.ts.map +1 -0
  45. package/dist/healthRoute.js +18 -0
  46. package/dist/healthRoute.js.map +1 -0
  47. package/dist/hostChanges.d.ts +7 -0
  48. package/dist/hostChanges.d.ts.map +1 -0
  49. package/dist/hostChanges.js +25 -0
  50. package/dist/hostChanges.js.map +1 -0
  51. package/dist/hostOptions.d.ts +34 -0
  52. package/dist/hostOptions.d.ts.map +1 -0
  53. package/dist/hostOptions.js +2 -0
  54. package/dist/hostOptions.js.map +1 -0
  55. package/dist/hostTransport.d.ts +16 -0
  56. package/dist/hostTransport.d.ts.map +1 -0
  57. package/dist/hostTransport.js +16 -0
  58. package/dist/hostTransport.js.map +1 -0
  59. package/dist/nativeClientOrigins.d.ts +4 -0
  60. package/dist/nativeClientOrigins.d.ts.map +1 -0
  61. package/dist/nativeClientOrigins.js +38 -0
  62. package/dist/nativeClientOrigins.js.map +1 -0
  63. package/dist/node-http/subscriptionSignal.d.ts +12 -0
  64. package/dist/node-http/subscriptionSignal.d.ts.map +1 -0
  65. package/dist/node-http/subscriptionSignal.js +55 -0
  66. package/dist/node-http/subscriptionSignal.js.map +1 -0
  67. package/dist/otlpHttpClient.d.ts +4 -0
  68. package/dist/otlpHttpClient.d.ts.map +1 -0
  69. package/dist/otlpHttpClient.js +36 -0
  70. package/dist/otlpHttpClient.js.map +1 -0
  71. package/dist/otlpTracing.d.ts +3 -0
  72. package/dist/otlpTracing.d.ts.map +1 -0
  73. package/dist/otlpTracing.js +35 -0
  74. package/dist/otlpTracing.js.map +1 -0
  75. package/dist/redactOtlpUrl.d.ts +2 -0
  76. package/dist/redactOtlpUrl.d.ts.map +1 -0
  77. package/dist/redactOtlpUrl.js +41 -0
  78. package/dist/redactOtlpUrl.js.map +1 -0
  79. package/dist/redactingDiagnostics.d.ts +5 -0
  80. package/dist/redactingDiagnostics.d.ts.map +1 -0
  81. package/dist/redactingDiagnostics.js +25 -0
  82. package/dist/redactingDiagnostics.js.map +1 -0
  83. package/dist/redactingRpcSerialization.d.ts +4 -0
  84. package/dist/redactingRpcSerialization.d.ts.map +1 -0
  85. package/dist/redactingRpcSerialization.js +74 -0
  86. package/dist/redactingRpcSerialization.js.map +1 -0
  87. package/dist/redactingTracer.d.ts +4 -0
  88. package/dist/redactingTracer.d.ts.map +1 -0
  89. package/dist/redactingTracer.js +32 -0
  90. package/dist/redactingTracer.js.map +1 -0
  91. package/dist/rpc-server/publicConfigLayer.d.ts +4 -0
  92. package/dist/rpc-server/publicConfigLayer.d.ts.map +1 -0
  93. package/dist/rpc-server/publicConfigLayer.js +9 -0
  94. package/dist/rpc-server/publicConfigLayer.js.map +1 -0
  95. package/dist/runtime/makePlatform.d.ts +4 -0
  96. package/dist/runtime/makePlatform.d.ts.map +1 -0
  97. package/dist/runtime/makePlatform.js +65 -0
  98. package/dist/runtime/makePlatform.js.map +1 -0
  99. package/dist/runtime/types.d.ts +19 -0
  100. package/dist/runtime/types.d.ts.map +1 -0
  101. package/dist/runtime/types.js +2 -0
  102. package/dist/runtime/types.js.map +1 -0
  103. package/dist/safeOtlpSerialization.d.ts +6 -0
  104. package/dist/safeOtlpSerialization.d.ts.map +1 -0
  105. package/dist/safeOtlpSerialization.js +67 -0
  106. package/dist/safeOtlpSerialization.js.map +1 -0
  107. package/dist/schemaSensitiveKeys.d.ts +5 -0
  108. package/dist/schemaSensitiveKeys.d.ts.map +1 -0
  109. package/dist/schemaSensitiveKeys.js +143 -0
  110. package/dist/schemaSensitiveKeys.js.map +1 -0
  111. package/dist/setupDatabases.d.ts +3 -0
  112. package/dist/setupDatabases.d.ts.map +1 -0
  113. package/dist/setupDatabases.js +12 -0
  114. package/dist/setupDatabases.js.map +1 -0
  115. package/dist/tracedGroup.d.ts +4 -0
  116. package/dist/tracedGroup.d.ts.map +1 -0
  117. package/dist/tracedGroup.js +13 -0
  118. package/dist/tracedGroup.js.map +1 -0
  119. package/package.json +110 -3
  120. package/src/AppHostFactory.ts +121 -0
  121. package/src/AppHostInput.ts +74 -0
  122. package/src/MigrationOptions.ts +15 -0
  123. package/src/RedactionPolicy.ts +4 -0
  124. package/src/appHost.ts +117 -0
  125. package/src/appHostHttp.ts +147 -0
  126. package/src/boundedRpcRequest.ts +28 -0
  127. package/src/databaseAddress.ts +25 -0
  128. package/src/databaseLayer.ts +127 -0
  129. package/src/development.ts +34 -0
  130. package/src/healthRoute.ts +27 -0
  131. package/src/hostChanges.ts +34 -0
  132. package/src/hostOptions.ts +30 -0
  133. package/src/hostTransport.ts +26 -0
  134. package/src/nativeClientOrigins.ts +44 -0
  135. package/src/node-http/subscriptionSignal.ts +64 -0
  136. package/src/otlpHttpClient.ts +54 -0
  137. package/src/otlpTracing.ts +61 -0
  138. package/src/redactOtlpUrl.ts +48 -0
  139. package/src/redactingDiagnostics.ts +33 -0
  140. package/src/redactingRpcSerialization.ts +85 -0
  141. package/src/redactingTracer.ts +37 -0
  142. package/src/rpc-server/publicConfigLayer.ts +12 -0
  143. package/src/runtime/makePlatform.ts +75 -0
  144. package/src/runtime/types.ts +24 -0
  145. package/src/safeOtlpSerialization.ts +73 -0
  146. package/src/schemaSensitiveKeys.ts +148 -0
  147. package/src/setupDatabases.ts +12 -0
  148. package/src/tracedGroup.ts +14 -0
@@ -0,0 +1,75 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import type { Layer } from "effect";
3
+ import { Cause, Config, Context, Effect, Exit, ManagedRuntime } from "effect";
4
+ import type { PlatformRuntime, PlatformRuntimeOptions } from "./types.ts";
5
+
6
+ const RuntimeCacheKey = Symbol.for("@shivaedev/server/runtime-cache");
7
+ const sharedCache: unknown = Reflect.get(globalThis, RuntimeCacheKey);
8
+ const runtimeCache: Map<string | symbol, unknown> = sharedCache instanceof Map ? sharedCache : new Map<string | symbol, unknown>();
9
+ Reflect.set(globalThis, RuntimeCacheKey, runtimeCache);
10
+ // One development cache key belongs to one makePlatformRuntime call site, so its entry has that site's services and build error.
11
+ function cachedEntry<TServices, TBuildError>(runtime: unknown): PlatformRuntime<TServices, TBuildError>;
12
+ function cachedEntry(runtime: unknown): unknown {
13
+ return runtime;
14
+ }
15
+ function make<TServices, TBuildError>(layer: Layer.Layer<TServices, TBuildError>): PlatformRuntime<TServices, TBuildError> {
16
+ const managed = ManagedRuntime.make(layer);
17
+ const services = new AsyncLocalStorage<Context.Context<never>>();
18
+ function currentServices() {
19
+ return services.getStore();
20
+ }
21
+ function withAmbient<A, E, TRequirements extends TServices>(
22
+ effect: Effect.Effect<A, E, TRequirements>,
23
+ ): Effect.Effect<A, E | TBuildError, TRequirements> {
24
+ const ambient = currentServices();
25
+ return ambient === undefined
26
+ ? effect
27
+ : Effect.flatMap(managed.contextEffect, (base) => Effect.provideContext(effect, Context.merge(base, ambient)));
28
+ }
29
+ const runPromiseExit: PlatformRuntime<TServices, TBuildError>["runPromiseExit"] = (effect, options) =>
30
+ managed.runPromiseExit(withAmbient(effect), options);
31
+ const runtime: PlatformRuntime<TServices, TBuildError> = {
32
+ contextEffect: Effect.map(managed.contextEffect, (base) => {
33
+ const ambient = currentServices();
34
+ return ambient === undefined ? base : Context.merge(base, ambient);
35
+ }),
36
+ currentServices,
37
+ dispose: () => managed.dispose(),
38
+ runPromise: async (effect, options) => {
39
+ const exit = await runPromiseExit(effect, options);
40
+ if (Exit.isSuccess(exit)) {
41
+ return exit.value;
42
+ }
43
+ throw Cause.squash(exit.cause);
44
+ },
45
+ runPromiseExit,
46
+ runWithServices: (context, evaluate) => services.run(context, evaluate),
47
+ };
48
+ return runtime;
49
+ }
50
+ const production = Effect.map(Effect.orDie(Config.String("NODE_ENV").pipe(Config.withDefault(""))), (environment) => environment === "production");
51
+ export function makePlatformRuntime<TServices, TBuildError>(
52
+ layer: Layer.Layer<TServices, TBuildError>,
53
+ options: PlatformRuntimeOptions = {},
54
+ ): PlatformRuntime<TServices, TBuildError> {
55
+ const key = Effect.runSync(production) ? undefined : options.developmentCacheKey;
56
+ if (key === undefined) {
57
+ return make(layer);
58
+ }
59
+ const cached = runtimeCache.get(key);
60
+ if (cached !== undefined) {
61
+ return cachedEntry<TServices, TBuildError>(cached);
62
+ }
63
+ const runtime = make(layer);
64
+ const cachedRuntime: PlatformRuntime<TServices, TBuildError> = {
65
+ ...runtime,
66
+ dispose: async () => {
67
+ if (runtimeCache.get(key) === cachedRuntime) {
68
+ runtimeCache.delete(key);
69
+ }
70
+ await runtime.dispose();
71
+ },
72
+ };
73
+ runtimeCache.set(key, cachedRuntime);
74
+ return cachedRuntime;
75
+ }
@@ -0,0 +1,24 @@
1
+ import type { Context, Effect, Exit } from "effect";
2
+ export interface PlatformRuntimeOptions {
3
+ readonly developmentCacheKey?: string | symbol;
4
+ }
5
+ export interface PlatformRuntime<TServices, TBuildError> {
6
+ readonly contextEffect: Effect.Effect<Context.Context<TServices>, TBuildError>;
7
+ readonly currentServices: () => Context.Context<never> | undefined;
8
+ readonly dispose: () => Promise<void>;
9
+ readonly runPromise: <A, E, TRequirements extends TServices>(
10
+ effect: Effect.Effect<A, E, TRequirements>,
11
+ options?: {
12
+ readonly signal?: AbortSignal;
13
+ },
14
+ ) => Promise<A>;
15
+ readonly runPromiseExit: <A, E, TRequirements extends TServices>(
16
+ effect: Effect.Effect<A, E, TRequirements>,
17
+ options?: {
18
+ readonly signal?: AbortSignal;
19
+ },
20
+ ) => Promise<Exit.Exit<A, E | TBuildError>>;
21
+ readonly runWithServices: <TProvided, TValue>(services: Context.Context<TProvided>, evaluate: () => TValue) => TValue;
22
+ }
23
+ export type PlatformRuntimeServices<TRuntime> = TRuntime extends PlatformRuntime<infer TServices, infer _TBuildError> ? TServices : never;
24
+ export type PlatformRuntimeBuildError<TRuntime> = TRuntime extends PlatformRuntime<infer _TServices, infer TBuildError> ? TBuildError : never;
@@ -0,0 +1,73 @@
1
+ import { Effect, Layer } from "effect";
2
+ import type { OtlpResource, OtlpTracer } from "effect/observability";
3
+ import { OtlpSerialization } from "effect/observability";
4
+ import { isSensitiveKey, REDACTED, redactText, type SensitiveKey } from "@shivaedev/errors/sensitive.ts";
5
+ import { RedactionPolicy } from "#RedactionPolicy.ts";
6
+ import { redactOtlpUrl } from "#redactOtlpUrl.ts";
7
+
8
+ function otlpSensitive(sensitive: SensitiveKey): SensitiveKey {
9
+ return (key) => sensitive(key) || ["db.user", "db.user.name", "db.connection_string"].includes(key.toLowerCase());
10
+ }
11
+ export function redactOtlpText(value: string, key = "", sensitive: SensitiveKey = isSensitiveKey) {
12
+ return redactText(redactOtlpUrl(value, key), otlpSensitive(sensitive));
13
+ }
14
+ function attributeValue(value: OtlpResource.AnyValue, key: string, sensitive: SensitiveKey): OtlpResource.AnyValue {
15
+ return {
16
+ ...value,
17
+ ...(typeof value.stringValue === "string" ? { stringValue: redactOtlpText(value.stringValue, key, sensitive) } : {}),
18
+ ...(value.arrayValue ? { arrayValue: { values: value.arrayValue.values.map((item) => attributeValue(item, key, sensitive)) } } : {}),
19
+ ...(value.kvlistValue ? { kvlistValue: { values: attributes(value.kvlistValue.values, sensitive) } } : {}),
20
+ };
21
+ }
22
+ function attributes(values: OtlpResource.KeyValue[], sensitive: SensitiveKey): OtlpResource.KeyValue[] {
23
+ return values.map(({ key, value }) => ({
24
+ key,
25
+ value: sensitive(key) ? { stringValue: REDACTED } : attributeValue(value, key, sensitive),
26
+ }));
27
+ }
28
+
29
+ function spanName(span: { readonly name: string; readonly kind: number; readonly attributes: OtlpResource.KeyValue[] }) {
30
+ if (span.kind === 2) {
31
+ const method = span.attributes.find(({ key }) => key === "http.request.method")?.value.stringValue;
32
+ const route = span.attributes.find(({ key }) => key === "http.route")?.value.stringValue;
33
+ if (typeof method === "string") {
34
+ return typeof route === "string" ? `${method} ${route}` : method;
35
+ }
36
+ }
37
+ return span.name;
38
+ }
39
+
40
+ function redactTraces(data: OtlpTracer.TraceData, sensitive: SensitiveKey): OtlpTracer.TraceData {
41
+ function text(value: string) {
42
+ return redactOtlpText(value, "", sensitive);
43
+ }
44
+ return {
45
+ ...data,
46
+ resourceSpans: data.resourceSpans.map((resource) => ({
47
+ ...resource,
48
+ resource: { ...resource.resource, attributes: attributes(resource.resource.attributes, sensitive) },
49
+ scopeSpans: resource.scopeSpans.map((scope) => ({
50
+ ...scope,
51
+ spans: scope.spans.map((span) => ({
52
+ ...span,
53
+ attributes: attributes(span.attributes, sensitive),
54
+ events: span.events.map((event) => ({ ...event, attributes: attributes(event.attributes, sensitive), name: text(event.name) })),
55
+ links: span.links.map((link) => ({ ...link, attributes: attributes(link.attributes, sensitive) })),
56
+ name: text(spanName(span)),
57
+ status: { ...span.status, ...(span.status.message === undefined ? {} : { message: text(span.status.message) }) },
58
+ })),
59
+ })),
60
+ })),
61
+ };
62
+ }
63
+ export const safeOtlpSerialization = Layer.effect(
64
+ OtlpSerialization.OtlpSerialization,
65
+ Effect.gen(function* () {
66
+ const serialization = yield* OtlpSerialization.OtlpSerialization;
67
+ const sensitive = otlpSensitive(yield* RedactionPolicy);
68
+ return OtlpSerialization.OtlpSerialization.of({
69
+ ...serialization,
70
+ traces: (data) => serialization.traces(redactTraces(data, sensitive)),
71
+ });
72
+ }),
73
+ );
@@ -0,0 +1,148 @@
1
+ import { Predicate, type Schema, type SchemaAST } from "effect";
2
+ import { Rpc, type RpcGroup } from "effect/rpc";
3
+ import { isSensitiveKey } from "@shivaedev/errors/sensitive.ts";
4
+
5
+ function children(ast: SchemaAST.AST): readonly SchemaAST.AST[] {
6
+ switch (ast._tag) {
7
+ case "Objects":
8
+ return [...ast.propertySignatures.map((property) => property.type), ...ast.indexSignatures.flatMap((index) => [index.parameter, index.type])];
9
+ case "Arrays":
10
+ return [...ast.elements, ...ast.rest];
11
+ case "Union":
12
+ return ast.types;
13
+ case "Declaration":
14
+ return ast.typeParameters;
15
+ case "Suspend":
16
+ return [ast.thunk()];
17
+ default:
18
+ return [];
19
+ }
20
+ }
21
+ function sensitiveCheck(check: SchemaAST.Check<never>): boolean {
22
+ return check.annotations?.sensitive === true || (check._tag === "FilterGroup" && check.checks.some(sensitiveCheck));
23
+ }
24
+ function marked(ast: SchemaAST.AST, seen = new Set<SchemaAST.AST>()): boolean {
25
+ if (seen.has(ast)) {
26
+ return false;
27
+ }
28
+ seen.add(ast);
29
+ if (
30
+ ast.annotations?.sensitive === true
31
+ || ast.context?.annotations?.sensitive === true
32
+ || ast.checks?.some(sensitiveCheck)
33
+ || (Predicate.hasProperty(ast.annotations?.representation, "id") && ast.annotations.representation.id === "effect/schema/Redacted")
34
+ ) {
35
+ return true;
36
+ }
37
+ const nested = ast._tag === "Objects" ? ast.indexSignatures.map((index) => index.type) : children(ast);
38
+ return (ast.encoding?.some((link) => marked(link.to, seen)) ?? false) || nested.some((type) => marked(type, seen));
39
+ }
40
+ function containsSensitive(ast: SchemaAST.AST, keys: ReadonlySet<string>): boolean {
41
+ return (
42
+ marked(ast)
43
+ || propertiesOf(ast).some((property) => typeof property.name === "string" && (keys.has(property.name) || isSensitiveKey(property.name)))
44
+ );
45
+ }
46
+ function collectEncodedKeys(ast: SchemaAST.AST, keys: Set<string>) {
47
+ let source = ast;
48
+ let sensitive = false;
49
+ for (const link of ast.encoding ?? []) {
50
+ sensitive ||= containsSensitive(source, keys);
51
+ if (sensitive) {
52
+ for (const property of propertiesOf(link.to)) {
53
+ if (typeof property.name === "string") {
54
+ keys.add(property.name);
55
+ }
56
+ }
57
+ }
58
+ source = link.to;
59
+ }
60
+ }
61
+ function fixedOutput(ast: SchemaAST.AST): boolean {
62
+ if (ast._tag === "Union") {
63
+ return ast.types.every(fixedOutput);
64
+ }
65
+ return ast._tag === "Objects" && ast.indexSignatures.length === 0;
66
+ }
67
+ function maskedValue(ast: SchemaAST.AST, keys: ReadonlySet<string>, seen = new Set<SchemaAST.AST>()): boolean {
68
+ if (seen.has(ast)) {
69
+ return false;
70
+ }
71
+ seen.add(ast);
72
+ if (marked(ast)) {
73
+ return true;
74
+ }
75
+ let source = ast;
76
+ let sensitive = false;
77
+ for (const link of ast.encoding ?? []) {
78
+ sensitive ||= containsSensitive(source, keys);
79
+ if (sensitive && !fixedOutput(link.to)) {
80
+ return true;
81
+ }
82
+ source = link.to;
83
+ }
84
+ const nested = ast._tag === "Objects" ? ast.indexSignatures.map((index) => index.type) : children(ast);
85
+ return nested.some((type) => maskedValue(type, keys, seen));
86
+ }
87
+ function collect(ast: SchemaAST.AST, nodes: Set<SchemaAST.AST>, encodings = true) {
88
+ if (nodes.has(ast)) {
89
+ return;
90
+ }
91
+ nodes.add(ast);
92
+ for (const child of [...children(ast), ...(encodings ? (ast.encoding?.map((link) => link.to) ?? []) : [])]) {
93
+ collect(child, nodes, encodings);
94
+ }
95
+ }
96
+ function propertiesOf(ast: SchemaAST.AST) {
97
+ const nodes = new Set<SchemaAST.AST>();
98
+ collect(ast, nodes, false);
99
+ return [...nodes].flatMap((node) => (node._tag === "Objects" ? node.propertySignatures : []));
100
+ }
101
+ function collectFieldKeys(properties: SchemaAST.Objects["propertySignatures"], keys: Set<string>, sensitive: (ast: SchemaAST.AST) => boolean) {
102
+ for (const property of properties) {
103
+ if (typeof property.name === "string" && sensitive(property.type)) {
104
+ keys.add(property.name);
105
+ }
106
+ }
107
+ }
108
+ export function schemaSensitiveKeys<R extends Rpc.Any>(group: RpcGroup.RpcGroup<R>, extra: readonly string[] = []) {
109
+ const keys = new Set(extra);
110
+ const nodes = new Set<SchemaAST.AST>();
111
+ const requests: Rpc.AnyWithProps[] = [];
112
+ for (const rpc of group.requests.values()) {
113
+ if (Rpc.isRpc(rpc)) {
114
+ requests.push(rpc);
115
+ }
116
+ }
117
+ for (const rpc of requests) {
118
+ const schemas: Schema.Top[] = [
119
+ rpc.payloadSchema,
120
+ rpc.successSchema,
121
+ rpc.errorSchema,
122
+ rpc.defectSchema,
123
+ ...[...rpc.middlewares].map((m) => m.error),
124
+ ];
125
+ for (const schema of schemas) {
126
+ collect(schema.ast, nodes);
127
+ }
128
+ }
129
+ const properties = [...nodes].flatMap((ast) => (ast._tag === "Objects" ? ast.propertySignatures : []));
130
+ collectFieldKeys(properties, keys, marked);
131
+ let previousSize: number;
132
+ do {
133
+ previousSize = keys.size;
134
+ collectFieldKeys(properties, keys, (ast) => maskedValue(ast, keys));
135
+ for (const ast of nodes) {
136
+ collectEncodedKeys(ast, keys);
137
+ }
138
+ } while (keys.size !== previousSize);
139
+ const roots = requests.map((rpc) => ({
140
+ die: maskedValue(rpc.defectSchema.ast, keys),
141
+ fail: [rpc.errorSchema, ...[...rpc.middlewares].map((m) => m.error)].some((schema) => maskedValue(schema.ast, keys)),
142
+ tag: rpc._tag,
143
+ }));
144
+ return Object.assign((key: string) => keys.has(key) || isSensitiveKey(key), {
145
+ maskFailure: (tag: string | undefined, reason: "Fail" | "Die") =>
146
+ roots.some((root) => (tag === undefined || tag === root.tag) && (reason === "Fail" ? root.fail : root.die)),
147
+ });
148
+ }
@@ -0,0 +1,12 @@
1
+ import { Config, Effect } from "effect";
2
+ import { localPostgres } from "@shivaedev/local-postgres/server/localPostgres.ts";
3
+
4
+ export const setupDatabases = Effect.fn("Platform.setupDatabases")(function* () {
5
+ const config = yield* Config.all({
6
+ "DATABASE_URL": Config.String("DATABASE_URL"),
7
+ "DOCKER_CONTEXT": Config.String("DOCKER_CONTEXT").pipe(Config.withDefault("")),
8
+ "DOCKER_HOST": Config.String("DOCKER_HOST").pipe(Config.withDefault("")),
9
+ "TEST_DATABASE_URL": Config.String("TEST_DATABASE_URL"),
10
+ });
11
+ yield* Effect.sync(() => localPostgres(config).prepareDatabases([config.DATABASE_URL, config.TEST_DATABASE_URL]));
12
+ });
@@ -0,0 +1,14 @@
1
+ import type { Rpc, RpcGroup } from "effect/rpc";
2
+ import { RequestTracing } from "@shivaedev/identity/middleware.ts";
3
+
4
+ export function tracedGroup<R extends Rpc.Any>(group: RpcGroup.RpcGroup<R>) {
5
+ const traced = group.middleware(RequestTracing);
6
+ for (const rpc of traced.requests.values()) {
7
+ const middlewares = new Set(rpc.middlewares);
8
+ middlewares.delete(RequestTracing);
9
+ middlewares.add(RequestTracing);
10
+ // Native middleware() clones each RPC but cannot move an existing middleware to the outermost position.
11
+ Object.assign(rpc, { middlewares });
12
+ }
13
+ return traced;
14
+ }