@homeflare/alchemy 0.59.0 → 0.60.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 +13 -11
- package/dist/caddy/config.d.ts +4 -6
- package/dist/caddy/config.d.ts.map +1 -1
- package/dist/caddy/index.js +61 -128
- package/dist/caddy/index.js.map +4 -6
- package/dist/scope/duplicate-id.d.ts +16 -16
- package/dist/scope/duplicate-id.d.ts.map +1 -1
- package/dist/scope/index.d.ts +16 -14
- package/dist/scope/index.d.ts.map +1 -1
- package/dist/scope/index.js +52 -57
- package/dist/scope/index.js.map +5 -5
- package/dist/scope/patch-check.d.ts +11 -0
- package/dist/scope/patch-check.d.ts.map +1 -0
- package/docs/duplicate-resource-id.md +90 -60
- package/docs/provider-standard.md +9 -9
- package/package.json +2 -1
- package/patches/alchemy@2.0.0-beta.81.patch +147 -0
- package/src/caddy/config.ts +7 -12
- package/src/scope/duplicate-id.test.ts +128 -69
- package/src/scope/duplicate-id.ts +39 -97
- package/src/scope/index.ts +16 -14
- package/src/scope/patch-check.test.ts +114 -0
- package/src/scope/patch-check.ts +118 -0
- package/dist/scope/guard.d.ts +0 -28
- package/dist/scope/guard.d.ts.map +0 -1
- package/src/scope/guard.test.ts +0 -128
- package/src/scope/guard.ts +0 -49
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A consuming stack's own `check` calls `checkAlchemyPatch` to assert it actually carries the
|
|
3
|
+
* duplicate-resource-id fix — not just that SOME version of `alchemy` is installed, but that
|
|
4
|
+
* THIS install carries the patch, for the alchemy version it was cut against.
|
|
5
|
+
*
|
|
6
|
+
* ★ WHY A BUMP CAN OTHERWISE DROP IT SILENTLY. `patchedDependencies` keys a patch to an EXACT
|
|
7
|
+
* version string (`alchemy@2.0.0-beta.81`). `bun add alchemy@2.0.0-beta.82` leaves that key
|
|
8
|
+
* pointing at a version nothing installs anymore — `bun install` neither errors nor warns,
|
|
9
|
+
* it just stops applying the patch, and the stack is back to the original silent collision
|
|
10
|
+
* with every other gate still green. This check is what turns that bump into a failing
|
|
11
|
+
* `check`, the same way `tests/dist.test.ts` turns a build regression into a failing test
|
|
12
|
+
* instead of a surprise three repos downstream (this package's own AGENTS.md, "the smoke
|
|
13
|
+
* test is not optional").
|
|
14
|
+
* ★ A STATIC CHECK, mirroring `@homeflare/config/check`'s `checkCopy`/`checkOxfmt` — not a
|
|
15
|
+
* functional probe that spins up a `Stack`/`Resource`/`Effect` harness on every `check` run.
|
|
16
|
+
* The functional proof that the patch's LOGIC works lives in this package's own
|
|
17
|
+
* `duplicate-id.test.ts`, against the exact pinned version; a consumer's `check` only needs
|
|
18
|
+
* to know whether ITS install carries that same patch, applied — three file reads, no
|
|
19
|
+
* `effect` runtime pulled into every repo's check step.
|
|
20
|
+
* ⛔ IT REPORTS, IT DOES NOT REPAIR, same as `checkProject` in `@homeflare/config/check` — this
|
|
21
|
+
* never edits `package.json` or re-runs `bun install` for the caller.
|
|
22
|
+
*/
|
|
23
|
+
import { PATCHED_ALCHEMY_VERSION } from './duplicate-id.ts';
|
|
24
|
+
|
|
25
|
+
/** One thing wrong with a consuming stack's alchemy patch, in the imperative. */
|
|
26
|
+
export type PatchProblem = string;
|
|
27
|
+
|
|
28
|
+
const patchKey = (version: string) => `alchemy@${version}`;
|
|
29
|
+
|
|
30
|
+
/** Where THIS package's own shipped copy of the patch lives, however it was installed. */
|
|
31
|
+
const shippedPatchPath = (version: string): string =>
|
|
32
|
+
new URL(`../../patches/${patchKey(version)}.patch`, import.meta.url).pathname;
|
|
33
|
+
|
|
34
|
+
type PackageJson = {
|
|
35
|
+
readonly patchedDependencies?: Readonly<Record<string, string>>;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param projectDir The consuming stack's OWN repo root (where its `package.json` and
|
|
40
|
+
* `patches/` directory live) — never this kit's own.
|
|
41
|
+
* @param alchemyVersion The version to check for — defaults to the version this package's
|
|
42
|
+
* own shipped patch targets ({@link PATCHED_ALCHEMY_VERSION}); a caller only overrides it to
|
|
43
|
+
* test a different pin.
|
|
44
|
+
*/
|
|
45
|
+
export const checkAlchemyPatch = async (
|
|
46
|
+
projectDir: string,
|
|
47
|
+
alchemyVersion: string = PATCHED_ALCHEMY_VERSION,
|
|
48
|
+
): Promise<readonly PatchProblem[]> => {
|
|
49
|
+
const problems: PatchProblem[] = [];
|
|
50
|
+
const key = patchKey(alchemyVersion);
|
|
51
|
+
|
|
52
|
+
const installedPkg = Bun.file(`${projectDir}/node_modules/alchemy/package.json`);
|
|
53
|
+
if (!(await installedPkg.exists())) {
|
|
54
|
+
problems.push('node_modules/alchemy: not installed — nothing to check the patch against');
|
|
55
|
+
return problems;
|
|
56
|
+
}
|
|
57
|
+
const installedVersion = ((await installedPkg.json()) as { version?: string }).version;
|
|
58
|
+
if (installedVersion !== alchemyVersion) {
|
|
59
|
+
problems.push(
|
|
60
|
+
`node_modules/alchemy is ${installedVersion ?? 'unknown'}, but the duplicate-resource-id ` +
|
|
61
|
+
`patch targets ${alchemyVersion} — bump the patch (@homeflare/alchemy's own ` +
|
|
62
|
+
'patches/) before bumping alchemy, or this install has no protection at all',
|
|
63
|
+
);
|
|
64
|
+
return problems;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const pkgFile = Bun.file(`${projectDir}/package.json`);
|
|
68
|
+
if (!(await pkgFile.exists())) {
|
|
69
|
+
problems.push('package.json: missing');
|
|
70
|
+
return problems;
|
|
71
|
+
}
|
|
72
|
+
const pkg = (await pkgFile.json()) as PackageJson;
|
|
73
|
+
const declaredPath = pkg.patchedDependencies?.[key];
|
|
74
|
+
if (declaredPath === undefined) {
|
|
75
|
+
problems.push(
|
|
76
|
+
`package.json: patchedDependencies has no entry for "${key}" — this stack has no ` +
|
|
77
|
+
'duplicate-resource-id protection at all; see @homeflare/alchemy/docs/duplicate-resource-id.md',
|
|
78
|
+
);
|
|
79
|
+
return problems;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const theirs = Bun.file(`${projectDir}/${declaredPath}`);
|
|
83
|
+
if (!(await theirs.exists())) {
|
|
84
|
+
problems.push(`package.json declares "${key}" at ${declaredPath}, but that file is missing`);
|
|
85
|
+
return problems;
|
|
86
|
+
}
|
|
87
|
+
const ours = Bun.file(shippedPatchPath(alchemyVersion));
|
|
88
|
+
if (!(await ours.exists())) {
|
|
89
|
+
// The kit's own tarball is missing its patch — a packaging bug, not the consumer's.
|
|
90
|
+
problems.push(`@homeflare/alchemy ships no patches/${key}.patch to compare against`);
|
|
91
|
+
return problems;
|
|
92
|
+
}
|
|
93
|
+
if ((await theirs.text()) !== (await ours.text())) {
|
|
94
|
+
problems.push(
|
|
95
|
+
`${declaredPath}: differs from @homeflare/alchemy's shipped patch — re-copy it, ` +
|
|
96
|
+
'or say why it differs',
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Declared and byte-identical is not yet APPLIED: a hand-edited patchedDependencies entry,
|
|
101
|
+
// or an install under a package manager that does not support bun's patch format, leaves
|
|
102
|
+
// node_modules unpatched with nothing above noticing. One cheap textual proof it landed.
|
|
103
|
+
const resourceSrc = Bun.file(`${projectDir}/node_modules/alchemy/src/Resource.ts`);
|
|
104
|
+
const resourceLib = Bun.file(`${projectDir}/node_modules/alchemy/lib/Resource.js`);
|
|
105
|
+
const marker = 'DuplicateResourceIdError';
|
|
106
|
+
const [srcText, libText] = await Promise.all([
|
|
107
|
+
resourceSrc.exists().then((ok) => (ok ? resourceSrc.text() : '')),
|
|
108
|
+
resourceLib.exists().then((ok) => (ok ? resourceLib.text() : '')),
|
|
109
|
+
]);
|
|
110
|
+
if (!srcText.includes(marker) && !libText.includes(marker)) {
|
|
111
|
+
problems.push(
|
|
112
|
+
'node_modules/alchemy/{src/Resource.ts,lib/Resource.js}: neither contains the patch — ' +
|
|
113
|
+
'declared in package.json but not applied; re-run `bun install`',
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return problems;
|
|
118
|
+
};
|
package/dist/scope/guard.d.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `guardDuplicateIds` — wrap a Resource class so every `("id", props)` call it makes first
|
|
3
|
-
* claims that id through `duplicate-id.ts`'s per-stack registry, naming both resource types if
|
|
4
|
-
* a second, different-typed resource already claimed it. See duplicate-id.ts's header for why
|
|
5
|
-
* the check has to live HERE (at the moment of registration) rather than after the stack body
|
|
6
|
-
* has finished running.
|
|
7
|
-
*
|
|
8
|
-
* Usage, in a resource's own file, right where it's exported today:
|
|
9
|
-
*
|
|
10
|
-
* ```ts
|
|
11
|
-
* export const CaddyConfig = guardDuplicateIds(Resource<CaddyConfig>('Caddy.Config', { … }));
|
|
12
|
-
* ```
|
|
13
|
-
*
|
|
14
|
-
* ★ A `Proxy` around the REAL class, not a reimplementation. `Resource()`'s return value is a
|
|
15
|
-
* callable object wearing an `Effectable.Prototype` (`Effect.isEffect(MyResource) === true`,
|
|
16
|
-
* for the bare-tag platform-resource pattern) plus `.ref`, `.Type`, `.Provider`, `.Self`,
|
|
17
|
-
* `.Aliases` — reconstructing that shape risks silently dropping one of them. A `Proxy` whose
|
|
18
|
-
* `get` trap is the default (`Reflect.get`) forwards every property read untouched; only the
|
|
19
|
-
* `apply` trap — the `MyResource("id", props)` call form — is new behaviour.
|
|
20
|
-
* ⚠️ THE "REGISTER METHODS" OVERLOAD IS DELIBERATELY LEFT ALONE. `ResourceClass` is also callable
|
|
21
|
-
* with a single object argument to attach methods (`Resource()`'s own `typeof args[0] ===
|
|
22
|
-
* 'object' ? Object.assign(...) : constructor(...)` branch) — that call mutates and returns the
|
|
23
|
-
* SAME class, carries no logical id, and has nothing to claim. Only a string first argument
|
|
24
|
-
* (the `(id, props)` form) goes through `claimResourceId`.
|
|
25
|
-
*/
|
|
26
|
-
import type { ResourceClass, ResourceLike } from 'alchemy/Resource';
|
|
27
|
-
export declare const guardDuplicateIds: <R extends ResourceLike>(resourceClass: ResourceClass<R>) => ResourceClass<R>;
|
|
28
|
-
//# sourceMappingURL=guard.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../../src/scope/guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAIpE,eAAO,MAAM,iBAAiB,GAAI,CAAC,SAAS,YAAY,iBACvC,aAAa,CAAC,CAAC,CAAC,KAC9B,aAAa,CAAC,CAAC,CAiBM,CAAC"}
|
package/src/scope/guard.test.ts
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `guardDuplicateIds` driven through a REAL `Alchemy.Stack(...)` body with real `Resource()`
|
|
3
|
-
* classes — the same shape `litellm/fake-stack.ts` and `github/repo-apply-order-fake-stack.ts`
|
|
4
|
-
* use for the same reason their own headers give: a check built only against this module's own
|
|
5
|
-
* logic (duplicate-id.test.ts) proves the registry works, not that wiring it onto a resource
|
|
6
|
-
* class via a `Proxy` actually intercepts the call Alchemy itself makes when a stack body
|
|
7
|
-
* `yield*`s it.
|
|
8
|
-
*
|
|
9
|
-
* ⚠️ `dup-experiment.ts` (this PR's own throwaway script, not checked in) first measured the
|
|
10
|
-
* UNGUARDED failure mode this guards against: two different `Resource()` classes sharing a
|
|
11
|
-
* logical id compile a stack whose only entry at that fqn is the FIRST type, with no trace the
|
|
12
|
-
* second one ever ran. These tests are the regression guard for exactly that, once wrapped.
|
|
13
|
-
*/
|
|
14
|
-
import { describe, expect, test } from 'bun:test';
|
|
15
|
-
import * as Alchemy from 'alchemy';
|
|
16
|
-
import { push as pushNamespace } from 'alchemy/Namespace';
|
|
17
|
-
import type { Resource, ResourceLike } from 'alchemy/Resource';
|
|
18
|
-
import * as Effect from 'effect/Effect';
|
|
19
|
-
import * as Layer from 'effect/Layer';
|
|
20
|
-
import { guardDuplicateIds } from './guard.ts';
|
|
21
|
-
|
|
22
|
-
interface Props {
|
|
23
|
-
readonly value: number;
|
|
24
|
-
}
|
|
25
|
-
interface TestResource extends Resource<string, Props, object> {}
|
|
26
|
-
|
|
27
|
-
/** A plain `Resource()` class, wrapped, for a given vendor-style type string. */
|
|
28
|
-
const guarded = (type: string) => guardDuplicateIds(Alchemy.Resource<TestResource>(type));
|
|
29
|
-
|
|
30
|
-
type StackBody = Effect.Effect<unknown, unknown, unknown>;
|
|
31
|
-
|
|
32
|
-
/** Same cast-at-the-typed-boundary shape as `litellm/fake-stack.ts`; see that file's own header. */
|
|
33
|
-
const runStack = (body: StackBody) => {
|
|
34
|
-
const state = Alchemy.inMemoryState();
|
|
35
|
-
const stack = Alchemy.Stack as unknown as (
|
|
36
|
-
stackName: string,
|
|
37
|
-
options: { providers: Layer.Layer<never>; state: typeof state },
|
|
38
|
-
body: StackBody,
|
|
39
|
-
) => Effect.Effect<
|
|
40
|
-
{ readonly resources: Readonly<Record<string, ResourceLike>> },
|
|
41
|
-
unknown,
|
|
42
|
-
never
|
|
43
|
-
>;
|
|
44
|
-
return Effect.runPromiseExit(
|
|
45
|
-
stack('GuardTestStack', { providers: Layer.empty, state }, body).pipe(
|
|
46
|
-
Effect.provideService(Alchemy.Stage, 'test'),
|
|
47
|
-
Effect.scoped,
|
|
48
|
-
),
|
|
49
|
-
);
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
describe('guardDuplicateIds, through a real stack', () => {
|
|
53
|
-
test('distinct ids both register', async () => {
|
|
54
|
-
const A = guarded('Test.A');
|
|
55
|
-
const B = guarded('Test.B');
|
|
56
|
-
const exit = await runStack(
|
|
57
|
-
Effect.gen(function* () {
|
|
58
|
-
yield* A('one', { value: 1 });
|
|
59
|
-
yield* B('two', { value: 2 });
|
|
60
|
-
}),
|
|
61
|
-
);
|
|
62
|
-
expect(exit._tag).toBe('Success');
|
|
63
|
-
if (exit._tag !== 'Success') return;
|
|
64
|
-
expect(Object.keys(exit.value.resources).sort()).toEqual(['one', 'two']);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
test('the same guarded type re-registering its own id stays a noop, not a death', async () => {
|
|
68
|
-
const A = guarded('Test.A');
|
|
69
|
-
const exit = await runStack(
|
|
70
|
-
Effect.gen(function* () {
|
|
71
|
-
yield* A('shared', { value: 1 });
|
|
72
|
-
yield* A('shared', { value: 1 });
|
|
73
|
-
}),
|
|
74
|
-
);
|
|
75
|
-
expect(exit._tag).toBe('Success');
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
test('two different guarded types sharing an id die naming both, before either resource "wins"', async () => {
|
|
79
|
-
const Retirement = guarded('Nix.JobRetirement');
|
|
80
|
-
const Caddy = guarded('Caddy.Config');
|
|
81
|
-
const exit = await runStack(
|
|
82
|
-
Effect.gen(function* () {
|
|
83
|
-
yield* Retirement('caddy', { value: 1 });
|
|
84
|
-
yield* Caddy('caddy', { value: 2 });
|
|
85
|
-
}),
|
|
86
|
-
);
|
|
87
|
-
expect(exit._tag).toBe('Failure');
|
|
88
|
-
if (exit._tag !== 'Failure') return;
|
|
89
|
-
const reasons = (exit.cause as { reasons: readonly { _tag: string; defect?: unknown }[] })
|
|
90
|
-
.reasons;
|
|
91
|
-
const reason = reasons[0];
|
|
92
|
-
expect(reason === undefined ? undefined : reason._tag).toBe('Die');
|
|
93
|
-
const defect = reason === undefined ? undefined : (reason.defect as Error | undefined);
|
|
94
|
-
expect(defect === undefined ? '' : String(defect.message)).toContain(
|
|
95
|
-
'REFUSING: duplicate resource id "caddy" in scope (root): ' +
|
|
96
|
-
'first declared by Nix.JobRetirement, again by Caddy.Config',
|
|
97
|
-
);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
test('one guarded, one unguarded type sharing an id still dies (the guarded side still claims it)', async () => {
|
|
101
|
-
const Guarded = guarded('Test.Guarded');
|
|
102
|
-
const Unguarded = Alchemy.Resource<TestResource>('Test.Unguarded');
|
|
103
|
-
const exit = await runStack(
|
|
104
|
-
Effect.gen(function* () {
|
|
105
|
-
yield* Guarded('shared', { value: 1 });
|
|
106
|
-
yield* Unguarded('shared', { value: 2 });
|
|
107
|
-
}),
|
|
108
|
-
);
|
|
109
|
-
// The unguarded side never calls `claimResourceId`, so Alchemy's own idempotent-registration
|
|
110
|
-
// branch runs instead (Resource.ts) and this SUCCEEDS as a silent noop — exactly the gap this
|
|
111
|
-
// PR documents as out of scope for a kit-only fix (docs/duplicate-resource-id.md). Asserting
|
|
112
|
-
// the current behaviour, not endorsing it: `guardDuplicateIds` only protects pairs where BOTH
|
|
113
|
-
// sides are wrapped.
|
|
114
|
-
expect(exit._tag).toBe('Success');
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
test('the same id in a nested namespace is a different scope', async () => {
|
|
118
|
-
const Retirement = guarded('Nix.JobRetirement');
|
|
119
|
-
const Caddy = guarded('Caddy.Config');
|
|
120
|
-
const exit = await runStack(
|
|
121
|
-
Effect.gen(function* () {
|
|
122
|
-
yield* Retirement('caddy', { value: 1 });
|
|
123
|
-
yield* pushNamespace('child', Caddy('caddy', { value: 2 }));
|
|
124
|
-
}),
|
|
125
|
-
);
|
|
126
|
-
expect(exit._tag).toBe('Success');
|
|
127
|
-
});
|
|
128
|
-
});
|
package/src/scope/guard.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `guardDuplicateIds` — wrap a Resource class so every `("id", props)` call it makes first
|
|
3
|
-
* claims that id through `duplicate-id.ts`'s per-stack registry, naming both resource types if
|
|
4
|
-
* a second, different-typed resource already claimed it. See duplicate-id.ts's header for why
|
|
5
|
-
* the check has to live HERE (at the moment of registration) rather than after the stack body
|
|
6
|
-
* has finished running.
|
|
7
|
-
*
|
|
8
|
-
* Usage, in a resource's own file, right where it's exported today:
|
|
9
|
-
*
|
|
10
|
-
* ```ts
|
|
11
|
-
* export const CaddyConfig = guardDuplicateIds(Resource<CaddyConfig>('Caddy.Config', { … }));
|
|
12
|
-
* ```
|
|
13
|
-
*
|
|
14
|
-
* ★ A `Proxy` around the REAL class, not a reimplementation. `Resource()`'s return value is a
|
|
15
|
-
* callable object wearing an `Effectable.Prototype` (`Effect.isEffect(MyResource) === true`,
|
|
16
|
-
* for the bare-tag platform-resource pattern) plus `.ref`, `.Type`, `.Provider`, `.Self`,
|
|
17
|
-
* `.Aliases` — reconstructing that shape risks silently dropping one of them. A `Proxy` whose
|
|
18
|
-
* `get` trap is the default (`Reflect.get`) forwards every property read untouched; only the
|
|
19
|
-
* `apply` trap — the `MyResource("id", props)` call form — is new behaviour.
|
|
20
|
-
* ⚠️ THE "REGISTER METHODS" OVERLOAD IS DELIBERATELY LEFT ALONE. `ResourceClass` is also callable
|
|
21
|
-
* with a single object argument to attach methods (`Resource()`'s own `typeof args[0] ===
|
|
22
|
-
* 'object' ? Object.assign(...) : constructor(...)` branch) — that call mutates and returns the
|
|
23
|
-
* SAME class, carries no logical id, and has nothing to claim. Only a string first argument
|
|
24
|
-
* (the `(id, props)` form) goes through `claimResourceId`.
|
|
25
|
-
*/
|
|
26
|
-
import type { ResourceClass, ResourceLike } from 'alchemy/Resource';
|
|
27
|
-
import * as Effect from 'effect/Effect';
|
|
28
|
-
import { claimResourceId } from './duplicate-id.ts';
|
|
29
|
-
|
|
30
|
-
export const guardDuplicateIds = <R extends ResourceLike>(
|
|
31
|
-
resourceClass: ResourceClass<R>,
|
|
32
|
-
): ResourceClass<R> =>
|
|
33
|
-
new Proxy(resourceClass, {
|
|
34
|
-
apply(target, thisArg, args: unknown[]) {
|
|
35
|
-
const id = args[0];
|
|
36
|
-
if (typeof id !== 'string') {
|
|
37
|
-
// The "attach methods" overload — not a registration, nothing to claim.
|
|
38
|
-
return Reflect.apply(target as (...a: unknown[]) => unknown, thisArg, args);
|
|
39
|
-
}
|
|
40
|
-
return Effect.gen(function* () {
|
|
41
|
-
yield* claimResourceId(resourceClass.Type, id);
|
|
42
|
-
return yield* Reflect.apply(
|
|
43
|
-
target as (...a: unknown[]) => unknown,
|
|
44
|
-
thisArg,
|
|
45
|
-
args,
|
|
46
|
-
) as Effect.Effect<R>;
|
|
47
|
-
});
|
|
48
|
-
},
|
|
49
|
-
}) as ResourceClass<R>;
|