dscs-server 0.1.0 → 0.1.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.
@@ -0,0 +1,8 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none" role="img" aria-label="dsh-session-cloudsync">
2
+ <g transform="translate(64 0) scale(-1 1)">
3
+ <path d="M18 16 H46 A7 7 0 0 1 46 30 H18 A7 7 0 0 0 18 44 H32"
4
+ stroke="#2D6CDF" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
5
+ <path d="M38 44 H46" stroke="#2D6CDF" stroke-width="8" stroke-linecap="round"/>
6
+ <circle cx="35" cy="44" r="3.4" fill="#E6E8EB"/>
7
+ </g>
8
+ </svg>
@@ -0,0 +1,9 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none" role="img" aria-label="dsh-session-cloudsync">
2
+ <title>dsh-session-cloudsync</title>
3
+ <g transform="translate(64 0) scale(-1 1)">
4
+ <path d="M18 16 H46 A7 7 0 0 1 46 30 H18 A7 7 0 0 0 18 44 H32"
5
+ stroke="#E6E8EB" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/>
6
+ <path d="M38 44 H46" stroke="#E6E8EB" stroke-width="7" stroke-linecap="round"/>
7
+ <circle cx="35" cy="44" r="3.2" fill="#6EA8FF"/>
8
+ </g>
9
+ </svg>
package/dist/http.js CHANGED
@@ -29,8 +29,8 @@ function readAsset(url) {
29
29
  return undefined;
30
30
  }
31
31
  }
32
- const LOGO_SVG = readAsset(new URL('../../../assets/logo-dark.svg', import.meta.url));
33
- const FAVICON_SVG = readAsset(new URL('../../../assets/favicon.svg', import.meta.url));
32
+ const LOGO_SVG = readAsset(new URL('../assets/logo-dark.svg', import.meta.url));
33
+ const FAVICON_SVG = readAsset(new URL('../assets/favicon.svg', import.meta.url));
34
34
  function readText(url, label) {
35
35
  try {
36
36
  return readFileSync(url, 'utf8');
@@ -223,6 +223,15 @@ function parseAppend(body) {
223
223
  if (body.workspaceKey !== undefined && (typeof body.workspaceKey !== 'string' || !/^[A-Za-z0-9_-]{16,64}$/u.test(body.workspaceKey))) {
224
224
  throw new SyncError('bad-request', 'workspaceKey 非法');
225
225
  }
226
+ if (body.parentSession !== undefined && (typeof body.parentSession !== 'string' || body.parentSession === '')) {
227
+ throw new SyncError('bad-request', 'parentSession 必须是非空字符串');
228
+ }
229
+ if (body.origin !== undefined && (typeof body.origin !== 'string' || body.origin === '')) {
230
+ throw new SyncError('bad-request', 'origin 必须是非空字符串');
231
+ }
232
+ if (body.delegationDepth !== undefined && (!Number.isSafeInteger(body.delegationDepth) || Number(body.delegationDepth) < 0)) {
233
+ throw new SyncError('bad-request', 'delegationDepth 必须是非负整数');
234
+ }
226
235
  return {
227
236
  baseIndex: Number(body.baseIndex),
228
237
  baseChainHash: baseChainHash,
@@ -237,6 +246,15 @@ function parseAppend(body) {
237
246
  },
238
247
  ...body.workspaceKey === undefined ? {} : {
239
248
  workspaceKey: body.workspaceKey
249
+ },
250
+ ...body.parentSession === undefined ? {} : {
251
+ parentSession: body.parentSession
252
+ },
253
+ ...body.origin === undefined ? {} : {
254
+ origin: body.origin
255
+ },
256
+ ...body.delegationDepth === undefined ? {} : {
257
+ delegationDepth: Number(body.delegationDepth)
240
258
  }
241
259
  };
242
260
  }
package/dist/sessions.js CHANGED
@@ -39,7 +39,7 @@ function toChain(frames) {
39
39
  function sessionRow(db, sessionId) {
40
40
  return db.handle.prepare('SELECT created_at, format_version, last_seq, frame_count, head_chain_hash, manifest_json, manifest_digest, revision, updated_at, deleted_at, compression, stored_bytes FROM sessions WHERE dataset = ? AND session_id = ?').get(DEFAULT_DATASET, sessionId);
41
41
  }
42
- function manifestOf(sessionId, formatVersion, createdAt, updatedAt, frames, tailOpen, safeThroughSeq, workspaceKey) {
42
+ function manifestOf(sessionId, formatVersion, createdAt, updatedAt, frames, tailOpen, safeThroughSeq, workspaceKey, parentSession, origin, delegationDepth) {
43
43
  assertChain(toChain(frames));
44
44
  const manifest = buildManifest({
45
45
  sessionId,
@@ -55,6 +55,15 @@ function manifestOf(sessionId, formatVersion, createdAt, updatedAt, frames, tail
55
55
  },
56
56
  ...workspaceKey === undefined ? {} : {
57
57
  workspaceKey
58
+ },
59
+ ...parentSession === undefined ? {} : {
60
+ parentSession
61
+ },
62
+ ...origin === undefined ? {} : {
63
+ origin
64
+ },
65
+ ...delegationDepth === undefined ? {} : {
66
+ delegationDepth
58
67
  }
59
68
  });
60
69
  return {
@@ -88,7 +97,10 @@ function appendRequestHash(input) {
88
97
  frames: input.frames,
89
98
  tailOpen: input.tailOpen ?? null,
90
99
  safeThroughSeq: input.safeThroughSeq ?? null,
91
- workspaceKey: input.workspaceKey ?? null
100
+ workspaceKey: input.workspaceKey ?? null,
101
+ parentSession: input.parentSession ?? null,
102
+ origin: input.origin ?? null,
103
+ delegationDepth: input.delegationDepth ?? null
92
104
  }));
93
105
  }
94
106
  export function appendFrames(db, dataDir, input, now = Date.now()) {
@@ -110,6 +122,9 @@ export function appendFrames(db, dataDir, input, now = Date.now()) {
110
122
  let formatVersion = input.formatVersion;
111
123
  let createdAt = input.createdAt;
112
124
  let workspaceKey = input.workspaceKey;
125
+ let parentSession = input.parentSession;
126
+ let origin = input.origin;
127
+ let delegationDepth = input.delegationDepth;
113
128
  if (row !== undefined && row.deleted_at !== null && row.created_at === input.createdAt) {
114
129
  throw new SyncError('gone', `会话 ${input.sessionId} 的这个生命周期已被删除`);
115
130
  }
@@ -134,6 +149,18 @@ export function appendFrames(db, dataDir, input, now = Date.now()) {
134
149
  if (workspaceKey !== undefined && existing.workspaceKey === undefined) {
135
150
  throw new SyncError('conflict', '已有会话缺少 workspaceKey,不能在后续 append 补写');
136
151
  }
152
+ if (parentSession !== undefined && existing.parentSession !== undefined && parentSession !== existing.parentSession) {
153
+ throw new SyncError('conflict', 'append 的 parentSession 与已存 manifest 不一致');
154
+ }
155
+ if (origin !== undefined && existing.origin !== undefined && origin !== existing.origin) {
156
+ throw new SyncError('conflict', 'append 的 origin 与已存 manifest 不一致');
157
+ }
158
+ if (delegationDepth !== undefined && existing.delegationDepth !== undefined && delegationDepth !== existing.delegationDepth) {
159
+ throw new SyncError('conflict', 'append 的 delegationDepth 与已存 manifest 不一致');
160
+ }
161
+ if (parentSession === undefined) parentSession = existing.parentSession;
162
+ if (origin === undefined) origin = existing.origin;
163
+ if (delegationDepth === undefined) delegationDepth = existing.delegationDepth;
137
164
  const start = input.baseIndex + 1;
138
165
  if (start < 0 || start > existing.frames.length) throw new SyncError('diverged', `baseIndex=${input.baseIndex} 超出链长 ${existing.frames.length}`);
139
166
  if (start < existing.frames.length - 1) throw new SyncError('diverged', '只能追加或替换尾帧');
@@ -153,7 +180,7 @@ export function appendFrames(db, dataDir, input, now = Date.now()) {
153
180
  if (!existsSync(blobPath(dataDir, frame.blobSha256))) throw new SyncError('bad-request', `帧 ${frame.index} 的 blob 尚未上传`);
154
181
  }
155
182
  input.frames.forEach((frame, offset)=>assertRef(frame, (row === undefined || replacingLifecycle ? 0 : input.baseIndex + 1) + offset));
156
- const { manifest, digest, headChainHash } = manifestOf(input.sessionId, formatVersion, createdAt, now, combined, input.tailOpen, input.safeThroughSeq, workspaceKey);
183
+ const { manifest, digest, headChainHash } = manifestOf(input.sessionId, formatVersion, createdAt, now, combined, input.tailOpen, input.safeThroughSeq, workspaceKey, parentSession, origin, delegationDepth);
157
184
  const revision = bumpRevision(db);
158
185
  const stats = statsOf(combined);
159
186
  db.handle.prepare(`INSERT INTO sessions (dataset, session_id, created_at, format_version, last_seq, frame_count, head_chain_hash, manifest_json, manifest_digest, revision, updated_at, deleted_at, compression, stored_bytes)
@@ -229,7 +256,10 @@ const V2_SUMMARY_SELECT = `s.session_id, s.created_at, s.format_version, s.last_
229
256
  s.compression, s.stored_bytes,
230
257
  json_extract(s.manifest_json, '$.workspaceKey') AS workspace_key,
231
258
  json_extract(s.manifest_json, '$.tailOpen') AS tail_open,
232
- json_extract(s.manifest_json, '$.safeThroughSeq') AS safe_through_seq`;
259
+ json_extract(s.manifest_json, '$.safeThroughSeq') AS safe_through_seq,
260
+ json_extract(s.manifest_json, '$.parentSession') AS parent_session,
261
+ json_extract(s.manifest_json, '$.origin') AS origin,
262
+ json_extract(s.manifest_json, '$.delegationDepth') AS delegation_depth`;
233
263
  function mapV2Summary(row) {
234
264
  const safe = typeof row.safe_through_seq === 'number' && Number.isSafeInteger(row.safe_through_seq) ? row.safe_through_seq : row.last_seq;
235
265
  return {
@@ -244,6 +274,9 @@ function mapV2Summary(row) {
244
274
  workspaceKey: typeof row.workspace_key === 'string' && row.workspace_key !== '' ? row.workspace_key : null,
245
275
  safeThroughSeq: safe,
246
276
  tailOpen: Number(row.tail_open) === 1,
277
+ parentSession: typeof row.parent_session === 'string' && row.parent_session !== '' ? row.parent_session : null,
278
+ origin: typeof row.origin === 'string' && row.origin !== '' ? row.origin : null,
279
+ delegationDepth: typeof row.delegation_depth === 'number' && Number.isSafeInteger(row.delegation_depth) ? row.delegation_depth : null,
247
280
  updatedAt: row.updated_at,
248
281
  deletedAt: row.deleted_at,
249
282
  compression: compressionOf(row.compression),
@@ -82,6 +82,15 @@ export function buildManifest(input) {
82
82
  ...input.safeThroughSeq === undefined ? {} : {
83
83
  safeThroughSeq: input.safeThroughSeq
84
84
  },
85
+ ...input.parentSession === undefined ? {} : {
86
+ parentSession: input.parentSession
87
+ },
88
+ ...input.origin === undefined ? {} : {
89
+ origin: input.origin
90
+ },
91
+ ...input.delegationDepth === undefined ? {} : {
92
+ delegationDepth: input.delegationDepth
93
+ },
85
94
  frames: input.frames,
86
95
  updatedAt: input.updatedAt
87
96
  };
@@ -108,6 +117,15 @@ export function assertManifest(manifest) {
108
117
  throw new Error('manifest 的 workspaceKey 非法');
109
118
  }
110
119
  if (manifest.tailOpen !== undefined && typeof manifest.tailOpen !== 'boolean') throw new Error('manifest 的 tailOpen 非法');
120
+ if (manifest.parentSession !== undefined && (typeof manifest.parentSession !== 'string' || manifest.parentSession === '')) {
121
+ throw new Error('manifest 的 parentSession 非法');
122
+ }
123
+ if (manifest.origin !== undefined && (typeof manifest.origin !== 'string' || manifest.origin === '')) {
124
+ throw new Error('manifest 的 origin 非法');
125
+ }
126
+ if (manifest.delegationDepth !== undefined && (!Number.isSafeInteger(manifest.delegationDepth) || manifest.delegationDepth < 0)) {
127
+ throw new Error('manifest 的 delegationDepth 非法');
128
+ }
111
129
  }
112
130
  function canonical(value) {
113
131
  if (Array.isArray(value)) return value.map((entry)=>canonical(entry));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dscs-server",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "DSH Session CloudSync self-hosted server.",
@@ -32,6 +32,7 @@
32
32
  "files": [
33
33
  "dist",
34
34
  "web",
35
+ "assets",
35
36
  "LICENSE",
36
37
  "README.md"
37
38
  ],