deepspace 0.5.0 → 0.5.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/dist/cli.js +1368 -1991
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +837 -3652
- package/dist/index.js +446 -539
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +121 -46
- package/dist/server.js +46 -17
- package/dist/server.js.map +1 -1
- package/dist/testing.d.ts +115 -0
- package/dist/worker.d.ts +113 -42
- package/dist/worker.js +40 -5
- package/dist/worker.js.map +1 -1
- package/features/docs/src/editor/useDocEditor.ts +5 -1
- package/features/messaging/src/ChatPage.tsx +6 -1
- package/features/messaging/src/hooks/useChatChannel.ts +18 -6
- package/package.json +7 -3
- package/scripts/add-feature.cjs +25 -16
|
@@ -175,7 +175,11 @@ export function useDocEditor({
|
|
|
175
175
|
// in dev (the editor view is destroyed and re-created, and any subscriber
|
|
176
176
|
// that runs in between throws on `editor.view`). Defer to a useEffect.
|
|
177
177
|
immediatelyRender: false,
|
|
178
|
-
//
|
|
178
|
+
// Bare directive on purpose: naming @typescript-eslint/no-explicit-any
|
|
179
|
+
// here is a hard "unknown rule" error under the scaffolded app's
|
|
180
|
+
// minimal eslint config (which never registers that plugin), while the
|
|
181
|
+
// SDK monorepo's config does enable the rule and needs the suppression.
|
|
182
|
+
// eslint-disable-next-line
|
|
179
183
|
extensions: extensions as any,
|
|
180
184
|
editorProps: {
|
|
181
185
|
transformPastedHTML: stripCollaborationArtifactsFromHTML,
|
|
@@ -37,8 +37,13 @@ export default function ChatPage({
|
|
|
37
37
|
}, [channelId, isMember, markAsRead])
|
|
38
38
|
|
|
39
39
|
if (status !== 'ready' || !channelId) {
|
|
40
|
+
// data-testid lets tests (and humans reading a trace) distinguish "stuck
|
|
41
|
+
// waiting for the default channel" from "route never mounted".
|
|
40
42
|
return (
|
|
41
|
-
<div
|
|
43
|
+
<div
|
|
44
|
+
data-testid="chat-page-loading"
|
|
45
|
+
className={`flex items-center justify-center h-full ${className ?? ''}`}
|
|
46
|
+
>
|
|
42
47
|
<div className="text-muted-foreground text-sm">Loading chat...</div>
|
|
43
48
|
</div>
|
|
44
49
|
)
|
|
@@ -26,12 +26,24 @@ export function useChatChannel(channelName: string = DEFAULT_CHANNEL_NAME) {
|
|
|
26
26
|
const { channels, status, create } = useChannels()
|
|
27
27
|
const hasInitialized = useRef(false)
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
)
|
|
29
|
+
// Nothing enforces name uniqueness server-side, so two first visitors can
|
|
30
|
+
// race the one-shot init below and BOTH create the default channel. With a
|
|
31
|
+
// bare .find(), each client then resolves whichever duplicate its local
|
|
32
|
+
// snapshot orders first — split-brain: A and B post into different rooms
|
|
33
|
+
// and never see each other. Pick deterministically (oldest createdAt,
|
|
34
|
+
// recordId tie-break) so every client converges on the same channel once
|
|
35
|
+
// the duplicates sync.
|
|
36
|
+
const defaultChannel = channels
|
|
37
|
+
.filter(
|
|
38
|
+
(c: RecordData<Channel>) =>
|
|
39
|
+
c.data.name === channelName &&
|
|
40
|
+
c.data.type === 'public' &&
|
|
41
|
+
!c.data.archived,
|
|
42
|
+
)
|
|
43
|
+
.sort(
|
|
44
|
+
(a: RecordData<Channel>, b: RecordData<Channel>) =>
|
|
45
|
+
a.createdAt.localeCompare(b.createdAt) || a.recordId.localeCompare(b.recordId),
|
|
46
|
+
)[0]
|
|
35
47
|
|
|
36
48
|
const { isMember, join, status: membersStatus } = useChannelMembers(defaultChannel?.recordId)
|
|
37
49
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deepspace",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
|
+
"engines": {
|
|
5
|
+
"node": ">=22.15.0"
|
|
6
|
+
},
|
|
4
7
|
"description": "DeepSpace SDK — build real-time collaborative apps on Cloudflare Workers",
|
|
5
8
|
"type": "module",
|
|
6
9
|
"sideEffects": false,
|
|
@@ -41,14 +44,15 @@
|
|
|
41
44
|
},
|
|
42
45
|
"dependencies": {
|
|
43
46
|
"@clack/prompts": "^1.2.0",
|
|
44
|
-
"better-auth": "1.
|
|
47
|
+
"better-auth": "1.6.23",
|
|
45
48
|
"citty": "^0.2.0",
|
|
46
49
|
"cross-spawn": "^7.0.6",
|
|
47
50
|
"esbuild": "^0.25.0",
|
|
48
51
|
"hono": "^4.6.0",
|
|
49
52
|
"jose": "^6.1.0",
|
|
50
53
|
"smol-toml": "^1.6.1",
|
|
51
|
-
"yjs": "^13.6.0"
|
|
54
|
+
"yjs": "^13.6.0",
|
|
55
|
+
"zod": "^4.3.6"
|
|
52
56
|
},
|
|
53
57
|
"peerDependencies": {
|
|
54
58
|
"@ai-sdk/anthropic": "^2.0.0",
|
package/scripts/add-feature.cjs
CHANGED
|
@@ -97,16 +97,18 @@ function copyFile(src, dest) {
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
// Copy + rewrite leading `../` imports. Used when a page file is redirected
|
|
100
|
-
// into src/pages/(
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
//
|
|
100
|
+
// into a deeper route group (e.g. src/pages/(app)/ adds one directory level,
|
|
101
|
+
// src/pages/(app)/(protected)/ adds two) — every relative import that
|
|
102
|
+
// traverses upward needs `levels` extra `../`. Matches static `from '...'`,
|
|
103
|
+
// side-effect `import '...'`, and dynamic/type `import('...')` (e.g.
|
|
104
|
+
// `import('../hooks/x').T` in a type position, or `await import('../x')`).
|
|
104
105
|
// Same-directory (`./...`) and bare-package imports are untouched.
|
|
105
|
-
function copyFileWithImportShift(src, dest) {
|
|
106
|
+
function copyFileWithImportShift(src, dest, levels) {
|
|
106
107
|
const destDir = path.dirname(dest)
|
|
107
108
|
if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true })
|
|
108
109
|
const content = fs.readFileSync(src, 'utf-8')
|
|
109
|
-
const
|
|
110
|
+
const prefix = '../'.repeat(levels)
|
|
111
|
+
const shifted = content.replace(/(from\s+|import\s+|import\()(['"])(\.\.\/)/g, `$1$2${prefix}$3`)
|
|
110
112
|
fs.writeFileSync(dest, shifted)
|
|
111
113
|
}
|
|
112
114
|
|
|
@@ -114,18 +116,21 @@ function copyFileWithImportShift(src, dest) {
|
|
|
114
116
|
// Install files
|
|
115
117
|
// ---------------------------------------------------------------------------
|
|
116
118
|
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
//
|
|
119
|
+
// Page files must land under src/pages/(app)/ so the (app)/_layout.tsx route
|
|
120
|
+
// group wraps them in the DeepSpace providers (auth + realtime records) — the
|
|
121
|
+
// top level of src/pages/ is reserved for static pages that mount no
|
|
122
|
+
// providers. Protected pages go one level deeper, under (app)/(protected)/,
|
|
123
|
+
// so the nested layout also wraps them in <AuthGate>. Both are
|
|
124
|
+
// folder-name-only route groups — the URL path is unchanged. Only page files
|
|
125
|
+
// (src/pages/*) get redirected; schemas, components, etc. keep their declared
|
|
126
|
+
// destinations. Page files already placed in the (app) group are left as-is.
|
|
122
127
|
function resolveDestPath(fileDest, route) {
|
|
123
128
|
const PAGES = 'src/pages/'
|
|
124
|
-
const
|
|
129
|
+
const APP = 'src/pages/(app)/'
|
|
130
|
+
if (!fileDest.startsWith(PAGES) || fileDest.startsWith(APP)) return fileDest
|
|
131
|
+
const rest = fileDest.slice(PAGES.length)
|
|
125
132
|
const isProtected = route && route.protected !== false
|
|
126
|
-
|
|
127
|
-
if (!fileDest.startsWith(PAGES) || fileDest.startsWith(PROTECTED)) return fileDest
|
|
128
|
-
return PROTECTED + fileDest.slice(PAGES.length)
|
|
133
|
+
return isProtected ? `${APP}(protected)/${rest}` : `${APP}${rest}`
|
|
129
134
|
}
|
|
130
135
|
|
|
131
136
|
function installFeature(config, targetDir) {
|
|
@@ -137,6 +142,10 @@ function installFeature(config, targetDir) {
|
|
|
137
142
|
const resolvedDest = resolveDestPath(file.dest, config.route)
|
|
138
143
|
const destPath = path.join(targetDir, resolvedDest)
|
|
139
144
|
const wasRedirected = resolvedDest !== file.dest
|
|
145
|
+
// Each extra path segment in the resolved dest is one directory level the
|
|
146
|
+
// page dropped into (e.g. (app)/ = 1, (app)/(protected)/ = 2), which is
|
|
147
|
+
// exactly how many `../` its upward relative imports must gain.
|
|
148
|
+
const shiftLevels = resolvedDest.split('/').length - file.dest.split('/').length
|
|
140
149
|
|
|
141
150
|
if (!fs.existsSync(srcPath)) {
|
|
142
151
|
console.error(` Warning: source not found: ${file.src}`)
|
|
@@ -149,7 +158,7 @@ function installFeature(config, targetDir) {
|
|
|
149
158
|
} else {
|
|
150
159
|
const replacing = fs.existsSync(destPath)
|
|
151
160
|
if (wasRedirected) {
|
|
152
|
-
copyFileWithImportShift(srcPath, destPath)
|
|
161
|
+
copyFileWithImportShift(srcPath, destPath, shiftLevels)
|
|
153
162
|
} else {
|
|
154
163
|
copyFile(srcPath, destPath)
|
|
155
164
|
}
|