@logixjs/domain 0.0.2 → 1.0.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/dist/Crud.cjs +27 -37
- package/dist/Crud.cjs.map +1 -1
- package/dist/Crud.d.cts +3 -3
- package/dist/Crud.d.ts +3 -3
- package/dist/Crud.js +1 -1
- package/dist/{chunk-SCBUT2ZQ.js → chunk-CAROO5QW.js} +29 -39
- package/dist/chunk-CAROO5QW.js.map +1 -0
- package/dist/index.cjs +27 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +11 -6
- package/dist/chunk-SCBUT2ZQ.js.map +0 -1
package/dist/Crud.cjs
CHANGED
|
@@ -85,7 +85,7 @@ var defineCrud = (id, spec, extend) => {
|
|
|
85
85
|
const QuerySchema = spec.query ?? DefaultQueryInputSchema;
|
|
86
86
|
const IdSchema = spec.id ?? import_effect.Schema.String;
|
|
87
87
|
const idField = spec.idField ?? "id";
|
|
88
|
-
class Api extends import_effect.
|
|
88
|
+
class Api extends import_effect.ServiceMap.Service()(`${id}/crud/api`) {
|
|
89
89
|
}
|
|
90
90
|
const services = { api: Api };
|
|
91
91
|
const Actions = makeActions(spec.entity, QuerySchema, IdSchema);
|
|
@@ -211,47 +211,37 @@ var defineCrud = (id, spec, extend) => {
|
|
|
211
211
|
) : Logix.Module.make(id, def);
|
|
212
212
|
const install = module2.logic(
|
|
213
213
|
($) => import_effect.Effect.gen(function* () {
|
|
214
|
+
const missingApiMessage = `[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`;
|
|
215
|
+
const runWithApi = (dispatchFailed, run) => import_effect.Effect.gen(function* () {
|
|
216
|
+
const apiOpt = yield* import_effect.Effect.serviceOption(services.api);
|
|
217
|
+
if (import_effect.Option.isNone(apiOpt)) {
|
|
218
|
+
yield* dispatchFailed(missingApiMessage);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
yield* run(apiOpt.value);
|
|
222
|
+
}).pipe(
|
|
223
|
+
import_effect.Effect.catch((error) => dispatchFailed(toErrorMessage(error))),
|
|
224
|
+
import_effect.Effect.asVoid
|
|
225
|
+
);
|
|
214
226
|
yield* $.onAction("query").runFork(
|
|
215
|
-
(action) =>
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
`[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`
|
|
220
|
-
);
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
const api = apiOpt.value;
|
|
224
|
-
const result = yield* api.list(action.payload);
|
|
225
|
-
yield* $.dispatchers.querySucceeded(result);
|
|
226
|
-
}).pipe(import_effect.Effect.catchAll((e) => $.dispatchers.queryFailed(toErrorMessage(e))))
|
|
227
|
+
(action) => runWithApi(
|
|
228
|
+
$.dispatchers.queryFailed,
|
|
229
|
+
(api) => api.list(action.payload).pipe(import_effect.Effect.flatMap((result) => $.dispatchers.querySucceeded(result)))
|
|
230
|
+
)
|
|
227
231
|
);
|
|
228
232
|
yield* $.onAction("save").runFork(
|
|
229
|
-
(action) =>
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
`[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`
|
|
234
|
-
);
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
const api = apiOpt.value;
|
|
238
|
-
const entity = yield* api.save(action.payload);
|
|
239
|
-
yield* $.dispatchers.saveSucceeded(entity);
|
|
240
|
-
}).pipe(import_effect.Effect.catchAll((e) => $.dispatchers.saveFailed(toErrorMessage(e))))
|
|
233
|
+
(action) => runWithApi(
|
|
234
|
+
$.dispatchers.saveFailed,
|
|
235
|
+
(api) => api.save(action.payload).pipe(import_effect.Effect.flatMap((entity) => $.dispatchers.saveSucceeded(entity)))
|
|
236
|
+
)
|
|
241
237
|
);
|
|
242
238
|
yield* $.onAction("remove").runFork(
|
|
243
|
-
(action) =>
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
const api = apiOpt.value;
|
|
252
|
-
yield* api.remove(action.payload);
|
|
253
|
-
yield* $.dispatchers.removeSucceeded(action.payload);
|
|
254
|
-
}).pipe(import_effect.Effect.catchAll((e) => $.dispatchers.removeFailed(toErrorMessage(e))))
|
|
239
|
+
(action) => runWithApi(
|
|
240
|
+
$.dispatchers.removeFailed,
|
|
241
|
+
(api) => api.remove(action.payload).pipe(
|
|
242
|
+
import_effect.Effect.flatMap(() => $.dispatchers.removeSucceeded(action.payload))
|
|
243
|
+
)
|
|
244
|
+
)
|
|
255
245
|
);
|
|
256
246
|
}),
|
|
257
247
|
{ id: "install" }
|
package/dist/Crud.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/Crud.ts","../src/internal/crud/Crud.ts"],"sourcesContent":["export * from './internal/crud/Crud.js'\nexport type * from './internal/crud/Crud.js'\n","import * as Logix from '@logixjs/core'\nimport { Context, Effect, Option, Schema } from 'effect'\n\nconst DefaultQueryInputSchema = Schema.Struct({\n pageSize: Schema.Number,\n})\n\nexport type CrudDefaultQueryInput = Schema.Schema.Type<typeof DefaultQueryInputSchema>\n\nexport interface CrudQueryResult<Entity> {\n readonly items: ReadonlyArray<Entity>\n readonly total?: number\n}\n\nexport interface CrudApi<Entity extends object, QueryInput, Id> {\n readonly list: (input: QueryInput) => Effect.Effect<CrudQueryResult<Entity>, unknown, never>\n readonly save: (entity: Entity) => Effect.Effect<Entity, unknown, never>\n readonly remove: (id: Id) => Effect.Effect<void, unknown, never>\n}\n\nexport interface CrudSpec<Entity extends object, QueryInput = CrudDefaultQueryInput, Id = string> {\n readonly entity: Schema.Schema<Entity>\n readonly query?: Schema.Schema<QueryInput>\n readonly id?: Schema.Schema<Id>\n readonly initial?: ReadonlyArray<Entity>\n /**\n * idField:\n * - The default primary key field for upsert/remove in reducers (default: \"id\").\n * - For more complex primary-key strategies, prefer handling results in a custom upper-layer Logic and writing back to state.\n */\n readonly idField?: string\n}\n\nexport type CrudState<Entity, QueryInput> = {\n readonly items: ReadonlyArray<Entity>\n readonly loading: boolean\n readonly error: string | undefined\n readonly lastQuery: QueryInput | undefined\n readonly total: number | undefined\n}\n\nexport type CrudActionMap<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = {\n readonly query: Schema.Schema<QueryInput>\n readonly querySucceeded: Schema.Schema<CrudQueryResult<Entity>>\n readonly queryFailed: Schema.Schema<string>\n\n readonly save: Schema.Schema<Entity>\n readonly saveSucceeded: Schema.Schema<Entity>\n readonly saveFailed: Schema.Schema<string>\n\n readonly remove: Schema.Schema<Id>\n readonly removeSucceeded: Schema.Schema<Id>\n readonly removeFailed: Schema.Schema<string>\n\n readonly clearError: Schema.Schema<void>\n} & ExtraActions\n\nexport type CrudAction<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = Logix.ActionsFromMap<CrudActionMap<Entity, QueryInput, Id, ExtraActions>>\n\nexport type CrudShape<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = Logix.Shape<Schema.Schema<CrudState<Entity, QueryInput>>, CrudActionMap<Entity, QueryInput, Id, ExtraActions>>\n\nexport type CrudServices<Entity extends object, QueryInput, Id> = {\n readonly api: Logix.State.Tag<CrudApi<Entity, QueryInput, Id>>\n}\n\nexport type CrudHandleExt<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = {\n readonly controller: CrudController<Entity, QueryInput, Id, ExtraActions>['controller']\n readonly services: CrudServices<Entity, QueryInput, Id>\n}\n\nexport interface CrudController<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> {\n readonly runtime: Logix.ModuleRuntime<CrudState<Entity, QueryInput>, CrudAction<Entity, QueryInput, Id, ExtraActions>>\n readonly getState: Effect.Effect<CrudState<Entity, QueryInput>>\n readonly dispatch: (action: CrudAction<Entity, QueryInput, Id, ExtraActions>) => Effect.Effect<void>\n readonly controller: {\n readonly list: (input: QueryInput) => Effect.Effect<void>\n readonly save: (entity: Entity) => Effect.Effect<void>\n readonly remove: (id: Id) => Effect.Effect<void>\n readonly clearError: () => Effect.Effect<void>\n readonly idField: string\n }\n}\n\nconst makeActions = <Entity extends object, QueryInput, Id>(\n entity: Schema.Schema<Entity>,\n query: Schema.Schema<QueryInput>,\n id: Schema.Schema<Id>,\n): CrudActionMap<Entity, QueryInput, Id> =>\n ({\n query,\n querySucceeded: Schema.Struct({\n items: Schema.Array(entity),\n total: Schema.optional(Schema.Number),\n }) as Schema.Schema<CrudQueryResult<Entity>>,\n queryFailed: Schema.String,\n\n save: entity,\n saveSucceeded: entity,\n saveFailed: Schema.String,\n\n remove: id,\n removeSucceeded: id,\n removeFailed: Schema.String,\n\n clearError: Schema.Void,\n }) satisfies CrudActionMap<Entity, QueryInput, Id>\n\nconst toErrorMessage = (error: unknown): string => {\n if (error === null || error === undefined) return 'unknown error'\n if (typeof error === 'string') return error\n if (error instanceof Error && typeof error.message === 'string' && error.message.length > 0) {\n return error.message\n }\n if (typeof error === 'object') {\n if ('message' in error) {\n const message = (error as { readonly message?: unknown }).message\n if (typeof message === 'string' && message.length > 0) return message\n }\n try {\n const json = JSON.stringify(error)\n if (typeof json === 'string' && json.length > 0) return json\n } catch {\n // ignore\n }\n }\n return String(error)\n}\n\nconst upsertByIdField = <Entity extends object>(\n items: ReadonlyArray<Entity>,\n entity: Entity,\n idField: string,\n): ReadonlyArray<Entity> => {\n const id = (entity as Record<string, unknown>)[idField]\n const idx = items.findIndex((x) => (x as Record<string, unknown>)[idField] === id)\n if (idx < 0) return [...items, entity]\n return items.map((x, i) => (i === idx ? entity : x))\n}\n\nconst removeByIdField = <Entity extends object>(\n items: ReadonlyArray<Entity>,\n id: unknown,\n idField: string,\n): ReadonlyArray<Entity> => items.filter((x) => (x as Record<string, unknown>)[idField] !== id)\n\nexport type CrudModule<\n Id extends string,\n Entity extends object,\n QueryInput,\n EntityId,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = Logix.Module.Module<\n Id,\n CrudShape<Entity, QueryInput, EntityId, ExtraActions>,\n CrudHandleExt<Entity, QueryInput, EntityId, ExtraActions>,\n unknown\n> & {\n readonly _kind: 'Module'\n readonly services: CrudServices<Entity, QueryInput, EntityId>\n}\n\nconst defineCrud = <\n Id extends string,\n Entity extends object,\n QueryInput = CrudDefaultQueryInput,\n EntityId = string,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n>(\n id: Id,\n spec: CrudSpec<Entity, QueryInput, EntityId>,\n extend?: Logix.Module.MakeExtendDef<\n Schema.Schema<CrudState<Entity, QueryInput>>,\n CrudActionMap<Entity, QueryInput, EntityId>,\n ExtraActions\n >,\n): CrudModule<Id, Entity, QueryInput, EntityId, ExtraActions> => {\n const QuerySchema = (spec.query ?? DefaultQueryInputSchema) as Schema.Schema<QueryInput>\n const IdSchema = (spec.id ?? Schema.String) as Schema.Schema<EntityId>\n\n const idField = spec.idField ?? 'id'\n\n class Api extends Context.Tag(`${id}/crud/api`)<Api, CrudApi<Entity, QueryInput, EntityId>>() {}\n\n const services = { api: Api } as const satisfies CrudServices<Entity, QueryInput, EntityId>\n\n const Actions = makeActions(spec.entity, QuerySchema, IdSchema)\n\n const StateSchema = Schema.Struct({\n items: Schema.Array(spec.entity),\n loading: Schema.Boolean,\n error: Schema.UndefinedOr(Schema.String),\n lastQuery: Schema.UndefinedOr(QuerySchema),\n total: Schema.UndefinedOr(Schema.Number),\n })\n\n type Reducers = Logix.ReducersFromMap<typeof StateSchema, CrudActionMap<Entity, QueryInput, EntityId>>\n\n const reducers: Reducers = {\n query: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('lastQuery')\n return {\n ...state,\n loading: true,\n error: undefined,\n lastQuery: action.payload,\n }\n },\n querySucceeded: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('items')\n sink?.('total')\n return {\n ...state,\n loading: false,\n error: undefined,\n items: action.payload.items,\n total: action.payload.total,\n }\n },\n queryFailed: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: false,\n error: action.payload,\n }\n },\n\n save: (state, _action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: true,\n error: undefined,\n }\n },\n saveSucceeded: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('items')\n return {\n ...state,\n loading: false,\n error: undefined,\n items: upsertByIdField(state.items, action.payload as Entity, idField),\n }\n },\n saveFailed: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: false,\n error: action.payload,\n }\n },\n\n remove: (state, _action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: true,\n error: undefined,\n }\n },\n removeSucceeded: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('items')\n return {\n ...state,\n loading: false,\n error: undefined,\n items: removeByIdField(state.items, action.payload, idField),\n }\n },\n removeFailed: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: false,\n error: action.payload,\n }\n },\n\n clearError: (state, _action, sink) => {\n sink?.('error')\n return {\n ...state,\n error: undefined,\n }\n },\n }\n\n const def = {\n state: StateSchema,\n actions: Actions,\n reducers,\n schemas: { entity: spec.entity },\n meta: { kind: 'crud', idField },\n services,\n }\n\n const module = extend\n ? Logix.Module.make<\n Id,\n typeof StateSchema,\n CrudActionMap<Entity, QueryInput, EntityId>,\n CrudHandleExt<Entity, QueryInput, EntityId, ExtraActions>\n >(\n id,\n def,\n extend as unknown as Logix.Module.MakeExtendDef<\n typeof StateSchema,\n CrudActionMap<Entity, QueryInput, EntityId>,\n ExtraActions\n >,\n )\n : Logix.Module.make<\n Id,\n typeof StateSchema,\n CrudActionMap<Entity, QueryInput, EntityId>,\n CrudHandleExt<Entity, QueryInput, EntityId, ExtraActions>\n >(id, def)\n\n const install = module.logic(\n ($) =>\n Effect.gen(function* () {\n yield* $.onAction('query').runFork((action) =>\n Effect.gen(function* () {\n const apiOpt = yield* Effect.serviceOption(services.api)\n if (Option.isNone(apiOpt)) {\n yield* $.dispatchers.queryFailed(\n `[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`,\n )\n return\n }\n const api = apiOpt.value\n\n const result = yield* api.list(action.payload as QueryInput)\n yield* $.dispatchers.querySucceeded(result)\n }).pipe(Effect.catchAll((e) => $.dispatchers.queryFailed(toErrorMessage(e)))),\n )\n\n yield* $.onAction('save').runFork((action) =>\n Effect.gen(function* () {\n const apiOpt = yield* Effect.serviceOption(services.api)\n if (Option.isNone(apiOpt)) {\n yield* $.dispatchers.saveFailed(\n `[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`,\n )\n return\n }\n const api = apiOpt.value\n\n const entity = yield* api.save(action.payload as Entity)\n yield* $.dispatchers.saveSucceeded(entity)\n }).pipe(Effect.catchAll((e) => $.dispatchers.saveFailed(toErrorMessage(e)))),\n )\n\n yield* $.onAction('remove').runFork((action) =>\n Effect.gen(function* () {\n const apiOpt = yield* Effect.serviceOption(services.api)\n if (Option.isNone(apiOpt)) {\n yield* $.dispatchers.removeFailed(\n `[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`,\n )\n return\n }\n const api = apiOpt.value\n\n yield* api.remove(action.payload as EntityId)\n yield* $.dispatchers.removeSucceeded(action.payload as EntityId)\n }).pipe(Effect.catchAll((e) => $.dispatchers.removeFailed(toErrorMessage(e)))),\n )\n }),\n { id: 'install' },\n )\n\n const controller = {\n make: (\n runtime: Logix.ModuleRuntime<\n CrudState<Entity, QueryInput>,\n CrudAction<Entity, QueryInput, EntityId, ExtraActions>\n >,\n ): CrudController<Entity, QueryInput, EntityId, ExtraActions> => ({\n runtime,\n getState: runtime.getState,\n dispatch: runtime.dispatch,\n controller: {\n list: (input: QueryInput) =>\n runtime.dispatch({ _tag: 'query', payload: input } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n save: (entity: Entity) =>\n runtime.dispatch({ _tag: 'save', payload: entity } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n remove: (id: EntityId) =>\n runtime.dispatch({ _tag: 'remove', payload: id } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n clearError: () =>\n runtime.dispatch({ _tag: 'clearError' } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n idField,\n },\n }),\n }\n\n const EXTEND_HANDLE = Symbol.for('logix.module.handle.extend')\n ;(module.tag as unknown as Record<PropertyKey, unknown>)[EXTEND_HANDLE] = (\n runtime: Logix.ModuleRuntime<CrudState<Entity, QueryInput>, CrudAction<Entity, QueryInput, EntityId, ExtraActions>>,\n base: Logix.ModuleHandle<Logix.AnyModuleShape>,\n ) => {\n const crud = controller.make(runtime)\n return {\n ...base,\n controller: crud.controller,\n services,\n }\n }\n\n return module.implement({\n initial: {\n items: Array.from(spec.initial ?? []),\n loading: false,\n error: undefined,\n lastQuery: undefined,\n total: undefined,\n } as CrudState<Entity, QueryInput>,\n logics: [install],\n }) as unknown as CrudModule<Id, Entity, QueryInput, EntityId, ExtraActions>\n}\n\nexport const CRUDModule = Logix.Module.Manage.make({\n kind: 'crud',\n define: defineCrud,\n})\n"],"mappings":"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","names":["module","id"]}
|
|
1
|
+
{"version":3,"sources":["../src/Crud.ts","../src/internal/crud/Crud.ts"],"sourcesContent":["export * from './internal/crud/Crud.js'\nexport type * from './internal/crud/Crud.js'\n","import * as Logix from '@logixjs/core'\nimport { Effect, Option, Schema, ServiceMap } from 'effect'\n\nconst DefaultQueryInputSchema = Schema.Struct({\n pageSize: Schema.Number,\n})\n\nexport type CrudDefaultQueryInput = Schema.Schema.Type<typeof DefaultQueryInputSchema>\n\nexport interface CrudQueryResult<Entity> {\n readonly items: ReadonlyArray<Entity>\n readonly total?: number\n}\n\nexport interface CrudApi<Entity extends object, QueryInput, Id> {\n readonly list: (input: QueryInput) => Effect.Effect<CrudQueryResult<Entity>, unknown, never>\n readonly save: (entity: Entity) => Effect.Effect<Entity, unknown, never>\n readonly remove: (id: Id) => Effect.Effect<void, unknown, never>\n}\n\nexport interface CrudSpec<Entity extends object, QueryInput = CrudDefaultQueryInput, Id = string> {\n readonly entity: Schema.Schema<Entity>\n readonly query?: Schema.Schema<QueryInput>\n readonly id?: Schema.Schema<Id>\n readonly initial?: ReadonlyArray<Entity>\n /**\n * idField:\n * - The default primary key field for upsert/remove in reducers (default: \"id\").\n * - For more complex primary-key strategies, prefer handling results in a custom upper-layer Logic and writing back to state.\n */\n readonly idField?: string\n}\n\nexport type CrudState<Entity, QueryInput> = {\n readonly items: ReadonlyArray<Entity>\n readonly loading: boolean\n readonly error: string | undefined\n readonly lastQuery: QueryInput | undefined\n readonly total: number | undefined\n}\n\nexport type CrudActionMap<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = {\n readonly query: Schema.Schema<QueryInput>\n readonly querySucceeded: Schema.Schema<CrudQueryResult<Entity>>\n readonly queryFailed: Schema.Schema<string>\n\n readonly save: Schema.Schema<Entity>\n readonly saveSucceeded: Schema.Schema<Entity>\n readonly saveFailed: Schema.Schema<string>\n\n readonly remove: Schema.Schema<Id>\n readonly removeSucceeded: Schema.Schema<Id>\n readonly removeFailed: Schema.Schema<string>\n\n readonly clearError: Schema.Schema<void>\n} & ExtraActions\n\nexport type CrudAction<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = Logix.ActionsFromMap<CrudActionMap<Entity, QueryInput, Id, ExtraActions>>\n\nexport type CrudShape<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = Logix.Shape<Schema.Schema<CrudState<Entity, QueryInput>>, CrudActionMap<Entity, QueryInput, Id, ExtraActions>>\n\nexport type CrudServices<Entity extends object, QueryInput, Id> = {\n readonly api: ServiceMap.Key<any, CrudApi<Entity, QueryInput, Id>>\n}\n\nexport type CrudHandleExt<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = {\n readonly controller: CrudController<Entity, QueryInput, Id, ExtraActions>['controller']\n readonly services: CrudServices<Entity, QueryInput, Id>\n}\n\nexport interface CrudController<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> {\n readonly runtime: Logix.ModuleRuntime<CrudState<Entity, QueryInput>, CrudAction<Entity, QueryInput, Id, ExtraActions>>\n readonly getState: Effect.Effect<CrudState<Entity, QueryInput>>\n readonly dispatch: (action: CrudAction<Entity, QueryInput, Id, ExtraActions>) => Effect.Effect<void>\n readonly controller: {\n readonly list: (input: QueryInput) => Effect.Effect<void>\n readonly save: (entity: Entity) => Effect.Effect<void>\n readonly remove: (id: Id) => Effect.Effect<void>\n readonly clearError: () => Effect.Effect<void>\n readonly idField: string\n }\n}\n\nconst makeActions = <Entity extends object, QueryInput, Id>(\n entity: Schema.Schema<Entity>,\n query: Schema.Schema<QueryInput>,\n id: Schema.Schema<Id>,\n): CrudActionMap<Entity, QueryInput, Id> =>\n ({\n query,\n querySucceeded: Schema.Struct({\n items: Schema.Array(entity),\n total: Schema.optional(Schema.Number),\n }) as Schema.Schema<CrudQueryResult<Entity>>,\n queryFailed: Schema.String,\n\n save: entity,\n saveSucceeded: entity,\n saveFailed: Schema.String,\n\n remove: id,\n removeSucceeded: id,\n removeFailed: Schema.String,\n\n clearError: Schema.Void,\n }) satisfies CrudActionMap<Entity, QueryInput, Id>\n\nconst toErrorMessage = (error: unknown): string => {\n if (error === null || error === undefined) return 'unknown error'\n if (typeof error === 'string') return error\n if (error instanceof Error && typeof error.message === 'string' && error.message.length > 0) {\n return error.message\n }\n if (typeof error === 'object') {\n if ('message' in error) {\n const message = (error as { readonly message?: unknown }).message\n if (typeof message === 'string' && message.length > 0) return message\n }\n try {\n const json = JSON.stringify(error)\n if (typeof json === 'string' && json.length > 0) return json\n } catch {\n // ignore\n }\n }\n return String(error)\n}\n\nconst upsertByIdField = <Entity extends object>(\n items: ReadonlyArray<Entity>,\n entity: Entity,\n idField: string,\n): ReadonlyArray<Entity> => {\n const id = (entity as Record<string, unknown>)[idField]\n const idx = items.findIndex((x) => (x as Record<string, unknown>)[idField] === id)\n if (idx < 0) return [...items, entity]\n return items.map((x, i) => (i === idx ? entity : x))\n}\n\nconst removeByIdField = <Entity extends object>(\n items: ReadonlyArray<Entity>,\n id: unknown,\n idField: string,\n): ReadonlyArray<Entity> => items.filter((x) => (x as Record<string, unknown>)[idField] !== id)\n\nexport type CrudModule<\n Id extends string,\n Entity extends object,\n QueryInput,\n EntityId,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = Logix.Module.Module<\n Id,\n CrudShape<Entity, QueryInput, EntityId, ExtraActions>,\n CrudHandleExt<Entity, QueryInput, EntityId, ExtraActions>,\n unknown\n> & {\n readonly _kind: 'Module'\n readonly services: CrudServices<Entity, QueryInput, EntityId>\n}\n\nconst defineCrud = <\n Id extends string,\n Entity extends object,\n QueryInput = CrudDefaultQueryInput,\n EntityId = string,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n>(\n id: Id,\n spec: CrudSpec<Entity, QueryInput, EntityId>,\n extend?: Logix.Module.MakeExtendDef<\n Schema.Schema<CrudState<Entity, QueryInput>>,\n CrudActionMap<Entity, QueryInput, EntityId>,\n ExtraActions\n >,\n): CrudModule<Id, Entity, QueryInput, EntityId, ExtraActions> => {\n const QuerySchema = (spec.query ?? DefaultQueryInputSchema) as Schema.Schema<QueryInput>\n const IdSchema = (spec.id ?? Schema.String) as Schema.Schema<EntityId>\n\n const idField = spec.idField ?? 'id'\n\n class Api extends ServiceMap.Service<Api, CrudApi<Entity, QueryInput, EntityId>>()( `${id}/crud/api`) {}\n\n const services = { api: Api } as const satisfies CrudServices<Entity, QueryInput, EntityId>\n\n const Actions = makeActions(spec.entity, QuerySchema, IdSchema)\n\n const StateSchema = Schema.Struct({\n items: Schema.Array(spec.entity),\n loading: Schema.Boolean,\n error: Schema.UndefinedOr(Schema.String),\n lastQuery: Schema.UndefinedOr(QuerySchema),\n total: Schema.UndefinedOr(Schema.Number),\n })\n\n type Reducers = Logix.ReducersFromMap<typeof StateSchema, CrudActionMap<Entity, QueryInput, EntityId>>\n\n const reducers: Reducers = {\n query: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('lastQuery')\n return {\n ...state,\n loading: true,\n error: undefined,\n lastQuery: action.payload,\n }\n },\n querySucceeded: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('items')\n sink?.('total')\n return {\n ...state,\n loading: false,\n error: undefined,\n items: action.payload.items,\n total: action.payload.total,\n }\n },\n queryFailed: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: false,\n error: action.payload,\n }\n },\n\n save: (state, _action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: true,\n error: undefined,\n }\n },\n saveSucceeded: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('items')\n return {\n ...state,\n loading: false,\n error: undefined,\n items: upsertByIdField(state.items, action.payload as Entity, idField),\n }\n },\n saveFailed: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: false,\n error: action.payload,\n }\n },\n\n remove: (state, _action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: true,\n error: undefined,\n }\n },\n removeSucceeded: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('items')\n return {\n ...state,\n loading: false,\n error: undefined,\n items: removeByIdField(state.items, action.payload, idField),\n }\n },\n removeFailed: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: false,\n error: action.payload,\n }\n },\n\n clearError: (state, _action, sink) => {\n sink?.('error')\n return {\n ...state,\n error: undefined,\n }\n },\n }\n\n const def = {\n state: StateSchema,\n actions: Actions,\n reducers,\n schemas: { entity: spec.entity },\n meta: { kind: 'crud', idField },\n services,\n }\n\n const module = extend\n ? Logix.Module.make<\n Id,\n typeof StateSchema,\n CrudActionMap<Entity, QueryInput, EntityId>,\n CrudHandleExt<Entity, QueryInput, EntityId, ExtraActions>\n >(\n id,\n def,\n extend as unknown as Logix.Module.MakeExtendDef<\n typeof StateSchema,\n CrudActionMap<Entity, QueryInput, EntityId>,\n ExtraActions\n >,\n )\n : Logix.Module.make<\n Id,\n typeof StateSchema,\n CrudActionMap<Entity, QueryInput, EntityId>,\n CrudHandleExt<Entity, QueryInput, EntityId, ExtraActions>\n >(id, def)\n\n const install = module.logic(\n ($) =>\n Effect.gen(function* () {\n const missingApiMessage = `[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`\n\n const runWithApi = (\n dispatchFailed: (message: string) => Effect.Effect<void, never, any>,\n run: (api: CrudApi<Entity, QueryInput, EntityId>) => Effect.Effect<unknown, unknown, any>,\n ): Effect.Effect<void, never, any> =>\n Effect.gen(function* () {\n const apiOpt = yield* Effect.serviceOption(services.api as ServiceMap.Key<any, CrudApi<Entity, QueryInput, EntityId>>)\n if (Option.isNone(apiOpt)) {\n yield* dispatchFailed(missingApiMessage)\n return\n }\n yield* run(apiOpt.value)\n }).pipe(\n Effect.catch((error) => dispatchFailed(toErrorMessage(error))),\n Effect.asVoid,\n )\n\n yield* $.onAction('query').runFork((action) =>\n runWithApi($.dispatchers.queryFailed, (api) =>\n api.list(action.payload as QueryInput).pipe(Effect.flatMap((result) => $.dispatchers.querySucceeded(result))),\n ),\n )\n\n yield* $.onAction('save').runFork((action) =>\n runWithApi($.dispatchers.saveFailed, (api) =>\n api.save(action.payload as Entity).pipe(Effect.flatMap((entity) => $.dispatchers.saveSucceeded(entity))),\n ),\n )\n\n yield* $.onAction('remove').runFork((action) =>\n runWithApi($.dispatchers.removeFailed, (api) =>\n api.remove(action.payload as EntityId).pipe(\n Effect.flatMap(() => $.dispatchers.removeSucceeded(action.payload as EntityId)),\n ),\n ),\n )\n }),\n { id: 'install' },\n )\n\n const controller = {\n make: (\n runtime: Logix.ModuleRuntime<\n CrudState<Entity, QueryInput>,\n CrudAction<Entity, QueryInput, EntityId, ExtraActions>\n >,\n ): CrudController<Entity, QueryInput, EntityId, ExtraActions> => ({\n runtime,\n getState: runtime.getState,\n dispatch: runtime.dispatch,\n controller: {\n list: (input: QueryInput) =>\n runtime.dispatch({ _tag: 'query', payload: input } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n save: (entity: Entity) =>\n runtime.dispatch({ _tag: 'save', payload: entity } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n remove: (id: EntityId) =>\n runtime.dispatch({ _tag: 'remove', payload: id } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n clearError: () =>\n runtime.dispatch({ _tag: 'clearError' } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n idField,\n },\n }),\n }\n\n const EXTEND_HANDLE = Symbol.for('logix.module.handle.extend')\n ;(module.tag as unknown as Record<PropertyKey, unknown>)[EXTEND_HANDLE] = (\n runtime: Logix.ModuleRuntime<CrudState<Entity, QueryInput>, CrudAction<Entity, QueryInput, EntityId, ExtraActions>>,\n base: Logix.ModuleHandle<Logix.AnyModuleShape>,\n ) => {\n const crud = controller.make(runtime)\n return {\n ...base,\n controller: crud.controller,\n services,\n }\n }\n\n return module.implement({\n initial: {\n items: Array.from(spec.initial ?? []),\n loading: false,\n error: undefined,\n lastQuery: undefined,\n total: undefined,\n } as CrudState<Entity, QueryInput>,\n logics: [install],\n }) as unknown as CrudModule<Id, Entity, QueryInput, EntityId, ExtraActions>\n}\n\nexport const CRUDModule = Logix.Module.Manage.make({\n kind: 'crud',\n define: defineCrud,\n})\n"],"mappings":"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","names":["module","id"]}
|
package/dist/Crud.d.cts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as Logix from '@logixjs/core';
|
|
2
|
-
import { Schema, Effect } from 'effect';
|
|
2
|
+
import { Schema, Effect, ServiceMap } from 'effect';
|
|
3
3
|
|
|
4
4
|
declare const DefaultQueryInputSchema: Schema.Struct<{
|
|
5
|
-
pageSize:
|
|
5
|
+
readonly pageSize: Schema.Number;
|
|
6
6
|
}>;
|
|
7
7
|
type CrudDefaultQueryInput = Schema.Schema.Type<typeof DefaultQueryInputSchema>;
|
|
8
8
|
interface CrudQueryResult<Entity> {
|
|
@@ -48,7 +48,7 @@ type CrudActionMap<Entity extends object, QueryInput, Id, ExtraActions extends R
|
|
|
48
48
|
type CrudAction<Entity extends object, QueryInput, Id, ExtraActions extends Record<string, Logix.AnySchema> = {}> = Logix.ActionsFromMap<CrudActionMap<Entity, QueryInput, Id, ExtraActions>>;
|
|
49
49
|
type CrudShape<Entity extends object, QueryInput, Id, ExtraActions extends Record<string, Logix.AnySchema> = {}> = Logix.Shape<Schema.Schema<CrudState<Entity, QueryInput>>, CrudActionMap<Entity, QueryInput, Id, ExtraActions>>;
|
|
50
50
|
type CrudServices<Entity extends object, QueryInput, Id> = {
|
|
51
|
-
readonly api:
|
|
51
|
+
readonly api: ServiceMap.Key<any, CrudApi<Entity, QueryInput, Id>>;
|
|
52
52
|
};
|
|
53
53
|
type CrudHandleExt<Entity extends object, QueryInput, Id, ExtraActions extends Record<string, Logix.AnySchema> = {}> = {
|
|
54
54
|
readonly controller: CrudController<Entity, QueryInput, Id, ExtraActions>['controller'];
|
package/dist/Crud.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as Logix from '@logixjs/core';
|
|
2
|
-
import { Schema, Effect } from 'effect';
|
|
2
|
+
import { Schema, Effect, ServiceMap } from 'effect';
|
|
3
3
|
|
|
4
4
|
declare const DefaultQueryInputSchema: Schema.Struct<{
|
|
5
|
-
pageSize:
|
|
5
|
+
readonly pageSize: Schema.Number;
|
|
6
6
|
}>;
|
|
7
7
|
type CrudDefaultQueryInput = Schema.Schema.Type<typeof DefaultQueryInputSchema>;
|
|
8
8
|
interface CrudQueryResult<Entity> {
|
|
@@ -48,7 +48,7 @@ type CrudActionMap<Entity extends object, QueryInput, Id, ExtraActions extends R
|
|
|
48
48
|
type CrudAction<Entity extends object, QueryInput, Id, ExtraActions extends Record<string, Logix.AnySchema> = {}> = Logix.ActionsFromMap<CrudActionMap<Entity, QueryInput, Id, ExtraActions>>;
|
|
49
49
|
type CrudShape<Entity extends object, QueryInput, Id, ExtraActions extends Record<string, Logix.AnySchema> = {}> = Logix.Shape<Schema.Schema<CrudState<Entity, QueryInput>>, CrudActionMap<Entity, QueryInput, Id, ExtraActions>>;
|
|
50
50
|
type CrudServices<Entity extends object, QueryInput, Id> = {
|
|
51
|
-
readonly api:
|
|
51
|
+
readonly api: ServiceMap.Key<any, CrudApi<Entity, QueryInput, Id>>;
|
|
52
52
|
};
|
|
53
53
|
type CrudHandleExt<Entity extends object, QueryInput, Id, ExtraActions extends Record<string, Logix.AnySchema> = {}> = {
|
|
54
54
|
readonly controller: CrudController<Entity, QueryInput, Id, ExtraActions>['controller'];
|
package/dist/Crud.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/internal/crud/Crud.ts
|
|
2
2
|
import * as Logix from "@logixjs/core";
|
|
3
|
-
import {
|
|
3
|
+
import { Effect, Option, Schema, ServiceMap } from "effect";
|
|
4
4
|
var DefaultQueryInputSchema = Schema.Struct({
|
|
5
5
|
pageSize: Schema.Number
|
|
6
6
|
});
|
|
@@ -49,7 +49,7 @@ var defineCrud = (id, spec, extend) => {
|
|
|
49
49
|
const QuerySchema = spec.query ?? DefaultQueryInputSchema;
|
|
50
50
|
const IdSchema = spec.id ?? Schema.String;
|
|
51
51
|
const idField = spec.idField ?? "id";
|
|
52
|
-
class Api extends
|
|
52
|
+
class Api extends ServiceMap.Service()(`${id}/crud/api`) {
|
|
53
53
|
}
|
|
54
54
|
const services = { api: Api };
|
|
55
55
|
const Actions = makeActions(spec.entity, QuerySchema, IdSchema);
|
|
@@ -175,47 +175,37 @@ var defineCrud = (id, spec, extend) => {
|
|
|
175
175
|
) : Logix.Module.make(id, def);
|
|
176
176
|
const install = module.logic(
|
|
177
177
|
($) => Effect.gen(function* () {
|
|
178
|
+
const missingApiMessage = `[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`;
|
|
179
|
+
const runWithApi = (dispatchFailed, run) => Effect.gen(function* () {
|
|
180
|
+
const apiOpt = yield* Effect.serviceOption(services.api);
|
|
181
|
+
if (Option.isNone(apiOpt)) {
|
|
182
|
+
yield* dispatchFailed(missingApiMessage);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
yield* run(apiOpt.value);
|
|
186
|
+
}).pipe(
|
|
187
|
+
Effect.catch((error) => dispatchFailed(toErrorMessage(error))),
|
|
188
|
+
Effect.asVoid
|
|
189
|
+
);
|
|
178
190
|
yield* $.onAction("query").runFork(
|
|
179
|
-
(action) =>
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
`[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`
|
|
184
|
-
);
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
const api = apiOpt.value;
|
|
188
|
-
const result = yield* api.list(action.payload);
|
|
189
|
-
yield* $.dispatchers.querySucceeded(result);
|
|
190
|
-
}).pipe(Effect.catchAll((e) => $.dispatchers.queryFailed(toErrorMessage(e))))
|
|
191
|
+
(action) => runWithApi(
|
|
192
|
+
$.dispatchers.queryFailed,
|
|
193
|
+
(api) => api.list(action.payload).pipe(Effect.flatMap((result) => $.dispatchers.querySucceeded(result)))
|
|
194
|
+
)
|
|
191
195
|
);
|
|
192
196
|
yield* $.onAction("save").runFork(
|
|
193
|
-
(action) =>
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
`[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`
|
|
198
|
-
);
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
const api = apiOpt.value;
|
|
202
|
-
const entity = yield* api.save(action.payload);
|
|
203
|
-
yield* $.dispatchers.saveSucceeded(entity);
|
|
204
|
-
}).pipe(Effect.catchAll((e) => $.dispatchers.saveFailed(toErrorMessage(e))))
|
|
197
|
+
(action) => runWithApi(
|
|
198
|
+
$.dispatchers.saveFailed,
|
|
199
|
+
(api) => api.save(action.payload).pipe(Effect.flatMap((entity) => $.dispatchers.saveSucceeded(entity)))
|
|
200
|
+
)
|
|
205
201
|
);
|
|
206
202
|
yield* $.onAction("remove").runFork(
|
|
207
|
-
(action) =>
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
const api = apiOpt.value;
|
|
216
|
-
yield* api.remove(action.payload);
|
|
217
|
-
yield* $.dispatchers.removeSucceeded(action.payload);
|
|
218
|
-
}).pipe(Effect.catchAll((e) => $.dispatchers.removeFailed(toErrorMessage(e))))
|
|
203
|
+
(action) => runWithApi(
|
|
204
|
+
$.dispatchers.removeFailed,
|
|
205
|
+
(api) => api.remove(action.payload).pipe(
|
|
206
|
+
Effect.flatMap(() => $.dispatchers.removeSucceeded(action.payload))
|
|
207
|
+
)
|
|
208
|
+
)
|
|
219
209
|
);
|
|
220
210
|
}),
|
|
221
211
|
{ id: "install" }
|
|
@@ -262,4 +252,4 @@ var CRUDModule = Logix.Module.Manage.make({
|
|
|
262
252
|
export {
|
|
263
253
|
CRUDModule
|
|
264
254
|
};
|
|
265
|
-
//# sourceMappingURL=chunk-
|
|
255
|
+
//# sourceMappingURL=chunk-CAROO5QW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/internal/crud/Crud.ts"],"sourcesContent":["import * as Logix from '@logixjs/core'\nimport { Effect, Option, Schema, ServiceMap } from 'effect'\n\nconst DefaultQueryInputSchema = Schema.Struct({\n pageSize: Schema.Number,\n})\n\nexport type CrudDefaultQueryInput = Schema.Schema.Type<typeof DefaultQueryInputSchema>\n\nexport interface CrudQueryResult<Entity> {\n readonly items: ReadonlyArray<Entity>\n readonly total?: number\n}\n\nexport interface CrudApi<Entity extends object, QueryInput, Id> {\n readonly list: (input: QueryInput) => Effect.Effect<CrudQueryResult<Entity>, unknown, never>\n readonly save: (entity: Entity) => Effect.Effect<Entity, unknown, never>\n readonly remove: (id: Id) => Effect.Effect<void, unknown, never>\n}\n\nexport interface CrudSpec<Entity extends object, QueryInput = CrudDefaultQueryInput, Id = string> {\n readonly entity: Schema.Schema<Entity>\n readonly query?: Schema.Schema<QueryInput>\n readonly id?: Schema.Schema<Id>\n readonly initial?: ReadonlyArray<Entity>\n /**\n * idField:\n * - The default primary key field for upsert/remove in reducers (default: \"id\").\n * - For more complex primary-key strategies, prefer handling results in a custom upper-layer Logic and writing back to state.\n */\n readonly idField?: string\n}\n\nexport type CrudState<Entity, QueryInput> = {\n readonly items: ReadonlyArray<Entity>\n readonly loading: boolean\n readonly error: string | undefined\n readonly lastQuery: QueryInput | undefined\n readonly total: number | undefined\n}\n\nexport type CrudActionMap<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = {\n readonly query: Schema.Schema<QueryInput>\n readonly querySucceeded: Schema.Schema<CrudQueryResult<Entity>>\n readonly queryFailed: Schema.Schema<string>\n\n readonly save: Schema.Schema<Entity>\n readonly saveSucceeded: Schema.Schema<Entity>\n readonly saveFailed: Schema.Schema<string>\n\n readonly remove: Schema.Schema<Id>\n readonly removeSucceeded: Schema.Schema<Id>\n readonly removeFailed: Schema.Schema<string>\n\n readonly clearError: Schema.Schema<void>\n} & ExtraActions\n\nexport type CrudAction<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = Logix.ActionsFromMap<CrudActionMap<Entity, QueryInput, Id, ExtraActions>>\n\nexport type CrudShape<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = Logix.Shape<Schema.Schema<CrudState<Entity, QueryInput>>, CrudActionMap<Entity, QueryInput, Id, ExtraActions>>\n\nexport type CrudServices<Entity extends object, QueryInput, Id> = {\n readonly api: ServiceMap.Key<any, CrudApi<Entity, QueryInput, Id>>\n}\n\nexport type CrudHandleExt<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = {\n readonly controller: CrudController<Entity, QueryInput, Id, ExtraActions>['controller']\n readonly services: CrudServices<Entity, QueryInput, Id>\n}\n\nexport interface CrudController<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> {\n readonly runtime: Logix.ModuleRuntime<CrudState<Entity, QueryInput>, CrudAction<Entity, QueryInput, Id, ExtraActions>>\n readonly getState: Effect.Effect<CrudState<Entity, QueryInput>>\n readonly dispatch: (action: CrudAction<Entity, QueryInput, Id, ExtraActions>) => Effect.Effect<void>\n readonly controller: {\n readonly list: (input: QueryInput) => Effect.Effect<void>\n readonly save: (entity: Entity) => Effect.Effect<void>\n readonly remove: (id: Id) => Effect.Effect<void>\n readonly clearError: () => Effect.Effect<void>\n readonly idField: string\n }\n}\n\nconst makeActions = <Entity extends object, QueryInput, Id>(\n entity: Schema.Schema<Entity>,\n query: Schema.Schema<QueryInput>,\n id: Schema.Schema<Id>,\n): CrudActionMap<Entity, QueryInput, Id> =>\n ({\n query,\n querySucceeded: Schema.Struct({\n items: Schema.Array(entity),\n total: Schema.optional(Schema.Number),\n }) as Schema.Schema<CrudQueryResult<Entity>>,\n queryFailed: Schema.String,\n\n save: entity,\n saveSucceeded: entity,\n saveFailed: Schema.String,\n\n remove: id,\n removeSucceeded: id,\n removeFailed: Schema.String,\n\n clearError: Schema.Void,\n }) satisfies CrudActionMap<Entity, QueryInput, Id>\n\nconst toErrorMessage = (error: unknown): string => {\n if (error === null || error === undefined) return 'unknown error'\n if (typeof error === 'string') return error\n if (error instanceof Error && typeof error.message === 'string' && error.message.length > 0) {\n return error.message\n }\n if (typeof error === 'object') {\n if ('message' in error) {\n const message = (error as { readonly message?: unknown }).message\n if (typeof message === 'string' && message.length > 0) return message\n }\n try {\n const json = JSON.stringify(error)\n if (typeof json === 'string' && json.length > 0) return json\n } catch {\n // ignore\n }\n }\n return String(error)\n}\n\nconst upsertByIdField = <Entity extends object>(\n items: ReadonlyArray<Entity>,\n entity: Entity,\n idField: string,\n): ReadonlyArray<Entity> => {\n const id = (entity as Record<string, unknown>)[idField]\n const idx = items.findIndex((x) => (x as Record<string, unknown>)[idField] === id)\n if (idx < 0) return [...items, entity]\n return items.map((x, i) => (i === idx ? entity : x))\n}\n\nconst removeByIdField = <Entity extends object>(\n items: ReadonlyArray<Entity>,\n id: unknown,\n idField: string,\n): ReadonlyArray<Entity> => items.filter((x) => (x as Record<string, unknown>)[idField] !== id)\n\nexport type CrudModule<\n Id extends string,\n Entity extends object,\n QueryInput,\n EntityId,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = Logix.Module.Module<\n Id,\n CrudShape<Entity, QueryInput, EntityId, ExtraActions>,\n CrudHandleExt<Entity, QueryInput, EntityId, ExtraActions>,\n unknown\n> & {\n readonly _kind: 'Module'\n readonly services: CrudServices<Entity, QueryInput, EntityId>\n}\n\nconst defineCrud = <\n Id extends string,\n Entity extends object,\n QueryInput = CrudDefaultQueryInput,\n EntityId = string,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n>(\n id: Id,\n spec: CrudSpec<Entity, QueryInput, EntityId>,\n extend?: Logix.Module.MakeExtendDef<\n Schema.Schema<CrudState<Entity, QueryInput>>,\n CrudActionMap<Entity, QueryInput, EntityId>,\n ExtraActions\n >,\n): CrudModule<Id, Entity, QueryInput, EntityId, ExtraActions> => {\n const QuerySchema = (spec.query ?? DefaultQueryInputSchema) as Schema.Schema<QueryInput>\n const IdSchema = (spec.id ?? Schema.String) as Schema.Schema<EntityId>\n\n const idField = spec.idField ?? 'id'\n\n class Api extends ServiceMap.Service<Api, CrudApi<Entity, QueryInput, EntityId>>()( `${id}/crud/api`) {}\n\n const services = { api: Api } as const satisfies CrudServices<Entity, QueryInput, EntityId>\n\n const Actions = makeActions(spec.entity, QuerySchema, IdSchema)\n\n const StateSchema = Schema.Struct({\n items: Schema.Array(spec.entity),\n loading: Schema.Boolean,\n error: Schema.UndefinedOr(Schema.String),\n lastQuery: Schema.UndefinedOr(QuerySchema),\n total: Schema.UndefinedOr(Schema.Number),\n })\n\n type Reducers = Logix.ReducersFromMap<typeof StateSchema, CrudActionMap<Entity, QueryInput, EntityId>>\n\n const reducers: Reducers = {\n query: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('lastQuery')\n return {\n ...state,\n loading: true,\n error: undefined,\n lastQuery: action.payload,\n }\n },\n querySucceeded: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('items')\n sink?.('total')\n return {\n ...state,\n loading: false,\n error: undefined,\n items: action.payload.items,\n total: action.payload.total,\n }\n },\n queryFailed: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: false,\n error: action.payload,\n }\n },\n\n save: (state, _action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: true,\n error: undefined,\n }\n },\n saveSucceeded: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('items')\n return {\n ...state,\n loading: false,\n error: undefined,\n items: upsertByIdField(state.items, action.payload as Entity, idField),\n }\n },\n saveFailed: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: false,\n error: action.payload,\n }\n },\n\n remove: (state, _action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: true,\n error: undefined,\n }\n },\n removeSucceeded: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('items')\n return {\n ...state,\n loading: false,\n error: undefined,\n items: removeByIdField(state.items, action.payload, idField),\n }\n },\n removeFailed: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: false,\n error: action.payload,\n }\n },\n\n clearError: (state, _action, sink) => {\n sink?.('error')\n return {\n ...state,\n error: undefined,\n }\n },\n }\n\n const def = {\n state: StateSchema,\n actions: Actions,\n reducers,\n schemas: { entity: spec.entity },\n meta: { kind: 'crud', idField },\n services,\n }\n\n const module = extend\n ? Logix.Module.make<\n Id,\n typeof StateSchema,\n CrudActionMap<Entity, QueryInput, EntityId>,\n CrudHandleExt<Entity, QueryInput, EntityId, ExtraActions>\n >(\n id,\n def,\n extend as unknown as Logix.Module.MakeExtendDef<\n typeof StateSchema,\n CrudActionMap<Entity, QueryInput, EntityId>,\n ExtraActions\n >,\n )\n : Logix.Module.make<\n Id,\n typeof StateSchema,\n CrudActionMap<Entity, QueryInput, EntityId>,\n CrudHandleExt<Entity, QueryInput, EntityId, ExtraActions>\n >(id, def)\n\n const install = module.logic(\n ($) =>\n Effect.gen(function* () {\n const missingApiMessage = `[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`\n\n const runWithApi = (\n dispatchFailed: (message: string) => Effect.Effect<void, never, any>,\n run: (api: CrudApi<Entity, QueryInput, EntityId>) => Effect.Effect<unknown, unknown, any>,\n ): Effect.Effect<void, never, any> =>\n Effect.gen(function* () {\n const apiOpt = yield* Effect.serviceOption(services.api as ServiceMap.Key<any, CrudApi<Entity, QueryInput, EntityId>>)\n if (Option.isNone(apiOpt)) {\n yield* dispatchFailed(missingApiMessage)\n return\n }\n yield* run(apiOpt.value)\n }).pipe(\n Effect.catch((error) => dispatchFailed(toErrorMessage(error))),\n Effect.asVoid,\n )\n\n yield* $.onAction('query').runFork((action) =>\n runWithApi($.dispatchers.queryFailed, (api) =>\n api.list(action.payload as QueryInput).pipe(Effect.flatMap((result) => $.dispatchers.querySucceeded(result))),\n ),\n )\n\n yield* $.onAction('save').runFork((action) =>\n runWithApi($.dispatchers.saveFailed, (api) =>\n api.save(action.payload as Entity).pipe(Effect.flatMap((entity) => $.dispatchers.saveSucceeded(entity))),\n ),\n )\n\n yield* $.onAction('remove').runFork((action) =>\n runWithApi($.dispatchers.removeFailed, (api) =>\n api.remove(action.payload as EntityId).pipe(\n Effect.flatMap(() => $.dispatchers.removeSucceeded(action.payload as EntityId)),\n ),\n ),\n )\n }),\n { id: 'install' },\n )\n\n const controller = {\n make: (\n runtime: Logix.ModuleRuntime<\n CrudState<Entity, QueryInput>,\n CrudAction<Entity, QueryInput, EntityId, ExtraActions>\n >,\n ): CrudController<Entity, QueryInput, EntityId, ExtraActions> => ({\n runtime,\n getState: runtime.getState,\n dispatch: runtime.dispatch,\n controller: {\n list: (input: QueryInput) =>\n runtime.dispatch({ _tag: 'query', payload: input } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n save: (entity: Entity) =>\n runtime.dispatch({ _tag: 'save', payload: entity } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n remove: (id: EntityId) =>\n runtime.dispatch({ _tag: 'remove', payload: id } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n clearError: () =>\n runtime.dispatch({ _tag: 'clearError' } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n idField,\n },\n }),\n }\n\n const EXTEND_HANDLE = Symbol.for('logix.module.handle.extend')\n ;(module.tag as unknown as Record<PropertyKey, unknown>)[EXTEND_HANDLE] = (\n runtime: Logix.ModuleRuntime<CrudState<Entity, QueryInput>, CrudAction<Entity, QueryInput, EntityId, ExtraActions>>,\n base: Logix.ModuleHandle<Logix.AnyModuleShape>,\n ) => {\n const crud = controller.make(runtime)\n return {\n ...base,\n controller: crud.controller,\n services,\n }\n }\n\n return module.implement({\n initial: {\n items: Array.from(spec.initial ?? []),\n loading: false,\n error: undefined,\n lastQuery: undefined,\n total: undefined,\n } as CrudState<Entity, QueryInput>,\n logics: [install],\n }) as unknown as CrudModule<Id, Entity, QueryInput, EntityId, ExtraActions>\n}\n\nexport const CRUDModule = Logix.Module.Manage.make({\n kind: 'crud',\n define: defineCrud,\n})\n"],"mappings":"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","names":["id"]}
|
package/dist/index.cjs
CHANGED
|
@@ -85,7 +85,7 @@ var defineCrud = (id, spec, extend) => {
|
|
|
85
85
|
const QuerySchema = spec.query ?? DefaultQueryInputSchema;
|
|
86
86
|
const IdSchema = spec.id ?? import_effect.Schema.String;
|
|
87
87
|
const idField = spec.idField ?? "id";
|
|
88
|
-
class Api extends import_effect.
|
|
88
|
+
class Api extends import_effect.ServiceMap.Service()(`${id}/crud/api`) {
|
|
89
89
|
}
|
|
90
90
|
const services = { api: Api };
|
|
91
91
|
const Actions = makeActions(spec.entity, QuerySchema, IdSchema);
|
|
@@ -211,47 +211,37 @@ var defineCrud = (id, spec, extend) => {
|
|
|
211
211
|
) : Logix.Module.make(id, def);
|
|
212
212
|
const install = module2.logic(
|
|
213
213
|
($) => import_effect.Effect.gen(function* () {
|
|
214
|
+
const missingApiMessage = `[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`;
|
|
215
|
+
const runWithApi = (dispatchFailed, run) => import_effect.Effect.gen(function* () {
|
|
216
|
+
const apiOpt = yield* import_effect.Effect.serviceOption(services.api);
|
|
217
|
+
if (import_effect.Option.isNone(apiOpt)) {
|
|
218
|
+
yield* dispatchFailed(missingApiMessage);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
yield* run(apiOpt.value);
|
|
222
|
+
}).pipe(
|
|
223
|
+
import_effect.Effect.catch((error) => dispatchFailed(toErrorMessage(error))),
|
|
224
|
+
import_effect.Effect.asVoid
|
|
225
|
+
);
|
|
214
226
|
yield* $.onAction("query").runFork(
|
|
215
|
-
(action) =>
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
`[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`
|
|
220
|
-
);
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
const api = apiOpt.value;
|
|
224
|
-
const result = yield* api.list(action.payload);
|
|
225
|
-
yield* $.dispatchers.querySucceeded(result);
|
|
226
|
-
}).pipe(import_effect.Effect.catchAll((e) => $.dispatchers.queryFailed(toErrorMessage(e))))
|
|
227
|
+
(action) => runWithApi(
|
|
228
|
+
$.dispatchers.queryFailed,
|
|
229
|
+
(api) => api.list(action.payload).pipe(import_effect.Effect.flatMap((result) => $.dispatchers.querySucceeded(result)))
|
|
230
|
+
)
|
|
227
231
|
);
|
|
228
232
|
yield* $.onAction("save").runFork(
|
|
229
|
-
(action) =>
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
`[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`
|
|
234
|
-
);
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
const api = apiOpt.value;
|
|
238
|
-
const entity = yield* api.save(action.payload);
|
|
239
|
-
yield* $.dispatchers.saveSucceeded(entity);
|
|
240
|
-
}).pipe(import_effect.Effect.catchAll((e) => $.dispatchers.saveFailed(toErrorMessage(e))))
|
|
233
|
+
(action) => runWithApi(
|
|
234
|
+
$.dispatchers.saveFailed,
|
|
235
|
+
(api) => api.save(action.payload).pipe(import_effect.Effect.flatMap((entity) => $.dispatchers.saveSucceeded(entity)))
|
|
236
|
+
)
|
|
241
237
|
);
|
|
242
238
|
yield* $.onAction("remove").runFork(
|
|
243
|
-
(action) =>
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
const api = apiOpt.value;
|
|
252
|
-
yield* api.remove(action.payload);
|
|
253
|
-
yield* $.dispatchers.removeSucceeded(action.payload);
|
|
254
|
-
}).pipe(import_effect.Effect.catchAll((e) => $.dispatchers.removeFailed(toErrorMessage(e))))
|
|
239
|
+
(action) => runWithApi(
|
|
240
|
+
$.dispatchers.removeFailed,
|
|
241
|
+
(api) => api.remove(action.payload).pipe(
|
|
242
|
+
import_effect.Effect.flatMap(() => $.dispatchers.removeSucceeded(action.payload))
|
|
243
|
+
)
|
|
244
|
+
)
|
|
255
245
|
);
|
|
256
246
|
}),
|
|
257
247
|
{ id: "install" }
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/internal/crud/Crud.ts"],"sourcesContent":["export * from './Crud.js'\nexport type * from './Crud.js'\n","import * as Logix from '@logixjs/core'\nimport { Context, Effect, Option, Schema } from 'effect'\n\nconst DefaultQueryInputSchema = Schema.Struct({\n pageSize: Schema.Number,\n})\n\nexport type CrudDefaultQueryInput = Schema.Schema.Type<typeof DefaultQueryInputSchema>\n\nexport interface CrudQueryResult<Entity> {\n readonly items: ReadonlyArray<Entity>\n readonly total?: number\n}\n\nexport interface CrudApi<Entity extends object, QueryInput, Id> {\n readonly list: (input: QueryInput) => Effect.Effect<CrudQueryResult<Entity>, unknown, never>\n readonly save: (entity: Entity) => Effect.Effect<Entity, unknown, never>\n readonly remove: (id: Id) => Effect.Effect<void, unknown, never>\n}\n\nexport interface CrudSpec<Entity extends object, QueryInput = CrudDefaultQueryInput, Id = string> {\n readonly entity: Schema.Schema<Entity>\n readonly query?: Schema.Schema<QueryInput>\n readonly id?: Schema.Schema<Id>\n readonly initial?: ReadonlyArray<Entity>\n /**\n * idField:\n * - The default primary key field for upsert/remove in reducers (default: \"id\").\n * - For more complex primary-key strategies, prefer handling results in a custom upper-layer Logic and writing back to state.\n */\n readonly idField?: string\n}\n\nexport type CrudState<Entity, QueryInput> = {\n readonly items: ReadonlyArray<Entity>\n readonly loading: boolean\n readonly error: string | undefined\n readonly lastQuery: QueryInput | undefined\n readonly total: number | undefined\n}\n\nexport type CrudActionMap<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = {\n readonly query: Schema.Schema<QueryInput>\n readonly querySucceeded: Schema.Schema<CrudQueryResult<Entity>>\n readonly queryFailed: Schema.Schema<string>\n\n readonly save: Schema.Schema<Entity>\n readonly saveSucceeded: Schema.Schema<Entity>\n readonly saveFailed: Schema.Schema<string>\n\n readonly remove: Schema.Schema<Id>\n readonly removeSucceeded: Schema.Schema<Id>\n readonly removeFailed: Schema.Schema<string>\n\n readonly clearError: Schema.Schema<void>\n} & ExtraActions\n\nexport type CrudAction<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = Logix.ActionsFromMap<CrudActionMap<Entity, QueryInput, Id, ExtraActions>>\n\nexport type CrudShape<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = Logix.Shape<Schema.Schema<CrudState<Entity, QueryInput>>, CrudActionMap<Entity, QueryInput, Id, ExtraActions>>\n\nexport type CrudServices<Entity extends object, QueryInput, Id> = {\n readonly api: Logix.State.Tag<CrudApi<Entity, QueryInput, Id>>\n}\n\nexport type CrudHandleExt<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = {\n readonly controller: CrudController<Entity, QueryInput, Id, ExtraActions>['controller']\n readonly services: CrudServices<Entity, QueryInput, Id>\n}\n\nexport interface CrudController<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> {\n readonly runtime: Logix.ModuleRuntime<CrudState<Entity, QueryInput>, CrudAction<Entity, QueryInput, Id, ExtraActions>>\n readonly getState: Effect.Effect<CrudState<Entity, QueryInput>>\n readonly dispatch: (action: CrudAction<Entity, QueryInput, Id, ExtraActions>) => Effect.Effect<void>\n readonly controller: {\n readonly list: (input: QueryInput) => Effect.Effect<void>\n readonly save: (entity: Entity) => Effect.Effect<void>\n readonly remove: (id: Id) => Effect.Effect<void>\n readonly clearError: () => Effect.Effect<void>\n readonly idField: string\n }\n}\n\nconst makeActions = <Entity extends object, QueryInput, Id>(\n entity: Schema.Schema<Entity>,\n query: Schema.Schema<QueryInput>,\n id: Schema.Schema<Id>,\n): CrudActionMap<Entity, QueryInput, Id> =>\n ({\n query,\n querySucceeded: Schema.Struct({\n items: Schema.Array(entity),\n total: Schema.optional(Schema.Number),\n }) as Schema.Schema<CrudQueryResult<Entity>>,\n queryFailed: Schema.String,\n\n save: entity,\n saveSucceeded: entity,\n saveFailed: Schema.String,\n\n remove: id,\n removeSucceeded: id,\n removeFailed: Schema.String,\n\n clearError: Schema.Void,\n }) satisfies CrudActionMap<Entity, QueryInput, Id>\n\nconst toErrorMessage = (error: unknown): string => {\n if (error === null || error === undefined) return 'unknown error'\n if (typeof error === 'string') return error\n if (error instanceof Error && typeof error.message === 'string' && error.message.length > 0) {\n return error.message\n }\n if (typeof error === 'object') {\n if ('message' in error) {\n const message = (error as { readonly message?: unknown }).message\n if (typeof message === 'string' && message.length > 0) return message\n }\n try {\n const json = JSON.stringify(error)\n if (typeof json === 'string' && json.length > 0) return json\n } catch {\n // ignore\n }\n }\n return String(error)\n}\n\nconst upsertByIdField = <Entity extends object>(\n items: ReadonlyArray<Entity>,\n entity: Entity,\n idField: string,\n): ReadonlyArray<Entity> => {\n const id = (entity as Record<string, unknown>)[idField]\n const idx = items.findIndex((x) => (x as Record<string, unknown>)[idField] === id)\n if (idx < 0) return [...items, entity]\n return items.map((x, i) => (i === idx ? entity : x))\n}\n\nconst removeByIdField = <Entity extends object>(\n items: ReadonlyArray<Entity>,\n id: unknown,\n idField: string,\n): ReadonlyArray<Entity> => items.filter((x) => (x as Record<string, unknown>)[idField] !== id)\n\nexport type CrudModule<\n Id extends string,\n Entity extends object,\n QueryInput,\n EntityId,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = Logix.Module.Module<\n Id,\n CrudShape<Entity, QueryInput, EntityId, ExtraActions>,\n CrudHandleExt<Entity, QueryInput, EntityId, ExtraActions>,\n unknown\n> & {\n readonly _kind: 'Module'\n readonly services: CrudServices<Entity, QueryInput, EntityId>\n}\n\nconst defineCrud = <\n Id extends string,\n Entity extends object,\n QueryInput = CrudDefaultQueryInput,\n EntityId = string,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n>(\n id: Id,\n spec: CrudSpec<Entity, QueryInput, EntityId>,\n extend?: Logix.Module.MakeExtendDef<\n Schema.Schema<CrudState<Entity, QueryInput>>,\n CrudActionMap<Entity, QueryInput, EntityId>,\n ExtraActions\n >,\n): CrudModule<Id, Entity, QueryInput, EntityId, ExtraActions> => {\n const QuerySchema = (spec.query ?? DefaultQueryInputSchema) as Schema.Schema<QueryInput>\n const IdSchema = (spec.id ?? Schema.String) as Schema.Schema<EntityId>\n\n const idField = spec.idField ?? 'id'\n\n class Api extends Context.Tag(`${id}/crud/api`)<Api, CrudApi<Entity, QueryInput, EntityId>>() {}\n\n const services = { api: Api } as const satisfies CrudServices<Entity, QueryInput, EntityId>\n\n const Actions = makeActions(spec.entity, QuerySchema, IdSchema)\n\n const StateSchema = Schema.Struct({\n items: Schema.Array(spec.entity),\n loading: Schema.Boolean,\n error: Schema.UndefinedOr(Schema.String),\n lastQuery: Schema.UndefinedOr(QuerySchema),\n total: Schema.UndefinedOr(Schema.Number),\n })\n\n type Reducers = Logix.ReducersFromMap<typeof StateSchema, CrudActionMap<Entity, QueryInput, EntityId>>\n\n const reducers: Reducers = {\n query: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('lastQuery')\n return {\n ...state,\n loading: true,\n error: undefined,\n lastQuery: action.payload,\n }\n },\n querySucceeded: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('items')\n sink?.('total')\n return {\n ...state,\n loading: false,\n error: undefined,\n items: action.payload.items,\n total: action.payload.total,\n }\n },\n queryFailed: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: false,\n error: action.payload,\n }\n },\n\n save: (state, _action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: true,\n error: undefined,\n }\n },\n saveSucceeded: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('items')\n return {\n ...state,\n loading: false,\n error: undefined,\n items: upsertByIdField(state.items, action.payload as Entity, idField),\n }\n },\n saveFailed: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: false,\n error: action.payload,\n }\n },\n\n remove: (state, _action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: true,\n error: undefined,\n }\n },\n removeSucceeded: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('items')\n return {\n ...state,\n loading: false,\n error: undefined,\n items: removeByIdField(state.items, action.payload, idField),\n }\n },\n removeFailed: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: false,\n error: action.payload,\n }\n },\n\n clearError: (state, _action, sink) => {\n sink?.('error')\n return {\n ...state,\n error: undefined,\n }\n },\n }\n\n const def = {\n state: StateSchema,\n actions: Actions,\n reducers,\n schemas: { entity: spec.entity },\n meta: { kind: 'crud', idField },\n services,\n }\n\n const module = extend\n ? Logix.Module.make<\n Id,\n typeof StateSchema,\n CrudActionMap<Entity, QueryInput, EntityId>,\n CrudHandleExt<Entity, QueryInput, EntityId, ExtraActions>\n >(\n id,\n def,\n extend as unknown as Logix.Module.MakeExtendDef<\n typeof StateSchema,\n CrudActionMap<Entity, QueryInput, EntityId>,\n ExtraActions\n >,\n )\n : Logix.Module.make<\n Id,\n typeof StateSchema,\n CrudActionMap<Entity, QueryInput, EntityId>,\n CrudHandleExt<Entity, QueryInput, EntityId, ExtraActions>\n >(id, def)\n\n const install = module.logic(\n ($) =>\n Effect.gen(function* () {\n yield* $.onAction('query').runFork((action) =>\n Effect.gen(function* () {\n const apiOpt = yield* Effect.serviceOption(services.api)\n if (Option.isNone(apiOpt)) {\n yield* $.dispatchers.queryFailed(\n `[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`,\n )\n return\n }\n const api = apiOpt.value\n\n const result = yield* api.list(action.payload as QueryInput)\n yield* $.dispatchers.querySucceeded(result)\n }).pipe(Effect.catchAll((e) => $.dispatchers.queryFailed(toErrorMessage(e)))),\n )\n\n yield* $.onAction('save').runFork((action) =>\n Effect.gen(function* () {\n const apiOpt = yield* Effect.serviceOption(services.api)\n if (Option.isNone(apiOpt)) {\n yield* $.dispatchers.saveFailed(\n `[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`,\n )\n return\n }\n const api = apiOpt.value\n\n const entity = yield* api.save(action.payload as Entity)\n yield* $.dispatchers.saveSucceeded(entity)\n }).pipe(Effect.catchAll((e) => $.dispatchers.saveFailed(toErrorMessage(e)))),\n )\n\n yield* $.onAction('remove').runFork((action) =>\n Effect.gen(function* () {\n const apiOpt = yield* Effect.serviceOption(services.api)\n if (Option.isNone(apiOpt)) {\n yield* $.dispatchers.removeFailed(\n `[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`,\n )\n return\n }\n const api = apiOpt.value\n\n yield* api.remove(action.payload as EntityId)\n yield* $.dispatchers.removeSucceeded(action.payload as EntityId)\n }).pipe(Effect.catchAll((e) => $.dispatchers.removeFailed(toErrorMessage(e)))),\n )\n }),\n { id: 'install' },\n )\n\n const controller = {\n make: (\n runtime: Logix.ModuleRuntime<\n CrudState<Entity, QueryInput>,\n CrudAction<Entity, QueryInput, EntityId, ExtraActions>\n >,\n ): CrudController<Entity, QueryInput, EntityId, ExtraActions> => ({\n runtime,\n getState: runtime.getState,\n dispatch: runtime.dispatch,\n controller: {\n list: (input: QueryInput) =>\n runtime.dispatch({ _tag: 'query', payload: input } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n save: (entity: Entity) =>\n runtime.dispatch({ _tag: 'save', payload: entity } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n remove: (id: EntityId) =>\n runtime.dispatch({ _tag: 'remove', payload: id } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n clearError: () =>\n runtime.dispatch({ _tag: 'clearError' } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n idField,\n },\n }),\n }\n\n const EXTEND_HANDLE = Symbol.for('logix.module.handle.extend')\n ;(module.tag as unknown as Record<PropertyKey, unknown>)[EXTEND_HANDLE] = (\n runtime: Logix.ModuleRuntime<CrudState<Entity, QueryInput>, CrudAction<Entity, QueryInput, EntityId, ExtraActions>>,\n base: Logix.ModuleHandle<Logix.AnyModuleShape>,\n ) => {\n const crud = controller.make(runtime)\n return {\n ...base,\n controller: crud.controller,\n services,\n }\n }\n\n return module.implement({\n initial: {\n items: Array.from(spec.initial ?? []),\n loading: false,\n error: undefined,\n lastQuery: undefined,\n total: undefined,\n } as CrudState<Entity, QueryInput>,\n logics: [install],\n }) as unknown as CrudModule<Id, Entity, QueryInput, EntityId, ExtraActions>\n}\n\nexport const CRUDModule = Logix.Module.Manage.make({\n kind: 'crud',\n define: defineCrud,\n})\n"],"mappings":"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","names":["module","id"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/internal/crud/Crud.ts"],"sourcesContent":["export * from './Crud.js'\nexport type * from './Crud.js'\n","import * as Logix from '@logixjs/core'\nimport { Effect, Option, Schema, ServiceMap } from 'effect'\n\nconst DefaultQueryInputSchema = Schema.Struct({\n pageSize: Schema.Number,\n})\n\nexport type CrudDefaultQueryInput = Schema.Schema.Type<typeof DefaultQueryInputSchema>\n\nexport interface CrudQueryResult<Entity> {\n readonly items: ReadonlyArray<Entity>\n readonly total?: number\n}\n\nexport interface CrudApi<Entity extends object, QueryInput, Id> {\n readonly list: (input: QueryInput) => Effect.Effect<CrudQueryResult<Entity>, unknown, never>\n readonly save: (entity: Entity) => Effect.Effect<Entity, unknown, never>\n readonly remove: (id: Id) => Effect.Effect<void, unknown, never>\n}\n\nexport interface CrudSpec<Entity extends object, QueryInput = CrudDefaultQueryInput, Id = string> {\n readonly entity: Schema.Schema<Entity>\n readonly query?: Schema.Schema<QueryInput>\n readonly id?: Schema.Schema<Id>\n readonly initial?: ReadonlyArray<Entity>\n /**\n * idField:\n * - The default primary key field for upsert/remove in reducers (default: \"id\").\n * - For more complex primary-key strategies, prefer handling results in a custom upper-layer Logic and writing back to state.\n */\n readonly idField?: string\n}\n\nexport type CrudState<Entity, QueryInput> = {\n readonly items: ReadonlyArray<Entity>\n readonly loading: boolean\n readonly error: string | undefined\n readonly lastQuery: QueryInput | undefined\n readonly total: number | undefined\n}\n\nexport type CrudActionMap<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = {\n readonly query: Schema.Schema<QueryInput>\n readonly querySucceeded: Schema.Schema<CrudQueryResult<Entity>>\n readonly queryFailed: Schema.Schema<string>\n\n readonly save: Schema.Schema<Entity>\n readonly saveSucceeded: Schema.Schema<Entity>\n readonly saveFailed: Schema.Schema<string>\n\n readonly remove: Schema.Schema<Id>\n readonly removeSucceeded: Schema.Schema<Id>\n readonly removeFailed: Schema.Schema<string>\n\n readonly clearError: Schema.Schema<void>\n} & ExtraActions\n\nexport type CrudAction<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = Logix.ActionsFromMap<CrudActionMap<Entity, QueryInput, Id, ExtraActions>>\n\nexport type CrudShape<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = Logix.Shape<Schema.Schema<CrudState<Entity, QueryInput>>, CrudActionMap<Entity, QueryInput, Id, ExtraActions>>\n\nexport type CrudServices<Entity extends object, QueryInput, Id> = {\n readonly api: ServiceMap.Key<any, CrudApi<Entity, QueryInput, Id>>\n}\n\nexport type CrudHandleExt<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = {\n readonly controller: CrudController<Entity, QueryInput, Id, ExtraActions>['controller']\n readonly services: CrudServices<Entity, QueryInput, Id>\n}\n\nexport interface CrudController<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> {\n readonly runtime: Logix.ModuleRuntime<CrudState<Entity, QueryInput>, CrudAction<Entity, QueryInput, Id, ExtraActions>>\n readonly getState: Effect.Effect<CrudState<Entity, QueryInput>>\n readonly dispatch: (action: CrudAction<Entity, QueryInput, Id, ExtraActions>) => Effect.Effect<void>\n readonly controller: {\n readonly list: (input: QueryInput) => Effect.Effect<void>\n readonly save: (entity: Entity) => Effect.Effect<void>\n readonly remove: (id: Id) => Effect.Effect<void>\n readonly clearError: () => Effect.Effect<void>\n readonly idField: string\n }\n}\n\nconst makeActions = <Entity extends object, QueryInput, Id>(\n entity: Schema.Schema<Entity>,\n query: Schema.Schema<QueryInput>,\n id: Schema.Schema<Id>,\n): CrudActionMap<Entity, QueryInput, Id> =>\n ({\n query,\n querySucceeded: Schema.Struct({\n items: Schema.Array(entity),\n total: Schema.optional(Schema.Number),\n }) as Schema.Schema<CrudQueryResult<Entity>>,\n queryFailed: Schema.String,\n\n save: entity,\n saveSucceeded: entity,\n saveFailed: Schema.String,\n\n remove: id,\n removeSucceeded: id,\n removeFailed: Schema.String,\n\n clearError: Schema.Void,\n }) satisfies CrudActionMap<Entity, QueryInput, Id>\n\nconst toErrorMessage = (error: unknown): string => {\n if (error === null || error === undefined) return 'unknown error'\n if (typeof error === 'string') return error\n if (error instanceof Error && typeof error.message === 'string' && error.message.length > 0) {\n return error.message\n }\n if (typeof error === 'object') {\n if ('message' in error) {\n const message = (error as { readonly message?: unknown }).message\n if (typeof message === 'string' && message.length > 0) return message\n }\n try {\n const json = JSON.stringify(error)\n if (typeof json === 'string' && json.length > 0) return json\n } catch {\n // ignore\n }\n }\n return String(error)\n}\n\nconst upsertByIdField = <Entity extends object>(\n items: ReadonlyArray<Entity>,\n entity: Entity,\n idField: string,\n): ReadonlyArray<Entity> => {\n const id = (entity as Record<string, unknown>)[idField]\n const idx = items.findIndex((x) => (x as Record<string, unknown>)[idField] === id)\n if (idx < 0) return [...items, entity]\n return items.map((x, i) => (i === idx ? entity : x))\n}\n\nconst removeByIdField = <Entity extends object>(\n items: ReadonlyArray<Entity>,\n id: unknown,\n idField: string,\n): ReadonlyArray<Entity> => items.filter((x) => (x as Record<string, unknown>)[idField] !== id)\n\nexport type CrudModule<\n Id extends string,\n Entity extends object,\n QueryInput,\n EntityId,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = Logix.Module.Module<\n Id,\n CrudShape<Entity, QueryInput, EntityId, ExtraActions>,\n CrudHandleExt<Entity, QueryInput, EntityId, ExtraActions>,\n unknown\n> & {\n readonly _kind: 'Module'\n readonly services: CrudServices<Entity, QueryInput, EntityId>\n}\n\nconst defineCrud = <\n Id extends string,\n Entity extends object,\n QueryInput = CrudDefaultQueryInput,\n EntityId = string,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n>(\n id: Id,\n spec: CrudSpec<Entity, QueryInput, EntityId>,\n extend?: Logix.Module.MakeExtendDef<\n Schema.Schema<CrudState<Entity, QueryInput>>,\n CrudActionMap<Entity, QueryInput, EntityId>,\n ExtraActions\n >,\n): CrudModule<Id, Entity, QueryInput, EntityId, ExtraActions> => {\n const QuerySchema = (spec.query ?? DefaultQueryInputSchema) as Schema.Schema<QueryInput>\n const IdSchema = (spec.id ?? Schema.String) as Schema.Schema<EntityId>\n\n const idField = spec.idField ?? 'id'\n\n class Api extends ServiceMap.Service<Api, CrudApi<Entity, QueryInput, EntityId>>()( `${id}/crud/api`) {}\n\n const services = { api: Api } as const satisfies CrudServices<Entity, QueryInput, EntityId>\n\n const Actions = makeActions(spec.entity, QuerySchema, IdSchema)\n\n const StateSchema = Schema.Struct({\n items: Schema.Array(spec.entity),\n loading: Schema.Boolean,\n error: Schema.UndefinedOr(Schema.String),\n lastQuery: Schema.UndefinedOr(QuerySchema),\n total: Schema.UndefinedOr(Schema.Number),\n })\n\n type Reducers = Logix.ReducersFromMap<typeof StateSchema, CrudActionMap<Entity, QueryInput, EntityId>>\n\n const reducers: Reducers = {\n query: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('lastQuery')\n return {\n ...state,\n loading: true,\n error: undefined,\n lastQuery: action.payload,\n }\n },\n querySucceeded: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('items')\n sink?.('total')\n return {\n ...state,\n loading: false,\n error: undefined,\n items: action.payload.items,\n total: action.payload.total,\n }\n },\n queryFailed: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: false,\n error: action.payload,\n }\n },\n\n save: (state, _action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: true,\n error: undefined,\n }\n },\n saveSucceeded: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('items')\n return {\n ...state,\n loading: false,\n error: undefined,\n items: upsertByIdField(state.items, action.payload as Entity, idField),\n }\n },\n saveFailed: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: false,\n error: action.payload,\n }\n },\n\n remove: (state, _action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: true,\n error: undefined,\n }\n },\n removeSucceeded: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('items')\n return {\n ...state,\n loading: false,\n error: undefined,\n items: removeByIdField(state.items, action.payload, idField),\n }\n },\n removeFailed: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: false,\n error: action.payload,\n }\n },\n\n clearError: (state, _action, sink) => {\n sink?.('error')\n return {\n ...state,\n error: undefined,\n }\n },\n }\n\n const def = {\n state: StateSchema,\n actions: Actions,\n reducers,\n schemas: { entity: spec.entity },\n meta: { kind: 'crud', idField },\n services,\n }\n\n const module = extend\n ? Logix.Module.make<\n Id,\n typeof StateSchema,\n CrudActionMap<Entity, QueryInput, EntityId>,\n CrudHandleExt<Entity, QueryInput, EntityId, ExtraActions>\n >(\n id,\n def,\n extend as unknown as Logix.Module.MakeExtendDef<\n typeof StateSchema,\n CrudActionMap<Entity, QueryInput, EntityId>,\n ExtraActions\n >,\n )\n : Logix.Module.make<\n Id,\n typeof StateSchema,\n CrudActionMap<Entity, QueryInput, EntityId>,\n CrudHandleExt<Entity, QueryInput, EntityId, ExtraActions>\n >(id, def)\n\n const install = module.logic(\n ($) =>\n Effect.gen(function* () {\n const missingApiMessage = `[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`\n\n const runWithApi = (\n dispatchFailed: (message: string) => Effect.Effect<void, never, any>,\n run: (api: CrudApi<Entity, QueryInput, EntityId>) => Effect.Effect<unknown, unknown, any>,\n ): Effect.Effect<void, never, any> =>\n Effect.gen(function* () {\n const apiOpt = yield* Effect.serviceOption(services.api as ServiceMap.Key<any, CrudApi<Entity, QueryInput, EntityId>>)\n if (Option.isNone(apiOpt)) {\n yield* dispatchFailed(missingApiMessage)\n return\n }\n yield* run(apiOpt.value)\n }).pipe(\n Effect.catch((error) => dispatchFailed(toErrorMessage(error))),\n Effect.asVoid,\n )\n\n yield* $.onAction('query').runFork((action) =>\n runWithApi($.dispatchers.queryFailed, (api) =>\n api.list(action.payload as QueryInput).pipe(Effect.flatMap((result) => $.dispatchers.querySucceeded(result))),\n ),\n )\n\n yield* $.onAction('save').runFork((action) =>\n runWithApi($.dispatchers.saveFailed, (api) =>\n api.save(action.payload as Entity).pipe(Effect.flatMap((entity) => $.dispatchers.saveSucceeded(entity))),\n ),\n )\n\n yield* $.onAction('remove').runFork((action) =>\n runWithApi($.dispatchers.removeFailed, (api) =>\n api.remove(action.payload as EntityId).pipe(\n Effect.flatMap(() => $.dispatchers.removeSucceeded(action.payload as EntityId)),\n ),\n ),\n )\n }),\n { id: 'install' },\n )\n\n const controller = {\n make: (\n runtime: Logix.ModuleRuntime<\n CrudState<Entity, QueryInput>,\n CrudAction<Entity, QueryInput, EntityId, ExtraActions>\n >,\n ): CrudController<Entity, QueryInput, EntityId, ExtraActions> => ({\n runtime,\n getState: runtime.getState,\n dispatch: runtime.dispatch,\n controller: {\n list: (input: QueryInput) =>\n runtime.dispatch({ _tag: 'query', payload: input } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n save: (entity: Entity) =>\n runtime.dispatch({ _tag: 'save', payload: entity } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n remove: (id: EntityId) =>\n runtime.dispatch({ _tag: 'remove', payload: id } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n clearError: () =>\n runtime.dispatch({ _tag: 'clearError' } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n idField,\n },\n }),\n }\n\n const EXTEND_HANDLE = Symbol.for('logix.module.handle.extend')\n ;(module.tag as unknown as Record<PropertyKey, unknown>)[EXTEND_HANDLE] = (\n runtime: Logix.ModuleRuntime<CrudState<Entity, QueryInput>, CrudAction<Entity, QueryInput, EntityId, ExtraActions>>,\n base: Logix.ModuleHandle<Logix.AnyModuleShape>,\n ) => {\n const crud = controller.make(runtime)\n return {\n ...base,\n controller: crud.controller,\n services,\n }\n }\n\n return module.implement({\n initial: {\n items: Array.from(spec.initial ?? []),\n loading: false,\n error: undefined,\n lastQuery: undefined,\n total: undefined,\n } as CrudState<Entity, QueryInput>,\n logics: [install],\n }) as unknown as CrudModule<Id, Entity, QueryInput, EntityId, ExtraActions>\n}\n\nexport const CRUDModule = Logix.Module.Manage.make({\n kind: 'crud',\n define: defineCrud,\n})\n"],"mappings":"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","names":["module","id"]}
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logixjs/domain",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "https://github.com/yoyooyooo/logix",
|
|
7
|
+
"directory": "packages/domain"
|
|
8
|
+
},
|
|
4
9
|
"license": "Apache-2.0",
|
|
5
10
|
"description": "Domain modules (based on Logix.Module.Manage) for Logix v3",
|
|
6
11
|
"files": [
|
|
@@ -28,18 +33,18 @@
|
|
|
28
33
|
"access": "public"
|
|
29
34
|
},
|
|
30
35
|
"dependencies": {
|
|
31
|
-
"effect": "
|
|
32
|
-
"@logixjs/core": "0.0
|
|
36
|
+
"effect": "4.0.0-beta.28",
|
|
37
|
+
"@logixjs/core": "1.0.0"
|
|
33
38
|
},
|
|
34
39
|
"devDependencies": {
|
|
35
|
-
"@effect/vitest": "
|
|
40
|
+
"@effect/vitest": "4.0.0-beta.28",
|
|
36
41
|
"tsup": "^8.0.0",
|
|
37
42
|
"typescript": "^5.8.2",
|
|
38
43
|
"vitest": "^4.0.15"
|
|
39
44
|
},
|
|
40
45
|
"peerDependencies": {
|
|
41
|
-
"effect": "
|
|
42
|
-
"@logixjs/core": "0.0
|
|
46
|
+
"effect": "4.0.0-beta.28",
|
|
47
|
+
"@logixjs/core": "1.0.0"
|
|
43
48
|
},
|
|
44
49
|
"scripts": {
|
|
45
50
|
"build": "tsup --format cjs,esm --dts",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/internal/crud/Crud.ts"],"sourcesContent":["import * as Logix from '@logixjs/core'\nimport { Context, Effect, Option, Schema } from 'effect'\n\nconst DefaultQueryInputSchema = Schema.Struct({\n pageSize: Schema.Number,\n})\n\nexport type CrudDefaultQueryInput = Schema.Schema.Type<typeof DefaultQueryInputSchema>\n\nexport interface CrudQueryResult<Entity> {\n readonly items: ReadonlyArray<Entity>\n readonly total?: number\n}\n\nexport interface CrudApi<Entity extends object, QueryInput, Id> {\n readonly list: (input: QueryInput) => Effect.Effect<CrudQueryResult<Entity>, unknown, never>\n readonly save: (entity: Entity) => Effect.Effect<Entity, unknown, never>\n readonly remove: (id: Id) => Effect.Effect<void, unknown, never>\n}\n\nexport interface CrudSpec<Entity extends object, QueryInput = CrudDefaultQueryInput, Id = string> {\n readonly entity: Schema.Schema<Entity>\n readonly query?: Schema.Schema<QueryInput>\n readonly id?: Schema.Schema<Id>\n readonly initial?: ReadonlyArray<Entity>\n /**\n * idField:\n * - The default primary key field for upsert/remove in reducers (default: \"id\").\n * - For more complex primary-key strategies, prefer handling results in a custom upper-layer Logic and writing back to state.\n */\n readonly idField?: string\n}\n\nexport type CrudState<Entity, QueryInput> = {\n readonly items: ReadonlyArray<Entity>\n readonly loading: boolean\n readonly error: string | undefined\n readonly lastQuery: QueryInput | undefined\n readonly total: number | undefined\n}\n\nexport type CrudActionMap<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = {\n readonly query: Schema.Schema<QueryInput>\n readonly querySucceeded: Schema.Schema<CrudQueryResult<Entity>>\n readonly queryFailed: Schema.Schema<string>\n\n readonly save: Schema.Schema<Entity>\n readonly saveSucceeded: Schema.Schema<Entity>\n readonly saveFailed: Schema.Schema<string>\n\n readonly remove: Schema.Schema<Id>\n readonly removeSucceeded: Schema.Schema<Id>\n readonly removeFailed: Schema.Schema<string>\n\n readonly clearError: Schema.Schema<void>\n} & ExtraActions\n\nexport type CrudAction<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = Logix.ActionsFromMap<CrudActionMap<Entity, QueryInput, Id, ExtraActions>>\n\nexport type CrudShape<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = Logix.Shape<Schema.Schema<CrudState<Entity, QueryInput>>, CrudActionMap<Entity, QueryInput, Id, ExtraActions>>\n\nexport type CrudServices<Entity extends object, QueryInput, Id> = {\n readonly api: Logix.State.Tag<CrudApi<Entity, QueryInput, Id>>\n}\n\nexport type CrudHandleExt<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = {\n readonly controller: CrudController<Entity, QueryInput, Id, ExtraActions>['controller']\n readonly services: CrudServices<Entity, QueryInput, Id>\n}\n\nexport interface CrudController<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> {\n readonly runtime: Logix.ModuleRuntime<CrudState<Entity, QueryInput>, CrudAction<Entity, QueryInput, Id, ExtraActions>>\n readonly getState: Effect.Effect<CrudState<Entity, QueryInput>>\n readonly dispatch: (action: CrudAction<Entity, QueryInput, Id, ExtraActions>) => Effect.Effect<void>\n readonly controller: {\n readonly list: (input: QueryInput) => Effect.Effect<void>\n readonly save: (entity: Entity) => Effect.Effect<void>\n readonly remove: (id: Id) => Effect.Effect<void>\n readonly clearError: () => Effect.Effect<void>\n readonly idField: string\n }\n}\n\nconst makeActions = <Entity extends object, QueryInput, Id>(\n entity: Schema.Schema<Entity>,\n query: Schema.Schema<QueryInput>,\n id: Schema.Schema<Id>,\n): CrudActionMap<Entity, QueryInput, Id> =>\n ({\n query,\n querySucceeded: Schema.Struct({\n items: Schema.Array(entity),\n total: Schema.optional(Schema.Number),\n }) as Schema.Schema<CrudQueryResult<Entity>>,\n queryFailed: Schema.String,\n\n save: entity,\n saveSucceeded: entity,\n saveFailed: Schema.String,\n\n remove: id,\n removeSucceeded: id,\n removeFailed: Schema.String,\n\n clearError: Schema.Void,\n }) satisfies CrudActionMap<Entity, QueryInput, Id>\n\nconst toErrorMessage = (error: unknown): string => {\n if (error === null || error === undefined) return 'unknown error'\n if (typeof error === 'string') return error\n if (error instanceof Error && typeof error.message === 'string' && error.message.length > 0) {\n return error.message\n }\n if (typeof error === 'object') {\n if ('message' in error) {\n const message = (error as { readonly message?: unknown }).message\n if (typeof message === 'string' && message.length > 0) return message\n }\n try {\n const json = JSON.stringify(error)\n if (typeof json === 'string' && json.length > 0) return json\n } catch {\n // ignore\n }\n }\n return String(error)\n}\n\nconst upsertByIdField = <Entity extends object>(\n items: ReadonlyArray<Entity>,\n entity: Entity,\n idField: string,\n): ReadonlyArray<Entity> => {\n const id = (entity as Record<string, unknown>)[idField]\n const idx = items.findIndex((x) => (x as Record<string, unknown>)[idField] === id)\n if (idx < 0) return [...items, entity]\n return items.map((x, i) => (i === idx ? entity : x))\n}\n\nconst removeByIdField = <Entity extends object>(\n items: ReadonlyArray<Entity>,\n id: unknown,\n idField: string,\n): ReadonlyArray<Entity> => items.filter((x) => (x as Record<string, unknown>)[idField] !== id)\n\nexport type CrudModule<\n Id extends string,\n Entity extends object,\n QueryInput,\n EntityId,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = Logix.Module.Module<\n Id,\n CrudShape<Entity, QueryInput, EntityId, ExtraActions>,\n CrudHandleExt<Entity, QueryInput, EntityId, ExtraActions>,\n unknown\n> & {\n readonly _kind: 'Module'\n readonly services: CrudServices<Entity, QueryInput, EntityId>\n}\n\nconst defineCrud = <\n Id extends string,\n Entity extends object,\n QueryInput = CrudDefaultQueryInput,\n EntityId = string,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n>(\n id: Id,\n spec: CrudSpec<Entity, QueryInput, EntityId>,\n extend?: Logix.Module.MakeExtendDef<\n Schema.Schema<CrudState<Entity, QueryInput>>,\n CrudActionMap<Entity, QueryInput, EntityId>,\n ExtraActions\n >,\n): CrudModule<Id, Entity, QueryInput, EntityId, ExtraActions> => {\n const QuerySchema = (spec.query ?? DefaultQueryInputSchema) as Schema.Schema<QueryInput>\n const IdSchema = (spec.id ?? Schema.String) as Schema.Schema<EntityId>\n\n const idField = spec.idField ?? 'id'\n\n class Api extends Context.Tag(`${id}/crud/api`)<Api, CrudApi<Entity, QueryInput, EntityId>>() {}\n\n const services = { api: Api } as const satisfies CrudServices<Entity, QueryInput, EntityId>\n\n const Actions = makeActions(spec.entity, QuerySchema, IdSchema)\n\n const StateSchema = Schema.Struct({\n items: Schema.Array(spec.entity),\n loading: Schema.Boolean,\n error: Schema.UndefinedOr(Schema.String),\n lastQuery: Schema.UndefinedOr(QuerySchema),\n total: Schema.UndefinedOr(Schema.Number),\n })\n\n type Reducers = Logix.ReducersFromMap<typeof StateSchema, CrudActionMap<Entity, QueryInput, EntityId>>\n\n const reducers: Reducers = {\n query: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('lastQuery')\n return {\n ...state,\n loading: true,\n error: undefined,\n lastQuery: action.payload,\n }\n },\n querySucceeded: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('items')\n sink?.('total')\n return {\n ...state,\n loading: false,\n error: undefined,\n items: action.payload.items,\n total: action.payload.total,\n }\n },\n queryFailed: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: false,\n error: action.payload,\n }\n },\n\n save: (state, _action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: true,\n error: undefined,\n }\n },\n saveSucceeded: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('items')\n return {\n ...state,\n loading: false,\n error: undefined,\n items: upsertByIdField(state.items, action.payload as Entity, idField),\n }\n },\n saveFailed: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: false,\n error: action.payload,\n }\n },\n\n remove: (state, _action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: true,\n error: undefined,\n }\n },\n removeSucceeded: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('items')\n return {\n ...state,\n loading: false,\n error: undefined,\n items: removeByIdField(state.items, action.payload, idField),\n }\n },\n removeFailed: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: false,\n error: action.payload,\n }\n },\n\n clearError: (state, _action, sink) => {\n sink?.('error')\n return {\n ...state,\n error: undefined,\n }\n },\n }\n\n const def = {\n state: StateSchema,\n actions: Actions,\n reducers,\n schemas: { entity: spec.entity },\n meta: { kind: 'crud', idField },\n services,\n }\n\n const module = extend\n ? Logix.Module.make<\n Id,\n typeof StateSchema,\n CrudActionMap<Entity, QueryInput, EntityId>,\n CrudHandleExt<Entity, QueryInput, EntityId, ExtraActions>\n >(\n id,\n def,\n extend as unknown as Logix.Module.MakeExtendDef<\n typeof StateSchema,\n CrudActionMap<Entity, QueryInput, EntityId>,\n ExtraActions\n >,\n )\n : Logix.Module.make<\n Id,\n typeof StateSchema,\n CrudActionMap<Entity, QueryInput, EntityId>,\n CrudHandleExt<Entity, QueryInput, EntityId, ExtraActions>\n >(id, def)\n\n const install = module.logic(\n ($) =>\n Effect.gen(function* () {\n yield* $.onAction('query').runFork((action) =>\n Effect.gen(function* () {\n const apiOpt = yield* Effect.serviceOption(services.api)\n if (Option.isNone(apiOpt)) {\n yield* $.dispatchers.queryFailed(\n `[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`,\n )\n return\n }\n const api = apiOpt.value\n\n const result = yield* api.list(action.payload as QueryInput)\n yield* $.dispatchers.querySucceeded(result)\n }).pipe(Effect.catchAll((e) => $.dispatchers.queryFailed(toErrorMessage(e)))),\n )\n\n yield* $.onAction('save').runFork((action) =>\n Effect.gen(function* () {\n const apiOpt = yield* Effect.serviceOption(services.api)\n if (Option.isNone(apiOpt)) {\n yield* $.dispatchers.saveFailed(\n `[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`,\n )\n return\n }\n const api = apiOpt.value\n\n const entity = yield* api.save(action.payload as Entity)\n yield* $.dispatchers.saveSucceeded(entity)\n }).pipe(Effect.catchAll((e) => $.dispatchers.saveFailed(toErrorMessage(e)))),\n )\n\n yield* $.onAction('remove').runFork((action) =>\n Effect.gen(function* () {\n const apiOpt = yield* Effect.serviceOption(services.api)\n if (Option.isNone(apiOpt)) {\n yield* $.dispatchers.removeFailed(\n `[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`,\n )\n return\n }\n const api = apiOpt.value\n\n yield* api.remove(action.payload as EntityId)\n yield* $.dispatchers.removeSucceeded(action.payload as EntityId)\n }).pipe(Effect.catchAll((e) => $.dispatchers.removeFailed(toErrorMessage(e)))),\n )\n }),\n { id: 'install' },\n )\n\n const controller = {\n make: (\n runtime: Logix.ModuleRuntime<\n CrudState<Entity, QueryInput>,\n CrudAction<Entity, QueryInput, EntityId, ExtraActions>\n >,\n ): CrudController<Entity, QueryInput, EntityId, ExtraActions> => ({\n runtime,\n getState: runtime.getState,\n dispatch: runtime.dispatch,\n controller: {\n list: (input: QueryInput) =>\n runtime.dispatch({ _tag: 'query', payload: input } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n save: (entity: Entity) =>\n runtime.dispatch({ _tag: 'save', payload: entity } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n remove: (id: EntityId) =>\n runtime.dispatch({ _tag: 'remove', payload: id } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n clearError: () =>\n runtime.dispatch({ _tag: 'clearError' } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n idField,\n },\n }),\n }\n\n const EXTEND_HANDLE = Symbol.for('logix.module.handle.extend')\n ;(module.tag as unknown as Record<PropertyKey, unknown>)[EXTEND_HANDLE] = (\n runtime: Logix.ModuleRuntime<CrudState<Entity, QueryInput>, CrudAction<Entity, QueryInput, EntityId, ExtraActions>>,\n base: Logix.ModuleHandle<Logix.AnyModuleShape>,\n ) => {\n const crud = controller.make(runtime)\n return {\n ...base,\n controller: crud.controller,\n services,\n }\n }\n\n return module.implement({\n initial: {\n items: Array.from(spec.initial ?? []),\n loading: false,\n error: undefined,\n lastQuery: undefined,\n total: undefined,\n } as CrudState<Entity, QueryInput>,\n logics: [install],\n }) as unknown as CrudModule<Id, Entity, QueryInput, EntityId, ExtraActions>\n}\n\nexport const CRUDModule = Logix.Module.Manage.make({\n kind: 'crud',\n define: defineCrud,\n})\n"],"mappings":"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","names":["id"]}
|