Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .changeset/native-effect-duplicates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
"effect-app": patch
"@effect-app/infra": patch
"@effect-app/vue": patch
"@effect-app/vue-components": patch
---

Remove effect-app helpers that Effect already provides, and add `migrateEffectAppSource` to rewrite call sites.

Deleted aliases and wrappers now live in Effect: `String.capitalize` / `uncapitalize`, `Struct.keys`, `Record.values`, `absurd`, `Tuple.make`, `Option.isSome`, `Option.liftPredicate` for non-empty arrays, `Array.chunksOf`, `Chunk.findFirst` / `findLast` / `partition` / `containsWith`, `Array.dedupeWith`, `Effect.annotateLogsScoped`, `Deferred.await`, `Effect.fromOption`, `Result.fromOption`, and `DateTime.add` / `subtract`. `Schema.DateValid` was an alias of `Schema.Date`. The date-fns calendar wrappers and the `date-fns` dependency are gone.

`effect-app/migration/nativeEffect` exports the replacement table and `migrateEffectAppSource(source)`, which rewrites those imports in one file. `dropUndefined` and `fromBool` stay, along with `groupByT` (any key, entry list), `randomElement`, `findFirstMap`, and the schema constructors that add construction defaults.

`effect-app/DateTime` adds `makeZonedLocal` and `pipeZonedLocal`. `makeZonedLocal` attaches the process timezone to a `Date` without moving the instant. `pipeZonedLocal` runs the functions passed to it and returns that instant with `DateTime.toDateUtc`. `DateAdd*` and `DateSub*` migrate to `pipeZonedLocal(date, DateTime.add({ days }))`, which keeps the local calendar.

`Chunk.containsWith` calls the equivalence as `(needle, element)`. The old `elem` called it as `(element, needle)`, so only a symmetric equivalence is unchanged. `Record.values` expects one value type, so a mapped type whose value depends on its key should use `Object.values`.
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
"subtree:effect": "node scripts/sync-effect-subtree.js"
},
"dependencies": {
"date-fns": "^4.4.0",
"fast-check": "^4.9.0",
"vue": "^3.5.35"
},
Expand Down
4 changes: 2 additions & 2 deletions packages/e2e/test/repoInvalidation.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { QueryClient, VueQueryPlugin } from "@tanstack/vue-query"
import { ApiClientFactory, InvalidStateError, makeRpcClient, NotLoggedInError, OptimisticConcurrencyException } from "effect-app/client"
import * as Context from "effect-app/Context"
import * as Effect from "effect-app/Effect"
import { tuple } from "effect-app/Function"
import { HttpRouter, HttpServer } from "effect-app/http"
import * as Layer from "effect-app/Layer"
import { DefaultGenericMiddlewares } from "effect-app/middleware"
Expand All @@ -22,6 +21,7 @@ import * as Option from "effect/Option"
import * as Reactivity from "effect/reactivity/Reactivity"
import { RpcSerialization } from "effect/rpc"
import * as Scope from "effect/Scope"
import * as Tuple from "effect/Tuple"
import { createServer } from "http"
import { createApp, effectScope, ref } from "vue"
import { RequestContextMiddleware } from "../../infra/src/internal/RequestContextMiddleware.ts"
Expand Down Expand Up @@ -167,7 +167,7 @@ const setup = () => {
Effect.context<any>()
] as const
)
.pipe(Effect.map(([client, context]) => tuple(runtime, client, context)), runtime.runPromise)
.pipe(Effect.map(([client, context]) => Tuple.make(runtime, client, context)), runtime.runPromise)
}

it("atom engine: rpc repo write invalidates and refetches; unrelated rpc write does not", async () => {
Expand Down
1 change: 0 additions & 1 deletion packages/effect-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
},
"dependencies": {
"@tsconfig/strictest": "^2.0.8",
"date-fns": "^4.4.0",
"nanoid": "^5.1.11",
"path-parser": "^6.1.0",
"query-string": "^9.4.0",
Expand Down
40 changes: 4 additions & 36 deletions packages/effect-app/src/Array.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,9 @@
import type { NonEmptyArray, NonEmptyReadonlyArray } from "effect/Array"
import * as Array from "effect/Array"
import * as Chunk from "effect/Chunk"
import * as Effect from "effect/Effect"
import { dual, type Predicate } from "./Function.ts"
import * as Option from "./Option.ts"

export const toNonEmptyArray = Option.liftPredicate(Array.isReadonlyArrayNonEmpty)

export const isArray: {
// uses ReadonlyArray here because otherwise the second overload don't work when ROA is involved.
(self: unknown): self is ReadonlyArray<unknown>
<T>(self: T): self is Extract<T, ReadonlyArray<any>>
} = Array.isArray

export function NEAFromArray<T>(ar: Array<T>) {
return ar.length ? Option.some(ar as NonEmptyArray<T>) : Option.none()
}

export function NEROArrayFromArray<T>(ar: ReadonlyArray<T>) {
return ar.length ? Option.some(ar as NonEmptyReadonlyArray<T>) : Option.none()
}

export const groupByT = dual<
<A, Key>(
f: (a: NoInfer<A>) => Key
Expand Down Expand Up @@ -85,26 +68,11 @@ export function filterWith<A>(self: ReadonlyArray<A>, predicates: ReadonlyArray<
return self.filter((_) => predicates.every((f) => f(_)))
}

/**
* Split the `items` array into multiple, smaller chunks of the given `size`.
*/
export function* _chunk_<T>(items_: Iterable<T>, size: number) {
const items = [...items_]

while (items.length) {
yield items.splice(0, size)
}
}

/**
* Split the `items` array into multiple, smaller chunks of the given `size`.
*/
export function chunk_<T>(items_: Iterable<T>, size: number) {
return Chunk.fromIterable(_chunk_(items_, size))
}

export function forEachEffectNA<A, R, E, B>(as: NonEmptyReadonlyArray<A>, f: (a: A) => Effect.Effect<B, E, R>) {
return Effect.map(Effect.forEach(as, f), (_) => Option.getOrNull(toNonEmptyArray(_)))
return Effect.map(
Effect.forEach(as, f),
(_) => Option.getOrNull(Option.liftPredicate(Array.isReadonlyArrayNonEmpty)(_))
)
}

export * from "effect/Array"
57 changes: 4 additions & 53 deletions packages/effect-app/src/Chunk.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import { pipe, type Predicate, type Refinement } from "./Function.ts"
import { pipe } from "./Function.ts"
import * as Option from "./Option.ts"

import * as Chunk from "effect/Chunk"
import { type Equivalence } from "effect/Equivalence"
import * as Array from "./Array.ts"

export * from "effect/Chunk"

export const fromIterable = Chunk.fromIterable

export function groupByTChunk_<A, Key extends PropertyKey>(c: Chunk.Chunk<A>, f: (a: A) => Key) {
return pipe(Chunk.toReadonlyArray(c), Array.groupByT(f), Chunk.fromIterable)
}

/**
* Returns the first element that satisfies the predicate.
* Returns the first `Some` produced by `f`.
*
* Effect's `Chunk.findFirst` takes a predicate, not an `Option`-returning function.
*/
export function findFirstMap<A, B>(
f: (a: A) => Option.Option<B>
Expand All @@ -31,51 +30,3 @@ export function findFirstMap<A, B>(
return Option.none()
}
}

export function toArray<T>(c: Chunk.Chunk<T>): T[] {
return Chunk.toReadonlyArray(c) as T[]
}

/**
* Remove duplicates from an array, keeping the first occurrence of an element.
*/
export function uniq<A>(E: Equivalence<A>) {
return (self: Chunk.Chunk<A>): Chunk.Chunk<A> => {
let out = Chunk.fromIterable([] as A[])
for (let i = 0; i < self.length; i++) {
const a = Chunk.getUnsafe(self, i)
if (!elem(E, a)(out)) {
out = Chunk.append(out, a)
}
}
return self.length === out.length ? self : out
}
}

/**
* Test if a value is a member of an array. Takes a `Equivalence<A>` as a single
* argument which returns the function to use to search for a value of type `A`
* in an array of type `Chunk<A>`.
*/
export function elem<A>(E: Equivalence<A>, value: A) {
return (self: Chunk.Chunk<A>): boolean => {
for (let i = 0; i < self.length; i++) {
if (E(Chunk.getUnsafe(self, i), value)) {
return true
}
}
return false
}
}

export const ChunkPartition = Chunk.partition

export const findFirstSimple: {
<A, B extends A>(self: Chunk.Chunk<A>, refinement: Refinement<A, B>): Option.Option<B>
<A>(self: Chunk.Chunk<A>, predicate: Predicate<A>): Option.Option<A>
} = Chunk.findFirst

export const findLastSimple: {
<A, B extends A>(self: Chunk.Chunk<A>, refinement: Refinement<A, B>): Option.Option<B>
<A>(self: Chunk.Chunk<A>, predicate: Predicate<A>): Option.Option<A>
} = Chunk.findLast
Loading
Loading