ignotum 0.0.12 → 0.0.13
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/cli/bin.mjs +825 -403
- package/dist/cli/bin.mjs.map +1 -1
- package/dist/runtime/{api-DzcR7spt.js → api-CAgKDij7.js} +38 -2
- package/dist/runtime/api-CAgKDij7.js.map +1 -0
- package/dist/runtime/{api-5XSrIeqW.d.ts → api-Cv3hMbzo.d.ts} +4 -4
- package/dist/runtime/client.d.ts +39 -9
- package/dist/runtime/client.js +339 -60
- package/dist/runtime/client.js.map +1 -1
- package/dist/runtime/descriptor-Cyo9FP9n-C0SRVXNW.js +154 -0
- package/dist/runtime/descriptor-Cyo9FP9n-C0SRVXNW.js.map +1 -0
- package/dist/runtime/id-Bt9XWRGL.js +423 -0
- package/dist/runtime/id-Bt9XWRGL.js.map +1 -0
- package/dist/runtime/{id-Cs82tq9Q-CK-maMgN.d.ts → id-BzFHf3Wo-DGPCjgrf.d.ts} +2 -2
- package/dist/runtime/id-Dz0apuB3.d.ts +1 -0
- package/dist/runtime/{index-CrWg4Z0y.d.ts → index-D54flWtH.d.ts} +9 -5
- package/dist/runtime/internal/api.d.ts +1 -1
- package/dist/runtime/internal/api.js +1 -1
- package/dist/runtime/internal/host.d.ts +9 -6
- package/dist/runtime/internal/host.js +15 -6
- package/dist/runtime/internal/host.js.map +1 -1
- package/dist/runtime/internal/server.d.ts +1 -1
- package/dist/runtime/internal/server.js +1 -1
- package/dist/runtime/internal/types.d.ts +1 -1
- package/dist/runtime/internal/types.js +1 -1
- package/dist/runtime/{pagination-D-R9NR61-CpIoHFoI.d.ts → pagination-DnKg3dkI-r5ZUxBBx.d.ts} +31 -6
- package/dist/runtime/pagination-Dz0apuB3.d.ts +1 -0
- package/dist/runtime/result-DKAA4gpS.d.ts +1 -0
- package/dist/runtime/{schema-B9XxyRO8.js → schema-1Zs03-iS.js} +5 -3
- package/dist/runtime/schema-1Zs03-iS.js.map +1 -0
- package/dist/runtime/server.d.ts +6 -4
- package/dist/runtime/server.js +2 -2
- package/dist/runtime/server.js.map +1 -1
- package/dist/runtime/{sync-avN7NkcU.d.ts → sync-Bs8J3fIr.d.ts} +2 -2
- package/package.json +1 -1
- package/src/cli/agent-files.ts +6 -0
- package/src/client/hooks.ts +68 -0
- package/src/client/id.ts +259 -0
- package/src/client/index.ts +5 -2
- package/src/client/sync.ts +143 -10
- package/src/dev-runtime/functions.ts +111 -88
- package/src/dev-runtime/id.ts +175 -0
- package/src/dev-runtime/query-cache.ts +105 -0
- package/src/dev-runtime/sync.ts +149 -17
- package/src/server/index.ts +2 -0
- package/dist/runtime/api-DzcR7spt.js.map +0 -1
- package/dist/runtime/descriptor-XzDX2JDw-j3O6YHgW.js +0 -330
- package/dist/runtime/descriptor-XzDX2JDw-j3O6YHgW.js.map +0 -1
- package/dist/runtime/file-C1abuMgd.js +0 -173
- package/dist/runtime/file-C1abuMgd.js.map +0 -1
- package/dist/runtime/pagination-CX5IPbEi.d.ts +0 -1
- package/dist/runtime/result-DIjKM-p4.d.ts +0 -1
- package/dist/runtime/schema-B9XxyRO8.js.map +0 -1
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { Context, Effect, FileSystem, Layer, Path, Schema, Semaphore } from "effect";
|
|
2
|
+
import {
|
|
3
|
+
DevelopmentUsername,
|
|
4
|
+
developmentSelectionKey,
|
|
5
|
+
ProfileFields,
|
|
6
|
+
SessionRequest,
|
|
7
|
+
User,
|
|
8
|
+
UserId,
|
|
9
|
+
type SessionState,
|
|
10
|
+
} from "@ignotum/contracts/id";
|
|
11
|
+
import * as Versioned from "@ignotum/contracts/versioned";
|
|
12
|
+
|
|
13
|
+
const Profile = Schema.Struct({ user: User, share: ProfileFields, revision: Schema.Natural });
|
|
14
|
+
const ProfilesV1 = Schema.Struct({
|
|
15
|
+
formatVersion: Schema.Literal(1),
|
|
16
|
+
profiles: Schema.Record(Schema.String, Profile),
|
|
17
|
+
});
|
|
18
|
+
const Profiles = Versioned.initial(ProfilesV1);
|
|
19
|
+
const ProfilesJson = Schema.fromJsonString(Profiles);
|
|
20
|
+
|
|
21
|
+
export const localIdPath = "/_ignotum/v1/id";
|
|
22
|
+
export const LocalCompletion = Schema.Struct({
|
|
23
|
+
request: SessionRequest,
|
|
24
|
+
username: DevelopmentUsername,
|
|
25
|
+
name: Schema.String,
|
|
26
|
+
email: Schema.String,
|
|
27
|
+
image: Schema.String,
|
|
28
|
+
share: ProfileFields,
|
|
29
|
+
manage: Schema.optional(Schema.Boolean),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const projection = (user: User, share: ReadonlyArray<string>): User => ({
|
|
33
|
+
id: user.id,
|
|
34
|
+
name: share.includes("name") ? user.name : undefined,
|
|
35
|
+
email: share.includes("email") ? user.email : undefined,
|
|
36
|
+
image: share.includes("image") ? user.image : undefined,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export class LocalId extends Context.Service<
|
|
40
|
+
LocalId,
|
|
41
|
+
{
|
|
42
|
+
readonly profile: (username: string) => Effect.Effect<typeof Profile.Type | undefined>;
|
|
43
|
+
readonly session: (username: string | null, epoch: string) => Effect.Effect<SessionState>;
|
|
44
|
+
readonly complete: (input: typeof LocalCompletion.Type) => Effect.Effect<void>;
|
|
45
|
+
}
|
|
46
|
+
>()("ignotum/dev-runtime/id/LocalId") {
|
|
47
|
+
static readonly layer = (directory: string) =>
|
|
48
|
+
Layer.effect(
|
|
49
|
+
LocalId,
|
|
50
|
+
Effect.gen(function* () {
|
|
51
|
+
const fs = yield* FileSystem.FileSystem;
|
|
52
|
+
const path = yield* Path.Path;
|
|
53
|
+
const file = path.join(directory, ".ignotum", "id.json");
|
|
54
|
+
const lock = yield* Semaphore.make(1);
|
|
55
|
+
const load = Effect.fn("LocalId.load")(function* () {
|
|
56
|
+
if (!(yield* fs.exists(file))) return Profiles.make({ formatVersion: 1, profiles: {} });
|
|
57
|
+
return yield* fs
|
|
58
|
+
.readFileString(file)
|
|
59
|
+
.pipe(Effect.flatMap(Schema.decodeEffect(ProfilesJson)));
|
|
60
|
+
});
|
|
61
|
+
return LocalId.of({
|
|
62
|
+
profile: Effect.fn("LocalId.profile")(function* (username) {
|
|
63
|
+
return (yield* load().pipe(Effect.orDie)).profiles[username];
|
|
64
|
+
}),
|
|
65
|
+
session: Effect.fn("LocalId.session")(function* (username, epoch) {
|
|
66
|
+
if (username === null)
|
|
67
|
+
return {
|
|
68
|
+
sessionEpoch: epoch,
|
|
69
|
+
viewRevision: 0,
|
|
70
|
+
user: null,
|
|
71
|
+
validUntil: Number.MAX_SAFE_INTEGER,
|
|
72
|
+
};
|
|
73
|
+
const id = yield* Schema.decodeEffect(UserId)(username).pipe(Effect.orDie);
|
|
74
|
+
const profile = (yield* load().pipe(Effect.orDie)).profiles[id];
|
|
75
|
+
const user: User = projection(profile?.user ?? { id }, profile?.share ?? []);
|
|
76
|
+
return {
|
|
77
|
+
sessionEpoch: epoch,
|
|
78
|
+
viewRevision: profile?.revision ?? 0,
|
|
79
|
+
user,
|
|
80
|
+
validUntil: Number.MAX_SAFE_INTEGER,
|
|
81
|
+
};
|
|
82
|
+
}),
|
|
83
|
+
complete: Effect.fn("LocalId.complete")(function* (input) {
|
|
84
|
+
yield* lock
|
|
85
|
+
.withPermits(1)(
|
|
86
|
+
Effect.gen(function* () {
|
|
87
|
+
const stored = yield* load();
|
|
88
|
+
const previous = stored.profiles[input.username];
|
|
89
|
+
const user = User.make({
|
|
90
|
+
id: UserId.make(input.username),
|
|
91
|
+
name: input.name === "" ? undefined : input.name,
|
|
92
|
+
email: input.email === "" ? undefined : input.email,
|
|
93
|
+
image: input.image === "" ? undefined : input.image,
|
|
94
|
+
});
|
|
95
|
+
const share =
|
|
96
|
+
input.manage === true
|
|
97
|
+
? input.share
|
|
98
|
+
: [
|
|
99
|
+
...new Set([
|
|
100
|
+
...(previous?.share ?? []),
|
|
101
|
+
...input.share.filter((field) => input.request.profile.includes(field)),
|
|
102
|
+
]),
|
|
103
|
+
];
|
|
104
|
+
const updated = Profiles.make({
|
|
105
|
+
formatVersion: 1,
|
|
106
|
+
profiles: {
|
|
107
|
+
...stored.profiles,
|
|
108
|
+
[input.username]: {
|
|
109
|
+
user,
|
|
110
|
+
share,
|
|
111
|
+
revision:
|
|
112
|
+
(previous?.revision ?? 0) +
|
|
113
|
+
Number(
|
|
114
|
+
JSON.stringify(
|
|
115
|
+
projection(previous?.user ?? { id: user.id }, previous?.share ?? []),
|
|
116
|
+
) !== JSON.stringify(projection(user, share)),
|
|
117
|
+
),
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
yield* fs.makeDirectory(path.dirname(file), { recursive: true });
|
|
122
|
+
yield* Effect.scoped(
|
|
123
|
+
Effect.gen(function* () {
|
|
124
|
+
const temporary = yield* fs.makeTempFileScoped({
|
|
125
|
+
directory: path.dirname(file),
|
|
126
|
+
prefix: ".id-",
|
|
127
|
+
});
|
|
128
|
+
yield* fs.writeFileString(
|
|
129
|
+
temporary,
|
|
130
|
+
yield* Schema.encodeEffect(ProfilesJson)(updated),
|
|
131
|
+
);
|
|
132
|
+
yield* fs.rename(temporary, file);
|
|
133
|
+
}),
|
|
134
|
+
);
|
|
135
|
+
}),
|
|
136
|
+
)
|
|
137
|
+
.pipe(Effect.orDie);
|
|
138
|
+
}),
|
|
139
|
+
});
|
|
140
|
+
}),
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const escapeHtml = (text: string) =>
|
|
145
|
+
text.replaceAll("&", "&").replaceAll("<", "<").replaceAll('"', """);
|
|
146
|
+
|
|
147
|
+
export const localIdPage = (request: typeof SessionRequest.Type): string => {
|
|
148
|
+
const encoded = JSON.stringify(request).replaceAll("<", "\\u003c");
|
|
149
|
+
return `<!doctype html><html><head><meta charset="utf-8"><title>Ignotum ID · Development</title>
|
|
150
|
+
<meta name="viewport" content="width=device-width,initial-scale=1"><style>
|
|
151
|
+
body{font:16px system-ui;background:#f7f7f5;color:#20211f;max-width:420px;margin:8vh auto;padding:24px}
|
|
152
|
+
label{display:block;margin:16px 0}input:not([type=checkbox]){box-sizing:border-box;width:100%;padding:10px;border:1px solid #bbb;border-radius:6px}
|
|
153
|
+
button{padding:12px 18px;border:0;border-radius:6px;background:#222;color:white;cursor:pointer;margin-right:8px}
|
|
154
|
+
p{line-height:1.5}small{color:#666}</style></head><body><h1>Ignotum ID</h1><p>Choose a local username to try your app.</p>
|
|
155
|
+
<form id="form"><label>Username<input name="username" required pattern="[a-z][a-z0-9_-]{0,63}" placeholder="john"></label>
|
|
156
|
+
<label>Name<input name="name"></label><label>Email<input name="email" type="email"></label><label>Image URL<input name="image" type="url"></label>
|
|
157
|
+
${["name", "email", "image"].map((field) => `<label><input type="checkbox" name="share" value="${escapeHtml(field)}"> Share ${escapeHtml(field)}</label>`).join("")}
|
|
158
|
+
<label><input type="checkbox" name="manage"> Replace existing sharing choices</label>
|
|
159
|
+
<p><small>Profile sharing is optional. No password or account is needed in development.</small></p>
|
|
160
|
+
<button>Continue</button><button type="button" id="cancel">Cancel</button><p id="error"></p></form>
|
|
161
|
+
<script type="module">
|
|
162
|
+
const request=${encoded};const form=document.querySelector('#form');
|
|
163
|
+
const previous=JSON.parse(sessionStorage.getItem('${developmentSelectionKey}')||'null');
|
|
164
|
+
if(previous?.username)form.elements.username.value=previous.username;
|
|
165
|
+
if(request.intent==='requestProfile')form.elements.username.readOnly=true;
|
|
166
|
+
let loading=false;const loadProfile=async()=>{loading=true;try{const response=await fetch('${localIdPath}/profile?username='+encodeURIComponent(form.elements.username.value),{headers:{'x-ignotum-request':'1'}});if(!response.ok)throw new Error('Could not load the local profile.');const profile=await response.json();for(const field of ['name','email','image']){form.elements[field].value=profile?.user[field]??'';form.querySelector('input[name=share][value='+field+']').checked=profile?.share.includes(field)??false;}}finally{loading=false;}};form.elements.username.onchange=loadProfile;if(previous?.username)await loadProfile();
|
|
167
|
+
document.querySelector('#cancel').onclick=()=>{sessionStorage.setItem('ignotum.id.outcome','Cancelled');location.replace(request.returnTo)};
|
|
168
|
+
form.onsubmit=async(e)=>{e.preventDefault();if(loading)return;const data=new FormData(form);
|
|
169
|
+
try{const response=await fetch('${localIdPath}',{method:'POST',headers:{'content-type':'application/json','x-ignotum-request':'1'},body:JSON.stringify({request,username:data.get('username'),name:data.get('name'),email:data.get('email'),image:data.get('image'),share:data.getAll('share'),manage:data.get('manage')==='on'})});
|
|
170
|
+
if(!response.ok)throw new Error('Could not update your local ID.');
|
|
171
|
+
sessionStorage.setItem('${developmentSelectionKey}',JSON.stringify({formatVersion:1,username:data.get('username'),epoch:request.intent==='requestProfile'?previous.epoch:'dev:'+crypto.randomUUID()}));
|
|
172
|
+
sessionStorage.setItem('ignotum.id.outcome',request.intent==='requestProfile'?'ProfileUpdated':'SignedIn');location.replace(request.returnTo);
|
|
173
|
+
}catch(error){document.querySelector('#error').textContent=error.message}}
|
|
174
|
+
</script></body></html>`;
|
|
175
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { Context, Deferred, Effect, Exit, Layer, Semaphore } from "effect";
|
|
2
|
+
import { idViewKey, type SessionState } from "@ignotum/contracts/id";
|
|
3
|
+
import type {
|
|
4
|
+
RuntimeInvocationResult,
|
|
5
|
+
RuntimeQueryResult,
|
|
6
|
+
} from "@ignotum/contracts/runtime/hosted";
|
|
7
|
+
import { makeDependencyIndex, type QueryInvalidationEvent } from "@ignotum/runtime/sync";
|
|
8
|
+
|
|
9
|
+
export class LocalQueryCache extends Context.Service<
|
|
10
|
+
LocalQueryCache,
|
|
11
|
+
{
|
|
12
|
+
readonly execute: (
|
|
13
|
+
base: string,
|
|
14
|
+
identity: SessionState,
|
|
15
|
+
minimumRevision: number,
|
|
16
|
+
execute: Effect.Effect<RuntimeInvocationResult>,
|
|
17
|
+
) => Effect.Effect<RuntimeInvocationResult>;
|
|
18
|
+
readonly invalidate: (event: QueryInvalidationEvent) => void;
|
|
19
|
+
}
|
|
20
|
+
>()("ignotum/dev-runtime/query-cache/LocalQueryCache") {
|
|
21
|
+
static readonly layer = Layer.effect(
|
|
22
|
+
LocalQueryCache,
|
|
23
|
+
Effect.sync(() => {
|
|
24
|
+
const results = new Map<string, RuntimeQueryResult>();
|
|
25
|
+
const dependencies = makeDependencyIndex<string>();
|
|
26
|
+
const flights = new Map<string, Deferred.Deferred<RuntimeInvocationResult>>();
|
|
27
|
+
const publicQueries = new Set<string>();
|
|
28
|
+
const locks = new Map<string, Semaphore.Semaphore>();
|
|
29
|
+
let generation = 0;
|
|
30
|
+
const save = (key: string, result: RuntimeQueryResult) => {
|
|
31
|
+
results.delete(key);
|
|
32
|
+
results.set(key, result);
|
|
33
|
+
dependencies.record(key, result.dependencies);
|
|
34
|
+
while (results.size > 128) {
|
|
35
|
+
const oldest = results.keys().next().value;
|
|
36
|
+
if (oldest === undefined) break;
|
|
37
|
+
results.delete(oldest);
|
|
38
|
+
dependencies.remove(oldest);
|
|
39
|
+
publicQueries.delete(oldest);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
return LocalQueryCache.of({
|
|
43
|
+
invalidate: (event) => {
|
|
44
|
+
generation++;
|
|
45
|
+
const keys =
|
|
46
|
+
event.type === "All"
|
|
47
|
+
? [...results.keys()]
|
|
48
|
+
: [...dependencies.affected(event.invalidations)];
|
|
49
|
+
for (const key of keys) {
|
|
50
|
+
results.delete(key);
|
|
51
|
+
dependencies.remove(key);
|
|
52
|
+
}
|
|
53
|
+
if (event.type === "All") publicQueries.clear();
|
|
54
|
+
},
|
|
55
|
+
execute: Effect.fn("LocalQueryCache.execute")(
|
|
56
|
+
function* (base, identity, minimumRevision, execute) {
|
|
57
|
+
const admitted = generation;
|
|
58
|
+
const scoped = `${base}:id:${idViewKey(identity)}`;
|
|
59
|
+
const cached = results.get(base) ?? results.get(scoped);
|
|
60
|
+
if (cached !== undefined && cached.observedRevision >= minimumRevision) return cached;
|
|
61
|
+
const key = `${scoped}:${admitted}`;
|
|
62
|
+
const pending = flights.get(key);
|
|
63
|
+
if (pending !== undefined) return yield* Deferred.await(pending);
|
|
64
|
+
const done = yield* Deferred.make<RuntimeInvocationResult>();
|
|
65
|
+
flights.set(key, done);
|
|
66
|
+
const evaluate = Effect.gen(function* () {
|
|
67
|
+
const shared = results.get(base);
|
|
68
|
+
if (shared !== undefined && shared.observedRevision >= minimumRevision) return shared;
|
|
69
|
+
const result = yield* execute;
|
|
70
|
+
if (result.type === "Query" && generation === admitted) {
|
|
71
|
+
const readsId = result.dependencies.some((dependency) => dependency.type === "Id");
|
|
72
|
+
save(scoped, result);
|
|
73
|
+
if (!readsId) {
|
|
74
|
+
publicQueries.add(base);
|
|
75
|
+
save(base, result);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return result;
|
|
79
|
+
});
|
|
80
|
+
const lockKey = `${base}:${admitted}`;
|
|
81
|
+
let lock = locks.get(lockKey);
|
|
82
|
+
if (publicQueries.has(base) && lock === undefined) {
|
|
83
|
+
lock = Semaphore.makeUnsafe(1);
|
|
84
|
+
locks.set(lockKey, lock);
|
|
85
|
+
}
|
|
86
|
+
return yield* Effect.uninterruptibleMask((restore) =>
|
|
87
|
+
Effect.gen(function* () {
|
|
88
|
+
const exit = yield* restore(
|
|
89
|
+
lock === undefined ? evaluate : lock.withPermits(1)(evaluate),
|
|
90
|
+
).pipe(Effect.exit);
|
|
91
|
+
Deferred.doneUnsafe(
|
|
92
|
+
done,
|
|
93
|
+
Exit.isSuccess(exit) ? Effect.succeed(exit.value) : Effect.failCause(exit.cause),
|
|
94
|
+
);
|
|
95
|
+
flights.delete(key);
|
|
96
|
+
locks.delete(lockKey);
|
|
97
|
+
return yield* exit;
|
|
98
|
+
}),
|
|
99
|
+
);
|
|
100
|
+
},
|
|
101
|
+
),
|
|
102
|
+
});
|
|
103
|
+
}),
|
|
104
|
+
);
|
|
105
|
+
}
|
package/src/dev-runtime/sync.ts
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
import { LocalQueryCache } from "./query-cache.js";
|
|
2
|
+
import {
|
|
3
|
+
SessionRequest,
|
|
4
|
+
idViewKey,
|
|
5
|
+
sessionPath,
|
|
6
|
+
sessionRequestsPath,
|
|
7
|
+
type SessionState,
|
|
8
|
+
} from "@ignotum/contracts/id";
|
|
9
|
+
import { LocalId, LocalCompletion, localIdPage, localIdPath } from "./id.js";
|
|
1
10
|
// @effect-diagnostics-next-line nodeBuiltinImport:off Vite exposes its HTTP server through Node's adapter types.
|
|
2
11
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
3
12
|
|
|
@@ -25,6 +34,7 @@ import {
|
|
|
25
34
|
} from "@ignotum/contracts/runtime/sync";
|
|
26
35
|
import {
|
|
27
36
|
Effect,
|
|
37
|
+
Clock,
|
|
28
38
|
Context,
|
|
29
39
|
FileSystem,
|
|
30
40
|
Function,
|
|
@@ -95,12 +105,14 @@ const reloadEnvironmentAndInvalidate = <Error, Requirements>(
|
|
|
95
105
|
export const queryInvalidationLayer = Layer.effect(
|
|
96
106
|
QueryInvalidation,
|
|
97
107
|
Effect.gen(function* () {
|
|
108
|
+
const cache = yield* LocalQueryCache;
|
|
98
109
|
const pubsub = yield* PubSub.unbounded<QueryInvalidationEvent>();
|
|
99
110
|
const latestRevision = yield* Ref.make(AppStateRevision.make(0));
|
|
100
111
|
return QueryInvalidation.of({
|
|
101
112
|
latestRevision: Ref.get(latestRevision),
|
|
102
113
|
publish: (event) =>
|
|
103
114
|
Effect.gen(function* () {
|
|
115
|
+
cache.invalidate(event);
|
|
104
116
|
if (event.type === "Dependencies") {
|
|
105
117
|
yield* Ref.update(latestRevision, (current) =>
|
|
106
118
|
AppStateRevision.make(Math.max(current, event.committedRevision)),
|
|
@@ -111,7 +123,7 @@ export const queryInvalidationLayer = Layer.effect(
|
|
|
111
123
|
subscribe: PubSub.subscribe(pubsub),
|
|
112
124
|
});
|
|
113
125
|
}),
|
|
114
|
-
);
|
|
126
|
+
).pipe(Layer.provideMerge(LocalQueryCache.layer));
|
|
115
127
|
|
|
116
128
|
class InvocationIdConflict extends Schema.TaggedError<InvocationIdConflict>()(
|
|
117
129
|
"InvocationIdConflict",
|
|
@@ -198,10 +210,21 @@ const syncError = (code: ErrorCode, message: string, operation?: Operation): Ser
|
|
|
198
210
|
return { type: "Error", code, message, operation };
|
|
199
211
|
};
|
|
200
212
|
|
|
201
|
-
export const runSession = Effect.fn("SyncServer.runSession")(function* (
|
|
213
|
+
export const runSession = Effect.fn("SyncServer.runSession")(function* (
|
|
214
|
+
socket: Socket.Socket,
|
|
215
|
+
initialIdentity?: SessionState,
|
|
216
|
+
refreshIdentity?: Effect.Effect<SessionState>,
|
|
217
|
+
) {
|
|
218
|
+
let identity = initialIdentity ?? {
|
|
219
|
+
sessionEpoch: "dev:anonymous",
|
|
220
|
+
user: null,
|
|
221
|
+
viewRevision: 0,
|
|
222
|
+
validUntil: Number.MAX_SAFE_INTEGER,
|
|
223
|
+
};
|
|
202
224
|
const runtime = yield* FunctionRuntime;
|
|
203
225
|
const invalidation = yield* QueryInvalidation;
|
|
204
226
|
const mutationReplay = yield* MutationReplay;
|
|
227
|
+
const queryCache = yield* LocalQueryCache;
|
|
205
228
|
const files = yield* LocalApplicationFiles;
|
|
206
229
|
const subscriptions = yield* Ref.make(HashMap.empty<SubscriptionId, QuerySubscription>());
|
|
207
230
|
const dependencyIndex = makeDependencyIndex<SubscriptionId>();
|
|
@@ -211,8 +234,12 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (socket:
|
|
|
211
234
|
const write = yield* socket.writer;
|
|
212
235
|
|
|
213
236
|
const send = Effect.fn("SyncServer.send")(function* (message: ServerMessage) {
|
|
237
|
+
const bound =
|
|
238
|
+
message.type === "Result" || message.type === "Preparation" || message.type === "Error"
|
|
239
|
+
? { ...message, sessionEpoch: identity.sessionEpoch }
|
|
240
|
+
: message;
|
|
214
241
|
yield* writeSemaphore.withPermits(1)(
|
|
215
|
-
Schema.encodeEffect(ServerMessageJson)(
|
|
242
|
+
Schema.encodeEffect(ServerMessageJson)(bound).pipe(Effect.flatMap(write)),
|
|
216
243
|
);
|
|
217
244
|
});
|
|
218
245
|
|
|
@@ -261,7 +288,7 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (socket:
|
|
|
261
288
|
while (yield* isActive(subscriptionId, subscription)) {
|
|
262
289
|
if (prepared === undefined) {
|
|
263
290
|
const resolved = yield* runtime
|
|
264
|
-
.prepare(subscription.function, "Query", subscription.args)
|
|
291
|
+
.prepare(subscription.function, "Query", subscription.args, identity)
|
|
265
292
|
.pipe(
|
|
266
293
|
Effect.catchTags({
|
|
267
294
|
FunctionUnavailable: (error) => sendResolutionError(operation, error, deliver),
|
|
@@ -274,7 +301,12 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (socket:
|
|
|
274
301
|
prepared = resolved;
|
|
275
302
|
}
|
|
276
303
|
|
|
277
|
-
const result = yield*
|
|
304
|
+
const result = yield* queryCache.execute(
|
|
305
|
+
subscription.queryKey,
|
|
306
|
+
identity,
|
|
307
|
+
yield* invalidation.latestRevision,
|
|
308
|
+
prepared.execute,
|
|
309
|
+
);
|
|
278
310
|
prepared = undefined;
|
|
279
311
|
if (result.type !== "Query") return;
|
|
280
312
|
if (
|
|
@@ -291,6 +323,11 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (socket:
|
|
|
291
323
|
id: subscriptionId,
|
|
292
324
|
result: result.result,
|
|
293
325
|
revision: result.observedRevision,
|
|
326
|
+
identity: {
|
|
327
|
+
sessionEpoch: identity.sessionEpoch,
|
|
328
|
+
viewRevision: identity.viewRevision,
|
|
329
|
+
dependsOnId: result.dependencies.some((dependency) => dependency.type === "Id"),
|
|
330
|
+
},
|
|
294
331
|
} as const;
|
|
295
332
|
yield* deliver(granted.length === 0 ? snapshot : { ...snapshot, files: granted });
|
|
296
333
|
return;
|
|
@@ -315,6 +352,19 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (socket:
|
|
|
315
352
|
};
|
|
316
353
|
|
|
317
354
|
const refreshAll = Effect.fn("SyncServer.refreshAll")(function* () {
|
|
355
|
+
if (refreshIdentity !== undefined) {
|
|
356
|
+
const next = yield* refreshIdentity;
|
|
357
|
+
const changed =
|
|
358
|
+
idViewKey(identity) !== idViewKey(next) || identity.validUntil !== next.validUntil;
|
|
359
|
+
identity = next;
|
|
360
|
+
if (changed)
|
|
361
|
+
yield* send({
|
|
362
|
+
type: "Session",
|
|
363
|
+
event: "Updated",
|
|
364
|
+
session: identity,
|
|
365
|
+
serverTime: yield* Clock.currentTimeMillis,
|
|
366
|
+
});
|
|
367
|
+
}
|
|
318
368
|
const current = yield* Ref.get(subscriptions);
|
|
319
369
|
yield* Effect.forEach(HashMap.toEntries(current), ([subscriptionId, subscription]) =>
|
|
320
370
|
scheduleQuery(subscriptionId, subscription),
|
|
@@ -359,7 +409,7 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (socket:
|
|
|
359
409
|
return;
|
|
360
410
|
}
|
|
361
411
|
|
|
362
|
-
const prepared = yield* runtime.prepare(message.function, "Query", message.args).pipe(
|
|
412
|
+
const prepared = yield* runtime.prepare(message.function, "Query", message.args, identity).pipe(
|
|
363
413
|
Effect.catchTags({
|
|
364
414
|
FunctionUnavailable: (error) => sendResolutionError(operation, error),
|
|
365
415
|
InvalidArguments: (error) => sendResolutionError(operation, error),
|
|
@@ -402,14 +452,16 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (socket:
|
|
|
402
452
|
yield* files.releasePreparation(message.id);
|
|
403
453
|
return;
|
|
404
454
|
}
|
|
405
|
-
const prepared = yield* runtime
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
455
|
+
const prepared = yield* runtime
|
|
456
|
+
.prepare(message.function, "Mutation", message.args, identity)
|
|
457
|
+
.pipe(
|
|
458
|
+
Effect.catchTags({
|
|
459
|
+
FunctionUnavailable: (error) => sendResolutionError(operation, error),
|
|
460
|
+
InvalidArguments: (error) => sendResolutionError(operation, error),
|
|
461
|
+
UnknownFunction: (error) => sendResolutionError(operation, error),
|
|
462
|
+
WrongFunctionKind: (error) => sendResolutionError(operation, error),
|
|
463
|
+
}),
|
|
464
|
+
);
|
|
413
465
|
|
|
414
466
|
if (prepared === undefined) {
|
|
415
467
|
yield* files.releasePreparation(message.id);
|
|
@@ -421,6 +473,7 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (socket:
|
|
|
421
473
|
"Mutation",
|
|
422
474
|
message.function,
|
|
423
475
|
message.args,
|
|
476
|
+
identity.sessionEpoch,
|
|
424
477
|
);
|
|
425
478
|
yield* mutationReplay
|
|
426
479
|
.execute(
|
|
@@ -525,7 +578,12 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (socket:
|
|
|
525
578
|
|
|
526
579
|
yield* socket
|
|
527
580
|
.runString((text) => messageSemaphore.withPermits(1)(handleMessage(text)), {
|
|
528
|
-
onOpen: send({
|
|
581
|
+
onOpen: send({
|
|
582
|
+
type: "Handshake",
|
|
583
|
+
...localSyncIdentity,
|
|
584
|
+
session: identity,
|
|
585
|
+
serverTime: yield* Clock.currentTimeMillis,
|
|
586
|
+
}).pipe(Effect.orDie),
|
|
529
587
|
})
|
|
530
588
|
.pipe(
|
|
531
589
|
Effect.ensuring(
|
|
@@ -583,6 +641,7 @@ const makeHandlersLayer = (server: ViteDevServer, appDirectory: string, database
|
|
|
583
641
|
queryInvalidationLayer,
|
|
584
642
|
persistenceLayer,
|
|
585
643
|
NodeServices.layer,
|
|
644
|
+
LocalId.layer(appDirectory).pipe(Layer.provide(NodeServices.layer)),
|
|
586
645
|
localApplicationFilesLayer(appDirectory).pipe(
|
|
587
646
|
Layer.provideMerge(DevelopmentDatabase.layer),
|
|
588
647
|
Layer.provide(Layer.mergeAll(IdGenerator.layer, sqliteLayer, NodeServices.layer)),
|
|
@@ -592,6 +651,7 @@ const makeHandlersLayer = (server: ViteDevServer, appDirectory: string, database
|
|
|
592
651
|
return Layer.effect(
|
|
593
652
|
SyncHandlers,
|
|
594
653
|
Effect.gen(function* () {
|
|
654
|
+
const localId = yield* LocalId;
|
|
595
655
|
const invalidation = yield* QueryInvalidation;
|
|
596
656
|
const environment = yield* DevelopmentEnvironment;
|
|
597
657
|
const fileSystem = yield* FileSystem.FileSystem;
|
|
@@ -609,7 +669,72 @@ const makeHandlersLayer = (server: ViteDevServer, appDirectory: string, database
|
|
|
609
669
|
);
|
|
610
670
|
const httpApp = Effect.gen(function* () {
|
|
611
671
|
const request = yield* HttpServerRequest.HttpServerRequest;
|
|
612
|
-
const
|
|
672
|
+
const url = new URL(request.url, `http://${request.headers.host ?? "ignotum.local"}`);
|
|
673
|
+
const pathname = url.pathname;
|
|
674
|
+
if (pathname === sessionPath && request.method === "GET") {
|
|
675
|
+
const session = yield* localId.session(
|
|
676
|
+
url.searchParams.get("username"),
|
|
677
|
+
url.searchParams.get("epoch") ?? "dev:anonymous",
|
|
678
|
+
);
|
|
679
|
+
return yield* HttpServerResponse.json(
|
|
680
|
+
{
|
|
681
|
+
...session,
|
|
682
|
+
serverTime: yield* Clock.currentTimeMillis,
|
|
683
|
+
expiresAt: session.validUntil,
|
|
684
|
+
development: true,
|
|
685
|
+
},
|
|
686
|
+
{ headers: { "cache-control": "no-store" } },
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
if (pathname === sessionRequestsPath && request.method === "POST") {
|
|
690
|
+
if (request.headers.origin !== url.origin || request.headers["x-ignotum-request"] !== "1")
|
|
691
|
+
return HttpServerResponse.empty({ status: 403 });
|
|
692
|
+
const input = yield* request.json.pipe(
|
|
693
|
+
Effect.flatMap(Schema.decodeUnknownEffect(SessionRequest)),
|
|
694
|
+
Effect.option,
|
|
695
|
+
);
|
|
696
|
+
if (Option.isNone(input)) return HttpServerResponse.empty({ status: 400 });
|
|
697
|
+
return yield* HttpServerResponse.json(
|
|
698
|
+
{
|
|
699
|
+
redirectUrl: `${localIdPath}?request=${encodeURIComponent(JSON.stringify(input.value))}`,
|
|
700
|
+
expiresAt: (yield* Clock.currentTimeMillis) + 600_000,
|
|
701
|
+
},
|
|
702
|
+
{ status: 201, headers: { "cache-control": "no-store" } },
|
|
703
|
+
);
|
|
704
|
+
}
|
|
705
|
+
if (pathname === `${localIdPath}/profile` && request.method === "GET") {
|
|
706
|
+
if (request.headers["x-ignotum-request"] !== "1")
|
|
707
|
+
return HttpServerResponse.empty({ status: 403 });
|
|
708
|
+
return yield* HttpServerResponse.json(
|
|
709
|
+
(yield* localId.profile(url.searchParams.get("username") ?? "")) ?? null,
|
|
710
|
+
{ headers: { "cache-control": "no-store" } },
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
if (pathname === localIdPath && request.method === "GET") {
|
|
714
|
+
const input = yield* Schema.decodeEffect(Schema.fromJsonString(SessionRequest))(
|
|
715
|
+
url.searchParams.get("request") ?? "",
|
|
716
|
+
).pipe(Effect.option);
|
|
717
|
+
return Option.isNone(input)
|
|
718
|
+
? HttpServerResponse.empty({ status: 400 })
|
|
719
|
+
: HttpServerResponse.html(localIdPage(input.value));
|
|
720
|
+
}
|
|
721
|
+
if (pathname === localIdPath && request.method === "POST") {
|
|
722
|
+
if (request.headers.origin !== url.origin || request.headers["x-ignotum-request"] !== "1")
|
|
723
|
+
return HttpServerResponse.empty({ status: 403 });
|
|
724
|
+
const input = yield* request.json.pipe(
|
|
725
|
+
Effect.flatMap(Schema.decodeUnknownEffect(LocalCompletion)),
|
|
726
|
+
Effect.option,
|
|
727
|
+
);
|
|
728
|
+
if (Option.isNone(input)) return HttpServerResponse.empty({ status: 400 });
|
|
729
|
+
yield* localId.complete(input.value);
|
|
730
|
+
yield* invalidation.publish({ type: "All" });
|
|
731
|
+
return HttpServerResponse.empty({ status: 204 });
|
|
732
|
+
}
|
|
733
|
+
if (pathname === sessionPath && request.method === "DELETE") {
|
|
734
|
+
if (request.headers.origin !== url.origin || request.headers["x-ignotum-request"] !== "1")
|
|
735
|
+
return HttpServerResponse.empty({ status: 403 });
|
|
736
|
+
return HttpServerResponse.empty({ status: 204 });
|
|
737
|
+
}
|
|
613
738
|
if (pathname.startsWith(fileUploadUrlPrefix)) {
|
|
614
739
|
if (request.method !== "PUT")
|
|
615
740
|
return HttpServerResponse.text("Method Not Allowed", { status: 405 });
|
|
@@ -661,8 +786,15 @@ const makeHandlersLayer = (server: ViteDevServer, appDirectory: string, database
|
|
|
661
786
|
});
|
|
662
787
|
const socketApp = Effect.gen(function* () {
|
|
663
788
|
const request = yield* HttpServerRequest.HttpServerRequest;
|
|
789
|
+
const url = new URL(request.url, `http://${request.headers.host ?? "ignotum.local"}`);
|
|
790
|
+
if (request.headers.origin !== url.origin) return HttpServerResponse.empty({ status: 403 });
|
|
791
|
+
const refresh = localId.session(
|
|
792
|
+
url.searchParams.get("username"),
|
|
793
|
+
url.searchParams.get("epoch") ?? "dev:anonymous",
|
|
794
|
+
);
|
|
795
|
+
const identity = yield* refresh;
|
|
664
796
|
const socket = yield* request.upgrade;
|
|
665
|
-
yield* runSession(socket);
|
|
797
|
+
yield* runSession(socket, identity, refresh);
|
|
666
798
|
return HttpServerResponse.empty();
|
|
667
799
|
});
|
|
668
800
|
const http = yield* NodeHttpServer.makeHandler(httpApp, { scope });
|
package/src/server/index.ts
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"api-DzcR7spt.js","names":[],"sources":["../../../contracts/dist/runtime/identity.js","../../../contracts/dist/runtime/sync.js","../../src/internal/api.ts"],"sourcesContent":["import { AppId, AuthAccountId, AuthDeviceCodeId, AuthInternalId, AuthInvitationId, AuthMemberId, AuthSessionId, AuthVerificationId, ConnectionId, DeploymentId, DevDatabaseLockId, InvocationId, PlatformPrincipalId, RequestId, RuntimeRequestNonce, SubscriptionId, TableId, TeamId } from \"./id.js\";\nimport { Schema } from \"effect\";\n//#region src/runtime/identity.ts\nconst InvocationKey = Schema.String.pipe(Schema.brand(\"ignotum/hosted/InvocationKey\"));\nconst DeploymentGeneration = Schema.Natural.pipe(Schema.brand(\"ignotum/hosted/DeploymentGeneration\"));\nconst AppStateRevision = Schema.Natural.pipe(Schema.brand(\"ignotum/hosted/AppStateRevision\"));\n//#endregion\nexport { AppId, AppStateRevision, AuthAccountId, AuthDeviceCodeId, AuthInternalId, AuthInvitationId, AuthMemberId, AuthSessionId, AuthVerificationId, ConnectionId, DeploymentGeneration, DeploymentId, DevDatabaseLockId, InvocationId, InvocationKey, PlatformPrincipalId, RequestId, RuntimeRequestNonce, SubscriptionId, TableId, TeamId };\n\n//# sourceMappingURL=identity.js.map","import { AppId, DeploymentId, InvocationId, SubscriptionId } from \"./id.js\";\nimport { AppStateRevision, DeploymentGeneration } from \"./identity.js\";\nimport { FileId } from \"../schema/file.js\";\nimport { TransportValueSchema, datePathsOf, datePathsOfObject, decodeTransportObject, decodeTransportValue, encodeTransportObject, encodeTransportValue } from \"./value.js\";\nimport { Schema } from \"effect\";\n//#region src/runtime/sync.ts\nconst FunctionNamePart = Schema.String.check(Schema.isPattern(/^[A-Za-z_$][A-Za-z0-9_$]*$/));\nconst ApiFunctionAddressParts = Schema.TemplateLiteralParser([\n\t\"api.\",\n\tFunctionNamePart,\n\t\".\",\n\tFunctionNamePart\n]);\nconst FunctionAddress = Schema.TemplateLiteral([\n\t\"api.\",\n\tFunctionNamePart,\n\t\".\",\n\tFunctionNamePart\n]);\nconst apiFunctionParts = (address) => {\n\tconst [, moduleName, , functionName] = Schema.decodeSync(ApiFunctionAddressParts)(address);\n\treturn {\n\t\tfunctionName,\n\t\tmoduleName\n\t};\n};\nconst Subscribe = Schema.Struct({\n\ttype: Schema.Literal(\"Subscribe\"),\n\tid: SubscriptionId,\n\tfunction: FunctionAddress,\n\targs: Schema.Json\n});\nconst Unsubscribe = Schema.Struct({\n\ttype: Schema.Literal(\"Unsubscribe\"),\n\tid: SubscriptionId\n});\nconst Invoke = Schema.Struct({\n\ttype: Schema.Literal(\"Invoke\"),\n\tid: InvocationId,\n\tkind: Schema.Literal(\"Mutation\"),\n\tfunction: FunctionAddress,\n\targs: Schema.Json\n});\nconst Prepare = Schema.Struct({\n\ttype: Schema.Literal(\"Prepare\"),\n\tid: InvocationId,\n\tkind: Schema.Literal(\"Mutation\"),\n\tfunction: FunctionAddress,\n\targs: Schema.Json,\n\tfiles: Schema.Array(FileId)\n});\nconst ClientMessage = Schema.Union([\n\tSubscribe,\n\tUnsubscribe,\n\tPrepare,\n\tInvoke\n]);\nconst SubscriptionOperation = Schema.Struct({\n\ttype: Schema.Literal(\"Subscription\"),\n\tid: SubscriptionId\n});\nconst InvocationOperation = Schema.Struct({\n\ttype: Schema.Literal(\"Invocation\"),\n\tid: InvocationId\n});\nconst Operation = Schema.Union([SubscriptionOperation, InvocationOperation]);\nconst DatePath = Schema.Array(Schema.Union([Schema.String, Schema.Natural]));\nconst DatePaths = Schema.Array(DatePath);\nconst WireSuccess = Schema.Struct({\n\ttype: Schema.Literal(\"Success\"),\n\tvalue: Schema.optional(Schema.Json),\n\tdates: Schema.optional(DatePaths)\n});\nconst WireFailure = Schema.Struct({\n\ttype: Schema.Literal(\"Failure\"),\n\terror: Schema.Json,\n\tdates: Schema.optional(DatePaths)\n});\nconst WireResult = Schema.Union([WireSuccess, WireFailure]);\nconst SyncHandshake = Schema.Struct({\n\ttype: Schema.Literal(\"Handshake\"),\n\tappId: AppId,\n\tdeploymentId: DeploymentId,\n\tgeneration: DeploymentGeneration\n});\nconst Snapshot = Schema.Struct({\n\ttype: Schema.Literal(\"Snapshot\"),\n\tid: SubscriptionId,\n\tresult: WireResult,\n\trevision: AppStateRevision,\n\tfiles: Schema.optional(Schema.Array(Schema.Struct({\n\t\tid: FileId,\n\t\turl: Schema.String\n\t})))\n});\nconst Preparation = Schema.Struct({\n\ttype: Schema.Literal(\"Preparation\"),\n\tid: InvocationId,\n\tkind: Schema.Literal(\"Mutation\"),\n\tuploads: Schema.Array(Schema.Struct({\n\t\tid: FileId,\n\t\turl: Schema.optional(Schema.String)\n\t}))\n});\nconst SyncResultSuccess = Schema.Struct({\n\ttype: Schema.Literal(\"Result\"),\n\tid: InvocationId,\n\tresult: WireSuccess,\n\tcommittedRevision: AppStateRevision\n});\nconst SyncResultFailure = Schema.Struct({\n\ttype: Schema.Literal(\"Result\"),\n\tid: InvocationId,\n\tresult: WireFailure\n});\nconst ErrorCode = Schema.Literals([\n\t\"DuplicateOperationId\",\n\t\"FunctionUnavailable\",\n\t\"InvalidArguments\",\n\t\"InvalidMessage\",\n\t\"InvocationIdConflict\",\n\t\"ResourceLimitExceeded\",\n\t\"UnknownFunction\",\n\t\"WrongFunctionKind\"\n]);\nconst SyncError = Schema.Struct({\n\ttype: Schema.Literal(\"Error\"),\n\toperation: Schema.optional(Operation),\n\tcode: ErrorCode,\n\tmessage: Schema.String\n});\nconst Deployment = Schema.Struct({\n\ttype: Schema.Literal(\"Deployment\"),\n\tdeploymentId: DeploymentId,\n\tgeneration: DeploymentGeneration\n});\nconst DeploymentCloseCode = 4409;\nconst ServerMessage = Schema.Union([\n\tSyncHandshake,\n\tSnapshot,\n\tPreparation,\n\tSyncResultSuccess,\n\tSyncResultFailure,\n\tSyncError,\n\tDeployment\n]);\nconst ClientMessageJson = Schema.fromJsonString(ClientMessage);\nconst ServerMessageJson = Schema.fromJsonString(ServerMessage);\n//#endregion\nexport { ClientMessage, ClientMessageJson, DatePath, Deployment, DeploymentCloseCode, ErrorCode, FunctionAddress, FunctionNamePart, InvocationId, Operation, ServerMessage, ServerMessageJson, SubscriptionId, SyncHandshake, TransportValueSchema, WireFailure, WireResult, WireSuccess, apiFunctionParts, datePathsOf, datePathsOfObject, decodeTransportObject, decodeTransportValue, encodeTransportObject, encodeTransportValue };\n\n//# sourceMappingURL=sync.js.map","import { Predicate } from \"effect\";\nimport type { Effect } from \"effect\";\n\nimport type { ErrorValue, InternalServerError } from \"@ignotum/contracts/runtime/result\";\nimport { FunctionAddress } from \"@ignotum/contracts/runtime/sync\";\nimport type { FileValue } from \"@ignotum/contracts/schema/file\";\n\nconst FunctionReferenceTypeId: unique symbol = Symbol.for(\"ignotum/internal/api/FunctionReference\");\ndeclare const FunctionReferenceTypesTypeId: unique symbol;\n\ntype FunctionKind = \"Mutation\" | \"Query\";\n\nexport type MutationInput<Value> = Value extends FileValue\n ? Value | File\n : Value extends Date\n ? Value\n : Value extends ReadonlyArray<infer Item>\n ? ReadonlyArray<MutationInput<Item>>\n : Value extends object\n ? { readonly [Key in keyof Value]: MutationInput<Value[Key]> }\n : Value;\n\nexport interface FunctionReference<\n Kind extends FunctionKind,\n Args,\n Success,\n Failure extends ErrorValue,\n> {\n readonly [FunctionReferenceTypeId]: FunctionAddress;\n readonly [FunctionReferenceTypesTypeId]?: {\n readonly kind: Kind;\n readonly args: Args;\n readonly value: Success;\n readonly error: Failure;\n };\n}\n\ntype ReferenceTypes<Reference> = Reference extends {\n readonly [FunctionReferenceTypesTypeId]?: infer Types;\n}\n ? Exclude<Types, undefined>\n : never;\n\nexport declare namespace FunctionReference {\n type Args<Reference> =\n ReferenceTypes<Reference> extends { readonly args: infer Args } ? Args : never;\n type Failure<Reference> =\n ReferenceTypes<Reference> extends {\n readonly error: infer Failure;\n }\n ? Failure\n : never;\n type Kind<Reference> =\n ReferenceTypes<Reference> extends { readonly kind: infer Kind } ? Kind : never;\n type Success<Reference> =\n ReferenceTypes<Reference> extends {\n readonly value: infer Success;\n }\n ? Success\n : never;\n}\n\ntype ReferenceOf<Definition> = Definition extends {\n readonly _tag: infer Kind extends FunctionKind;\n readonly handler: (\n ...arguments_: infer HandlerArguments\n ) => Generator<infer Yielded, infer Success, never>;\n}\n ? HandlerArguments extends readonly [infer _Context, ...infer Rest]\n ? FunctionReference<\n Kind,\n Rest extends readonly [infer Args, ...ReadonlyArray<unknown>]\n ? Kind extends \"Mutation\"\n ? MutationInput<Args>\n : Args\n : void,\n Success,\n | (Yielded extends Effect.Effect<unknown, infer Failure extends ErrorValue, never>\n ? Failure\n : never)\n | InternalServerError\n >\n : never\n : never;\n\ntype ApiModule<Module> = {\n readonly [FunctionName in keyof Module as FunctionName extends string\n ? ReferenceOf<Module[FunctionName]> extends never\n ? never\n : FunctionName\n : never]: ReferenceOf<Module[FunctionName]>;\n};\n\nexport type Api<Modules> = {\n readonly [ModuleName in keyof Modules]: ApiModule<Modules[ModuleName]>;\n};\n\nexport const functionPathOf = <\n Kind extends FunctionKind,\n Args,\n Success,\n Failure extends ErrorValue,\n>(\n reference: FunctionReference<Kind, Args, Success, Failure>,\n) => reference[FunctionReferenceTypeId];\n\nconst makeModuleReference = (moduleName: string) => {\n const references = new Map<string, object>();\n\n return new Proxy(\n {},\n {\n get: (_target, functionName) => {\n if (!Predicate.isString(functionName)) {\n return undefined;\n }\n\n const existing = references.get(functionName);\n if (existing !== undefined) {\n return existing;\n }\n\n const reference = {\n [FunctionReferenceTypeId]: FunctionAddress.make(`api.${moduleName}.${functionName}`),\n };\n references.set(functionName, reference);\n return reference;\n },\n },\n );\n};\n\nexport function createApi<Modules>(): Api<Modules>;\nexport function createApi() {\n const modules = new Map<string, object>();\n\n return new Proxy(\n {},\n {\n get: (_target, moduleName) => {\n if (!Predicate.isString(moduleName)) {\n return undefined;\n }\n\n const existing = modules.get(moduleName);\n if (existing !== undefined) {\n return existing;\n }\n\n const moduleReference = makeModuleReference(moduleName);\n modules.set(moduleName, moduleReference);\n return moduleReference;\n },\n },\n );\n}\n"],"mappings":"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"}
|