@homeflare/alchemy 0.24.0 → 0.25.1

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 (100) hide show
  1. package/README.md +10 -0
  2. package/dist/linux/fake-linux-host.d.ts.map +1 -1
  3. package/dist/linux/fake-systemd.d.ts +15 -0
  4. package/dist/linux/fake-systemd.d.ts.map +1 -1
  5. package/dist/linux/index.js +18 -7
  6. package/dist/linux/index.js.map +5 -5
  7. package/dist/linux/unit-form.d.ts +41 -0
  8. package/dist/linux/unit-form.d.ts.map +1 -1
  9. package/dist/linux/unit-lifecycle.d.ts.map +1 -1
  10. package/dist/linux/unit-settle.d.ts.map +1 -1
  11. package/dist/paperless/client.d.ts +37 -0
  12. package/dist/paperless/client.d.ts.map +1 -0
  13. package/dist/paperless/constraint-guard.d.ts +19 -0
  14. package/dist/paperless/constraint-guard.d.ts.map +1 -0
  15. package/dist/paperless/constraints.d.ts +50 -0
  16. package/dist/paperless/constraints.d.ts.map +1 -0
  17. package/dist/paperless/credentials.d.ts +40 -0
  18. package/dist/paperless/credentials.d.ts.map +1 -0
  19. package/dist/paperless/custom-field.d.ts +75 -0
  20. package/dist/paperless/custom-field.d.ts.map +1 -0
  21. package/dist/paperless/document-type.d.ts +30 -0
  22. package/dist/paperless/document-type.d.ts.map +1 -0
  23. package/dist/paperless/errors.d.ts +49 -0
  24. package/dist/paperless/errors.d.ts.map +1 -0
  25. package/dist/paperless/fake-paperless.d.ts +39 -0
  26. package/dist/paperless/fake-paperless.d.ts.map +1 -0
  27. package/dist/paperless/generated/constraints/index.d.ts +13 -0
  28. package/dist/paperless/generated/constraints/index.d.ts.map +1 -0
  29. package/dist/paperless/generated/constraints/paperless-custom_fields.d.ts +21 -0
  30. package/dist/paperless/generated/constraints/paperless-custom_fields.d.ts.map +1 -0
  31. package/dist/paperless/generated/constraints/paperless-document_types.d.ts +21 -0
  32. package/dist/paperless/generated/constraints/paperless-document_types.d.ts.map +1 -0
  33. package/dist/paperless/generated/constraints/paperless-storage_paths.d.ts +21 -0
  34. package/dist/paperless/generated/constraints/paperless-storage_paths.d.ts.map +1 -0
  35. package/dist/paperless/generated/constraints/paperless-tags.d.ts +21 -0
  36. package/dist/paperless/generated/constraints/paperless-tags.d.ts.map +1 -0
  37. package/dist/paperless/generated/types/custom-fields.d.ts +23 -0
  38. package/dist/paperless/generated/types/custom-fields.d.ts.map +1 -0
  39. package/dist/paperless/generated/types/document-types.d.ts +39 -0
  40. package/dist/paperless/generated/types/document-types.d.ts.map +1 -0
  41. package/dist/paperless/generated/types/index.d.ts +10 -0
  42. package/dist/paperless/generated/types/index.d.ts.map +1 -0
  43. package/dist/paperless/generated/types/storage-paths.d.ts +31 -0
  44. package/dist/paperless/generated/types/storage-paths.d.ts.map +1 -0
  45. package/dist/paperless/generated/types/tags.d.ts +37 -0
  46. package/dist/paperless/generated/types/tags.d.ts.map +1 -0
  47. package/dist/paperless/index.d.ts +19 -0
  48. package/dist/paperless/index.d.ts.map +1 -0
  49. package/dist/paperless/index.js +524 -0
  50. package/dist/paperless/index.js.map +26 -0
  51. package/dist/paperless/matching-locate.d.ts +33 -0
  52. package/dist/paperless/matching-locate.d.ts.map +1 -0
  53. package/dist/paperless/matching-types.d.ts +60 -0
  54. package/dist/paperless/matching-types.d.ts.map +1 -0
  55. package/dist/paperless/matching.d.ts +61 -0
  56. package/dist/paperless/matching.d.ts.map +1 -0
  57. package/dist/paperless/providers.d.ts +15 -0
  58. package/dist/paperless/providers.d.ts.map +1 -0
  59. package/dist/paperless/storage-path.d.ts +32 -0
  60. package/dist/paperless/storage-path.d.ts.map +1 -0
  61. package/dist/paperless/tag.d.ts +47 -0
  62. package/dist/paperless/tag.d.ts.map +1 -0
  63. package/docs/linux-host.md +2 -0
  64. package/docs/paperless.md +115 -0
  65. package/package.json +5 -1
  66. package/src/linux/fake-linux-host.ts +2 -0
  67. package/src/linux/fake-systemd.ts +36 -2
  68. package/src/linux/unit-activating.test.ts +164 -0
  69. package/src/linux/unit-form.ts +51 -0
  70. package/src/linux/unit-lifecycle.ts +9 -2
  71. package/src/linux/unit-settle.ts +8 -4
  72. package/src/paperless/client.ts +88 -0
  73. package/src/paperless/constraint-guard.ts +50 -0
  74. package/src/paperless/constraints.test.ts +74 -0
  75. package/src/paperless/constraints.ts +111 -0
  76. package/src/paperless/credentials.ts +69 -0
  77. package/src/paperless/custom-field.test.ts +74 -0
  78. package/src/paperless/custom-field.ts +95 -0
  79. package/src/paperless/document-type.ts +74 -0
  80. package/src/paperless/errors.ts +62 -0
  81. package/src/paperless/fake-paperless.ts +156 -0
  82. package/src/paperless/generated/constraints/index.ts +23 -0
  83. package/src/paperless/generated/constraints/paperless-custom_fields.ts +29 -0
  84. package/src/paperless/generated/constraints/paperless-document_types.ts +32 -0
  85. package/src/paperless/generated/constraints/paperless-storage_paths.ts +34 -0
  86. package/src/paperless/generated/constraints/paperless-tags.ts +34 -0
  87. package/src/paperless/generated/types/custom-fields.ts +23 -0
  88. package/src/paperless/generated/types/document-types.ts +30 -0
  89. package/src/paperless/generated/types/index.ts +9 -0
  90. package/src/paperless/generated/types/storage-paths.ts +31 -0
  91. package/src/paperless/generated/types/tags.ts +37 -0
  92. package/src/paperless/index.ts +56 -0
  93. package/src/paperless/matching-identity.test.ts +194 -0
  94. package/src/paperless/matching-locate.ts +104 -0
  95. package/src/paperless/matching-types.ts +61 -0
  96. package/src/paperless/matching.test.ts +128 -0
  97. package/src/paperless/matching.ts +239 -0
  98. package/src/paperless/providers.ts +27 -0
  99. package/src/paperless/storage-path.ts +78 -0
  100. package/src/paperless/tag.ts +91 -0
@@ -0,0 +1,524 @@
1
+ // src/paperless/credentials.ts
2
+ import * as Context from "effect/Context";
3
+ import * as Data from "effect/Data";
4
+ import * as Effect from "effect/Effect";
5
+ import * as Layer from "effect/Layer";
6
+
7
+ class PaperlessCredentialsError extends Data.TaggedError("PaperlessCredentialsError") {
8
+ }
9
+ var readEnvironment = Effect.suspend(() => {
10
+ const url = process.env["PAPERLESS_URL"];
11
+ const token = process.env["PAPERLESS_TOKEN"];
12
+ if (url === undefined || url.trim() === "") {
13
+ return Effect.fail(new PaperlessCredentialsError({
14
+ message: "PAPERLESS_URL is not set. Point it at your Paperless-ngx instance."
15
+ }));
16
+ }
17
+ if (token === undefined || token.trim() === "") {
18
+ return Effect.fail(new PaperlessCredentialsError({
19
+ message: "PAPERLESS_TOKEN is not set. Mint a scoped token and export it — never store it in Alchemy props."
20
+ }));
21
+ }
22
+ return Effect.succeed({ token: token.trim(), url: url.trim().replace(/\/$/, "") });
23
+ });
24
+
25
+ class PaperlessCredentials extends Context.Service()("PaperlessCredentials") {
26
+ }
27
+ var environmentLayer = Layer.succeed(PaperlessCredentials, readEnvironment);
28
+ // src/paperless/errors.ts
29
+ import * as Data2 from "effect/Data";
30
+
31
+ class PaperlessNotFound extends Data2.TaggedError("PaperlessNotFound") {
32
+ }
33
+
34
+ class PaperlessUnauthorized extends Data2.TaggedError("PaperlessUnauthorized") {
35
+ }
36
+
37
+ class PaperlessValidation extends Data2.TaggedError("PaperlessValidation") {
38
+ }
39
+
40
+ class PaperlessUnavailable extends Data2.TaggedError("PaperlessUnavailable") {
41
+ }
42
+ var statusToError = (status, method, path, body) => {
43
+ if (status === 404)
44
+ return new PaperlessNotFound({ method, path });
45
+ if (status === 401 || status === 403)
46
+ return new PaperlessUnauthorized({ method, path, status });
47
+ if (status >= 500)
48
+ return new PaperlessUnavailable({ detail: body, method, path, status });
49
+ return new PaperlessValidation({ body, method, path });
50
+ };
51
+ // src/paperless/constraint-guard.ts
52
+ import * as Effect2 from "effect/Effect";
53
+
54
+ // src/paperless/constraints.ts
55
+ var lengthOf = (value) => [...value].length;
56
+ var checkString = (name, value, rule) => {
57
+ const out = [];
58
+ if (rule.maxLength !== undefined && lengthOf(value) > rule.maxLength) {
59
+ out.push(`${name}: at most ${String(rule.maxLength)} characters`);
60
+ }
61
+ if (rule.minLength !== undefined && lengthOf(value) < rule.minLength) {
62
+ out.push(`${name}: at least ${String(rule.minLength)} characters`);
63
+ }
64
+ if (rule.enum !== undefined && !rule.enum.includes(value)) {
65
+ out.push(`${name}: must be one of ${rule.enum.join(", ")}`);
66
+ }
67
+ if (rule.pattern !== undefined && !new RegExp(rule.pattern).test(value)) {
68
+ out.push(`${name}: must match ${rule.patternSource ?? rule.pattern}`);
69
+ }
70
+ return out;
71
+ };
72
+ var checkNumber = (name, value, rule) => {
73
+ const out = [];
74
+ if (!Number.isFinite(value))
75
+ return out;
76
+ if (rule.minimum !== undefined && value < rule.minimum)
77
+ out.push(`${name}: at least ${String(rule.minimum)}`);
78
+ if (rule.maximum !== undefined && value > rule.maximum)
79
+ out.push(`${name}: at most ${String(rule.maximum)}`);
80
+ return out;
81
+ };
82
+ var checkValue = (name, value, rule) => {
83
+ if (typeof value === "string")
84
+ return checkString(name, value, rule);
85
+ if (typeof value === "number")
86
+ return checkNumber(name, value, rule);
87
+ return [];
88
+ };
89
+ var violations = (table, body, options = {}) => {
90
+ const out = [];
91
+ for (const [name, rule] of Object.entries(table)) {
92
+ if (!Object.hasOwn(body, name)) {
93
+ if (options.presence === true && rule.required === true)
94
+ out.push(`${name}: required`);
95
+ continue;
96
+ }
97
+ const value = body[name];
98
+ for (const item of Array.isArray(value) ? value : [value])
99
+ out.push(...checkValue(name, item, rule));
100
+ }
101
+ return out;
102
+ };
103
+ var refusal = (endpoint, found) => `${endpoint} would be rejected by the vendor:
104
+ - ${found.join(`
105
+ - `)}
106
+ ` + "These are Paperless-ngx's own limits, read from its served OpenAPI document " + "(packages/alchemy/src/paperless/generated/constraints). Fix the declaration.";
107
+
108
+ // src/paperless/generated/constraints/paperless-custom_fields.ts
109
+ var PAPERLESS_CUSTOM_FIELDS_CONSTRAINTS = {
110
+ "paperless:PATCH /api/custom_fields/{id}/": {
111
+ name: { maxLength: 128, minLength: 1, type: "string" }
112
+ },
113
+ "paperless:POST /api/custom_fields/": {
114
+ data_type: { required: true },
115
+ name: { maxLength: 128, minLength: 1, required: true, type: "string" }
116
+ }
117
+ };
118
+
119
+ // src/paperless/generated/constraints/paperless-document_types.ts
120
+ var PAPERLESS_DOCUMENT_TYPES_CONSTRAINTS = {
121
+ "paperless:PATCH /api/document_types/{id}/": {
122
+ match: { maxLength: 256, type: "string" },
123
+ matching_algorithm: { enum: [0, 1, 2, 3, 4, 5, 6], type: "integer" },
124
+ name: { maxLength: 128, minLength: 1, type: "string" }
125
+ },
126
+ "paperless:POST /api/document_types/": {
127
+ match: { maxLength: 256, type: "string" },
128
+ matching_algorithm: { enum: [0, 1, 2, 3, 4, 5, 6], type: "integer" },
129
+ name: { maxLength: 128, minLength: 1, required: true, type: "string" }
130
+ }
131
+ };
132
+
133
+ // src/paperless/generated/constraints/paperless-storage_paths.ts
134
+ var PAPERLESS_STORAGE_PATHS_CONSTRAINTS = {
135
+ "paperless:PATCH /api/storage_paths/{id}/": {
136
+ match: { maxLength: 256, type: "string" },
137
+ matching_algorithm: { enum: [0, 1, 2, 3, 4, 5, 6], type: "integer" },
138
+ name: { maxLength: 128, minLength: 1, type: "string" },
139
+ path: { minLength: 1, type: "string" }
140
+ },
141
+ "paperless:POST /api/storage_paths/": {
142
+ match: { maxLength: 256, type: "string" },
143
+ matching_algorithm: { enum: [0, 1, 2, 3, 4, 5, 6], type: "integer" },
144
+ name: { maxLength: 128, minLength: 1, required: true, type: "string" },
145
+ path: { minLength: 1, required: true, type: "string" }
146
+ }
147
+ };
148
+
149
+ // src/paperless/generated/constraints/paperless-tags.ts
150
+ var PAPERLESS_TAGS_CONSTRAINTS = {
151
+ "paperless:PATCH /api/tags/{id}/": {
152
+ color: { maxLength: 7, minLength: 1, type: "string" },
153
+ match: { maxLength: 256, type: "string" },
154
+ matching_algorithm: { enum: [0, 1, 2, 3, 4, 5, 6], type: "integer" },
155
+ name: { maxLength: 128, minLength: 1, type: "string" }
156
+ },
157
+ "paperless:POST /api/tags/": {
158
+ color: { maxLength: 7, minLength: 1, type: "string" },
159
+ match: { maxLength: 256, type: "string" },
160
+ matching_algorithm: { enum: [0, 1, 2, 3, 4, 5, 6], type: "integer" },
161
+ name: { maxLength: 128, minLength: 1, required: true, type: "string" }
162
+ }
163
+ };
164
+
165
+ // src/paperless/generated/constraints/index.ts
166
+ var PAPERLESS_CONSTRAINTS = {
167
+ ...PAPERLESS_CUSTOM_FIELDS_CONSTRAINTS,
168
+ ...PAPERLESS_DOCUMENT_TYPES_CONSTRAINTS,
169
+ ...PAPERLESS_STORAGE_PATHS_CONSTRAINTS,
170
+ ...PAPERLESS_TAGS_CONSTRAINTS
171
+ };
172
+ var PAPERLESS_CONSTRAINTS_DIGEST = "66e61dce8c099d7d";
173
+
174
+ // src/paperless/constraint-guard.ts
175
+ var constraintsFor = (key) => {
176
+ const table = PAPERLESS_CONSTRAINTS[key];
177
+ if (table === undefined) {
178
+ throw new Error(`no vendor constraint table for '${key}'. It is named in this package but not generated: ` + "run `bun codegen/paperless.ts` (see codegen/README.md).");
179
+ }
180
+ return table;
181
+ };
182
+ var bodyViolations = (key, body, presence) => violations(constraintsFor(key), body, { presence });
183
+ var guardBody = (key, body, presence) => Effect2.suspend(() => {
184
+ if (key === undefined)
185
+ return Effect2.void;
186
+ const found = bodyViolations(key, body, presence);
187
+ return found.length === 0 ? Effect2.void : Effect2.die(new Error(refusal(key, found)));
188
+ });
189
+ // src/paperless/tag.ts
190
+ import { Resource } from "alchemy";
191
+ import * as Provider from "alchemy/Provider";
192
+ import * as Effect6 from "effect/Effect";
193
+
194
+ // src/paperless/matching.ts
195
+ import { Unowned } from "alchemy/AdoptPolicy";
196
+ import { isResolved } from "alchemy/Diff";
197
+ import * as Effect5 from "effect/Effect";
198
+
199
+ // src/paperless/client.ts
200
+ import * as Effect3 from "effect/Effect";
201
+ import * as HttpClient from "effect/unstable/http/HttpClient";
202
+ import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest";
203
+ var PATH_PREFIX = "/api";
204
+ var API_VERSION = "10";
205
+ var paperless = (method, path, body) => Effect3.gen(function* () {
206
+ const credentials = yield* yield* PaperlessCredentials;
207
+ const url = `${credentials.url}${PATH_PREFIX}/${path.replace(/^\//, "")}`;
208
+ const client = yield* HttpClient.HttpClient;
209
+ const request = HttpClientRequest.make(method)(url).pipe(HttpClientRequest.setHeaders({
210
+ Accept: `application/json; version=${API_VERSION}`,
211
+ Authorization: `Token ${credentials.token}`
212
+ }), body === undefined ? (self) => self : HttpClientRequest.bodyText(JSON.stringify(body), "application/json"));
213
+ const response = yield* client.execute(request).pipe(Effect3.mapError((cause) => new PaperlessUnavailable({ detail: String(cause), method, path, status: 0 })));
214
+ if (response.status < 200 || response.status >= 300) {
215
+ const detail = yield* response.text.pipe(Effect3.orElseSucceed(() => ""));
216
+ return yield* Effect3.fail(statusToError(response.status, method, path, detail.slice(0, 500)));
217
+ }
218
+ const text = yield* response.text.pipe(Effect3.mapError(() => new PaperlessUnavailable({
219
+ detail: "body read failed",
220
+ method,
221
+ path,
222
+ status: response.status
223
+ })));
224
+ return text === "" ? undefined : JSON.parse(text);
225
+ });
226
+
227
+ // src/paperless/matching-locate.ts
228
+ import * as Effect4 from "effect/Effect";
229
+ var absentOn404 = (io) => io.pipe(Effect4.catchIf((cause) => cause instanceof PaperlessNotFound, () => Effect4.succeed(undefined)));
230
+ var LOCATE_PAGE = 20;
231
+ var ownerOf = (row) => typeof row["owner"] === "number" ? row["owner"] : undefined;
232
+ var matchingLocate = (spec) => {
233
+ const fetchByName = (props) => absentOn404(paperless("GET", `${spec.collection}/?name__iexact=${encodeURIComponent(props.name)}&page_size=${String(LOCATE_PAGE)}`)).pipe(Effect4.map((list) => {
234
+ if (list !== undefined && list.count > LOCATE_PAGE) {
235
+ throw new Error(`${spec.describe(props)}: name__iexact matched ${String(list.count)} rows, more than ` + `the ${String(LOCATE_PAGE)}-row page this reads. Narrow the name rather than paging.`);
236
+ }
237
+ const rows = (list?.results ?? []).filter((row) => row["name"] === props.name && ownerOf(row) === (props.owner ?? undefined));
238
+ if (rows.length > 1) {
239
+ throw new Error(`${spec.describe(props)}: matched ${String(rows.length)} Paperless objects with the same (name, owner).`);
240
+ }
241
+ return rows[0];
242
+ }));
243
+ const fetchById = (id) => absentOn404(paperless("GET", `${spec.collection}/${String(id)}/`));
244
+ const fetchLive = (props, output) => output === undefined ? fetchByName(props) : fetchById(output.id).pipe(Effect4.flatMap((live) => live === undefined ? Effect4.die(new Error(`${spec.describe(props)}: id ${String(output.id)} is recorded in state but no ` + "longer exists live (deleted out of band). Refusing to create a replacement " + "— adopt the live object back under this declaration, or remove it from " + "state, before deploying again.")) : Effect4.succeed(live)));
245
+ const objectPath = (live) => `${spec.collection}/${String(live["id"])}`;
246
+ return { fetchByName, fetchById, fetchLive, objectPath };
247
+ };
248
+
249
+ // src/paperless/matching.ts
250
+ var sameValue = (a, b) => typeof a === "object" && a !== null ? JSON.stringify(a) === JSON.stringify(b) : Object.is(a, b);
251
+ var matchingOperations = (spec) => {
252
+ const { fetchByName, fetchById, fetchLive, objectPath } = matchingLocate(spec);
253
+ const read = (props) => fetchByName(props).pipe(Effect5.map((live) => live === undefined ? undefined : spec.attributes(live, props)));
254
+ const readById = (props, id) => fetchById(id).pipe(Effect5.map((live) => live === undefined ? undefined : spec.attributes(live, props)));
255
+ const createBody = (props) => {
256
+ const out = spec.owned === false ? { name: props.name } : { name: props.name, owner: props.owner ?? null };
257
+ for (const field of [...spec.fields, ...spec.createOnly ?? []]) {
258
+ const value = field.prop(props);
259
+ if (value !== undefined)
260
+ out[field.key] = value;
261
+ }
262
+ return out;
263
+ };
264
+ const patchBody = (props, live) => {
265
+ const out = {};
266
+ if (props.name !== live["name"])
267
+ out["name"] = props.name;
268
+ if (spec.owned !== false && ownerOf(live) !== (props.owner ?? undefined)) {
269
+ out["owner"] = props.owner ?? null;
270
+ }
271
+ for (const field of spec.fields) {
272
+ const value = field.prop(props);
273
+ if (value !== undefined && !sameValue(value, field.live(live)))
274
+ out[field.key] = value;
275
+ }
276
+ return out;
277
+ };
278
+ const matches = (props, live) => {
279
+ if (props.name !== live["name"])
280
+ return false;
281
+ if (spec.owned !== false && ownerOf(live) !== (props.owner ?? undefined))
282
+ return false;
283
+ return spec.fields.every((field) => {
284
+ const value = field.prop(props);
285
+ return value === undefined || sameValue(value, field.live(live));
286
+ });
287
+ };
288
+ return {
289
+ read,
290
+ diff: (news, output) => Effect5.gen(function* () {
291
+ if (output === undefined || !isResolved(news))
292
+ return;
293
+ const live = yield* fetchLive(news, output);
294
+ if (live === undefined)
295
+ return { action: "update" };
296
+ const refusal2 = spec.immutable?.(live, news);
297
+ if (refusal2 !== undefined)
298
+ return yield* Effect5.die(new Error(refusal2));
299
+ if (matches(news, live))
300
+ return { action: "noop" };
301
+ return spec.endpoint.update === undefined ? { action: "replace" } : { action: "update" };
302
+ }),
303
+ reconcile: (news, output) => Effect5.gen(function* () {
304
+ const before = yield* fetchLive(news, output);
305
+ let id;
306
+ if (before === undefined) {
307
+ const body = createBody(news);
308
+ yield* guardBody(spec.endpoint.create, body, true);
309
+ yield* paperless("POST", `${spec.collection}/`, body);
310
+ } else {
311
+ id = typeof before["id"] === "number" ? before["id"] : undefined;
312
+ const refusal2 = spec.immutable?.(before, news);
313
+ if (refusal2 !== undefined)
314
+ return yield* Effect5.die(new Error(refusal2));
315
+ if (spec.endpoint.update !== undefined) {
316
+ const body = patchBody(news, before);
317
+ if (Object.keys(body).length > 0) {
318
+ yield* guardBody(spec.endpoint.update, body, false);
319
+ yield* paperless("PATCH", `${objectPath(before)}/`, body);
320
+ }
321
+ }
322
+ }
323
+ const after = yield* id === undefined ? read(news) : readById(news, id);
324
+ if (after === undefined) {
325
+ return yield* Effect5.die(new Error(`${spec.describe(news)}: the write returned no error but the object is still absent.`));
326
+ }
327
+ return after;
328
+ }),
329
+ destroy: (olds, output) => Effect5.gen(function* () {
330
+ const live = output === undefined ? yield* fetchByName(olds) : yield* fetchById(output.id);
331
+ if (live === undefined)
332
+ return;
333
+ yield* absentOn404(paperless("DELETE", `${objectPath(live)}/`));
334
+ })
335
+ };
336
+ };
337
+ var matchingHandlers = (spec) => {
338
+ const ops = matchingOperations(spec);
339
+ return {
340
+ list: () => Effect5.succeed([]),
341
+ read: ({ olds }) => ops.read(olds).pipe(Effect5.map((attrs) => attrs === undefined ? undefined : Unowned(attrs))),
342
+ diff: ({ news, output }) => ops.diff(news, output),
343
+ reconcile: ({ news, output }) => ops.reconcile(news, output),
344
+ delete: ({ olds, output }) => ops.destroy(olds, output)
345
+ };
346
+ };
347
+
348
+ // src/paperless/tag.ts
349
+ var Tag = Resource("Paperless.Tag", { defaultRemovalPolicy: "retain" });
350
+ var handlers = matchingHandlers({
351
+ attributes: (live, props) => {
352
+ const id = live["id"];
353
+ if (typeof id !== "number")
354
+ return;
355
+ return {
356
+ color: typeof live["color"] === "string" ? live["color"] : "",
357
+ id,
358
+ isInboxTag: live["is_inbox_tag"] === true,
359
+ isInsensitive: live["is_insensitive"] === true,
360
+ match: typeof live["match"] === "string" ? live["match"] : "",
361
+ matchingAlgorithm: typeof live["matching_algorithm"] === "number" ? live["matching_algorithm"] : 0,
362
+ name: typeof live["name"] === "string" ? live["name"] : props.name,
363
+ owner: typeof live["owner"] === "number" ? live["owner"] : undefined,
364
+ parent: typeof live["parent"] === "number" ? live["parent"] : undefined
365
+ };
366
+ },
367
+ collection: "tags",
368
+ describe: (props) => `tags ${props.name}`,
369
+ endpoint: { create: "paperless:POST /api/tags/", update: "paperless:PATCH /api/tags/{id}/" },
370
+ fields: [
371
+ { key: "color", live: (r) => r["color"], prop: (p) => p.color },
372
+ { key: "match", live: (r) => r["match"], prop: (p) => p.match },
373
+ {
374
+ key: "matching_algorithm",
375
+ live: (r) => r["matching_algorithm"],
376
+ prop: (p) => p.matchingAlgorithm
377
+ },
378
+ { key: "is_insensitive", live: (r) => r["is_insensitive"], prop: (p) => p.isInsensitive },
379
+ { key: "is_inbox_tag", live: (r) => r["is_inbox_tag"], prop: (p) => p.isInboxTag },
380
+ { key: "parent", live: (r) => r["parent"], prop: (p) => p.parent }
381
+ ]
382
+ });
383
+ var TagProvider = () => Provider.effect(Tag, Effect6.succeed(Tag.Provider.of(handlers)));
384
+ // src/paperless/document-type.ts
385
+ import { Resource as Resource2 } from "alchemy";
386
+ import * as Provider2 from "alchemy/Provider";
387
+ import * as Effect7 from "effect/Effect";
388
+ var DocumentType = Resource2("Paperless.DocumentType", {
389
+ defaultRemovalPolicy: "retain"
390
+ });
391
+ var handlers2 = matchingHandlers({
392
+ attributes: (live, props) => {
393
+ const id = live["id"];
394
+ if (typeof id !== "number")
395
+ return;
396
+ return {
397
+ id,
398
+ isInsensitive: live["is_insensitive"] === true,
399
+ match: typeof live["match"] === "string" ? live["match"] : "",
400
+ matchingAlgorithm: typeof live["matching_algorithm"] === "number" ? live["matching_algorithm"] : 0,
401
+ name: typeof live["name"] === "string" ? live["name"] : props.name,
402
+ owner: typeof live["owner"] === "number" ? live["owner"] : undefined
403
+ };
404
+ },
405
+ collection: "document_types",
406
+ describe: (props) => `document_types ${props.name}`,
407
+ endpoint: {
408
+ create: "paperless:POST /api/document_types/",
409
+ update: "paperless:PATCH /api/document_types/{id}/"
410
+ },
411
+ fields: [
412
+ { key: "match", live: (r) => r["match"], prop: (p) => p.match },
413
+ {
414
+ key: "matching_algorithm",
415
+ live: (r) => r["matching_algorithm"],
416
+ prop: (p) => p.matchingAlgorithm
417
+ },
418
+ { key: "is_insensitive", live: (r) => r["is_insensitive"], prop: (p) => p.isInsensitive }
419
+ ]
420
+ });
421
+ var DocumentTypeProvider = () => Provider2.effect(DocumentType, Effect7.succeed(DocumentType.Provider.of(handlers2)));
422
+ // src/paperless/storage-path.ts
423
+ import { Resource as Resource3 } from "alchemy";
424
+ import * as Provider3 from "alchemy/Provider";
425
+ import * as Effect8 from "effect/Effect";
426
+ var StoragePath = Resource3("Paperless.StoragePath", {
427
+ defaultRemovalPolicy: "retain"
428
+ });
429
+ var handlers3 = matchingHandlers({
430
+ attributes: (live, props) => {
431
+ const id = live["id"];
432
+ if (typeof id !== "number")
433
+ return;
434
+ return {
435
+ id,
436
+ isInsensitive: live["is_insensitive"] === true,
437
+ match: typeof live["match"] === "string" ? live["match"] : "",
438
+ matchingAlgorithm: typeof live["matching_algorithm"] === "number" ? live["matching_algorithm"] : 0,
439
+ name: typeof live["name"] === "string" ? live["name"] : props.name,
440
+ owner: typeof live["owner"] === "number" ? live["owner"] : undefined,
441
+ path: typeof live["path"] === "string" ? live["path"] : props.path
442
+ };
443
+ },
444
+ collection: "storage_paths",
445
+ describe: (props) => `storage_paths ${props.name}`,
446
+ endpoint: {
447
+ create: "paperless:POST /api/storage_paths/",
448
+ update: "paperless:PATCH /api/storage_paths/{id}/"
449
+ },
450
+ fields: [
451
+ { key: "path", live: (r) => r["path"], prop: (p) => p.path },
452
+ { key: "match", live: (r) => r["match"], prop: (p) => p.match },
453
+ {
454
+ key: "matching_algorithm",
455
+ live: (r) => r["matching_algorithm"],
456
+ prop: (p) => p.matchingAlgorithm
457
+ },
458
+ { key: "is_insensitive", live: (r) => r["is_insensitive"], prop: (p) => p.isInsensitive }
459
+ ]
460
+ });
461
+ var StoragePathProvider = () => Provider3.effect(StoragePath, Effect8.succeed(StoragePath.Provider.of(handlers3)));
462
+ // src/paperless/custom-field.ts
463
+ import { Resource as Resource4 } from "alchemy";
464
+ import * as Provider4 from "alchemy/Provider";
465
+ import * as Effect9 from "effect/Effect";
466
+ var CustomField = Resource4("Paperless.CustomField", {
467
+ defaultRemovalPolicy: "retain"
468
+ });
469
+ var dataTypeImmutable = (live, props) => typeof live["data_type"] === "string" && live["data_type"] !== props.dataType ? `Paperless.CustomField "${props.name}": data_type is create-only (live: ${live["data_type"]}, ` + `declared: ${props.dataType}). Changing it would delete this field's value on every ` + "document. Create a new custom field instead, and retire this one by hand." : undefined;
470
+ var customFieldHandlers = matchingHandlers({
471
+ attributes: (live, props) => {
472
+ const id = live["id"];
473
+ if (typeof id !== "number")
474
+ return;
475
+ return {
476
+ dataType: typeof live["data_type"] === "string" ? live["data_type"] : props.dataType,
477
+ extraData: live["extra_data"],
478
+ id,
479
+ name: typeof live["name"] === "string" ? live["name"] : props.name
480
+ };
481
+ },
482
+ collection: "custom_fields",
483
+ describe: (props) => `custom_fields ${props.name}`,
484
+ endpoint: {
485
+ create: "paperless:POST /api/custom_fields/",
486
+ update: "paperless:PATCH /api/custom_fields/{id}/"
487
+ },
488
+ createOnly: [{ key: "data_type", live: (r) => r["data_type"], prop: (p) => p.dataType }],
489
+ fields: [{ key: "extra_data", live: (r) => r["extra_data"], prop: (p) => p.extraData }],
490
+ immutable: dataTypeImmutable,
491
+ owned: false
492
+ });
493
+ var CustomFieldProvider = () => Provider4.effect(CustomField, Effect9.succeed(CustomField.Provider.of(customFieldHandlers)));
494
+ // src/paperless/providers.ts
495
+ import * as Layer2 from "effect/Layer";
496
+ var paperlessProviders = (credentials = environmentLayer) => Layer2.mergeAll(TagProvider(), DocumentTypeProvider(), StoragePathProvider(), CustomFieldProvider()).pipe(Layer2.provide(credentials));
497
+ export {
498
+ CustomField,
499
+ CustomFieldProvider,
500
+ DocumentType,
501
+ DocumentTypeProvider,
502
+ PAPERLESS_CONSTRAINTS,
503
+ PAPERLESS_CONSTRAINTS_DIGEST,
504
+ PaperlessCredentials,
505
+ PaperlessCredentialsError,
506
+ PaperlessNotFound,
507
+ PaperlessUnauthorized,
508
+ PaperlessUnavailable,
509
+ PaperlessValidation,
510
+ StoragePath,
511
+ StoragePathProvider,
512
+ Tag,
513
+ TagProvider,
514
+ bodyViolations,
515
+ constraintsFor,
516
+ guardBody,
517
+ environmentLayer as paperlessEnvironmentLayer,
518
+ paperlessProviders,
519
+ refusal,
520
+ violations
521
+ };
522
+
523
+ //# debugId=EE28611444A13F1E64756E2164756E21
524
+ //# sourceMappingURL=index.js.map