@alchemy.run/axiom 0.0.0-stage → 2.0.0-beta.84
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/LICENSE +201 -0
- package/README.md +58 -2
- package/lib/Annotation.d.ts +51 -0
- package/lib/Annotation.d.ts.map +1 -0
- package/lib/Annotation.js +97 -0
- package/lib/Annotation.js.map +1 -0
- package/lib/ApiToken.d.ts +64 -0
- package/lib/ApiToken.d.ts.map +1 -0
- package/lib/ApiToken.js +123 -0
- package/lib/ApiToken.js.map +1 -0
- package/lib/AuthProvider.d.ts +40 -0
- package/lib/AuthProvider.d.ts.map +1 -0
- package/lib/AuthProvider.js +99 -0
- package/lib/AuthProvider.js.map +1 -0
- package/lib/Chart.d.ts +130 -0
- package/lib/Chart.d.ts.map +1 -0
- package/lib/Chart.js +14 -0
- package/lib/Chart.js.map +1 -0
- package/lib/Contributions.d.ts +7 -0
- package/lib/Contributions.d.ts.map +1 -0
- package/lib/Contributions.js +6 -0
- package/lib/Contributions.js.map +1 -0
- package/lib/Credentials.d.ts +10 -0
- package/lib/Credentials.d.ts.map +1 -0
- package/lib/Credentials.js +30 -0
- package/lib/Credentials.js.map +1 -0
- package/lib/Dashboard.d.ts +112 -0
- package/lib/Dashboard.d.ts.map +1 -0
- package/lib/Dashboard.js +157 -0
- package/lib/Dashboard.js.map +1 -0
- package/lib/Dataset.d.ts +120 -0
- package/lib/Dataset.d.ts.map +1 -0
- package/lib/Dataset.js +215 -0
- package/lib/Dataset.js.map +1 -0
- package/lib/Monitor.d.ts +76 -0
- package/lib/Monitor.d.ts.map +1 -0
- package/lib/Monitor.js +126 -0
- package/lib/Monitor.js.map +1 -0
- package/lib/Notifier.d.ts +65 -0
- package/lib/Notifier.d.ts.map +1 -0
- package/lib/Notifier.js +112 -0
- package/lib/Notifier.js.map +1 -0
- package/lib/OtelHeaders.d.ts +9 -0
- package/lib/OtelHeaders.d.ts.map +1 -0
- package/lib/OtelHeaders.js +9 -0
- package/lib/OtelHeaders.js.map +1 -0
- package/lib/Providers.d.ts +15 -0
- package/lib/Providers.d.ts.map +1 -0
- package/lib/Providers.js +34 -0
- package/lib/Providers.js.map +1 -0
- package/lib/Telemetry.d.ts +66 -0
- package/lib/Telemetry.d.ts.map +1 -0
- package/lib/Telemetry.js +68 -0
- package/lib/Telemetry.js.map +1 -0
- package/lib/View.d.ts +57 -0
- package/lib/View.d.ts.map +1 -0
- package/lib/View.js +100 -0
- package/lib/View.js.map +1 -0
- package/lib/VirtualField.d.ts +44 -0
- package/lib/VirtualField.d.ts.map +1 -0
- package/lib/VirtualField.js +97 -0
- package/lib/VirtualField.js.map +1 -0
- package/lib/index.d.ts +14 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +14 -0
- package/lib/index.js.map +1 -0
- package/package.json +68 -3
- package/src/Annotation.ts +121 -0
- package/src/ApiToken.ts +148 -0
- package/src/AuthProvider.ts +135 -0
- package/src/Chart.ts +156 -0
- package/src/Contributions.ts +9 -0
- package/src/Credentials.ts +68 -0
- package/src/Dashboard.ts +192 -0
- package/src/Dataset.ts +326 -0
- package/src/Monitor.ts +148 -0
- package/src/Notifier.ts +136 -0
- package/src/OtelHeaders.ts +9 -0
- package/src/Providers.ts +60 -0
- package/src/Telemetry.ts +116 -0
- package/src/View.ts +130 -0
- package/src/VirtualField.ts +123 -0
- package/src/index.ts +13 -0
package/src/Telemetry.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { Input } from "alchemy/Input";
|
|
2
|
+
import * as Output from "alchemy/Output";
|
|
3
|
+
import { layerOtlp, type OtlpSignalOptions } from "alchemy/Telemetry";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import * as Layer from "effect/Layer";
|
|
6
|
+
import * as Redacted from "effect/Redacted";
|
|
7
|
+
import type { ApiToken } from "./ApiToken.ts";
|
|
8
|
+
import type { Dataset } from "./Dataset.ts";
|
|
9
|
+
import { otelDatasetHeader } from "./OtelHeaders.ts";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A resource passed to the layer: either the module-scope declaration (an
|
|
13
|
+
* Effect that resolves to the instance, same as what capability bindings
|
|
14
|
+
* accept) or an already-yielded instance.
|
|
15
|
+
*/
|
|
16
|
+
export type ResourceInput<T> = T | Effect.Effect<T, never, any>;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Options for {@link Telemetry}: an ingest {@link ApiToken} plus one
|
|
20
|
+
* {@link Dataset} per OTel signal to export.
|
|
21
|
+
*/
|
|
22
|
+
export interface AxiomTelemetryProps {
|
|
23
|
+
/**
|
|
24
|
+
* The ingest credential. Must have `ingest: ["create"]` capability on
|
|
25
|
+
* every dataset passed below.
|
|
26
|
+
*/
|
|
27
|
+
token: ResourceInput<ApiToken>;
|
|
28
|
+
/** Dataset (kind `otel:traces:v1`) to export traces into. */
|
|
29
|
+
traces?: ResourceInput<Dataset> | undefined;
|
|
30
|
+
/** Dataset (kind `otel:logs:v1`) to export logs into. */
|
|
31
|
+
logs?: ResourceInput<Dataset> | undefined;
|
|
32
|
+
/** Dataset (kind `otel:metrics:v1`) to export metrics into. */
|
|
33
|
+
metrics?: ResourceInput<Dataset> | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* The exported `service.name`.
|
|
36
|
+
* @default the deployed Function/Worker's physical name
|
|
37
|
+
*/
|
|
38
|
+
serviceName?: Input<string> | undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Resource declarations are Effects — yield them to get the instance with
|
|
43
|
+
* attribute Output accessors, same as `Binding.Service`'s callable does for
|
|
44
|
+
* capability bindings.
|
|
45
|
+
*/
|
|
46
|
+
const instance = <T>(resource: ResourceInput<T>): Effect.Effect<T> =>
|
|
47
|
+
Effect.isEffect(resource) ? (resource as Effect.Effect<T>) : Effect.succeed(resource);
|
|
48
|
+
|
|
49
|
+
const signal = (
|
|
50
|
+
token: ApiToken,
|
|
51
|
+
dataset: Dataset | undefined,
|
|
52
|
+
urlAttr: "otelTracesEndpoint" | "otelLogsEndpoint" | "otelMetricsEndpoint",
|
|
53
|
+
): OtlpSignalOptions | undefined =>
|
|
54
|
+
dataset === undefined
|
|
55
|
+
? undefined
|
|
56
|
+
: {
|
|
57
|
+
url: dataset[urlAttr],
|
|
58
|
+
headers: {
|
|
59
|
+
Authorization: Output.map(
|
|
60
|
+
token.token,
|
|
61
|
+
(bearer) =>
|
|
62
|
+
Redacted.make(`Bearer ${Redacted.value(bearer)}`) as Redacted.Redacted<string>,
|
|
63
|
+
),
|
|
64
|
+
[otelDatasetHeader(urlAttr === "otelMetricsEndpoint")]: dataset.name,
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Export a Function/Worker's telemetry to Axiom.
|
|
70
|
+
*
|
|
71
|
+
* A binding layer over {@link layerOtlp | Alchemy.Telemetry.layerOtlp}:
|
|
72
|
+
* building it binds each dataset's OTLP endpoint and the ingest token's
|
|
73
|
+
* `Authorization` header (as a secret) onto the host, and at runtime the
|
|
74
|
+
* built-in exporter ships each signal to its dataset, flushed per event.
|
|
75
|
+
*
|
|
76
|
+
* Compose it into the Function/Worker's single `Effect.provide`:
|
|
77
|
+
*
|
|
78
|
+
* ```ts
|
|
79
|
+
* import * as Axiom from "@alchemy.run/axiom";
|
|
80
|
+
* import { Bucket } from "./bucket.ts";
|
|
81
|
+
* import { Ingest, Logs, Traces } from "./observability.ts";
|
|
82
|
+
*
|
|
83
|
+
* export default Cloudflare.Worker(
|
|
84
|
+
* "Worker",
|
|
85
|
+
* { main: import.meta.url },
|
|
86
|
+
* Effect.gen(function* () {
|
|
87
|
+
* // ...
|
|
88
|
+
* }).pipe(
|
|
89
|
+
* Effect.provide(
|
|
90
|
+
* Layer.mergeAll(
|
|
91
|
+
* Cloudflare.R2.ReadWriteBucketBinding,
|
|
92
|
+
* Axiom.Telemetry({ token: Ingest, traces: Traces, logs: Logs }),
|
|
93
|
+
* ),
|
|
94
|
+
* ),
|
|
95
|
+
* ),
|
|
96
|
+
* );
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export const Telemetry = (props: AxiomTelemetryProps): Layer.Layer<never> =>
|
|
100
|
+
Layer.unwrap(
|
|
101
|
+
Effect.gen(function* () {
|
|
102
|
+
// Declarations are yielded to instances (registering them on the
|
|
103
|
+
// Stack if the enclosing host is the first to reference them), so
|
|
104
|
+
// attribute accessors below produce real Outputs.
|
|
105
|
+
const token = yield* instance(props.token);
|
|
106
|
+
const traces = props.traces && (yield* instance(props.traces));
|
|
107
|
+
const logs = props.logs && (yield* instance(props.logs));
|
|
108
|
+
const metrics = props.metrics && (yield* instance(props.metrics));
|
|
109
|
+
return layerOtlp({
|
|
110
|
+
serviceName: props.serviceName,
|
|
111
|
+
traces: signal(token, traces, "otelTracesEndpoint"),
|
|
112
|
+
logs: signal(token, logs, "otelLogsEndpoint"),
|
|
113
|
+
metrics: signal(token, metrics, "otelMetricsEndpoint"),
|
|
114
|
+
});
|
|
115
|
+
}),
|
|
116
|
+
) as Layer.Layer<never>;
|
package/src/View.ts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import * as Axiom from "@distilled.cloud/axiom";
|
|
2
|
+
import { isResolved } from "alchemy/Diff";
|
|
3
|
+
import * as Provider from "alchemy/Provider";
|
|
4
|
+
import { Resource } from "alchemy/Resource";
|
|
5
|
+
import * as Effect from "effect/Effect";
|
|
6
|
+
import type { Providers } from "./Providers.ts";
|
|
7
|
+
|
|
8
|
+
export type ViewProps = Axiom.CreateViewRequest;
|
|
9
|
+
|
|
10
|
+
export type View = Resource<
|
|
11
|
+
"Axiom.View",
|
|
12
|
+
ViewProps,
|
|
13
|
+
Axiom.View & {
|
|
14
|
+
/**
|
|
15
|
+
* Path identifier used by `updateView` / `getView` / `deleteView`.
|
|
16
|
+
* Currently derived from `name` because Axiom's view list/get responses
|
|
17
|
+
* don't expose a separate id field.
|
|
18
|
+
*/
|
|
19
|
+
id: string;
|
|
20
|
+
},
|
|
21
|
+
never,
|
|
22
|
+
Providers
|
|
23
|
+
>;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* An Axiom saved view — a named, shareable APL query. Useful for building
|
|
27
|
+
* starter dashboards, providing canned "open in Axiom" links from your app,
|
|
28
|
+
* or pinning common investigations the team revisits.
|
|
29
|
+
*
|
|
30
|
+
* The path identifier is `name`. Renaming a view triggers a replacement
|
|
31
|
+
* (the old one is deleted, a new one is created).
|
|
32
|
+
* @see https://axiom.co/docs/query-data/datasets — APL query reference
|
|
33
|
+
*
|
|
34
|
+
* ### Creating a View
|
|
35
|
+
* **Example:** Recent errors across one dataset
|
|
36
|
+
* ```typescript
|
|
37
|
+
* yield* Axiom.View("recent-errors", {
|
|
38
|
+
* name: "recent-errors",
|
|
39
|
+
* description: "Last 100 5xx responses",
|
|
40
|
+
* datasets: ["my-app-traces"],
|
|
41
|
+
* aplQuery: `
|
|
42
|
+
* ['my-app-traces']
|
|
43
|
+
* | where status >= 500
|
|
44
|
+
* | order by _time desc
|
|
45
|
+
* | take 100
|
|
46
|
+
* `,
|
|
47
|
+
* });
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* **Example:** Cross-dataset join (logs + traces by trace_id)
|
|
51
|
+
* ```typescript
|
|
52
|
+
* yield* Axiom.View("trace-with-logs", {
|
|
53
|
+
* name: "trace-with-logs",
|
|
54
|
+
* datasets: ["my-app-traces", "my-app-logs"],
|
|
55
|
+
* aplQuery: `
|
|
56
|
+
* ['my-app-traces']
|
|
57
|
+
* | where duration_ms > 1000
|
|
58
|
+
* | join kind=leftouter (['my-app-logs']) on trace_id
|
|
59
|
+
* `,
|
|
60
|
+
* });
|
|
61
|
+
* ```
|
|
62
|
+
*
|
|
63
|
+
* @resource
|
|
64
|
+
* @product Dashboard
|
|
65
|
+
*/
|
|
66
|
+
export const View = Resource<View>("Axiom.View");
|
|
67
|
+
|
|
68
|
+
export const ViewProvider = () =>
|
|
69
|
+
Provider.effect(
|
|
70
|
+
View,
|
|
71
|
+
Effect.gen(function* () {
|
|
72
|
+
const create = yield* Axiom.createView;
|
|
73
|
+
const update = yield* Axiom.updateView;
|
|
74
|
+
const get = yield* Axiom.getView;
|
|
75
|
+
const listViews = yield* Axiom.getViews;
|
|
76
|
+
const del = yield* Axiom.deleteView;
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
stables: ["id"],
|
|
80
|
+
// Enumerate every view in the org. Axiom exposes a single account-wide
|
|
81
|
+
// `GET /v2/views` collection op (no pagination) that already returns
|
|
82
|
+
// the full view objects, so we fetch it once and hydrate each row into
|
|
83
|
+
// the exact `read` Attributes shape (`id` is derived from `name`, the
|
|
84
|
+
// path identifier) — directly usable by `delete` with no follow-up get.
|
|
85
|
+
list: () =>
|
|
86
|
+
Effect.gen(function* () {
|
|
87
|
+
const views = yield* listViews({});
|
|
88
|
+
return views.map((view) => ({ ...view, id: view.name }));
|
|
89
|
+
}),
|
|
90
|
+
diff: Effect.fn(function* ({ news, output }) {
|
|
91
|
+
if (!isResolved(news)) return undefined;
|
|
92
|
+
if (output && news.name !== output.name) {
|
|
93
|
+
return { action: "replace" } as const;
|
|
94
|
+
}
|
|
95
|
+
return undefined;
|
|
96
|
+
}),
|
|
97
|
+
reconcile: Effect.fn(function* ({ news, output }) {
|
|
98
|
+
// Observe — `name` is the path identifier for views. Renames are
|
|
99
|
+
// forced to a replacement by `diff` above, so the cached
|
|
100
|
+
// `output.id` (set to `news.name` on first create) and the
|
|
101
|
+
// current `news.name` always agree by the time we land here.
|
|
102
|
+
const viewId = output?.id ?? news.name;
|
|
103
|
+
const observed = yield* get({ id: viewId }).pipe(
|
|
104
|
+
Effect.catchTag("NotFound", () => Effect.succeed(undefined)),
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
// Ensure — POST creates the view under `news.name`.
|
|
108
|
+
if (observed === undefined) {
|
|
109
|
+
const result = yield* create(news);
|
|
110
|
+
return { ...result, id: news.name };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Sync — the view exists; PUT against its id with the desired
|
|
114
|
+
// props.
|
|
115
|
+
const result = yield* update({ ...news, id: viewId });
|
|
116
|
+
return { ...result, id: viewId };
|
|
117
|
+
}),
|
|
118
|
+
delete: Effect.fn(function* ({ output }) {
|
|
119
|
+
yield* del({ id: output.id }).pipe(Effect.catchTag("NotFound", () => Effect.void));
|
|
120
|
+
}),
|
|
121
|
+
read: Effect.fn(function* ({ output }) {
|
|
122
|
+
if (!output?.id) return undefined;
|
|
123
|
+
return yield* get({ id: output.id }).pipe(
|
|
124
|
+
Effect.map((current) => ({ ...current, id: output.id })),
|
|
125
|
+
Effect.catchTag("NotFound", () => Effect.succeed(undefined)),
|
|
126
|
+
);
|
|
127
|
+
}),
|
|
128
|
+
};
|
|
129
|
+
}),
|
|
130
|
+
);
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import * as Axiom from "@distilled.cloud/axiom";
|
|
2
|
+
import { isResolved } from "alchemy/Diff";
|
|
3
|
+
import * as Provider from "alchemy/Provider";
|
|
4
|
+
import { Resource } from "alchemy/Resource";
|
|
5
|
+
import * as Effect from "effect/Effect";
|
|
6
|
+
import type { Providers } from "./Providers.ts";
|
|
7
|
+
|
|
8
|
+
export type VirtualFieldProps = Axiom.CreateVirtualFieldRequest;
|
|
9
|
+
|
|
10
|
+
export type VirtualField = Resource<
|
|
11
|
+
"Axiom.VirtualField",
|
|
12
|
+
VirtualFieldProps,
|
|
13
|
+
Axiom.CreateVirtualFieldResponse,
|
|
14
|
+
never,
|
|
15
|
+
Providers
|
|
16
|
+
>;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* An Axiom virtual field — a saved APL expression that appears as a derived
|
|
20
|
+
* column on a dataset at query time. Use these to standardise common
|
|
21
|
+
* computations (status classes, latency buckets, parsed JSON paths) so
|
|
22
|
+
* dashboards and monitors don't have to redefine them.
|
|
23
|
+
*
|
|
24
|
+
* Bound to a single `dataset`; changing the dataset triggers a replacement.
|
|
25
|
+
* @see https://axiom.co/docs/query-data/virtual-fields
|
|
26
|
+
*
|
|
27
|
+
* ### Creating a Virtual Field
|
|
28
|
+
* **Example:** HTTP status class (e.g. 200 → "2xx")
|
|
29
|
+
* ```typescript
|
|
30
|
+
* yield* Axiom.VirtualField("status-class", {
|
|
31
|
+
* dataset: "my-app-traces",
|
|
32
|
+
* name: "status_class",
|
|
33
|
+
* description: "HTTP response class bucket",
|
|
34
|
+
* expression: 'strcat(tostring(toint(status / 100)), "xx")',
|
|
35
|
+
* type: "string",
|
|
36
|
+
* });
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* **Example:** Latency bucket in seconds
|
|
40
|
+
* ```typescript
|
|
41
|
+
* yield* Axiom.VirtualField("latency-bucket", {
|
|
42
|
+
* dataset: "my-app-traces",
|
|
43
|
+
* name: "latency_bucket_s",
|
|
44
|
+
* expression: "bin(duration_ms / 1000.0, 0.5)",
|
|
45
|
+
* type: "number",
|
|
46
|
+
* unit: "s",
|
|
47
|
+
* });
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* @resource
|
|
51
|
+
* @product Dataset
|
|
52
|
+
*/
|
|
53
|
+
export const VirtualField = Resource<VirtualField>("Axiom.VirtualField");
|
|
54
|
+
|
|
55
|
+
export const VirtualFieldProvider = () =>
|
|
56
|
+
Provider.effect(
|
|
57
|
+
VirtualField,
|
|
58
|
+
Effect.gen(function* () {
|
|
59
|
+
const create = yield* Axiom.createVirtualField;
|
|
60
|
+
const update = yield* Axiom.updateVirtualField;
|
|
61
|
+
const get = yield* Axiom.getVirtualField;
|
|
62
|
+
const del = yield* Axiom.deleteVirtualField;
|
|
63
|
+
const listVirtualFields = yield* Axiom.getVirtualFields;
|
|
64
|
+
const listDatasets = yield* Axiom.getDatasets;
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
stables: ["id"],
|
|
68
|
+
// Axiom only exposes a per-dataset virtual-field enumeration
|
|
69
|
+
// (`GET /v2/vfields?dataset=...`); there is no account-wide list. So
|
|
70
|
+
// enumerate every dataset (`GET /v2/datasets`), fan out the per-dataset
|
|
71
|
+
// vfields list with bounded concurrency, and flatten. Each row already
|
|
72
|
+
// matches the `read` Attributes shape (`CreateVirtualFieldOutput`), so
|
|
73
|
+
// it's directly usable by `delete` with no follow-up read.
|
|
74
|
+
list: () =>
|
|
75
|
+
Effect.gen(function* () {
|
|
76
|
+
const datasets = yield* listDatasets({});
|
|
77
|
+
const perDataset = yield* Effect.forEach(
|
|
78
|
+
datasets,
|
|
79
|
+
(ds) => listVirtualFields({ dataset: ds.name }),
|
|
80
|
+
{ concurrency: 10 },
|
|
81
|
+
);
|
|
82
|
+
return perDataset.flat().map((vf) => ({ ...vf }));
|
|
83
|
+
}),
|
|
84
|
+
diff: Effect.fn(function* ({ news, output }) {
|
|
85
|
+
if (!isResolved(news)) return undefined;
|
|
86
|
+
if (output && news.dataset !== output.dataset) {
|
|
87
|
+
return { action: "replace" } as const;
|
|
88
|
+
}
|
|
89
|
+
return undefined;
|
|
90
|
+
}),
|
|
91
|
+
reconcile: Effect.fn(function* ({ news, output }) {
|
|
92
|
+
// Observe — Axiom assigns the virtual-field id server-side, so
|
|
93
|
+
// the only handle to a previously-created field is the cached
|
|
94
|
+
// `output.id`. Probe for live state with that id; treat NotFound
|
|
95
|
+
// (deleted out-of-band) as "no observed state" so we converge by
|
|
96
|
+
// re-creating.
|
|
97
|
+
const observed = output?.id
|
|
98
|
+
? yield* get({ id: output.id }).pipe(
|
|
99
|
+
Effect.catchTag("NotFound", () => Effect.succeed(undefined)),
|
|
100
|
+
)
|
|
101
|
+
: undefined;
|
|
102
|
+
|
|
103
|
+
// Ensure — POST mints a new virtual field with a fresh id.
|
|
104
|
+
if (observed === undefined) {
|
|
105
|
+
return yield* create(news);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Sync — the field exists; PUT against its id with the desired
|
|
109
|
+
// props. `dataset` is replacement-only (handled in diff).
|
|
110
|
+
return yield* update({ ...news, id: observed.id });
|
|
111
|
+
}),
|
|
112
|
+
delete: Effect.fn(function* ({ output }) {
|
|
113
|
+
yield* del({ id: output.id }).pipe(Effect.catchTag("NotFound", () => Effect.void));
|
|
114
|
+
}),
|
|
115
|
+
read: Effect.fn(function* ({ output }) {
|
|
116
|
+
if (!output?.id) return undefined;
|
|
117
|
+
return yield* get({ id: output.id }).pipe(
|
|
118
|
+
Effect.catchTag("NotFound", () => Effect.succeed(undefined)),
|
|
119
|
+
);
|
|
120
|
+
}),
|
|
121
|
+
};
|
|
122
|
+
}),
|
|
123
|
+
);
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export * from "./Annotation.ts";
|
|
2
|
+
export * from "./ApiToken.ts";
|
|
3
|
+
export * from "./AuthProvider.ts";
|
|
4
|
+
export * from "./Chart.ts";
|
|
5
|
+
export * from "./Credentials.ts";
|
|
6
|
+
export * from "./Dashboard.ts";
|
|
7
|
+
export * from "./Dataset.ts";
|
|
8
|
+
export * from "./Monitor.ts";
|
|
9
|
+
export * from "./Notifier.ts";
|
|
10
|
+
export * from "./Telemetry.ts";
|
|
11
|
+
export * from "./Providers.ts";
|
|
12
|
+
export * from "./View.ts";
|
|
13
|
+
export * from "./VirtualField.ts";
|