@stage5/lumine 0.1.1 → 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.
- package/package.json +1 -1
- package/sdk/BUILD_SDK_INDEX.md +83 -1
package/package.json
CHANGED
package/sdk/BUILD_SDK_INDEX.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Version: 1.23.0
|
|
4
4
|
Updated: 2026-05-24
|
|
5
|
-
Generated: 2026-05-
|
|
5
|
+
Generated: 2026-05-26T08:23:45.901Z
|
|
6
6
|
|
|
7
7
|
## Notes
|
|
8
8
|
- This SDK is injected into Build iframes via the Build preview/runtime.
|
|
@@ -27,211 +27,293 @@ files:read, user:read, users:read, dailyReflections:read, content:read, sharedDb
|
|
|
27
27
|
|
|
28
28
|
### Twinkle.capabilities
|
|
29
29
|
- async get() | scopes: none
|
|
30
|
+
- Returns: Capability snapshot
|
|
30
31
|
- async can(actionName) | scopes: none
|
|
32
|
+
- Returns: boolean
|
|
31
33
|
- async listActions() | scopes: none
|
|
34
|
+
- Returns: { available, blocked, details }
|
|
32
35
|
- async refresh() | scopes: none
|
|
36
|
+
- Returns: Capability snapshot
|
|
33
37
|
|
|
34
38
|
### Twinkle.viewer
|
|
35
39
|
- async get() | scopes: none
|
|
40
|
+
- Returns: { id, username, profilePicUrl, isLoggedIn, isOwner, isGuest }
|
|
36
41
|
- async refresh() | scopes: none
|
|
42
|
+
- Returns: Viewer info
|
|
37
43
|
|
|
38
44
|
### Twinkle.preview
|
|
39
45
|
- getLayout() | scopes: none
|
|
46
|
+
- Returns: { mode, viewport, stage, safeInsets, playfield }
|
|
40
47
|
- Read the host preview layout and derive a fixed-world scale from layout.playfield before sizing a canvas, sprites, or mobile game UI.
|
|
41
48
|
- Example: const WORLD = { width: 360, height: 640 }; const layout = Twinkle.preview.getLayout(); const scale = Math.min(layout.playfield.width / WORLD.width, layout.playfield.height / WORLD.height);
|
|
42
49
|
- reserveInsets({ top, right, bottom, left }) | scopes: none
|
|
50
|
+
- Returns: { mode, viewport, stage, safeInsets, playfield }
|
|
43
51
|
- Reserve host-aware safe space for HUD bars, touch controls, or other overlays before clamping gameplay.
|
|
44
52
|
- Example: Twinkle.preview.reserveInsets({ top: 72, bottom: 120, left: 0, right: 0 });
|
|
45
53
|
- setPlayfield({ x, y, width, height } | null) | scopes: none
|
|
54
|
+
- Returns: { playfieldBounds, playerBounds, overflowTop, overflowRight, overflowBottom, overflowLeft, status, reportedAt } | null
|
|
46
55
|
- Declare the actual playable rectangle when the game area is smaller than the raw canvas.
|
|
47
56
|
- Example: Twinkle.preview.setPlayfield({ x: layout.playfield.x, y: layout.playfield.y, width: layout.playfield.width, height: layout.playfield.height });
|
|
48
57
|
- reportGameplayState({ playfieldBounds?, playerBounds? } | null) | scopes: none
|
|
58
|
+
- Returns: { playfieldBounds, playerBounds, overflowTop, overflowRight, overflowBottom, overflowLeft, status, reportedAt } | null
|
|
49
59
|
- Report live player or avatar bounds so the preview host can detect floor, wall, or out-of-bounds issues.
|
|
50
60
|
- Example: Twinkle.preview.reportGameplayState({ playerBounds: { x: player.x, y: player.y, width: player.width, height: player.height } });
|
|
51
61
|
- getGameplayTelemetry() | scopes: none
|
|
62
|
+
- Returns: { playfieldBounds, playerBounds, overflowTop, overflowRight, overflowBottom, overflowLeft, status, reportedAt } | null
|
|
52
63
|
- Read the latest preview-side gameplay telemetry snapshot.
|
|
53
64
|
- clearGameplayState() | scopes: none
|
|
65
|
+
- Returns: { playfieldBounds, playerBounds, overflowTop, overflowRight, overflowBottom, overflowLeft, status, reportedAt } | null
|
|
54
66
|
- clearReservedInsets() | scopes: none
|
|
67
|
+
- Returns: { mode, viewport, stage, safeInsets, playfield }
|
|
55
68
|
- subscribe(listener, { immediate } = {}) | scopes: none
|
|
69
|
+
- Returns: unsubscribe()
|
|
56
70
|
- Listen for host layout changes so a fixed-world game scale or canvas surface stays synced after resize, mobile viewport changes, or embedded runtime layout shifts.
|
|
57
71
|
- Example: const unsubscribe = Twinkle.preview.subscribe((layout) => syncGameLayout(layout), { immediate: true });
|
|
58
72
|
|
|
59
73
|
### Twinkle.mount
|
|
60
74
|
- async get() | scopes: none
|
|
75
|
+
- Returns: { type: 'subject', id: number } | null
|
|
61
76
|
- async refresh() | scopes: none
|
|
77
|
+
- Returns: { type: 'subject', id: number } | null
|
|
62
78
|
|
|
63
79
|
### Twinkle.notifications
|
|
64
80
|
- getLaunchTarget() | scopes: none
|
|
81
|
+
- Returns: { notificationId, buildId, eventKey, eventLabel, target } | null
|
|
65
82
|
- Read the current notification launch target, if the app was opened from a Build notification.
|
|
66
83
|
- onLaunchTarget(listener, { immediate } = {}) | scopes: none
|
|
84
|
+
- Returns: unsubscribe()
|
|
67
85
|
- Listen for notification launch targets while the Build app is already open.
|
|
68
86
|
- Example: const off = Twinkle.notifications.onLaunchTarget((launchTarget) => focusEntry(launchTarget?.target?.focus?.entryId));
|
|
69
87
|
- async getSubjectUpdateSubscription(subjectId) | scopes: notifications:read
|
|
88
|
+
- Returns: { subscription }
|
|
70
89
|
- Read whether the current viewer is subscribed to Build notifications for updates to a subject.
|
|
71
90
|
- Example: const { subscription } = await Twinkle.notifications.getSubjectUpdateSubscription(subjectId);
|
|
72
91
|
- async subscribeToSubjectUpdates(subjectId, { target } = {}) | scopes: notifications:write
|
|
92
|
+
- Returns: { subscription }
|
|
73
93
|
- Subscribe the current viewer to notifications when the original subject author adds a new page or update.
|
|
74
94
|
- Example: await Twinkle.notifications.subscribeToSubjectUpdates(subjectId, { target: { view: 'book', subjectId } });
|
|
75
95
|
- async unsubscribeFromSubjectUpdates(subjectId) | scopes: notifications:write
|
|
96
|
+
- Returns: { subscription: null }
|
|
76
97
|
- Unsubscribe the current viewer from Build notifications for a subject's new pages or updates.
|
|
77
98
|
|
|
78
99
|
### Twinkle.chess
|
|
79
100
|
- async bestMove({ fen, depth?, skillLevel?, maxTimeMs?, timeoutMs? }) | scopes: none
|
|
101
|
+
- Returns: { success, move, bestMove, from, to, promotion, evaluation, depth, mate, error, engine }
|
|
80
102
|
- Ask the parent-hosted Stockfish engine for the best move from a FEN position.
|
|
81
103
|
- Example: const result = await Twinkle.chess.bestMove({ fen: game.fen(), skillLevel: 8, maxTimeMs: 1000 });
|
|
82
104
|
if (result.success) game.move({ from: result.from, to: result.to, promotion: result.promotion || undefined });
|
|
83
105
|
- async evaluate({ fen, depth?, skillLevel?, maxTimeMs?, timeoutMs? }) | scopes: none
|
|
106
|
+
- Returns: { success, move, bestMove, from, to, promotion, evaluation, depth, mate, error, engine }
|
|
84
107
|
- Analyze a FEN position and return Stockfish's current best move plus centipawn or mate evaluation.
|
|
85
108
|
- Example: const analysis = await Twinkle.chess.evaluate({ fen: game.fen(), depth: 12 });
|
|
86
109
|
console.log(analysis.bestMove, analysis.evaluation, analysis.mate);
|
|
87
110
|
|
|
88
111
|
### Twinkle.files
|
|
89
112
|
- async saveAs({ fileName, url, dataUrl, data, text, json, bytes, blob, file, mimeType } = {}) | scopes: none
|
|
113
|
+
- Returns: { success, fileName, size?, mimeType?, method }
|
|
90
114
|
- Download a generated or remote file to the viewer's local device through the parent frame without opening a popup.
|
|
91
115
|
- Example: await Twinkle.files.saveAs({ fileName: 'fashion-guide.png', dataUrl: imageUrl, mimeType: 'image/png' });
|
|
92
116
|
- async uploadGenerated({ fileName, url, dataUrl, data, text, json, bytes, blob, file, mimeType } = {}) | scopes: files:write
|
|
117
|
+
- Returns: { assets: [{ id, buildId, fileName, originalFileName, mimeType, sizeBytes, filePath, url, thumbUrl, fileType, uploadedByUserId, createdAt }], failed?: [{ fileName, message }], canceled }
|
|
93
118
|
- Upload an app-generated file to Twinkle-hosted cloud storage without opening a picker, then store the returned asset refs in sharedDb/privateDb/userDb.
|
|
94
119
|
- Example: const { assets } = await Twinkle.files.uploadGenerated({ fileName: 'fashion-guide.png', dataUrl: generatedImageUrl, mimeType: 'image/png' });
|
|
95
120
|
- async pickAndUpload({ accept, multiple } = {}) | scopes: files:write
|
|
121
|
+
- Returns: { assets: [{ id, buildId, fileName, originalFileName, mimeType, sizeBytes, filePath, url, thumbUrl, fileType, uploadedByUserId, createdAt }], failed?: [{ fileName, message }], canceled }
|
|
96
122
|
- Pick supported local files and upload them to Twinkle-hosted cloud storage, then store the returned asset refs in sharedDb/privateDb/userDb.
|
|
97
123
|
- Example: const { assets, canceled } = await Twinkle.files.pickAndUpload({ accept: 'image/*,.pdf', multiple: true });
|
|
98
124
|
- async list({ cursor, limit } = {}) | scopes: files:read
|
|
125
|
+
- Returns: { assets: [{ id, buildId, fileName, originalFileName, mimeType, sizeBytes, filePath, url, thumbUrl, fileType, uploadedByUserId, createdAt }], nextCursor, usage: { totalBytes, fileCount, maxRuntimeFileStorageBytes, remainingBytes } | null }
|
|
99
126
|
- List the current viewer's uploaded runtime files for this build.
|
|
100
127
|
- Example: const { assets, usage } = await Twinkle.files.list({ limit: 20 });
|
|
101
128
|
- async delete(assetId) | scopes: files:write
|
|
129
|
+
- Returns: { success, deletedAssetId, usage: { totalBytes, fileCount, maxRuntimeFileStorageBytes, remainingBytes } | null }
|
|
102
130
|
- Delete one of the current viewer's uploaded runtime files and free up Twinkle.files quota.
|
|
103
131
|
- Example: await Twinkle.files.delete(assetId);
|
|
104
132
|
|
|
105
133
|
### Twinkle.ai
|
|
106
134
|
- async listPrompts() | scopes: none
|
|
135
|
+
- Returns: Array<{ id, title, description }>
|
|
107
136
|
- async chat({ promptId, message, history, systemPrompt, requestId, onText, onStatus } = {}) | scopes: none
|
|
137
|
+
- Returns: { text, response, model, aiUsagePolicy }
|
|
108
138
|
- Generate text with the default Lumine text model, optionally streaming text updates through onText.
|
|
109
139
|
- Example: const chatHistory = conversation.slice(-12).map((entry) => ({ role: entry.role === 'assistant' ? 'assistant' : 'user', content: entry.text }));
|
|
110
140
|
const result = await Twinkle.ai.chat({ message, history: chatHistory, systemPrompt: 'You are a cheerful pirate helper who answers in one sentence.', onText: (text, meta) => renderReply(text), onStatus: (status) => setThinking(status === 'thinking') });
|
|
111
141
|
- async generateObject({ prompt, expectedStructure, thinkingMode, mode, instructions, systemPrompt } = {}) | scopes: none
|
|
142
|
+
- Returns: { object, result, model, provider, thinkingMode, requestedThinkingMode, aiUsagePolicy }
|
|
112
143
|
- Generate a validated structured JSON object for app decisions, routing, grading, and game-state logic.
|
|
113
144
|
- Example: const { object } = await Twinkle.ai.generateObject({ thinkingMode: 'medium', prompt: 'Classify the player intent from: ' + playerText, expectedStructure: { action: 'string', targetCharacter: 'string', confidence: 0, shouldAskFollowUp: false } });
|
|
114
145
|
- onChatStatus(listener) | scopes: none
|
|
146
|
+
- Returns: unsubscribe function
|
|
115
147
|
- Listen to shared runtime AI chat stream events.
|
|
116
148
|
- async generateImage({ prompt, referenceImageB64, previousResponseId, previousImageId, engine, quality, requestId, onStatus, timeoutMs } = {}) | scopes: none
|
|
149
|
+
- Returns: { success, imageUrl, responseId, imageId, engine, quality, aiUsagePolicy } or { success: false, error, reason, code, aiUsagePolicy }
|
|
117
150
|
- Generate or edit an image from a prompt and optional base64/data-URL reference image.
|
|
118
151
|
- Example: const result = await Twinkle.ai.generateImage({ prompt: 'Create a fashion guide portrait for this face with flattering colors and outfit ideas', referenceImageB64, quality: 'high', onStatus: (status) => console.log(status.stage) });
|
|
119
152
|
- onImageGenerationStatus(listener) | scopes: none
|
|
153
|
+
- Returns: unsubscribe function
|
|
120
154
|
- Subscribe to real-time image generation status events forwarded into the build iframe.
|
|
121
155
|
- Example: const unsubscribe = Twinkle.ai.onImageGenerationStatus((status) => console.log(status.stage));
|
|
122
156
|
|
|
123
157
|
### Twinkle.characters
|
|
124
158
|
- async chat({ character, thinkingMode, message, history, roomContext, scene, systemPrompt, instructions, includeWebsiteContext, requestId, onText, onStatus } = {}) | scopes: none
|
|
159
|
+
- Returns: { text, response, character, aiUsername, thinkingMode, requestedThinkingMode, includeWebsiteContext, model, provider, aiUsagePolicy }
|
|
125
160
|
- Talk to Zero or Ciel from a Build app, either as a final-response call or streaming RPG-style dialogue text with onText/onStatus.
|
|
126
161
|
- Example: const dialogueHistory = recentTurns.slice(-16).map((entry) => ({ role: entry.role === 'assistant' ? 'assistant' : 'user', content: entry.text, speaker: entry.speaker }));
|
|
127
162
|
const result = await Twinkle.characters.chat({ character: 'zero', thinkingMode: thinkHard ? 'high' : 'medium', message: playerText, history: dialogueHistory, roomContext, scene: { location: 'classroom', nearbyCharacters: ['zero', 'ciel'] }, includeWebsiteContext: false, onText: (text) => renderDialogue(text) });
|
|
128
163
|
- onChatStatus(listener) | scopes: none
|
|
164
|
+
- Returns: unsubscribe function
|
|
129
165
|
- Listen to shared Zero/Ciel runtime chat stream events.
|
|
130
166
|
|
|
131
167
|
### Twinkle.userDb
|
|
132
168
|
- async query(sql, params) | scopes: none
|
|
169
|
+
- Returns: { rows, rowCount, truncated }
|
|
133
170
|
- Run a SELECT against advanced private per-user SQLite. Use Twinkle.privateDb instead for simple preferences, drafts, settings, or small JSON state.
|
|
134
171
|
- async exec(sql, params) | scopes: none
|
|
172
|
+
- Returns: { changes, lastInsertRowid }
|
|
135
173
|
- Run a write or schema statement against advanced private per-user SQLite.
|
|
136
174
|
|
|
137
175
|
### Twinkle.subjects
|
|
138
176
|
- async getMySubjects({ limit, cursor } = {}) | scopes: content:read
|
|
177
|
+
- Returns: { subjects: [{ id, title, description, filePath, fileName, fileSize, thumbUrl, timeStamp, rootType, rootId, rewardLevel }], cursor? }
|
|
139
178
|
- async search({ query, limit, cursor } = {}) | scopes: content:read
|
|
179
|
+
- Returns: { subjects: [{ id, contentType, contentId, title, description, filePath, fileName, fileSize, thumbUrl, timeStamp, userId, username, profilePicUrl, rootType, rootId, rewardLevel, numComments }], cursor?, pagination: { limit, hasMore, nextCursor }, filters: { query } }
|
|
140
180
|
- Search Twinkle subjects by text for subject picker UIs, book apps, scrapbooks, and galleries.
|
|
141
181
|
- Example: const { subjects } = await Twinkle.subjects.search({ query: searchText, limit: 12 });
|
|
142
182
|
- async getSubject(subjectId) | scopes: content:read
|
|
183
|
+
- Returns: { subject: { id, title, description, filePath, fileName, fileSize, thumbUrl, secretAnswer, secretAttachment, timeStamp, userId, username, profilePicUrl, rootType, rootId, rewardLevel } }
|
|
143
184
|
- async getSubjectComments(subjectId, { limit, cursor } = {}) | scopes: content:read
|
|
185
|
+
- Returns: { comments: [{ id, content, filePath, fileName, fileSize, thumbUrl, timeStamp }], cursor? }
|
|
144
186
|
|
|
145
187
|
### Twinkle.aiStories
|
|
146
188
|
- async list({ limit, cursor, difficulty, type, isListening, userId, hasImage, hasQuestions } = {}) | scopes: content:read
|
|
189
|
+
- Returns: { stories: [{ id, contentType, contentId, topic, topicKey, type, story, explanation, difficulty, isListening, imagePath, imageUrl, audioPath, audioUrl, questions, questionsBy, hasImage, hasQuestions, userId, username, profilePicUrl, timeStamp }], cursor?, pagination: { limit, hasMore, nextCursor }, filters }
|
|
147
190
|
- List completed existing user-generated AI Stories newest first for galleries, quiz apps, readers, and image/story collections.
|
|
148
191
|
- Example: const { stories } = await Twinkle.aiStories.list({ hasImage: true, hasQuestions: true, limit: 12 });
|
|
149
192
|
- async search({ query, limit, cursor, difficulty, type, isListening, userId, hasImage, hasQuestions } = {}) | scopes: content:read
|
|
193
|
+
- Returns: { stories: [{ id, contentType, contentId, topic, topicKey, type, story, explanation, difficulty, isListening, imagePath, imageUrl, audioPath, audioUrl, questions, questionsBy, hasImage, hasQuestions, userId, username, profilePicUrl, timeStamp }], cursor?, pagination: { limit, hasMore, nextCursor }, filters }
|
|
150
194
|
- Search completed existing user-generated AI Stories by topic or story text for galleries, quizzes, and readers.
|
|
151
195
|
- Example: const { stories } = await Twinkle.aiStories.search({ query: searchText, hasQuestions: true, limit: 12 });
|
|
152
196
|
- async get(storyId) | scopes: content:read
|
|
197
|
+
- Returns: { story: { id, contentType, contentId, topic, topicKey, type, story, explanation, difficulty, isListening, imagePath, imageUrl, audioPath, audioUrl, questions, questionsBy, hasImage, hasQuestions, userId, username, profilePicUrl, timeStamp } }
|
|
153
198
|
- Fetch one completed existing AI Story by id, including story text, media URLs, and normalized questions when available.
|
|
154
199
|
- Example: const { story } = await Twinkle.aiStories.get(storyId);
|
|
155
200
|
|
|
156
201
|
### Twinkle.grammarbles
|
|
157
202
|
- async listQuestions({ level, limit, cursor } = {}) | scopes: content:read
|
|
203
|
+
- Returns: { questions: [{ id, level, rating, question, choices, answerIndex, correctChoice, correctChoiceKey, isChecked, explanation }], cursor?, pagination: { level, limit, hasMore, nextCursor } }
|
|
158
204
|
- Read public Grammarbles questions and answers by level with rating/id cursor pagination.
|
|
159
205
|
- Example: const page = await Twinkle.grammarbles.listQuestions({ level: 3, limit: 100 }); const question = page.questions[Math.floor(Math.random() * page.questions.length)];
|
|
160
206
|
- async getMyQuestionHistory({ level, limit, cursor } = {}) | scopes: content:read
|
|
207
|
+
- Returns: { attempts: [{ id, questionId, level, grade, gradeRank, isCorrect, attemptNumber, timeStamp }], cursor?, pagination: { level, limit, hasMore, nextCursor } }
|
|
161
208
|
- Read the signed-in viewer's real Grammarbles attempt rows for trainer filtering.
|
|
162
209
|
- Example: const history = await Twinkle.grammarbles.getMyQuestionHistory({ level: selectedLevel, limit: 500 }); const answeredIds = new Set(history.attempts.map((attempt) => attempt.questionId));
|
|
163
210
|
|
|
164
211
|
### Twinkle.subjectComments
|
|
165
212
|
- async list(subjectId, { limit, cursor, sortBy, includeReplies, author, authorUserId, replyScope } = {}) | scopes: content:read
|
|
213
|
+
- Returns: { comments: [{ id, content, filePath, fileName, fileSize, thumbUrl, timeStamp, userId, username, profilePicUrl, commentId, replyId }], cursor?, pagination: { limit, hasMore, nextCursor }, filters: { subjectId, sortBy, includeReplies, author, replyScope, authorUserId } }
|
|
166
214
|
- Read a subject's comment stream with stable keyset pagination, oldest/newest ordering, author filters, and optional same-author reply scoping.
|
|
167
215
|
- Example: const { subjects } = await Twinkle.subjects.search({ query: searchText, limit: 12 }); const subjectId = pickedSubject.id; const page = await Twinkle.subjectComments.list(subjectId, { sortBy: 'oldest', author: 'subjectPoster', includeReplies: true, replyScope: 'ownThread', limit: 50 });
|
|
168
216
|
|
|
169
217
|
### Twinkle.profileComments
|
|
170
218
|
- async getProfileComments({ profileUserId, limit, offset, sortBy, includeReplies, range, since, until } = {}) | scopes: content:read
|
|
219
|
+
- Returns: { comments: [{ id, content, filePath, fileName, fileSize, thumbUrl, timeStamp, userId, username, profilePicUrl, likes, replies, commentId, replyId }], pagination: { limit, offset, hasMore, nextOffset }, filters: { profileUserId, sortBy, includeReplies, since, until } }
|
|
171
220
|
- async getProfileCommentIds({ profileUserId, limit, offset, sortBy, includeReplies, range, since, until } = {}) | scopes: content:read
|
|
221
|
+
- Returns: { ids: number[], pagination: { limit, offset, hasMore, nextOffset }, filters: { profileUserId, sortBy, includeReplies, since, until } }
|
|
172
222
|
- async getCommentsByIds(idsOrOpts) | scopes: content:read
|
|
223
|
+
- Returns: { comments: [{ id, content, filePath, fileName, fileSize, thumbUrl, timeStamp, userId, username, profilePicUrl, commentId, replyId }] }
|
|
173
224
|
- async getProfileCommentCounts(idsOrOpts) | scopes: content:read
|
|
225
|
+
- Returns: { countsById: { [commentId]: { likes, replies } } }
|
|
174
226
|
|
|
175
227
|
### Twinkle.leaderboards
|
|
176
228
|
- async get({ boardKey = 'default', limit, cursor } = {}) | scopes: none
|
|
229
|
+
- Returns: { entries: [{ rank, id, buildId, boardKey, viewerKind, userId, displayName, score, meta, achievedAt, createdAt, updatedAt }], scores, cursor, hasMore, personalBest: { id, buildId, boardKey, viewerKind, userId, displayName, score, meta, achievedAt, createdAt, updatedAt } | null }
|
|
177
230
|
- Read score-sorted personal-best leaderboard rows for this Build app.
|
|
178
231
|
- async submit({ boardKey = 'default', score, displayName, meta } = {}) | scopes: none
|
|
232
|
+
- Returns: { entry: { id, buildId, boardKey, viewerKind, userId, displayName, score, meta, achievedAt, createdAt, updatedAt } | null, personalBest: { id, buildId, boardKey, viewerKind, userId, displayName, score, meta, achievedAt, createdAt, updatedAt } | null, improved, previousScore }
|
|
179
233
|
- Submit a score to a public Build leaderboard using server-owned viewer identity.
|
|
180
234
|
|
|
181
235
|
### Twinkle.sharedDb
|
|
182
236
|
- async getTopics() | scopes: sharedDb:read
|
|
237
|
+
- Returns: { topics: [{ id, name, createdBy, createdAt }] }
|
|
183
238
|
- async createTopic(name) | scopes: sharedDb:write
|
|
239
|
+
- Returns: { topic: { id, name, createdBy, createdAt } }
|
|
184
240
|
- async getEntries(topicName, { limit, pageSize, cursor, order, sort, direction } = {}) | scopes: sharedDb:read
|
|
241
|
+
- Returns: { entries: [{ id, topicId, userId, username, profilePicUrl, data, createdAt, updatedAt }], cursor?, hasMore }
|
|
185
242
|
- Read shared topic rows with cursor pagination.
|
|
186
243
|
- async loadMoreEntries(topicName, { limit, pageSize, cursor, order, sort, direction } = {}) | scopes: sharedDb:read
|
|
244
|
+
- Returns: { entries: [{ id, topicId, userId, username, profilePicUrl, data, createdAt, updatedAt }], cursor?, hasMore }
|
|
187
245
|
- Fetch the next sharedDb page.
|
|
188
246
|
- async addEntry(topicName, data, { notify } = {}) | scopes: sharedDb:write
|
|
247
|
+
- Returns: { entry: { id, topicId, userId, username, profilePicUrl, data, createdAt, updatedAt } }
|
|
189
248
|
- Append a shared JSON row, optionally creating a Twinkle notification from the canonical write.
|
|
190
249
|
- async updateEntry(entryId, data, { notify } = {}) | scopes: sharedDb:write
|
|
250
|
+
- Returns: { entry: { id, topicId, userId, username, profilePicUrl, data, createdAt, updatedAt } }
|
|
191
251
|
- Update a viewer-owned shared row, optionally notifying safe recipients from the canonical write.
|
|
192
252
|
- async deleteEntry(entryId) | scopes: sharedDb:write
|
|
253
|
+
- Returns: { success: true }
|
|
193
254
|
|
|
194
255
|
### Twinkle.chat
|
|
195
256
|
- async listRooms() | scopes: chat:read
|
|
257
|
+
- Returns: { rooms: [{ id, buildId, key, name, createdByUserId, createdAt, updatedAt }] }
|
|
196
258
|
- async createRoom({ roomKey, name }) | scopes: chat:write
|
|
259
|
+
- Returns: { room: { id, buildId, key, name, createdByUserId, createdAt, updatedAt } }
|
|
197
260
|
- async listMessages(roomKey, { cursor, limit } = {}) | scopes: chat:read
|
|
261
|
+
- Returns: { messages: [{ id, roomId, roomKey, userId, username, profilePicUrl, role, status, text, metadata, clientMessageId, createdAt, updatedAt }], cursor? }
|
|
198
262
|
- async sendMessage(roomKey, textOrOptions, options) | scopes: chat:write
|
|
263
|
+
- Returns: { message: { id, buildId, roomId, roomKey, userId, username, profilePicUrl, role, status, text, metadata, clientMessageId, createdAt, updatedAt }, room: { id, buildId, key, name, createdByUserId, createdAt, updatedAt }, created }
|
|
199
264
|
- async deleteMessage(messageId) | scopes: chat:write
|
|
265
|
+
- Returns: { success: true, messageId }
|
|
200
266
|
- subscribe(roomKey, listener) | scopes: chat:read
|
|
267
|
+
- Returns: unsubscribe function
|
|
201
268
|
|
|
202
269
|
### Twinkle.world
|
|
203
270
|
- async join({ worldKey = 'default', roomKey = 'main', instanceId = 'main', presence, player } = {}) | scopes: none
|
|
271
|
+
- Returns: { sessionId, session, room, players, snapshot, subscribe(listener), updatePresence(patch), send(actionOrType, data), leave() }
|
|
204
272
|
- Join a realtime Build world room and receive a snapshot plus a session handle for presence updates, actions, and room events.
|
|
205
273
|
- Example: const world = await Twinkle.world.join({ roomKey: 'town-square', presence: { x: 0, y: 0, z: 0, facing: 'south' }, player: { name: avatarName } });
|
|
206
274
|
world.subscribe((event) => updateRemotePlayers(event.players));
|
|
207
275
|
world.updatePresence({ x, y, z, facing });
|
|
208
276
|
- leaveAll() | scopes: none
|
|
277
|
+
- Returns: void
|
|
209
278
|
- Leave every active world session in the current iframe.
|
|
210
279
|
|
|
211
280
|
### Twinkle.users
|
|
212
281
|
- async getUser(userId) | scopes: user:read
|
|
282
|
+
- Returns: { id, username, profilePicUrl, realName } | null
|
|
213
283
|
- async getUsers({ search, userIds, cursor, limit } = {}) | scopes: users:read
|
|
284
|
+
- Returns: { users: [{ id, username, profilePicUrl, realName }], cursor? }
|
|
214
285
|
|
|
215
286
|
### Twinkle.reflections
|
|
216
287
|
- async getDailyReflections({ userIds, cursor, lastId, limit } = {}) | scopes: dailyReflections:read
|
|
288
|
+
- Returns: { reflections: [{ id, userId, response, questionId, submittedAt, sharedAt, username, profilePicUrl, question }], cursor? }
|
|
217
289
|
- async getDailyReflectionsByUser(userId, { cursor, lastId, limit } = {}) | scopes: dailyReflections:read
|
|
290
|
+
- Returns: { reflections: [{ id, userId, response, questionId, submittedAt, sharedAt, username, profilePicUrl, question }], cursor? }
|
|
218
291
|
|
|
219
292
|
### Twinkle.privateDb
|
|
220
293
|
- async get(key) | scopes: privateDb:read
|
|
294
|
+
- Returns: { item: { id, key, value, updatedAt } | null }
|
|
221
295
|
- Read one key from the default private per-user JSON store.
|
|
222
296
|
- async list({ prefix, limit, cursor } = {}) | scopes: privateDb:read
|
|
297
|
+
- Returns: { items: [{ id, key, value, updatedAt }], cursor? }
|
|
223
298
|
- List keys from the default private per-user JSON store.
|
|
224
299
|
- async set(key, value) | scopes: privateDb:write
|
|
300
|
+
- Returns: { item: { id, key, value, updatedAt } }
|
|
225
301
|
- Upsert one JSON-serializable value in the default private per-user store.
|
|
226
302
|
- async remove(key) | scopes: privateDb:write
|
|
303
|
+
- Returns: { success: true, deleted: boolean }
|
|
227
304
|
- Delete one key from the default private per-user JSON store.
|
|
228
305
|
|
|
229
306
|
### Twinkle.reminders
|
|
230
307
|
- async list({ includeDisabled, limit } = {}) | scopes: reminders:read
|
|
308
|
+
- Returns: { reminders: [{ id, buildId, userId, title, body, targetPath, payload, isEnabled, schedule, lastTriggeredAt, createdAt, updatedAt }] }
|
|
231
309
|
- async create({ title, body, targetPath, payload, schedule, isEnabled }) | scopes: reminders:write
|
|
310
|
+
- Returns: { reminder: { id, buildId, userId, title, body, targetPath, payload, isEnabled, schedule, lastTriggeredAt, createdAt, updatedAt } | null }
|
|
232
311
|
- async update(reminderId, patch) | scopes: reminders:write
|
|
312
|
+
- Returns: { reminder: { id, buildId, userId, title, body, targetPath, payload, isEnabled, schedule, lastTriggeredAt, createdAt, updatedAt } | null }
|
|
233
313
|
- async remove(reminderId) | scopes: reminders:write
|
|
314
|
+
- Returns: { success: true, deleted: boolean }
|
|
234
315
|
- async getDue({ now, autoAcknowledge, limit } = {}) | scopes: reminders:read
|
|
316
|
+
- Returns: { now, reminders: [{ id, buildId, userId, title, body, targetPath, payload, isEnabled, schedule, lastTriggeredAt, createdAt, updatedAt }] }
|
|
235
317
|
|
|
236
318
|
## Examples
|
|
237
319
|
|