@homeflare/alchemy 0.27.4 → 0.29.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.
- package/README.md +2 -0
- package/dist/caddy/admin-address.d.ts +27 -0
- package/dist/caddy/admin-address.d.ts.map +1 -0
- package/dist/caddy/admin-calls.d.ts +25 -28
- package/dist/caddy/admin-calls.d.ts.map +1 -1
- package/dist/caddy/admin.d.ts +28 -38
- package/dist/caddy/admin.d.ts.map +1 -1
- package/dist/caddy/caddy-http-client.d.ts +25 -0
- package/dist/caddy/caddy-http-client.d.ts.map +1 -0
- package/dist/caddy/config-errors.d.ts +24 -0
- package/dist/caddy/config-errors.d.ts.map +1 -0
- package/dist/caddy/config-lifecycle.d.ts +20 -49
- package/dist/caddy/config-lifecycle.d.ts.map +1 -1
- package/dist/caddy/config-reconcile.d.ts +45 -0
- package/dist/caddy/config-reconcile.d.ts.map +1 -0
- package/dist/caddy/config.d.ts +4 -3
- package/dist/caddy/config.d.ts.map +1 -1
- package/dist/caddy/fake-caddy.d.ts +10 -2
- package/dist/caddy/fake-caddy.d.ts.map +1 -1
- package/dist/caddy/index.d.ts +3 -3
- package/dist/caddy/index.d.ts.map +1 -1
- package/dist/caddy/index.js +487 -444
- package/dist/caddy/index.js.map +16 -12
- package/dist/caddy/local-admin.d.ts +20 -34
- package/dist/caddy/local-admin.d.ts.map +1 -1
- package/dist/caddy/providers.d.ts +19 -3
- package/dist/caddy/providers.d.ts.map +1 -1
- package/dist/cloudflare/mesh-node-token.d.ts +1 -1
- package/dist/cloudflare/mesh-node-token.d.ts.map +1 -1
- package/dist/forgejo/branch-protection.d.ts +4 -4
- package/dist/forgejo/org-actions-secrets.d.ts +4 -4
- package/dist/forgejo/org-label.d.ts +4 -4
- package/dist/forgejo/org-team.d.ts +4 -4
- package/dist/forgejo/repo-webhook.d.ts +4 -4
- package/dist/forgejo/repository.d.ts +4 -4
- package/dist/forgejo/team-member.d.ts +4 -4
- package/dist/github/declare-repo-baseline.d.ts +1 -1
- package/dist/github/declare-repo-baseline.d.ts.map +1 -1
- package/dist/github/index.d.ts +9 -5
- package/dist/github/index.d.ts.map +1 -1
- package/dist/github/index.js +161 -25
- package/dist/github/index.js.map +15 -13
- package/dist/github/repo-baseline-data.d.ts +10 -1
- package/dist/github/repo-baseline-data.d.ts.map +1 -1
- package/dist/github/repo-policy.d.ts +1 -1
- package/dist/github/repo-policy.d.ts.map +1 -1
- package/dist/github/repository-ruleset-errors.d.ts +37 -3
- package/dist/github/repository-ruleset-errors.d.ts.map +1 -1
- package/dist/github/repository-ruleset-form.d.ts.map +1 -1
- package/dist/github/repository-ruleset-guards.d.ts +15 -2
- package/dist/github/repository-ruleset-guards.d.ts.map +1 -1
- package/dist/github/repository-ruleset-narrowing-guards.d.ts +39 -0
- package/dist/github/repository-ruleset-narrowing-guards.d.ts.map +1 -0
- package/dist/github/repository-ruleset-octokit.d.ts +49 -0
- package/dist/github/repository-ruleset-octokit.d.ts.map +1 -1
- package/dist/github/repository-ruleset-probe.d.ts +2 -1
- package/dist/github/repository-ruleset-probe.d.ts.map +1 -1
- package/dist/github/repository-ruleset-props.d.ts +91 -0
- package/dist/github/repository-ruleset-props.d.ts.map +1 -0
- package/dist/github/repository-ruleset-providers.d.ts +42 -0
- package/dist/github/repository-ruleset-providers.d.ts.map +1 -0
- package/dist/github/repository-ruleset-reconcile.d.ts.map +1 -1
- package/dist/github/repository-ruleset.d.ts +28 -34
- package/dist/github/repository-ruleset.d.ts.map +1 -1
- package/dist/litellm/fake-litellm.d.ts +27 -16
- package/dist/litellm/fake-litellm.d.ts.map +1 -1
- package/dist/litellm/fake-stack.d.ts +9 -4
- package/dist/litellm/fake-stack.d.ts.map +1 -1
- package/dist/litellm/index.d.ts +7 -5
- package/dist/litellm/index.d.ts.map +1 -1
- package/dist/litellm/index.js +53 -123
- package/dist/litellm/index.js.map +8 -8
- package/dist/litellm/operations.d.ts +60 -0
- package/dist/litellm/operations.d.ts.map +1 -0
- package/dist/litellm/pass-through-endpoint.d.ts +10 -28
- package/dist/litellm/pass-through-endpoint.d.ts.map +1 -1
- package/dist/litellm/pass-through-errors.d.ts +37 -0
- package/dist/litellm/pass-through-errors.d.ts.map +1 -0
- package/dist/litellm/pass-through-form.d.ts +11 -4
- package/dist/litellm/pass-through-form.d.ts.map +1 -1
- package/dist/litellm/providers.d.ts +7 -5
- package/dist/litellm/providers.d.ts.map +1 -1
- package/dist/netbox/prefix.d.ts +4 -4
- package/dist/paperless/custom-field.d.ts +21 -21
- package/dist/paperless/custom-field.d.ts.map +1 -1
- package/dist/paperless/document-type.d.ts +34 -4
- package/dist/paperless/document-type.d.ts.map +1 -1
- package/dist/paperless/fake-paperless.d.ts +36 -19
- package/dist/paperless/fake-paperless.d.ts.map +1 -1
- package/dist/paperless/index.d.ts +7 -2
- package/dist/paperless/index.d.ts.map +1 -1
- package/dist/paperless/index.js +164 -228
- package/dist/paperless/index.js.map +10 -13
- package/dist/paperless/matching-locate.d.ts +42 -20
- package/dist/paperless/matching-locate.d.ts.map +1 -1
- package/dist/paperless/matching-types.d.ts +47 -18
- package/dist/paperless/matching-types.d.ts.map +1 -1
- package/dist/paperless/matching.d.ts +30 -27
- package/dist/paperless/matching.d.ts.map +1 -1
- package/dist/paperless/providers.d.ts +13 -7
- package/dist/paperless/providers.d.ts.map +1 -1
- package/dist/paperless/storage-path.d.ts +34 -4
- package/dist/paperless/storage-path.d.ts.map +1 -1
- package/dist/paperless/tag.d.ts +43 -8
- package/dist/paperless/tag.d.ts.map +1 -1
- package/dist/proxmox/acl.d.ts +30 -23
- package/dist/proxmox/acl.d.ts.map +1 -1
- package/dist/proxmox/distilled-pve.d.ts +89 -0
- package/dist/proxmox/distilled-pve.d.ts.map +1 -0
- package/dist/proxmox/index.js +663 -609
- package/dist/proxmox/index.js.map +12 -11
- package/dist/proxmox/members.d.ts +29 -10
- package/dist/proxmox/members.d.ts.map +1 -1
- package/docs/caddy-admin.md +1 -1
- package/docs/caddy.md +3 -2
- package/docs/credentials.md +16 -0
- package/docs/litellm.md +21 -13
- package/docs/paperless.md +12 -0
- package/docs/peers.md +5 -0
- package/docs/repo-policy.md +16 -1
- package/docs/repository-ruleset.md +53 -13
- package/docs/upstream-conformance.md +126 -10
- package/package.json +9 -2
- package/src/caddy/admin-address.ts +71 -0
- package/src/caddy/admin-calls.ts +48 -100
- package/src/caddy/admin.ts +37 -41
- package/src/caddy/adopt-scope.test.ts +1 -1
- package/src/caddy/caddy-http-client.ts +229 -0
- package/src/caddy/config-adopt-provider.test.ts +139 -0
- package/src/caddy/config-adopt.test.ts +60 -126
- package/src/caddy/config-errors.ts +47 -0
- package/src/caddy/config-lifecycle.test.ts +55 -42
- package/src/caddy/config-lifecycle.ts +72 -162
- package/src/caddy/config-reconcile.ts +138 -0
- package/src/caddy/config.ts +36 -42
- package/src/caddy/fake-caddy.ts +20 -2
- package/src/caddy/index.ts +3 -8
- package/src/caddy/local-admin.test.ts +35 -21
- package/src/caddy/local-admin.ts +60 -167
- package/src/caddy/providers.test.ts +14 -6
- package/src/caddy/providers.ts +20 -4
- package/src/github/declare-repo-baseline.ts +6 -6
- package/src/github/index.ts +10 -4
- package/src/github/repo-baseline-data.test.ts +13 -0
- package/src/github/repo-baseline-data.ts +11 -2
- package/src/github/repo-policy-stack.test.ts +59 -0
- package/src/github/repo-policy.ts +3 -2
- package/src/github/repository-ruleset-errors.ts +51 -4
- package/src/github/repository-ruleset-exact-adopt-k1.test.ts +109 -0
- package/src/github/repository-ruleset-exact-adopt.test.ts +184 -0
- package/src/github/repository-ruleset-form.ts +7 -3
- package/src/github/repository-ruleset-guards.ts +11 -4
- package/src/github/repository-ruleset-narrowing-guards.ts +141 -0
- package/src/github/repository-ruleset-narrowing.test.ts +210 -0
- package/src/github/repository-ruleset-octokit.test.ts +141 -0
- package/src/github/repository-ruleset-octokit.ts +121 -30
- package/src/github/repository-ruleset-probe.ts +2 -1
- package/src/github/repository-ruleset-props.ts +95 -0
- package/src/github/repository-ruleset-providers.ts +46 -0
- package/src/github/repository-ruleset-reconcile.ts +35 -0
- package/src/github/repository-ruleset-wire.test.ts +38 -27
- package/src/github/repository-ruleset-write-refusals.test.ts +6 -26
- package/src/github/repository-ruleset.ts +53 -43
- package/src/litellm/fake-litellm.ts +83 -78
- package/src/litellm/fake-stack.ts +13 -5
- package/src/litellm/index.ts +7 -21
- package/src/litellm/operations.ts +149 -0
- package/src/litellm/pass-through-endpoint-delete-swallow.test.ts +17 -20
- package/src/litellm/pass-through-endpoint-replace.test.ts +3 -4
- package/src/litellm/pass-through-endpoint.test.ts +8 -8
- package/src/litellm/pass-through-endpoint.ts +21 -31
- package/src/litellm/pass-through-errors.ts +37 -0
- package/src/litellm/pass-through-form.ts +20 -10
- package/src/litellm/providers.ts +8 -7
- package/src/paperless/custom-field.test.ts +49 -40
- package/src/paperless/custom-field.ts +57 -33
- package/src/paperless/document-type.test.ts +48 -0
- package/src/paperless/document-type.ts +51 -22
- package/src/paperless/fake-paperless.ts +63 -72
- package/src/paperless/index.ts +7 -13
- package/src/paperless/matching-locate.test.ts +102 -0
- package/src/paperless/matching-locate.ts +80 -86
- package/src/paperless/matching-types.ts +49 -16
- package/src/paperless/matching.ts +77 -90
- package/src/paperless/providers.ts +14 -10
- package/src/paperless/storage-path.test.ts +56 -0
- package/src/paperless/storage-path.ts +53 -24
- package/src/paperless/tag-identity.test.ts +127 -0
- package/src/paperless/tag.test.ts +100 -0
- package/src/paperless/tag.ts +62 -32
- package/src/proxmox/acl.test.ts +145 -0
- package/src/proxmox/acl.ts +128 -150
- package/src/proxmox/distilled-pve.test.ts +231 -0
- package/src/proxmox/distilled-pve.ts +147 -0
- package/src/proxmox/members-timeout.test.ts +161 -0
- package/src/proxmox/members.test.ts +9 -0
- package/src/proxmox/members.ts +36 -2
- package/dist/litellm/client.d.ts +0 -76
- package/dist/litellm/client.d.ts.map +0 -1
- package/dist/litellm/credentials.d.ts +0 -48
- package/dist/litellm/credentials.d.ts.map +0 -1
- package/dist/litellm/generated/pass-through.d.ts +0 -71
- package/dist/litellm/generated/pass-through.d.ts.map +0 -1
- package/dist/paperless/client.d.ts +0 -37
- package/dist/paperless/client.d.ts.map +0 -1
- package/dist/paperless/credentials.d.ts +0 -40
- package/dist/paperless/credentials.d.ts.map +0 -1
- package/dist/paperless/errors.d.ts +0 -49
- package/dist/paperless/errors.d.ts.map +0 -1
- package/src/litellm/client.ts +0 -195
- package/src/litellm/credentials.ts +0 -73
- package/src/litellm/generated/pass-through.ts +0 -73
- package/src/paperless/client.ts +0 -88
- package/src/paperless/credentials.ts +0 -69
- package/src/paperless/errors.ts +0 -62
- package/src/paperless/matching-identity.test.ts +0 -194
- package/src/paperless/matching.test.ts +0 -128
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
* ADDRESSED by the id-keyed update/delete calls this provider uses, so it is refused rather than
|
|
19
19
|
* silently adopted into an id-based lifecycle it cannot actually manage — see docs/litellm.md.
|
|
20
20
|
* ⛔ NO CREDENTIAL IS A PROP (S25). `LITELLM_PROXY_URL`/`LITELLM_PROXY_API_KEY` are read at call
|
|
21
|
-
* time by
|
|
21
|
+
* time by `@distilled.cloud/litellm`'s own `CredentialsFromEnv` (providers.ts) — the same two
|
|
22
|
+
* variable names the retired hand-rolled `credentials.ts` read.
|
|
22
23
|
*/
|
|
23
24
|
import { Resource } from 'alchemy';
|
|
24
25
|
import { Unowned } from 'alchemy/AdoptPolicy';
|
|
@@ -28,18 +29,21 @@ import { createPhysicalName } from 'alchemy/PhysicalName';
|
|
|
28
29
|
import * as Provider from 'alchemy/Provider';
|
|
29
30
|
import type { Stack } from 'alchemy/Stack';
|
|
30
31
|
import type { Stage } from 'alchemy/Stage';
|
|
31
|
-
import * as
|
|
32
|
+
import type * as misc from '@distilled.cloud/litellm/misc';
|
|
32
33
|
import * as Effect from 'effect/Effect';
|
|
33
34
|
import * as Predicate from 'effect/Predicate';
|
|
34
35
|
import {
|
|
35
|
-
type LitellmError,
|
|
36
36
|
type LitellmRequirements,
|
|
37
37
|
createPassThroughEndpoint,
|
|
38
38
|
deletePassThroughEndpoint,
|
|
39
39
|
listPassThroughEndpoints,
|
|
40
40
|
updatePassThroughEndpoint,
|
|
41
|
-
} from './
|
|
42
|
-
import
|
|
41
|
+
} from './operations.ts';
|
|
42
|
+
import {
|
|
43
|
+
LitellmConfigPathConflictError,
|
|
44
|
+
LitellmLiteralSecretHeaderError,
|
|
45
|
+
LitellmUnaddressableRowError,
|
|
46
|
+
} from './pass-through-errors.ts';
|
|
43
47
|
import {
|
|
44
48
|
type PassThroughEndpointAttributes,
|
|
45
49
|
type PassThroughEndpointProps,
|
|
@@ -51,26 +55,12 @@ import {
|
|
|
51
55
|
} from './pass-through-form.ts';
|
|
52
56
|
|
|
53
57
|
export type { PassThroughEndpointAttributes, PassThroughEndpointProps };
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
export class LitellmUnaddressableRowError extends Data.TaggedError('LitellmUnaddressableRowError')<{
|
|
61
|
-
readonly path: string;
|
|
62
|
-
}> {}
|
|
63
|
-
export class LitellmLiteralSecretHeaderError extends Data.TaggedError(
|
|
64
|
-
'LitellmLiteralSecretHeaderError',
|
|
65
|
-
)<{
|
|
66
|
-
readonly headers: readonly string[];
|
|
67
|
-
}> {}
|
|
68
|
-
|
|
69
|
-
export type PassThroughEndpointError =
|
|
70
|
-
| LitellmError
|
|
71
|
-
| LitellmConfigPathConflictError
|
|
72
|
-
| LitellmUnaddressableRowError
|
|
73
|
-
| LitellmLiteralSecretHeaderError;
|
|
58
|
+
export {
|
|
59
|
+
LitellmConfigPathConflictError,
|
|
60
|
+
LitellmLiteralSecretHeaderError,
|
|
61
|
+
LitellmUnaddressableRowError,
|
|
62
|
+
type PassThroughEndpointError,
|
|
63
|
+
} from './pass-through-errors.ts';
|
|
74
64
|
|
|
75
65
|
export interface LiteLLMPassThroughEndpoint extends Resource<
|
|
76
66
|
'LiteLLM.PassThroughEndpoint',
|
|
@@ -88,7 +78,7 @@ export const isLiteLLMPassThroughEndpoint = (value: unknown): value is LiteLLMPa
|
|
|
88
78
|
Predicate.hasProperty(value, 'Type') && value.Type === 'LiteLLM.PassThroughEndpoint';
|
|
89
79
|
|
|
90
80
|
const toAttributes = (
|
|
91
|
-
row: PassThroughGenericEndpoint,
|
|
81
|
+
row: misc.PassThroughGenericEndpoint,
|
|
92
82
|
fallbackId: string,
|
|
93
83
|
): PassThroughEndpointAttributes => ({
|
|
94
84
|
...row,
|
|
@@ -104,12 +94,12 @@ const physicalIdOf = (logicalId: string, instanceId: string) =>
|
|
|
104
94
|
* file header — or a genuine foreign DB row to hand back `Unowned`).
|
|
105
95
|
*/
|
|
106
96
|
const locate = (
|
|
107
|
-
rows: readonly PassThroughGenericEndpoint[],
|
|
97
|
+
rows: readonly misc.PassThroughGenericEndpoint[],
|
|
108
98
|
objectId: string,
|
|
109
99
|
path: string,
|
|
110
100
|
): {
|
|
111
|
-
readonly mine: PassThroughGenericEndpoint | undefined;
|
|
112
|
-
readonly conflict: PassThroughGenericEndpoint | undefined;
|
|
101
|
+
readonly mine: misc.PassThroughGenericEndpoint | undefined;
|
|
102
|
+
readonly conflict: misc.PassThroughGenericEndpoint | undefined;
|
|
113
103
|
} => {
|
|
114
104
|
const mine = rows.find((row) => row.id === objectId);
|
|
115
105
|
const conflict = mine === undefined ? rows.find((row) => row.path === path) : undefined;
|
|
@@ -117,13 +107,13 @@ const locate = (
|
|
|
117
107
|
};
|
|
118
108
|
|
|
119
109
|
/** The typed refusal for a `path` conflict that cannot ever be adopted (config row, or no id). */
|
|
120
|
-
const conflictRefusal = (conflict: PassThroughGenericEndpoint, path: string) =>
|
|
110
|
+
const conflictRefusal = (conflict: misc.PassThroughGenericEndpoint, path: string) =>
|
|
121
111
|
conflict.is_from_config === true
|
|
122
112
|
? new LitellmConfigPathConflictError({ path })
|
|
123
113
|
: new LitellmUnaddressableRowError({ path });
|
|
124
114
|
|
|
125
115
|
/** `read`'s answer for a path conflict: `Unowned` when it is a real, addressable DB row — a typed refusal otherwise. */
|
|
126
|
-
const readConflict = (conflict: PassThroughGenericEndpoint, path: string) =>
|
|
116
|
+
const readConflict = (conflict: misc.PassThroughGenericEndpoint, path: string) =>
|
|
127
117
|
conflict.is_from_config !== true && conflict.id !== undefined && conflict.id !== null
|
|
128
118
|
? Effect.succeed(Unowned(toAttributes(conflict, conflict.id)))
|
|
129
119
|
: Effect.fail(conflictRefusal(conflict, path));
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The typed refusals `LiteLLM.PassThroughEndpoint` raises on top of `@distilled.cloud/litellm`'s
|
|
3
|
+
* own SDK errors — split out of pass-through-endpoint.ts to keep that file under the house's
|
|
4
|
+
* 250-line cap once this migration added the four named SDK error-union re-exports.
|
|
5
|
+
*/
|
|
6
|
+
import type * as misc from '@distilled.cloud/litellm/misc';
|
|
7
|
+
import * as Data from 'effect/Data';
|
|
8
|
+
|
|
9
|
+
export class LitellmConfigPathConflictError extends Data.TaggedError(
|
|
10
|
+
'LitellmConfigPathConflictError',
|
|
11
|
+
)<{
|
|
12
|
+
readonly path: string;
|
|
13
|
+
}> {}
|
|
14
|
+
export class LitellmUnaddressableRowError extends Data.TaggedError('LitellmUnaddressableRowError')<{
|
|
15
|
+
readonly path: string;
|
|
16
|
+
}> {}
|
|
17
|
+
export class LitellmLiteralSecretHeaderError extends Data.TaggedError(
|
|
18
|
+
'LitellmLiteralSecretHeaderError',
|
|
19
|
+
)<{
|
|
20
|
+
readonly headers: readonly string[];
|
|
21
|
+
}> {}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* ⛔ THE FOUR SDK ERROR UNIONS, NAMED, NOT `LitellmOpError` ALONE. `catchTag`'s tag-literal
|
|
25
|
+
* inference needs a concrete union to resolve `"BadRequest"`/`"NotFound"` against (the same
|
|
26
|
+
* reason `netbox/resource.ts` leaves `NotFound` folding to each resource file, not its shared
|
|
27
|
+
* engine) — `operations.ts` already returns each call's own precise per-operation error type, so
|
|
28
|
+
* this is just their union, not a re-widening.
|
|
29
|
+
*/
|
|
30
|
+
export type PassThroughEndpointError =
|
|
31
|
+
| misc.GetPassThroughEndpointsConfigPassThroughEndpointGetError
|
|
32
|
+
| misc.CreatePassThroughEndpointsConfigPassThroughEndpointPostError
|
|
33
|
+
| misc.UpdatePassThroughEndpointsConfigPassThroughEndpointEndpointIdPostError
|
|
34
|
+
| misc.DeletePassThroughEndpointsConfigPassThroughEndpointDeleteError
|
|
35
|
+
| LitellmConfigPathConflictError
|
|
36
|
+
| LitellmUnaddressableRowError
|
|
37
|
+
| LitellmLiteralSecretHeaderError;
|
|
@@ -5,14 +5,17 @@
|
|
|
5
5
|
* ★ EXTRACTED SO IT CAN BE TESTED WITHOUT A SERVER — the same reason netbox keeps `prefix-form.ts`
|
|
6
6
|
* beside `prefix.ts` (S32/testing-and-docs.md).
|
|
7
7
|
*
|
|
8
|
-
* ⛔ PROPS ARE
|
|
9
|
-
* deterministic physical name — pass-through-endpoint.ts) and
|
|
10
|
-
* the vendor sets it, a declaration never does). Deriving
|
|
11
|
-
* hand-typing it means a vendor schema change
|
|
12
|
-
* as a compiler error, not silently ignored
|
|
8
|
+
* ⛔ PROPS ARE `@distilled.cloud/litellm`'s OWN GENERATED TYPE, MINUS THE TWO FIELDS THIS RESOURCE
|
|
9
|
+
* OWNS ITSELF: `id` (a deterministic physical name — pass-through-endpoint.ts) and
|
|
10
|
+
* `is_from_config` (response-only; the vendor sets it, a declaration never does). Deriving
|
|
11
|
+
* `Props` with `Omit<>` rather than hand-typing it means a vendor schema change the SDK's own
|
|
12
|
+
* next regeneration picks up is felt here as a compiler error, not silently ignored — the same
|
|
13
|
+
* reason this used to derive from the kit's own hand-generated `./generated/pass-through.ts`
|
|
14
|
+
* (retired by this migration: the SDK's type is the same shape, generated from the same LiteLLM
|
|
15
|
+
* 1.100.0 OpenAPI document, and keeping a second copy would only drift).
|
|
13
16
|
*/
|
|
14
17
|
import { deepEqual } from 'alchemy/Diff';
|
|
15
|
-
import type * as Generated from '
|
|
18
|
+
import type * as Generated from '@distilled.cloud/litellm/misc';
|
|
16
19
|
|
|
17
20
|
export type PassThroughEndpointProps = Omit<
|
|
18
21
|
Generated.PassThroughGenericEndpoint,
|
|
@@ -71,10 +74,17 @@ export const createBody = (
|
|
|
71
74
|
* `undefined` OR an explicit `null` here would be dropped by the server anyway; sending neither is
|
|
72
75
|
* the same outcome with no request made when nothing changed (`Object.keys(body).length === 0`
|
|
73
76
|
* checked by the caller in pass-through-endpoint.ts).
|
|
77
|
+
*
|
|
78
|
+
* ⛔ `path`/`target` ARE NOT `Partial` HERE, UNLIKE EVERY OTHER FIELD. The SDK's own update
|
|
79
|
+
* request type requires both non-optionally (the vendor's PATCH route still wants the full
|
|
80
|
+
* route/target pair on every call, unlike the other fields' `exclude_none` merge) — typing them
|
|
81
|
+
* as always-present here is what lets `operations.ts`'s `updatePassThroughEndpoint` pass this
|
|
82
|
+
* straight to the SDK without an unsafe cast.
|
|
74
83
|
*/
|
|
75
|
-
export
|
|
76
|
-
|
|
77
|
-
|
|
84
|
+
export type UpdateBody = Partial<Omit<Generated.PassThroughGenericEndpoint, 'path' | 'target'>> &
|
|
85
|
+
Pick<Generated.PassThroughGenericEndpoint, 'path' | 'target'>;
|
|
86
|
+
|
|
87
|
+
export const updateBody = (props: PassThroughEndpointProps): UpdateBody =>
|
|
78
88
|
prune({
|
|
79
89
|
...settled(props),
|
|
80
90
|
guardrails: isUnset(props.guardrails) ? undefined : props.guardrails,
|
|
@@ -82,7 +92,7 @@ export const updateBody = (
|
|
|
82
92
|
path: props.path,
|
|
83
93
|
target: props.target,
|
|
84
94
|
timeout: isUnset(props.timeout) ? undefined : props.timeout,
|
|
85
|
-
}) as
|
|
95
|
+
}) as UpdateBody;
|
|
86
96
|
|
|
87
97
|
/**
|
|
88
98
|
* Whether ANY of the three nullable fields went from set (in `live`) to unset (in `props`) — the
|
package/src/litellm/providers.ts
CHANGED
|
@@ -3,17 +3,18 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Layer.mergeAll(litellmProviders(), …the stack's other providers)
|
|
5
5
|
*
|
|
6
|
-
* ★ THE DEFAULT READS `LITELLM_PROXY_URL`/`LITELLM_PROXY_API_KEY` AT CALL TIME
|
|
7
|
-
*
|
|
8
|
-
* `
|
|
6
|
+
* ★ THE DEFAULT READS `LITELLM_PROXY_URL`/`LITELLM_PROXY_API_KEY` AT CALL TIME, through
|
|
7
|
+
* `@distilled.cloud/litellm`'s own `CredentialsFromEnv` — the same two variable names the
|
|
8
|
+
* retired hand-rolled `credentials.ts` read — never at layer build. Pass another
|
|
9
|
+
* `Layer.Layer<Credentials>` (see the SDK's own `credentials({ apiKey, baseUrl })` in a test) to
|
|
10
|
+
* source them another way.
|
|
9
11
|
* ⚠️ `HttpClient` is NOT provided here. It is ambient in every Alchemy runtime the same way it is
|
|
10
12
|
* for `netboxProviders`-style families in this kit; a stack that already deploys anything over
|
|
11
13
|
* HTTP already has one.
|
|
12
14
|
*/
|
|
15
|
+
import { type Credentials, CredentialsFromEnv } from '@distilled.cloud/litellm/Credentials';
|
|
13
16
|
import * as Layer from 'effect/Layer';
|
|
14
|
-
import { type LitellmCredentials, litellmCredentialsLayer } from './credentials.ts';
|
|
15
17
|
import { LiteLLMPassThroughEndpointProvider } from './pass-through-endpoint.ts';
|
|
16
18
|
|
|
17
|
-
export const litellmProviders = (
|
|
18
|
-
|
|
19
|
-
) => LiteLLMPassThroughEndpointProvider().pipe(Layer.provide(creds));
|
|
19
|
+
export const litellmProviders = (creds: Layer.Layer<Credentials> = CredentialsFromEnv) =>
|
|
20
|
+
LiteLLMPassThroughEndpointProvider().pipe(Layer.provide(creds));
|
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `Paperless.CustomField.dataType` is create-only: a change is refused at plan time, never
|
|
3
3
|
* silently PATCHed and never routed to a replace. See custom-field.ts's header for why — a
|
|
4
|
-
* replace would delete the field's value on every document.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* replace would delete the field's value on every document. The live-lifecycle tests below drive
|
|
5
|
+
* the real `@distilled.cloud/paperless-ngx` wire path (tag.test.ts's own note on why
|
|
6
|
+
* `matchingOperations(spec)`, not `customFieldHandlers`, is what a test can point at a fake).
|
|
7
7
|
*/
|
|
8
8
|
import { describe, expect, test } from 'bun:test';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { dataTypeImmutable, spec } from './custom-field.ts';
|
|
10
|
+
import { fakePaperless, run } from './fake-paperless.ts';
|
|
11
|
+
import { matchingOperations } from './matching.ts';
|
|
12
|
+
|
|
13
|
+
const ops = matchingOperations(spec);
|
|
11
14
|
|
|
12
15
|
describe('dataTypeImmutable, the pure rule', () => {
|
|
13
16
|
test('no refusal when the live value is unchanged', () => {
|
|
14
17
|
expect(
|
|
15
|
-
dataTypeImmutable({ data_type: 'string'
|
|
18
|
+
dataTypeImmutable({ data_type: 'string' }, { dataType: 'string', name: 'x' }),
|
|
16
19
|
).toBeUndefined();
|
|
17
20
|
});
|
|
18
21
|
|
|
19
22
|
test('a refusal when the live value differs, naming both values', () => {
|
|
20
23
|
const message = dataTypeImmutable(
|
|
21
|
-
{ data_type: 'select'
|
|
24
|
+
{ data_type: 'select' },
|
|
22
25
|
{ dataType: 'string', name: 'Status' },
|
|
23
26
|
);
|
|
24
27
|
expect(message).toMatch(/data_type is create-only/);
|
|
@@ -29,46 +32,52 @@ describe('dataTypeImmutable, the pure rule', () => {
|
|
|
29
32
|
|
|
30
33
|
describe('the live lifecycle refuses a data_type change', () => {
|
|
31
34
|
test('reconcile dies rather than PATCHing a changed data_type', async () => {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
const fake = fakePaperless();
|
|
36
|
+
const created = await run(
|
|
37
|
+
ops.reconcile({ dataType: 'select', name: 'Status' }, undefined),
|
|
38
|
+
fake.fetch,
|
|
39
|
+
);
|
|
40
|
+
const before = fake.seen.filter((s) => s.method !== 'GET').length;
|
|
35
41
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
42
|
+
await expect(
|
|
43
|
+
run(ops.reconcile({ dataType: 'string', name: 'Status' }, created), fake.fetch),
|
|
44
|
+
).rejects.toThrow(/data_type is create-only/);
|
|
39
45
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
});
|
|
46
|
+
// ⛔ NOTHING WAS WRITTEN — the refusal fires before the PATCH, not after a failed one.
|
|
47
|
+
expect(fake.seen.filter((s) => s.method !== 'GET').length).toBe(before);
|
|
43
48
|
});
|
|
44
49
|
|
|
45
50
|
test('a create followed by a declaration of the SAME data_type is a normal no-drift reconcile', async () => {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
const fake = fakePaperless();
|
|
52
|
+
const created = await run(
|
|
53
|
+
ops.reconcile({ dataType: 'select', name: 'Status' }, undefined),
|
|
54
|
+
fake.fetch,
|
|
55
|
+
);
|
|
56
|
+
const after = await run(
|
|
57
|
+
ops.reconcile({ dataType: 'select', name: 'Status' }, created),
|
|
58
|
+
fake.fetch,
|
|
59
|
+
);
|
|
60
|
+
expect(after).toMatchObject({ dataType: 'select', name: 'Status' });
|
|
53
61
|
});
|
|
54
62
|
|
|
55
63
|
test('extraData still updates through a normal PATCH', async () => {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
64
|
+
const fake = fakePaperless();
|
|
65
|
+
const created = await run(
|
|
66
|
+
ops.reconcile(
|
|
67
|
+
{ dataType: 'select', extraData: { select_options: ['a'] }, name: 'Status' },
|
|
68
|
+
undefined,
|
|
69
|
+
),
|
|
70
|
+
fake.fetch,
|
|
71
|
+
);
|
|
72
|
+
await run(
|
|
73
|
+
ops.reconcile(
|
|
74
|
+
{ dataType: 'select', extraData: { select_options: ['a', 'b'] }, name: 'Status' },
|
|
75
|
+
created,
|
|
76
|
+
),
|
|
77
|
+
fake.fetch,
|
|
78
|
+
);
|
|
79
|
+
const patch = fake.seen.find((s) => s.method === 'PATCH');
|
|
80
|
+
expect(patch).toBeDefined();
|
|
81
|
+
expect(JSON.parse(patch?.body ?? '{}')).toEqual({ extra_data: { select_options: ['a', 'b'] } });
|
|
73
82
|
});
|
|
74
83
|
});
|
|
@@ -7,28 +7,32 @@
|
|
|
7
7
|
* this refusal generically; `dataType` is simply never listed in `fields`, so nothing in
|
|
8
8
|
* `matching.ts` ever builds a PATCH body that includes it.
|
|
9
9
|
*
|
|
10
|
-
* ⛔ NO `owner` FIELD. Unlike `Tag`/`DocumentType`/`StoragePath`,
|
|
11
|
-
* Paperless-ngx's `OwnedObjectSerializer` — its wire body is
|
|
12
|
-
* extra_data}` (measured against the v3.1.1 schema
|
|
13
|
-
*
|
|
10
|
+
* ⛔ NO `owner` FIELD, AND NO `ownerOf` SPEC ENTRY. Unlike `Tag`/`DocumentType`/`StoragePath`,
|
|
11
|
+
* `CustomField` does not extend Paperless-ngx's `OwnedObjectSerializer` — its wire body is
|
|
12
|
+
* exactly `{name, data_type, extra_data}` (measured against the v3.1.1 schema, and confirmed
|
|
13
|
+
* again in `@distilled.cloud/paperless-ngx`'s generated `custom_fields.ts`, which has no
|
|
14
|
+
* `owner` field either). `owned: false` stops `matching.ts` sending or comparing one.
|
|
14
15
|
*
|
|
15
16
|
* ⚠️ `CustomField.select_options` (the vendor's own rule for a `select` field's option list) is
|
|
16
|
-
* not in the served schema at all, so it is recorded here and NOT enforced (groundTruth).
|
|
17
|
+
* not in the served schema at all, so it is recorded here and NOT enforced (groundTruth). See
|
|
18
|
+
* `tag.ts`'s header for the 2026-09-24 transport migration this file follows.
|
|
17
19
|
*/
|
|
18
20
|
import { Resource } from 'alchemy';
|
|
19
21
|
import * as Provider from 'alchemy/Provider';
|
|
22
|
+
import * as customFields from '@distilled.cloud/paperless-ngx/custom_fields';
|
|
20
23
|
import * as Effect from 'effect/Effect';
|
|
21
24
|
import type { PaperlessCustomFieldRequest } from './generated/types/custom-fields.ts';
|
|
22
|
-
import
|
|
23
|
-
|
|
25
|
+
import {
|
|
26
|
+
LOCATE_PAGE,
|
|
27
|
+
type MatchingRequirements,
|
|
28
|
+
type MatchingSpec,
|
|
29
|
+
matchingHandlers,
|
|
30
|
+
} from './matching.ts';
|
|
24
31
|
|
|
25
32
|
export interface CustomFieldProps {
|
|
26
|
-
/** ⚠️ maxLength 128 (generated constraint table).
|
|
33
|
+
/** ⚠️ maxLength 128 (generated constraint table). CustomField's name is globally unique (models.py:1169), with no owner to further scope it. */
|
|
27
34
|
name: string;
|
|
28
|
-
/**
|
|
29
|
-
* ⛔ CREATE-ONLY — see this file's header. `select`'s own option list
|
|
30
|
-
* (`select_options`) is not in the vendor schema and is unenforced here.
|
|
31
|
-
*/
|
|
35
|
+
/** ⛔ CREATE-ONLY — see this file's header. `select`'s own option list (`select_options`) is not in the vendor schema and is unenforced here. */
|
|
32
36
|
dataType: PaperlessCustomFieldRequest['data_type'];
|
|
33
37
|
/** Arbitrary JSON the vendor attaches no schema to (e.g. a `select` field's option list). */
|
|
34
38
|
extraData?: unknown;
|
|
@@ -54,42 +58,62 @@ export const CustomField = Resource<CustomField>('Paperless.CustomField', {
|
|
|
54
58
|
});
|
|
55
59
|
|
|
56
60
|
/**
|
|
57
|
-
* ⛔ EXTRACTED SO IT CAN BE TESTED WITHOUT A SERVER (custom-field.test.ts)
|
|
58
|
-
*
|
|
61
|
+
* ⛔ EXTRACTED SO IT CAN BE TESTED WITHOUT A SERVER (custom-field.test.ts). `Pick<…, 'data_type'>`
|
|
62
|
+
* rather than the full `CustomField`, so a test fixture need not spell out `name`/`document_count`.
|
|
59
63
|
*/
|
|
60
64
|
export const dataTypeImmutable = (
|
|
61
|
-
live:
|
|
65
|
+
live: Pick<customFields.CustomField, 'data_type'>,
|
|
62
66
|
props: CustomFieldProps,
|
|
63
67
|
): string | undefined =>
|
|
64
|
-
|
|
65
|
-
? `Paperless.CustomField "${props.name}": data_type is create-only (live: ${live
|
|
68
|
+
live.data_type !== props.dataType
|
|
69
|
+
? `Paperless.CustomField "${props.name}": data_type is create-only (live: ${live.data_type}, ` +
|
|
66
70
|
`declared: ${props.dataType}). Changing it would delete this field's value on every ` +
|
|
67
71
|
'document. Create a new custom field instead, and retire this one by hand.'
|
|
68
72
|
: undefined;
|
|
69
73
|
|
|
70
|
-
/**
|
|
71
|
-
export const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
74
|
+
/** ★ Exported for direct testing — see tag.ts's own note on this seam. */
|
|
75
|
+
export const spec: MatchingSpec<
|
|
76
|
+
CustomFieldProps,
|
|
77
|
+
customFields.CustomField,
|
|
78
|
+
CustomFieldAttributes,
|
|
79
|
+
| customFields.CreateCustomFieldError
|
|
80
|
+
| customFields.GetCustomFieldError
|
|
81
|
+
| customFields.ListCustomFieldsError
|
|
82
|
+
| customFields.UpdateCustomFieldsPartialError
|
|
83
|
+
| customFields.CustomFieldsDestroyError
|
|
84
|
+
> = {
|
|
85
|
+
attributes: (live, props) => ({
|
|
86
|
+
dataType: live.data_type ?? props.dataType,
|
|
87
|
+
extraData: live.extra_data,
|
|
88
|
+
id: live.id,
|
|
89
|
+
name: live.name,
|
|
90
|
+
}),
|
|
91
|
+
create: (body) =>
|
|
92
|
+
customFields.createCustomField(body as unknown as customFields.CreateCustomFieldRequest),
|
|
93
|
+
createOnly: [{ key: 'data_type', live: (r) => r.data_type, prop: (p) => p.dataType }],
|
|
83
94
|
describe: (props) => `custom_fields ${props.name}`,
|
|
95
|
+
destroy: (id) => customFields.customFieldsDestroy({ id }),
|
|
84
96
|
endpoint: {
|
|
85
97
|
create: 'paperless:POST /api/custom_fields/',
|
|
86
98
|
update: 'paperless:PATCH /api/custom_fields/{id}/',
|
|
87
99
|
},
|
|
88
|
-
|
|
89
|
-
|
|
100
|
+
fields: [{ key: 'extra_data', live: (r) => r.extra_data, prop: (p) => p.extraData }],
|
|
101
|
+
getById: (id) =>
|
|
102
|
+
customFields
|
|
103
|
+
.getCustomField({ id })
|
|
104
|
+
.pipe(Effect.catchTag('NotFound', () => Effect.succeed(undefined))),
|
|
90
105
|
immutable: dataTypeImmutable,
|
|
106
|
+
list: (props) =>
|
|
107
|
+
customFields.listCustomFields({ name__iexact: props.name, page_size: LOCATE_PAGE }),
|
|
91
108
|
owned: false,
|
|
92
|
-
|
|
109
|
+
update: (id, body) =>
|
|
110
|
+
customFields.updateCustomFieldsPartial({
|
|
111
|
+
...(body as unknown as Omit<customFields.UpdateCustomFieldsPartialRequest, 'id'>),
|
|
112
|
+
id,
|
|
113
|
+
}),
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export const customFieldHandlers = matchingHandlers(spec);
|
|
93
117
|
|
|
94
118
|
export const CustomFieldProvider = () =>
|
|
95
119
|
Provider.effect(CustomField, Effect.succeed(CustomField.Provider.of(customFieldHandlers)));
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `DocumentType`'s exported production `spec` against a fake Paperless-ngx — proves this file's
|
|
3
|
+
* own `@distilled.cloud/paperless-ngx/document_types` wiring (right operations, right field
|
|
4
|
+
* names) round-trips for real. The shared `matching.ts` engine and the PR 163 identity switch are
|
|
5
|
+
* proven generically by `tag.test.ts`/`tag-identity.test.ts`/`matching-locate.test.ts`; this file
|
|
6
|
+
* does not repeat that coverage.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, expect, test } from 'bun:test';
|
|
9
|
+
import { spec } from './document-type.ts';
|
|
10
|
+
import { fakePaperless, run } from './fake-paperless.ts';
|
|
11
|
+
import { matchingOperations } from './matching.ts';
|
|
12
|
+
|
|
13
|
+
const ops = matchingOperations(spec);
|
|
14
|
+
|
|
15
|
+
test('create sends owner: null when undeclared, and the constraint-checked body', async () => {
|
|
16
|
+
const fake = fakePaperless();
|
|
17
|
+
await run(ops.reconcile({ name: 'Invoices' }, undefined), fake.fetch);
|
|
18
|
+
const post = fake.seen.find((s) => s.method === 'POST');
|
|
19
|
+
expect(post).toBeDefined();
|
|
20
|
+
expect(JSON.parse(post?.body ?? '{}')).toMatchObject({ name: 'Invoices', owner: null });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('a full create → read → patch → delete round trip', () => {
|
|
24
|
+
test('the real distilled wire path carries every field through', async () => {
|
|
25
|
+
const fake = fakePaperless();
|
|
26
|
+
const created = await run(
|
|
27
|
+
ops.reconcile({ isInsensitive: true, match: 'invoice', name: 'Invoices' }, undefined),
|
|
28
|
+
fake.fetch,
|
|
29
|
+
);
|
|
30
|
+
expect(created).toMatchObject({ isInsensitive: true, match: 'invoice', name: 'Invoices' });
|
|
31
|
+
|
|
32
|
+
const after = await run(
|
|
33
|
+
ops.reconcile({ isInsensitive: true, match: 'receipt', name: 'Invoices' }, created),
|
|
34
|
+
fake.fetch,
|
|
35
|
+
);
|
|
36
|
+
const patch = fake.seen.find((s) => s.method === 'PATCH');
|
|
37
|
+
expect(JSON.parse(patch?.body ?? '{}')).toEqual({ match: 'receipt' });
|
|
38
|
+
expect(after?.match).toBe('receipt');
|
|
39
|
+
|
|
40
|
+
await run(ops.destroy({ name: 'Invoices' }, after), fake.fetch);
|
|
41
|
+
expect(fake.rows('document_types').length).toBe(0);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('a 404 on read folds to absent', async () => {
|
|
46
|
+
const fake = fakePaperless();
|
|
47
|
+
expect(await run(ops.read({ name: 'Nothing here' }), fake.fetch)).toBeUndefined();
|
|
48
|
+
});
|
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `Paperless.DocumentType` — a Paperless-ngx document type, create-or-update and never deleted.
|
|
3
3
|
* Same shape as `tag.ts` minus `color`/`isInboxTag`/`parent` — see that file's header for why
|
|
4
|
-
* `matchingAlgorithm`'s type comes off the generated request type rather than being retyped
|
|
4
|
+
* `matchingAlgorithm`'s type comes off the generated request type rather than being retyped, and
|
|
5
|
+
* for the 2026-09-24 transport migration this file follows exactly.
|
|
5
6
|
*/
|
|
6
7
|
import { Resource } from 'alchemy';
|
|
7
8
|
import * as Provider from 'alchemy/Provider';
|
|
9
|
+
import * as documentTypes from '@distilled.cloud/paperless-ngx/document_types';
|
|
8
10
|
import * as Effect from 'effect/Effect';
|
|
9
11
|
import type { PaperlessDocumentTypeRequest } from './generated/types/document-types.ts';
|
|
10
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
LOCATE_PAGE,
|
|
14
|
+
type MatchingRequirements,
|
|
15
|
+
type MatchingSpec,
|
|
16
|
+
matchingHandlers,
|
|
17
|
+
} from './matching.ts';
|
|
11
18
|
|
|
12
|
-
export interface DocumentTypeProps
|
|
19
|
+
export interface DocumentTypeProps {
|
|
13
20
|
/** ⚠️ maxLength 128 (generated constraint table). The locate key. */
|
|
14
21
|
name: string;
|
|
22
|
+
owner?: number;
|
|
15
23
|
/** ⚠️ maxLength 256. */
|
|
16
24
|
match?: string;
|
|
17
25
|
matchingAlgorithm?: PaperlessDocumentTypeRequest['matching_algorithm'];
|
|
@@ -39,36 +47,57 @@ export const DocumentType = Resource<DocumentType>('Paperless.DocumentType', {
|
|
|
39
47
|
defaultRemovalPolicy: 'retain',
|
|
40
48
|
});
|
|
41
49
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
50
|
+
/** ★ Exported for direct testing — see tag.ts's own note on this seam. */
|
|
51
|
+
export const spec: MatchingSpec<
|
|
52
|
+
DocumentTypeProps,
|
|
53
|
+
documentTypes.DocumentType,
|
|
54
|
+
DocumentTypeAttributes,
|
|
55
|
+
| documentTypes.CreateDocumentTypeError
|
|
56
|
+
| documentTypes.GetDocumentTypeError
|
|
57
|
+
| documentTypes.ListDocumentTypesError
|
|
58
|
+
| documentTypes.UpdateDocumentTypesPartialError
|
|
59
|
+
| documentTypes.DocumentTypesDestroyError
|
|
60
|
+
> = {
|
|
61
|
+
attributes: (live, props) => ({
|
|
62
|
+
id: live.id,
|
|
63
|
+
isInsensitive: live.is_insensitive === true,
|
|
64
|
+
match: live.match ?? '',
|
|
65
|
+
matchingAlgorithm: live.matching_algorithm ?? 0,
|
|
66
|
+
name: live.name,
|
|
67
|
+
owner: live.owner ?? undefined,
|
|
68
|
+
}),
|
|
69
|
+
create: (body) =>
|
|
70
|
+
documentTypes.createDocumentType(body as unknown as documentTypes.CreateDocumentTypeRequest),
|
|
57
71
|
describe: (props) => `document_types ${props.name}`,
|
|
72
|
+
destroy: (id) => documentTypes.documentTypesDestroy({ id }),
|
|
58
73
|
endpoint: {
|
|
59
74
|
create: 'paperless:POST /api/document_types/',
|
|
60
75
|
update: 'paperless:PATCH /api/document_types/{id}/',
|
|
61
76
|
},
|
|
62
77
|
fields: [
|
|
63
|
-
{ key: 'match', live: (r) => r
|
|
78
|
+
{ key: 'match', live: (r) => r.match, prop: (p) => p.match },
|
|
64
79
|
{
|
|
65
80
|
key: 'matching_algorithm',
|
|
66
|
-
live: (r) => r
|
|
81
|
+
live: (r) => r.matching_algorithm,
|
|
67
82
|
prop: (p) => p.matchingAlgorithm,
|
|
68
83
|
},
|
|
69
|
-
{ key: 'is_insensitive', live: (r) => r
|
|
84
|
+
{ key: 'is_insensitive', live: (r) => r.is_insensitive, prop: (p) => p.isInsensitive },
|
|
70
85
|
],
|
|
71
|
-
|
|
86
|
+
getById: (id) =>
|
|
87
|
+
documentTypes
|
|
88
|
+
.getDocumentType({ id })
|
|
89
|
+
.pipe(Effect.catchTag('NotFound', () => Effect.succeed(undefined))),
|
|
90
|
+
list: (props) =>
|
|
91
|
+
documentTypes.listDocumentTypes({ name__iexact: props.name, page_size: LOCATE_PAGE }),
|
|
92
|
+
ownerOf: (live) => live.owner ?? undefined,
|
|
93
|
+
update: (id, body) =>
|
|
94
|
+
documentTypes.updateDocumentTypesPartial({
|
|
95
|
+
...(body as unknown as Omit<documentTypes.UpdateDocumentTypesPartialRequest, 'id'>),
|
|
96
|
+
id,
|
|
97
|
+
}),
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const handlers = matchingHandlers(spec);
|
|
72
101
|
|
|
73
102
|
export const DocumentTypeProvider = () =>
|
|
74
103
|
Provider.effect(DocumentType, Effect.succeed(DocumentType.Provider.of(handlers)));
|