@deepseek-ai/dsh-schedule 0.1.7-alpha.2 → 0.1.7-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +75 -5
- package/README.md +65 -129
- package/README.zh.md +72 -137
- package/lib/index.js +2147 -797
- package/lib/invariant.js +79 -17
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +727 -0
- package/lib/typert.remote-client.d.ts +30 -0
- package/lib/typert.remote-client.js +477 -0
- package/lib/types/delivery-history.d.ts +24 -0
- package/lib/types/delivery-history.js +66 -0
- package/lib/types/domain.d.ts +191 -27
- package/lib/types/domain.js +1000 -113
- package/lib/types/index.d.ts +130 -24
- package/lib/types/index.js +428 -101
- package/lib/types/invariant.js +8 -1
- package/lib/types/runtime.d.ts +20 -47
- package/lib/types/runtime.js +129 -268
- package/lib/types/storage.d.ts +59 -0
- package/lib/types/storage.js +54 -0
- package/lib/types/tools.d.ts +5 -6
- package/lib/types/tools.js +305 -204
- package/lib/types/types.d.ts +259 -35
- package/lib/types/update.d.ts +26 -0
- package/lib/types/update.js +164 -0
- package/package.json +50 -28
- package/lib/types/persistence.d.ts +0 -19
- package/lib/types/persistence.js +0 -30
- package/lib/types/projection.d.ts +0 -35
- package/lib/types/projection.js +0 -68
- package/lib/types/transaction.d.ts +0 -10
- package/lib/types/transaction.js +0 -22
package/lib/invariant.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import "@js-temporal/polyfill";
|
|
1
2
|
import { SessionLogOffset } from "@deepseek-ai/dsh-session";
|
|
2
3
|
const MIN_FOUR_DIGIT_YEAR_MS = Date.parse("0001-01-01T00:00:00.000Z");
|
|
3
4
|
const MAX_FOUR_DIGIT_YEAR_MS = Date.parse("9999-12-31T23:59:59.999Z");
|
|
@@ -30,9 +31,45 @@ function isRecord(value) {
|
|
|
30
31
|
}
|
|
31
32
|
/** Require exactly the named durable object keys. */
|
|
32
33
|
function hasExactKeys(value, expected) {
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
const allowed = new Set(expected);
|
|
35
|
+
return expected.every((key) => key in value) && Object.keys(value).every((key) => allowed.has(key));
|
|
36
|
+
}
|
|
37
|
+
/** Require the named durable keys while admitting further optional members. */
|
|
38
|
+
function hasExactKeysWithOptional(value, required, optional) {
|
|
39
|
+
const allowed = new Set([...required, ...optional]);
|
|
40
|
+
return required.every((key) => key in value) && Object.keys(value).every((key) => allowed.has(key));
|
|
41
|
+
}
|
|
42
|
+
/** Stable diagnostic for a title that is missing or empty after trimming. */
|
|
43
|
+
const REQUIRED_TITLE_MESSAGE = "title is required and must be non-empty after trimming.";
|
|
44
|
+
/**
|
|
45
|
+
* Validate one required stored title at the durable boundary.
|
|
46
|
+
*
|
|
47
|
+
* Only records written after titles became required are read: a missing,
|
|
48
|
+
* blank-after-trim, untrimmed, or over-long stored title is invalid, and no name
|
|
49
|
+
* is derived from the instruction.
|
|
50
|
+
* @param value - Untrusted durable title field.
|
|
51
|
+
* @returns The stored title; an invalid title throws ScheduleLogError.
|
|
52
|
+
*/
|
|
53
|
+
function decodeStoredTitle(value) {
|
|
54
|
+
if (typeof value !== "string" || value.trim().length === 0) throw new ScheduleLogError(REQUIRED_TITLE_MESSAGE);
|
|
55
|
+
if (value.length > 120) throw new ScheduleLogError(`title must be at most 120 characters`);
|
|
56
|
+
if (value.trim() !== value) throw new ScheduleLogError("title must be a trimmed string");
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Decode the stored title of one historical `schedule/change` create record.
|
|
61
|
+
*
|
|
62
|
+
* A version-1 event written before names existed has no `title` member, so the
|
|
63
|
+
* key set is required without it and the absent member decodes as undefined. A
|
|
64
|
+
* present title stays subject to the canonical stored form.
|
|
65
|
+
* @param value - Untrusted durable record already known to be an object.
|
|
66
|
+
* @param expected - Exact keys the record may carry; `title` is optional within them.
|
|
67
|
+
* @param message - Diagnostic naming the record's keys.
|
|
68
|
+
* @returns The stored title, or undefined when the historical record predates it.
|
|
69
|
+
*/
|
|
70
|
+
function decodeHistoricalRecordTitle(value, expected, message) {
|
|
71
|
+
if (!hasExactKeysWithOptional(value, expected.filter((key) => key !== "title"), ["title"])) throw new ScheduleLogError(message);
|
|
72
|
+
return value["title"] === void 0 ? void 0 : decodeStoredTitle(value["title"]);
|
|
36
73
|
}
|
|
37
74
|
/** Validate one stable session-local id at the durable boundary. */
|
|
38
75
|
function decodeId(value) {
|
|
@@ -48,13 +85,16 @@ function decodeInstant(value) {
|
|
|
48
85
|
}
|
|
49
86
|
/** Decode the exact v1 after record shape. */
|
|
50
87
|
function decodeAfterRecord(value) {
|
|
51
|
-
|
|
88
|
+
/* v8 ignore next -- decodeLegacyScheduleRecord rejects a non-object before it dispatches here. */
|
|
89
|
+
if (!isRecord(value)) throw new ScheduleLogError("after schedule must be an object");
|
|
90
|
+
const title = decodeHistoricalRecordTitle(value, [
|
|
52
91
|
"id",
|
|
53
92
|
"kind",
|
|
93
|
+
"title",
|
|
54
94
|
"prompt",
|
|
55
95
|
"afterSeconds",
|
|
56
96
|
"scheduledAt"
|
|
57
|
-
]
|
|
97
|
+
], "after schedule must contain exactly id, kind, title, prompt, afterSeconds, and scheduledAt");
|
|
58
98
|
const prompt = value["prompt"];
|
|
59
99
|
if (typeof prompt !== "string" || prompt.length === 0 || prompt.trim() !== prompt) throw new ScheduleLogError("after prompt must be non-empty and already trimmed");
|
|
60
100
|
const afterSeconds = value["afterSeconds"];
|
|
@@ -62,6 +102,7 @@ function decodeAfterRecord(value) {
|
|
|
62
102
|
return Object.freeze({
|
|
63
103
|
id: decodeId(value["id"]),
|
|
64
104
|
kind: "after",
|
|
105
|
+
...title === void 0 ? {} : { title },
|
|
65
106
|
prompt,
|
|
66
107
|
afterSeconds,
|
|
67
108
|
scheduledAt: decodeInstant(value["scheduledAt"])
|
|
@@ -69,45 +110,60 @@ function decodeAfterRecord(value) {
|
|
|
69
110
|
}
|
|
70
111
|
/** Decode the exact v1 absolute one-shot record shape. */
|
|
71
112
|
function decodeAtRecord(value) {
|
|
72
|
-
|
|
113
|
+
/* v8 ignore next -- decodeLegacyScheduleRecord rejects a non-object before it dispatches here. */
|
|
114
|
+
if (!isRecord(value)) throw new ScheduleLogError("at schedule must be an object");
|
|
115
|
+
const title = decodeHistoricalRecordTitle(value, [
|
|
73
116
|
"id",
|
|
74
117
|
"kind",
|
|
118
|
+
"title",
|
|
75
119
|
"prompt",
|
|
76
120
|
"scheduledAt"
|
|
77
|
-
]
|
|
121
|
+
], "at schedule must contain exactly id, kind, title, prompt, and scheduledAt");
|
|
78
122
|
const prompt = value["prompt"];
|
|
79
123
|
if (typeof prompt !== "string" || prompt.length === 0 || prompt.trim() !== prompt) throw new ScheduleLogError("at prompt must be non-empty and already trimmed");
|
|
80
124
|
return Object.freeze({
|
|
81
125
|
id: decodeId(value["id"]),
|
|
82
126
|
kind: "at",
|
|
127
|
+
...title === void 0 ? {} : { title },
|
|
83
128
|
prompt,
|
|
84
129
|
scheduledAt: decodeInstant(value["scheduledAt"])
|
|
85
130
|
});
|
|
86
131
|
}
|
|
87
132
|
/** Decode the exact v1 fixed-rate record shape. */
|
|
88
133
|
function decodeEveryRecord(value) {
|
|
89
|
-
|
|
134
|
+
/* v8 ignore next -- decodeLegacyScheduleRecord rejects a non-object before it dispatches here. */
|
|
135
|
+
if (!isRecord(value)) throw new ScheduleLogError("every schedule must be an object");
|
|
136
|
+
const title = decodeHistoricalRecordTitle(value, [
|
|
90
137
|
"id",
|
|
91
138
|
"kind",
|
|
139
|
+
"title",
|
|
92
140
|
"prompt",
|
|
93
141
|
"everySeconds",
|
|
94
142
|
"scheduledAt"
|
|
95
|
-
]
|
|
143
|
+
], "every schedule must contain exactly id, kind, title, prompt, everySeconds, and scheduledAt");
|
|
96
144
|
const prompt = value["prompt"];
|
|
97
145
|
if (typeof prompt !== "string" || prompt.length === 0 || prompt.trim() !== prompt) throw new ScheduleLogError("every prompt must be non-empty and already trimmed");
|
|
98
146
|
const everySeconds = value["everySeconds"];
|
|
99
147
|
const interval = typeof everySeconds === "number" ? everySeconds * 1e3 : NaN;
|
|
100
|
-
if (!Number.isSafeInteger(everySeconds) || everySeconds <
|
|
148
|
+
if (!Number.isSafeInteger(everySeconds) || everySeconds < 60 || !Number.isSafeInteger(interval)) throw new ScheduleLogError(`everySeconds must be a safe integer of at least 60`);
|
|
101
149
|
return Object.freeze({
|
|
102
150
|
id: decodeId(value["id"]),
|
|
103
151
|
kind: "every",
|
|
152
|
+
...title === void 0 ? {} : { title },
|
|
104
153
|
prompt,
|
|
105
154
|
everySeconds,
|
|
106
155
|
scheduledAt: decodeInstant(value["scheduledAt"])
|
|
107
156
|
});
|
|
108
157
|
}
|
|
109
|
-
/**
|
|
110
|
-
|
|
158
|
+
/**
|
|
159
|
+
* Decode only the variants admitted by historical version-1 Session events.
|
|
160
|
+
*
|
|
161
|
+
* A record written before titles existed decodes without that member, so the
|
|
162
|
+
* fold keeps reading a log that the current creation path could not write.
|
|
163
|
+
* @param value - Untrusted durable JSON record from a Session event.
|
|
164
|
+
* @returns Detached frozen record, without a title when the event omitted it.
|
|
165
|
+
*/
|
|
166
|
+
function decodeLegacyScheduleRecord(value) {
|
|
111
167
|
if (!isRecord(value)) throw new ScheduleLogError("schedule record must be an object");
|
|
112
168
|
switch (value["kind"]) {
|
|
113
169
|
case "after": return decodeAfterRecord(value);
|
|
@@ -134,7 +190,7 @@ function decodeScheduleChange(value) {
|
|
|
134
190
|
return Object.freeze({
|
|
135
191
|
version: 1,
|
|
136
192
|
operation: "create",
|
|
137
|
-
schedule:
|
|
193
|
+
schedule: decodeLegacyScheduleRecord(value["schedule"])
|
|
138
194
|
});
|
|
139
195
|
case "delete":
|
|
140
196
|
if (!hasExactKeys(value, [
|
|
@@ -212,9 +268,8 @@ function dispatchedRecord(record, change) {
|
|
|
212
268
|
/**
|
|
213
269
|
* Apply already-decoded Schedule changes to one complete fold value.
|
|
214
270
|
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
* batch; the returned arrays are materialized and frozen once.
|
|
271
|
+
* The transition authority for full-log replay. One mutable Map/Set pair spans
|
|
272
|
+
* the whole batch; the returned arrays are materialized and frozen once.
|
|
218
273
|
* @param folded - complete active records and used-id history before the changes.
|
|
219
274
|
* @param changes - strictly decoded durable mutations in log order.
|
|
220
275
|
* @returns the complete fold value after every mutation.
|
|
@@ -285,7 +340,14 @@ function validate(events, fail) {
|
|
|
285
340
|
fail(error.message);
|
|
286
341
|
}
|
|
287
342
|
}
|
|
288
|
-
/**
|
|
343
|
+
/**
|
|
344
|
+
* Install replay and pre-append validation for the owned event stream.
|
|
345
|
+
*
|
|
346
|
+
* The durable task table is not asserted here: `scheduleDomain` validates every stored
|
|
347
|
+
* record through its table schema when the domain opens, and its only writer stores each
|
|
348
|
+
* task under `task.record.id`, so a check of the write payload would observe the same value
|
|
349
|
+
* the writer had just built.
|
|
350
|
+
*/
|
|
289
351
|
const install = Object.assign((ctx, fail) => {
|
|
290
352
|
for (const session of ctx.sessions.list()) validate(session.ownEvents(), fail);
|
|
291
353
|
ctx.on("session/created", (session) => {
|