1/**
2 * Describes request values whose results can be persisted.
3 *
4 * A `Persistable` request has a primary key and schemas for its success and
5 * error results. `Persistence` and `PersistedCache` use that information to
6 * store the request's `Exit` value and restore it later from a backing store.
7 *
8 * @stability unstable
9 * @since 4.0.0
10 */
11import type * as Duration from "../Duration.ts"
12import type * as Effect from "../Effect.ts"
13import type * as Exit from "../Exit.ts"
14import * as InternalRecord from "../internal/record.ts"
15import * as PrimaryKey from "../PrimaryKey.ts"
16import * as Request from "../Request.ts"
17import * as Schema from "../Schema.ts"
18import type * as Types from "../Types.ts"
19import type { PersistenceError } from "./Persistence.ts"
20
21/**
22 * Defines the property key used to attach success and error schemas to persistable
23 * requests.
24 *
25 * **When to use**
26 *
27 * Use to implement persistable request values by attaching success and error
28 * schemas at this property key.
29 *
30 * @stability unstable
31 * @category symbols
32 * @since 4.0.0
33 */
34export const symbol = "~effect/persistence/Persistable" as const
35
36/**
37 * A primary-keyed request value whose success and error results can be
38 * serialized for persistence.
39 *
40 * @stability unstable
41 * @category models
42 * @since 4.0.0
43 */
44export interface Persistable<A extends Schema.Constraint, E extends Schema.Constraint> extends PrimaryKey.PrimaryKey {
45 readonly [symbol]: {
46 readonly success: A
47 readonly error: E
48 }
49}
50
51/**
52 * Any persistable request regardless of its success and error schemas.
53 *
54 * @stability unstable
55 * @category models
56 * @since 4.0.0
57 */
58export type Any = Persistable<Schema.Constraint, Schema.Constraint>
59
60/**
61 * Extracts the success schema from a persistable request.
62 *
63 * @stability unstable
64 * @category utility types
65 * @since 4.0.0
66 */
67export type SuccessSchema<A extends Any> = A["~effect/persistence/Persistable"]["success"]
68
69/**
70 * Extracts the success value type from a persistable request.
71 *
72 * @stability unstable
73 * @category utility types
74 * @since 4.0.0
75 */
76export type Success<A extends Any> = A["~effect/persistence/Persistable"]["success"]["Type"]
77
78/**
79 * Extracts the error schema from a persistable request.
80 *
81 * @stability unstable
82 * @category utility types
83 * @since 4.0.0
84 */
85export type ErrorSchema<A extends Any> = A["~effect/persistence/Persistable"]["error"]
86
87/**
88 * Extracts the error value type from a persistable request.
89 *
90 * @stability unstable
91 * @category utility types
92 * @since 4.0.0
93 */
94export type Error<A extends Any> = A["~effect/persistence/Persistable"]["error"]["Type"]
95
96/**
97 * Services required to decode a persisted success or error value for the
98 * request.
99 *
100 * @stability unstable
101 * @category utility types
102 * @since 4.0.0
103 */
104export type DecodingServices<A extends Any> =
105 | A["~effect/persistence/Persistable"]["success"]["DecodingServices"]
106 | A["~effect/persistence/Persistable"]["error"]["DecodingServices"]
107
108/**
109 * Services required to encode a success or error value for persistence.
110 *
111 * @stability unstable
112 * @category utility types
113 * @since 4.0.0
114 */
115export type EncodingServices<A extends Any> =
116 | A["~effect/persistence/Persistable"]["success"]["EncodingServices"]
117 | A["~effect/persistence/Persistable"]["error"]["EncodingServices"]
118
119/**
120 * All schema services required to encode and decode a persistable request
121 * result.
122 *
123 * @stability unstable
124 * @category utility types
125 * @since 4.0.0
126 */
127export type Services<A extends Any> =
128 | A["~effect/persistence/Persistable"]["success"]["DecodingServices"]
129 | A["~effect/persistence/Persistable"]["success"]["EncodingServices"]
130 | A["~effect/persistence/Persistable"]["error"]["DecodingServices"]
131 | A["~effect/persistence/Persistable"]["error"]["EncodingServices"]
132
133/**
134 * Computes the time to live for a persisted result from the result `Exit` and
135 * request value.
136 *
137 * @stability unstable
138 * @category utility types
139 * @since 4.0.0
140 */
141export type TimeToLiveFn<K extends Any> = (exit: Exit.Exit<Success<K>, Error<K>>, request: K) => Duration.Input
142
143/**
144 * Creates request classes that implement `Persistable` and `Request.Request`.
145 *
146 * **Details**
147 *
148 * The generated class stores the supplied tag, derives its primary key from
149 * the payload, and carries schemas for persisted success and error exits.
150 *
151 * @stability unstable
152 * @category constructors
153 * @since 4.0.0
154 */
155export const Class = <
156 Config extends {
157 payload: Record<string, unknown>
158 requires?: any
159 requestError?: any
160 } = { payload: {} }
161>() =>
162<
163 const Tag extends string,
164 A extends Schema.Constraint = Schema.Void,
165 E extends Schema.Constraint = Schema.Never
166>(tag: Tag, options: {
167 readonly primaryKey: (payload: Config["payload"]) => string
168 readonly success?: A | undefined
169 readonly error?: E | undefined
170}): new(
171 args: Types.EqualsWith<
172 Config["payload"],
173 {},
174 void,
175 {
176 readonly [
177 P in keyof Config["payload"] as P extends "_tag" ? never : P
178 ]: Config["payload"][P]
179 }
180 >
181) =>
182 & { readonly _tag: Tag }
183 & { readonly [K in keyof Config["payload"]]: Config["payload"][K] }
184 & Persistable<A, E>
185 & Request.Request<
186 A["Type"],
187 | E["Type"]
188 | ("requestError" extends keyof Config ? Config["requestError"] : (PersistenceError | Schema.SchemaError)),
189 | A["DecodingServices"]
190 | A["EncodingServices"]
191 | E["DecodingServices"]
192 | E["EncodingServices"]
193 | ("requires" extends keyof Config ? Config["requires"] : never)
194 > =>
195{
196 function Persistable(this: any, props: object | undefined) {
197 this._tag = tag
198 if (props) {
199 InternalRecord.assignProperties(this, props)
200 }
201 }
202 Persistable.prototype = {
203 ...Request.RequestPrototype,
204 [PrimaryKey.symbol]() {
205 return options.primaryKey(this)
206 },
207 [symbol]: {
208 success: options.success ?? Schema.Void,
209 error: options.error ?? Schema.Never
210 }
211 }
212 return Persistable as any
213}
214
215/**
216 * Returns the cached `Exit` schema for a persistable request's success and
217 * error schemas.
218 *
219 * @stability unstable
220 * @category accessors
221 * @since 4.0.0
222 */
223export const exitSchema = <A extends Schema.Constraint, E extends Schema.Constraint>(
224 self: Persistable<A, E>
225): Schema.Exit<A, E, Schema.Defect> => {
226 let schema = exitSchemaCache.get(self)
227 if (schema) return schema as Schema.Exit<A, E, Schema.Defect>
228 schema = Schema.Exit(self[symbol].success, self[symbol].error, Schema.Defect())
229 exitSchemaCache.set(self, schema)
230 return schema as Schema.Exit<A, E, Schema.Defect>
231}
232
233const exitSchemaCache = new WeakMap<Persistable<any, any>, Schema.Exit<any, any, Schema.Defect>>()
234
235/**
236 * Encodes an `Exit` for a persistable request using its success and error
237 * schemas.
238 *
239 * @stability unstable
240 * @category serialization
241 * @since 4.0.0
242 */
243export const serializeExit = <A extends Schema.Constraint, E extends Schema.Constraint>(
244 self: Persistable<A, E>,
245 exit: Exit.Exit<A["Type"], E["Type"]>
246): Effect.Effect<unknown, Schema.SchemaError, A["EncodingServices"] | E["EncodingServices"]> => {
247 const schema = Schema.toCodecJson(exitSchema(self))
248 return Schema.encodeEffect(schema)(exit)
249}
250
251/**
252 * Decodes a persisted value into an `Exit` for a persistable request using its
253 * success and error schemas.
254 *
255 * @stability unstable
256 * @category serialization
257 * @since 4.0.0
258 */
259export const deserializeExit = <A extends Schema.Constraint, E extends Schema.Constraint>(
260 self: Persistable<A, E>,
261 encoded: unknown
262): Effect.Effect<
263 Exit.Exit<A["Type"], E["Type"]>,
264 Schema.SchemaError,
265 A["DecodingServices"] | E["DecodingServices"]
266> => {
267 const schema = Schema.toCodecJson(exitSchema(self))
268 return Schema.decodeUnknownEffect(schema)(encoded)
269}