lecodes-sdk 0.19.0
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.md +114 -0
- package/dist/global.d.ts +503 -0
- package/dist/host.d.ts +61 -0
- package/dist/inject.js +4730 -0
- package/dist/types/animate/animate.d.ts +20 -0
- package/dist/types/animate/bezier.d.ts +2 -0
- package/dist/types/animate/easings.d.ts +21 -0
- package/dist/types/canvas/Canvas.d.ts +168 -0
- package/dist/types/core/Aspect.d.ts +171 -0
- package/dist/types/core/InspectorUI.d.ts +88 -0
- package/dist/types/core/StateMachine.d.ts +81 -0
- package/dist/types/core/color.d.ts +13 -0
- package/dist/types/core/compWrite.d.ts +11 -0
- package/dist/types/core/events.d.ts +11 -0
- package/dist/types/core/fields.d.ts +58 -0
- package/dist/types/core/registry.d.ts +7 -0
- package/dist/types/core/signals.d.ts +29 -0
- package/dist/types/core/time.d.ts +31 -0
- package/dist/types/g2/Camera2D.d.ts +19 -0
- package/dist/types/g2/CharacterController2D.d.ts +134 -0
- package/dist/types/g2/Node2D.d.ts +91 -0
- package/dist/types/g2/OneWay2D.d.ts +24 -0
- package/dist/types/g2/Physics2D.d.ts +147 -0
- package/dist/types/g2/Scene2D.d.ts +72 -0
- package/dist/types/g2/Shape2D.d.ts +58 -0
- package/dist/types/g2/Sprite.d.ts +51 -0
- package/dist/types/g2/SpriteAnimation.d.ts +50 -0
- package/dist/types/g2/SpriteSheet.d.ts +69 -0
- package/dist/types/g2/Texture2D.d.ts +16 -0
- package/dist/types/g2/Tilemap.d.ts +20 -0
- package/dist/types/g2/Tileset.d.ts +32 -0
- package/dist/types/g2/Trigger2D.d.ts +20 -0
- package/dist/types/g2/autotile.d.ts +133 -0
- package/dist/types/g2/cells.d.ts +14 -0
- package/dist/types/g2/defineScene2d.d.ts +141 -0
- package/dist/types/g2/groups2d.d.ts +16 -0
- package/dist/types/g2/loop.d.ts +4 -0
- package/dist/types/g2/scenarios2d.d.ts +30 -0
- package/dist/types/g2/touch.d.ts +2 -0
- package/dist/types/gl/Camera.d.ts +79 -0
- package/dist/types/gl/CameraPlace.d.ts +26 -0
- package/dist/types/gl/CharacterController.d.ts +98 -0
- package/dist/types/gl/Gearbox.d.ts +86 -0
- package/dist/types/gl/Geometry.d.ts +39 -0
- package/dist/types/gl/IK.d.ts +53 -0
- package/dist/types/gl/InstancedMesh.d.ts +46 -0
- package/dist/types/gl/Light.d.ts +73 -0
- package/dist/types/gl/Lightmap.d.ts +85 -0
- package/dist/types/gl/Locomotion.d.ts +279 -0
- package/dist/types/gl/Material.d.ts +100 -0
- package/dist/types/gl/Mesh.d.ts +33 -0
- package/dist/types/gl/Model.d.ts +53 -0
- package/dist/types/gl/NavAgent.d.ts +99 -0
- package/dist/types/gl/NavMesh.d.ts +169 -0
- package/dist/types/gl/Node.d.ts +99 -0
- package/dist/types/gl/Noise.d.ts +20 -0
- package/dist/types/gl/Particles.d.ts +277 -0
- package/dist/types/gl/Physics.d.ts +91 -0
- package/dist/types/gl/Plane.d.ts +19 -0
- package/dist/types/gl/Ragdoll.d.ts +86 -0
- package/dist/types/gl/Ray.d.ts +8 -0
- package/dist/types/gl/Scene.d.ts +221 -0
- package/dist/types/gl/Shape.d.ts +122 -0
- package/dist/types/gl/Terrain.d.ts +280 -0
- package/dist/types/gl/Texture.d.ts +27 -0
- package/dist/types/gl/Trigger.d.ts +10 -0
- package/dist/types/gl/Vehicle.d.ts +191 -0
- package/dist/types/gl/Wheel.d.ts +95 -0
- package/dist/types/gl/animation/AnimationClip.d.ts +60 -0
- package/dist/types/gl/animation/Animator.d.ts +219 -0
- package/dist/types/gl/animation/Layer.d.ts +31 -0
- package/dist/types/gl/animation/Loop.d.ts +17 -0
- package/dist/types/gl/animation/Playback.d.ts +36 -0
- package/dist/types/gl/animation/core.d.ts +367 -0
- package/dist/types/gl/controls.d.ts +19 -0
- package/dist/types/gl/physicsEvents.d.ts +1 -0
- package/dist/types/gl/scenarios.d.ts +98 -0
- package/dist/types/gl/state.d.ts +5 -0
- package/dist/types/gl/terrainMesh.d.ts +46 -0
- package/dist/types/gl/touch.d.ts +2 -0
- package/dist/types/inject.d.ts +123 -0
- package/dist/types/math/Mathf.d.ts +39 -0
- package/dist/types/math/mat4.d.ts +66 -0
- package/dist/types/math/quat.d.ts +53 -0
- package/dist/types/math/vec.d.ts +129 -0
- package/dist/types/net/codec.d.ts +25 -0
- package/dist/types/net/core.d.ts +106 -0
- package/dist/types/net/index.d.ts +80 -0
- package/dist/types/net/replication.d.ts +101 -0
- package/dist/types/plugins/camera.d.ts +25 -0
- package/dist/types/plugins/geolocation.d.ts +39 -0
- package/dist/types/plugins/oauth.d.ts +25 -0
- package/dist/types/plugins/permission.d.ts +1 -0
- package/dist/types/plugins/push.d.ts +46 -0
- package/dist/types/plugins/qr.d.ts +21 -0
- package/dist/types/plugins/service.d.ts +33 -0
- package/dist/types/runtime/app.d.ts +67 -0
- package/dist/types/runtime/appEvents.d.ts +7 -0
- package/dist/types/runtime/channel.d.ts +1 -0
- package/dist/types/runtime/clipboard.d.ts +7 -0
- package/dist/types/runtime/datetime.d.ts +80 -0
- package/dist/types/runtime/device.d.ts +126 -0
- package/dist/types/runtime/fetch.d.ts +31 -0
- package/dist/types/runtime/files.d.ts +33 -0
- package/dist/types/runtime/input.d.ts +121 -0
- package/dist/types/runtime/media.d.ts +46 -0
- package/dist/types/runtime/misc.d.ts +11 -0
- package/dist/types/runtime/net.d.ts +15 -0
- package/dist/types/runtime/rpc.d.ts +44 -0
- package/dist/types/runtime/service.d.ts +1 -0
- package/dist/types/runtime/share.d.ts +2 -0
- package/dist/types/runtime/storage.d.ts +5 -0
- package/dist/types/runtime/touch.d.ts +56 -0
- package/dist/types/scene/defineScene.d.ts +296 -0
- package/dist/types/scene/editorPlugins.d.ts +82 -0
- package/dist/types/scene/gizmos.d.ts +56 -0
- package/dist/types/scene/grammar.d.ts +56 -0
- package/dist/types/scene/level.d.ts +98 -0
- package/dist/types/scene/material.d.ts +76 -0
- package/dist/types/server/auth/appConfig.d.ts +16 -0
- package/dist/types/server/auth/global.d.ts +56 -0
- package/dist/types/server/auth/models.d.ts +164 -0
- package/dist/types/server/auth/types.d.ts +56 -0
- package/dist/types/server/channel.d.ts +28 -0
- package/dist/types/server/context.d.ts +27 -0
- package/dist/types/server/db/defineDb.d.ts +36 -0
- package/dist/types/server/db/fields.d.ts +146 -0
- package/dist/types/server/db/index.d.ts +6 -0
- package/dist/types/server/db/marci/query.d.ts +326 -0
- package/dist/types/server/db/types.d.ts +163 -0
- package/dist/types/server/errors.d.ts +8 -0
- package/dist/types/server/inject.d.ts +5 -0
- package/dist/types/ui/NativeView.d.ts +38 -0
- package/dist/types/ui/UI.d.ts +27 -0
- package/dist/types/ui/UIBottomSheet.d.ts +71 -0
- package/dist/types/ui/UIButton.d.ts +30 -0
- package/dist/types/ui/UIContainer.d.ts +33 -0
- package/dist/types/ui/UIImage.d.ts +35 -0
- package/dist/types/ui/UIInput.d.ts +110 -0
- package/dist/types/ui/UIModal.d.ts +70 -0
- package/dist/types/ui/UINode.d.ts +363 -0
- package/dist/types/ui/UIPager.d.ts +123 -0
- package/dist/types/ui/UIPopover.d.ts +39 -0
- package/dist/types/ui/UIScreen.d.ts +57 -0
- package/dist/types/ui/UIScrollable.d.ts +40 -0
- package/dist/types/ui/UISpacer.d.ts +9 -0
- package/dist/types/ui/UITabs.d.ts +96 -0
- package/dist/types/ui/UIText.d.ts +19 -0
- package/dist/types/ui/UIVideo.d.ts +28 -0
- package/dist/types/ui/UIVirtualizedList.d.ts +97 -0
- package/dist/types/ui/UIWidget.d.ts +65 -0
- package/dist/types/ui/fonts.d.ts +9 -0
- package/dist/types/ui/presentable.d.ts +68 -0
- package/dist/types/ui/router.d.ts +40 -0
- package/dist/types/ui/theme.d.ts +26 -0
- package/dist/types.json +1 -0
- package/package.json +46 -0
- package/prompts/2d.md +395 -0
- package/prompts/3d-scene-files.md +113 -0
- package/prompts/3d-scene.md +119 -0
- package/prompts/3d.md +303 -0
- package/prompts/README.md +142 -0
- package/prompts/ar.md +109 -0
- package/prompts/canvas.md +27 -0
- package/prompts/compose.ts +46 -0
- package/prompts/concept.md +49 -0
- package/prompts/core-design.md +75 -0
- package/prompts/core.md +264 -0
- package/prompts/design.md +218 -0
- package/prompts/dist/2d-game.md +1530 -0
- package/prompts/dist/3d-app.md +1672 -0
- package/prompts/dist/ar-app.md +1548 -0
- package/prompts/dist/concept.md +49 -0
- package/prompts/dist/design.md +498 -0
- package/prompts/dist/ui-app.md +1134 -0
- package/prompts/index.md +37 -0
- package/prompts/intake-prompt.md +33 -0
- package/prompts/namer-prompt.md +12 -0
- package/prompts/router-prompt.md +50 -0
- package/prompts/select.ts +111 -0
- package/prompts/ui-design.md +203 -0
- package/prompts/ui.md +803 -0
- package/src/animate/animate.ts +238 -0
- package/src/animate/bezier.ts +138 -0
- package/src/animate/easings.ts +126 -0
- package/src/bridges.d.ts +1345 -0
- package/src/canvas/Canvas.ts +305 -0
- package/src/compile/__tests__/assetIconMacro.test.ts +219 -0
- package/src/compile/__tests__/assetMacro.test.ts +74 -0
- package/src/compile/__tests__/assetName.test.ts +55 -0
- package/src/compile/__tests__/compile.test.ts +287 -0
- package/src/compile/__tests__/detectEntry.test.ts +132 -0
- package/src/compile/__tests__/fontMacro.test.ts +199 -0
- package/src/compile/aspectMacro.ts +86 -0
- package/src/compile/assetIconMacro.ts +384 -0
- package/src/compile/assetMacro.ts +146 -0
- package/src/compile/assetName.ts +50 -0
- package/src/compile/bundler.ts +287 -0
- package/src/compile/compileProject.ts +127 -0
- package/src/compile/detectEntry.ts +128 -0
- package/src/compile/fontMacro.ts +459 -0
- package/src/compile/fontRegistry.ts +78 -0
- package/src/compile/header.ts +67 -0
- package/src/compile/index.ts +100 -0
- package/src/compile/libraryImports.ts +52 -0
- package/src/compile/liteMaterial.ts +247 -0
- package/src/compile/sceneEditor.ts +88 -0
- package/src/compile/serverSplit.ts +233 -0
- package/src/compile/serverTypes.ts +227 -0
- package/src/compile/sfnt.ts +98 -0
- package/src/compile/shaderSchema.ts +202 -0
- package/src/compile/shaderTargets.ts +77 -0
- package/src/compile/sourcemap.ts +25 -0
- package/src/core/Aspect.ts +568 -0
- package/src/core/InspectorUI.ts +212 -0
- package/src/core/StateMachine.ts +308 -0
- package/src/core/__tests__/stateMachine.test.ts +132 -0
- package/src/core/color.ts +66 -0
- package/src/core/compWrite.ts +42 -0
- package/src/core/events.ts +38 -0
- package/src/core/fields.ts +120 -0
- package/src/core/registry.ts +23 -0
- package/src/core/signals.ts +277 -0
- package/src/core/time.ts +84 -0
- package/src/g2/Camera2D.ts +40 -0
- package/src/g2/CharacterController2D.ts +276 -0
- package/src/g2/Node2D.ts +267 -0
- package/src/g2/OneWay2D.ts +66 -0
- package/src/g2/Physics2D.ts +346 -0
- package/src/g2/Scene2D.ts +209 -0
- package/src/g2/Shape2D.ts +259 -0
- package/src/g2/Sprite.ts +89 -0
- package/src/g2/SpriteAnimation.ts +171 -0
- package/src/g2/SpriteSheet.ts +166 -0
- package/src/g2/Texture2D.ts +47 -0
- package/src/g2/Tilemap.ts +41 -0
- package/src/g2/Tileset.ts +71 -0
- package/src/g2/Trigger2D.ts +77 -0
- package/src/g2/autotile.ts +433 -0
- package/src/g2/cells.ts +91 -0
- package/src/g2/defineScene2d.ts +381 -0
- package/src/g2/groups2d.ts +106 -0
- package/src/g2/loop.ts +50 -0
- package/src/g2/scenarios2d.ts +69 -0
- package/src/g2/touch.ts +83 -0
- package/src/gl/Camera.ts +160 -0
- package/src/gl/CameraPlace.ts +52 -0
- package/src/gl/CharacterController.ts +238 -0
- package/src/gl/Gearbox.ts +212 -0
- package/src/gl/Geometry.ts +279 -0
- package/src/gl/IK.ts +193 -0
- package/src/gl/InstancedMesh.ts +132 -0
- package/src/gl/Light.ts +135 -0
- package/src/gl/Lightmap.ts +402 -0
- package/src/gl/Locomotion.ts +498 -0
- package/src/gl/Material.ts +279 -0
- package/src/gl/Mesh.ts +83 -0
- package/src/gl/Model.ts +124 -0
- package/src/gl/NavAgent.ts +337 -0
- package/src/gl/NavMesh.ts +397 -0
- package/src/gl/Node.ts +350 -0
- package/src/gl/Noise.ts +30 -0
- package/src/gl/Particles.ts +676 -0
- package/src/gl/Physics.ts +222 -0
- package/src/gl/Plane.ts +53 -0
- package/src/gl/Ragdoll.ts +270 -0
- package/src/gl/Ray.ts +16 -0
- package/src/gl/Scene.ts +505 -0
- package/src/gl/Shape.ts +409 -0
- package/src/gl/Terrain.ts +1092 -0
- package/src/gl/Texture.ts +63 -0
- package/src/gl/Trigger.ts +45 -0
- package/src/gl/Vehicle.ts +473 -0
- package/src/gl/Wheel.ts +240 -0
- package/src/gl/animation/AnimationClip.ts +204 -0
- package/src/gl/animation/Animator.ts +329 -0
- package/src/gl/animation/Layer.ts +39 -0
- package/src/gl/animation/Loop.ts +31 -0
- package/src/gl/animation/Playback.ts +52 -0
- package/src/gl/animation/core.ts +670 -0
- package/src/gl/controls.ts +95 -0
- package/src/gl/physicsEvents.ts +20 -0
- package/src/gl/scenarios.ts +291 -0
- package/src/gl/state.ts +6 -0
- package/src/gl/terrainMesh.ts +219 -0
- package/src/gl/touch.ts +68 -0
- package/src/host.d.ts +61 -0
- package/src/inject.ts +203 -0
- package/src/math/Mathf.ts +118 -0
- package/src/math/mat4.ts +278 -0
- package/src/math/quat.ts +232 -0
- package/src/math/vec.ts +255 -0
- package/src/net/codec.ts +119 -0
- package/src/net/core.ts +384 -0
- package/src/net/index.ts +181 -0
- package/src/net/replication.ts +622 -0
- package/src/plugins/camera.ts +81 -0
- package/src/plugins/geolocation.ts +123 -0
- package/src/plugins/oauth.ts +61 -0
- package/src/plugins/permission.ts +7 -0
- package/src/plugins/push.ts +132 -0
- package/src/plugins/qr.ts +73 -0
- package/src/plugins/service.ts +47 -0
- package/src/runtime/app.ts +101 -0
- package/src/runtime/appEvents.ts +54 -0
- package/src/runtime/channel.ts +61 -0
- package/src/runtime/clipboard.ts +20 -0
- package/src/runtime/datetime.ts +329 -0
- package/src/runtime/device.ts +293 -0
- package/src/runtime/fetch.ts +77 -0
- package/src/runtime/files.ts +108 -0
- package/src/runtime/input.ts +175 -0
- package/src/runtime/media.ts +111 -0
- package/src/runtime/misc.ts +16 -0
- package/src/runtime/net.ts +36 -0
- package/src/runtime/rpc.ts +218 -0
- package/src/runtime/service.ts +83 -0
- package/src/runtime/share.ts +9 -0
- package/src/runtime/storage.ts +13 -0
- package/src/runtime/touch.ts +76 -0
- package/src/scene/defineScene.ts +1396 -0
- package/src/scene/editorPlugins.ts +110 -0
- package/src/scene/gizmos.ts +148 -0
- package/src/scene/grammar.ts +120 -0
- package/src/scene/level.ts +296 -0
- package/src/scene/material.ts +188 -0
- package/src/server/auth/appConfig.ts +12 -0
- package/src/server/auth/global.ts +80 -0
- package/src/server/auth/host.ts +318 -0
- package/src/server/auth/models.ts +83 -0
- package/src/server/auth/types.ts +50 -0
- package/src/server/channel.ts +56 -0
- package/src/server/context.ts +36 -0
- package/src/server/db/defineDb.ts +237 -0
- package/src/server/db/fields.ts +132 -0
- package/src/server/db/httpTransport.ts +93 -0
- package/src/server/db/index.ts +7 -0
- package/src/server/db/marci/query.ts +412 -0
- package/src/server/db/types.ts +202 -0
- package/src/server/errors.ts +12 -0
- package/src/server/host.ts +74 -0
- package/src/server/inject.ts +13 -0
- package/src/server/runtime.ts +133 -0
- package/src/server/validate.ts +87 -0
- package/src/ui/NativeView.ts +142 -0
- package/src/ui/UI.ts +39 -0
- package/src/ui/UIBottomSheet.ts +139 -0
- package/src/ui/UIButton.ts +101 -0
- package/src/ui/UIContainer.ts +60 -0
- package/src/ui/UIImage.ts +83 -0
- package/src/ui/UIInput.ts +185 -0
- package/src/ui/UIModal.ts +139 -0
- package/src/ui/UINode.ts +830 -0
- package/src/ui/UIPager.ts +362 -0
- package/src/ui/UIPopover.ts +100 -0
- package/src/ui/UIScreen.ts +123 -0
- package/src/ui/UIScrollable.ts +87 -0
- package/src/ui/UISpacer.ts +14 -0
- package/src/ui/UITabs.ts +236 -0
- package/src/ui/UIText.ts +51 -0
- package/src/ui/UIVideo.ts +88 -0
- package/src/ui/UIVirtualizedList.ts +241 -0
- package/src/ui/UIWidget.ts +127 -0
- package/src/ui/fonts.ts +13 -0
- package/src/ui/presentable.ts +117 -0
- package/src/ui/router.ts +132 -0
- package/src/ui/theme.ts +84 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `MarciTransport` over marcidb-server's HTTP API (docs/HTTP-API.md in the marci-db repo) — the
|
|
3
|
+
* transport the runner installs in every project worker (`setDbTransport(marciHttpTransport(url))`).
|
|
4
|
+
* Host-side code (Bun/Node): uses the platform `fetch`, never the SDK bridge. Mirrors marcidb's own
|
|
5
|
+
* `httpTransport` (crates/marcidb-ts/src/prefix.js) minus the binary read fast path.
|
|
6
|
+
*
|
|
7
|
+
* `baseUrl` = `http://host:port/<db>`; an optional bearer token (marcidb-server `MARCI_TOKEN`) is sent
|
|
8
|
+
* on every request. Pass `fetch` explicitly when the global one might be monkey-patched by untrusted
|
|
9
|
+
* code running in the same realm (the runner captures it before evaluating a project bundle).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { MarciOp, MarciTransport } from "./marci/query"
|
|
13
|
+
|
|
14
|
+
/** A failed marcidb request: `status` is the HTTP status (0 = network failure), `message` its body. */
|
|
15
|
+
export class MarciHttpError extends Error {
|
|
16
|
+
readonly status: number
|
|
17
|
+
constructor(status: number, message: string) {
|
|
18
|
+
super(message)
|
|
19
|
+
this.name = "MarciHttpError"
|
|
20
|
+
this.status = status
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type MarciHttpOptions = {
|
|
25
|
+
token?: string | null
|
|
26
|
+
fetch?: typeof fetch
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** `update/:id` and `delete/:id` take the key in the path: a bare value, or `k=v&k2=v2` for composite keys
|
|
30
|
+
* (nested ids flatten with dots — `{ chat: { id: 1 } }` → `chat.id=1`). */
|
|
31
|
+
export const encodeMarciId = (id: unknown): string => {
|
|
32
|
+
if (typeof id === "object" && id !== null) {
|
|
33
|
+
const parts: string[] = []
|
|
34
|
+
const walk = (obj: Record<string, unknown>, prefix: string) => {
|
|
35
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
36
|
+
const key = prefix ? `${prefix}.${k}` : k
|
|
37
|
+
if (typeof v === "object" && v !== null) walk(v as Record<string, unknown>, key)
|
|
38
|
+
else parts.push(`${key}=${encodeURIComponent(String(v))}`)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
walk(id as Record<string, unknown>, "")
|
|
42
|
+
return parts.join("&")
|
|
43
|
+
}
|
|
44
|
+
return encodeURIComponent(String(id))
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Low-level request helper (also used by the runner's admin calls: `$sync`, `DELETE /:db`). */
|
|
48
|
+
export const marciRequest = async (
|
|
49
|
+
url: string,
|
|
50
|
+
init: { method?: string, body?: unknown, rawBody?: string, token?: string | null, fetch?: typeof fetch } = {},
|
|
51
|
+
): Promise<any> => {
|
|
52
|
+
const f = init.fetch ?? fetch
|
|
53
|
+
const headers: Record<string, string> = {}
|
|
54
|
+
if (init.token) headers.authorization = `Bearer ${init.token}`
|
|
55
|
+
let body: string | undefined
|
|
56
|
+
if (init.rawBody !== undefined) { body = init.rawBody; headers["content-type"] = "text/plain" }
|
|
57
|
+
else if (init.body !== undefined) { body = JSON.stringify(init.body); headers["content-type"] = "application/json" }
|
|
58
|
+
let res: Response
|
|
59
|
+
try {
|
|
60
|
+
res = await f(url, { method: init.method ?? "POST", headers, body })
|
|
61
|
+
} catch (e) {
|
|
62
|
+
throw new MarciHttpError(0, `marcidb unreachable: ${(e as Error)?.message ?? e}`)
|
|
63
|
+
}
|
|
64
|
+
const text = await res.text()
|
|
65
|
+
if (!res.ok) throw new MarciHttpError(res.status, text || `HTTP ${res.status}`)
|
|
66
|
+
if (!text) return undefined
|
|
67
|
+
try { return JSON.parse(text) } catch { return text }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const marciHttpTransport = (baseUrl: string, options: MarciHttpOptions = {}): MarciTransport => {
|
|
71
|
+
const url = baseUrl.replace(/\/+$/, "")
|
|
72
|
+
const call = (path: string, body?: unknown) => marciRequest(`${url}/${path}`, { body, token: options.token, fetch: options.fetch })
|
|
73
|
+
return {
|
|
74
|
+
exec(op: MarciOp) {
|
|
75
|
+
switch (op.action) {
|
|
76
|
+
case "findMany": return call(`${op.model}/findMany`, op.query ?? {})
|
|
77
|
+
case "findFirst": return call(`${op.model}/findFirst`, op.query ?? {})
|
|
78
|
+
case "insert": return call(`${op.model}/insert`, op.data)
|
|
79
|
+
case "update": return call(`${op.model}/update/${encodeMarciId(op.id)}`, op.data).then(() => null)
|
|
80
|
+
// no id in the path — the filter and the payload travel together in the body
|
|
81
|
+
case "updateMany": return call(`${op.model}/updateMany`, { ...(op.query ?? {}), data: op.data })
|
|
82
|
+
case "delete": return call(`${op.model}/delete/${encodeMarciId(op.id)}`).then(() => null)
|
|
83
|
+
case "deleteMany": return call(`${op.model}/deleteMany`, op.query ?? {})
|
|
84
|
+
case "count": return call(`${op.model}/count`, op.query ?? {})
|
|
85
|
+
case "aggregate": return call(`${op.model}/aggregate`, op.query ?? {})
|
|
86
|
+
default: return Promise.reject(new Error(`marcidb: unknown action '${op.action}'`))
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
batch(ops: MarciOp[]) {
|
|
90
|
+
return call("$transaction", ops)
|
|
91
|
+
},
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Server-side db layer (phase 0 prototype of docs/backend-plan.md §3.1). Not yet part of inject.ts.
|
|
2
|
+
export { t } from "./fields"
|
|
3
|
+
export type { Field, FieldDef, FieldMeta } from "./fields"
|
|
4
|
+
export { model, defineDb, ref, setDbTransport, toMarci } from "./defineDb"
|
|
5
|
+
export type { DefineDbOptions } from "./defineDb"
|
|
6
|
+
export type { Db, Model, Schema, Row, Id, Insert, Update, Select, Where, Query, QueryObject, Result, Collection, ScalarSelect, Types } from "./types"
|
|
7
|
+
export type { Op, MarciOp, MarciTransport, JsonValue, Sub } from "./marci/query"
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
// GENERATED by scripts/sync-marcidb.ts from marci-db packages/marcidb-client/src/query.ts — do not edit here.
|
|
2
|
+
// Change it in the marci-db repo, then `bun run sync:marcidb`.
|
|
3
|
+
|
|
4
|
+
// The generic query layer of the marcidb client: the query-language types every generated client is built
|
|
5
|
+
// on (where / update operators, `GetResult`, `Op`, the query builder `Query<T>`) and the builder runtime
|
|
6
|
+
// (`createQueryLayer`). Everything here is schema-agnostic — the codegen (crates/marcidb-ts) emits the
|
|
7
|
+
// per-model type bags and MODELS metadata this file is parametrised by.
|
|
8
|
+
//
|
|
9
|
+
// Keep it dependency-free: besides shipping in `marcidb-client/runtime`, this file is vendored verbatim
|
|
10
|
+
// into the lecodes SDK (`packages/sdk/src/server/db/marci/query.ts`, `bun run sync:marcidb` there), which
|
|
11
|
+
// derives its model types from TS builders instead of codegen and plugs them into the same generics.
|
|
12
|
+
|
|
13
|
+
// ───────────────────────────── query language types ─────────────────────────────
|
|
14
|
+
|
|
15
|
+
export type ServiceKeys = "$where" | "$order" | "$limit" | "$skip" | "$cursor";
|
|
16
|
+
|
|
17
|
+
// A `Json` field: any JSON value. Stored as a JSONB binary blob server-side; on the wire it is a plain
|
|
18
|
+
// decoded JSON value.
|
|
19
|
+
export type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };
|
|
20
|
+
|
|
21
|
+
// Aggregate query over related records inside select
|
|
22
|
+
export type AggregateKeys = { $count: true } | { $sum: string } | { $avg: string } | { $min: string } | { $max: string }
|
|
23
|
+
|
|
24
|
+
// Distributive over union models (discriminated union for payload enums):
|
|
25
|
+
// keys not present in the current union branch are dropped
|
|
26
|
+
export type GetResult<TModel, TSelect> = TModel extends any ? {
|
|
27
|
+
-readonly [K in keyof Omit<TSelect, ServiceKeys> as TSelect[K] extends false | undefined ? never : K extends keyof TModel ? K : never]:
|
|
28
|
+
K extends keyof TModel
|
|
29
|
+
? TSelect[K] extends true
|
|
30
|
+
? TModel[K] // selected the whole field
|
|
31
|
+
: TSelect[K] extends Record<string, any>
|
|
32
|
+
? TModel[K] extends readonly object[]
|
|
33
|
+
? TSelect[K] extends AggregateKeys
|
|
34
|
+
? AggregateResult<NonNullable<TModel[K][number]>, TSelect[K]> // aggregate over the relation
|
|
35
|
+
: GetResult<NonNullable<TModel[K][number]>, TSelect[K]>[]
|
|
36
|
+
: GetResult<NonNullable<TModel[K]>, TSelect[K]> | Extract<TModel[K], null>
|
|
37
|
+
: TModel[K] // fallback
|
|
38
|
+
: never
|
|
39
|
+
} : never
|
|
40
|
+
|
|
41
|
+
export type RefUpdate<I> = {
|
|
42
|
+
"$connect"?: I
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type RefUpdateStruct<I,U> = {
|
|
46
|
+
"$update"?: U,
|
|
47
|
+
"$ensure"?: I,
|
|
48
|
+
"$set"?: I
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Relation to independent rows — link operations only, the rows themselves are never created
|
|
52
|
+
* or deleted. `$set` replaces link membership with exactly the given set (missing links are
|
|
53
|
+
* disconnected, new ones connected); `$connect` links (idempotent); `$remove` unlinks. */
|
|
54
|
+
export type RefListUpdate<I> = {
|
|
55
|
+
"$set"?: I[],
|
|
56
|
+
"$connect"?: I | I[],
|
|
57
|
+
"$remove"?: I | I[],
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** `@list` relation: an ordered inline id array — a sequence, so the same id may appear several
|
|
61
|
+
* times. `$set` replaces the whole array (also the reorder operation); `$connect` appends at the
|
|
62
|
+
* end (an already-present id gains another occurrence); `$connectUnique` appends only ids not
|
|
63
|
+
* already present; `$remove` removes every occurrence. */
|
|
64
|
+
export type RefListUpdateOrdered<I> = {
|
|
65
|
+
"$set"?: I[],
|
|
66
|
+
"$connect"?: I | I[],
|
|
67
|
+
"$connectUnique"?: I | I[],
|
|
68
|
+
"$remove"?: I | I[],
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Owned (struct) list: the children live and die with the parent. `$push` creates children,
|
|
72
|
+
* `$update` edits single children in place (each item is the child's id fields — the shape
|
|
73
|
+
* query results return — plus the changes under `data`), `$remove` deletes children by id,
|
|
74
|
+
* `$set` replaces all children (deletes the current ones, creates the new). */
|
|
75
|
+
export type RefListUpdateStruct<I,U,Id> = {
|
|
76
|
+
"$push"?: I | I[],
|
|
77
|
+
"$update"?: (Id & { data: U }) | (Id & { data: U })[],
|
|
78
|
+
"$remove"?: Id | Id[],
|
|
79
|
+
"$set"?: I[]
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Variable-length primitive array — a sequence, so the same value may appear several times.
|
|
83
|
+
* `$push` appends at the end (duplicates kept); `$pushUnique` appends only values not already
|
|
84
|
+
* present; `$remove` removes every occurrence; `$set` replaces the whole array (also the
|
|
85
|
+
* positional-edit path — send the full new array). One operator per update. */
|
|
86
|
+
export type PrimitiveListUpdate<T> = {
|
|
87
|
+
"$set"?: T[],
|
|
88
|
+
"$push"?: T | T[],
|
|
89
|
+
"$pushUnique"?: T | T[],
|
|
90
|
+
"$remove"?: T | T[],
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Marks a set of keys as forbidden. Needed because TypeScript's excess-property check against a *union*
|
|
94
|
+
// permits any key present in some member: a plain union of single-key objects accepts `{ $gte, $lt }`,
|
|
95
|
+
// and a plain `T | { $and: T[] }` accepts a field sitting next to `$and`. Both are runtime errors or
|
|
96
|
+
// silently-wrong queries, so every branch below spells out the keys it excludes.
|
|
97
|
+
export type Never<T> = { [K in keyof T]?: never }
|
|
98
|
+
|
|
99
|
+
// Every field-level operator, so a field's condition can exclude the ones its type doesn't support.
|
|
100
|
+
export type FieldOps = {
|
|
101
|
+
"$eq": unknown, "$ne": unknown, "$not": unknown, "$in": unknown, "$notIn": unknown,
|
|
102
|
+
"$gt": unknown, "$gte": unknown, "$lt": unknown, "$lte": unknown,
|
|
103
|
+
"$startsWith": unknown, "$includes": unknown,
|
|
104
|
+
"$every": unknown, "$some": unknown, "$none": unknown,
|
|
105
|
+
"$near": unknown, "$search": unknown
|
|
106
|
+
}
|
|
107
|
+
export type Only<K extends keyof FieldOps, V> = { [P in K]: V } & Never<Omit<FieldOps, K>>
|
|
108
|
+
|
|
109
|
+
// A `$where` is either a set of field conditions or exactly one boolean combinator wrapping more of the
|
|
110
|
+
// same. Conditions belong *inside* the array: the engine returns on the first combinator it finds, so a
|
|
111
|
+
// sibling key next to `$and`/`$or` would be silently ignored — here it is a type error instead.
|
|
112
|
+
export type WhereValue<T> =
|
|
113
|
+
| (T & Never<{ "$and": unknown, "$or": unknown, "$not": unknown }>)
|
|
114
|
+
| ({ "$and": WhereValue<T>[] } & Never<T> & Never<{ "$or": unknown, "$not": unknown }>)
|
|
115
|
+
| ({ "$or": WhereValue<T>[] } & Never<T> & Never<{ "$and": unknown, "$not": unknown }>)
|
|
116
|
+
| ({ "$not": WhereValue<T> } & Never<T> & Never<{ "$and": unknown, "$or": unknown }>)
|
|
117
|
+
|
|
118
|
+
// Operators on one field are ANDed, so they combine freely: `{ $gte: 18, $lt: 65 }` is a half-open
|
|
119
|
+
// range and `{ $gte: 18, $ne: 30 }` punches a hole in one. Each group below is all-optional; a field's
|
|
120
|
+
// condition type intersects the groups its type supports and marks every remaining operator forbidden,
|
|
121
|
+
// so a string operator on a number is still a type error.
|
|
122
|
+
export type ValueOps<T> = { "$eq"?: T, "$ne"?: T, "$not"?: T, "$in"?: T[], "$notIn"?: T[] }
|
|
123
|
+
export type NumOps<T> = { "$gt"?: T, "$gte"?: T, "$lt"?: T, "$lte"?: T }
|
|
124
|
+
export type StrOps = { "$includes"?: string, "$startsWith"?: string }
|
|
125
|
+
|
|
126
|
+
// The numeric and string variants re-admit the type-agnostic operators, so a mixed condition such as
|
|
127
|
+
// `{ $gte: 18, $ne: 30 }` still matches a single member of the union the field's type resolves to.
|
|
128
|
+
export type CompareValue<T> = T | (ValueOps<T> & Never<Omit<FieldOps, keyof ValueOps<T>>>)
|
|
129
|
+
export type CompareNumValue<T> = ValueOps<T> & NumOps<T> & Never<Omit<FieldOps, keyof ValueOps<T> | keyof NumOps<T>>>
|
|
130
|
+
export type CompareStrValue<T> = ValueOps<T> & StrOps & Never<Omit<FieldOps, keyof ValueOps<T> | keyof StrOps>>
|
|
131
|
+
|
|
132
|
+
// A numeric field can also be updated in place: `{ balance: { $increment: -800 } }` reads, adds and writes
|
|
133
|
+
// inside the update's own transaction, so it is atomic against concurrent writers. The delta is signed, so
|
|
134
|
+
// decrementing requires a signed field (`Int`) — a negative delta on an unsigned field is rejected.
|
|
135
|
+
// Incrementing a field that is currently null is a no-op.
|
|
136
|
+
export type UpdateNumValue = { "$increment": number }
|
|
137
|
+
|
|
138
|
+
// Filtering into a Json field by dot-path. Keys are JSON paths (e.g. "address.city", "items.0"); a numeric
|
|
139
|
+
// segment indexes an array. A bare value is shorthand for `$eq` (a plain object matches the whole subtree).
|
|
140
|
+
// Path keys must not start with `$`. A missing leaf or a type mismatch simply doesn't match.
|
|
141
|
+
export type JsonType = "string" | "number" | "boolean" | "object" | "array" | "null"
|
|
142
|
+
// As on scalar fields, operators on one path are ANDed and may be combined.
|
|
143
|
+
export type JsonCondition =
|
|
144
|
+
| JsonValue
|
|
145
|
+
| {
|
|
146
|
+
"$eq"?: JsonValue, "$ne"?: JsonValue, "$not"?: JsonValue,
|
|
147
|
+
"$gt"?: number | string, "$gte"?: number | string,
|
|
148
|
+
"$lt"?: number | string, "$lte"?: number | string,
|
|
149
|
+
"$in"?: JsonValue[], "$notIn"?: JsonValue[],
|
|
150
|
+
"$startsWith"?: string, "$includes"?: string,
|
|
151
|
+
"$contains"?: JsonValue,
|
|
152
|
+
"$exists"?: boolean,
|
|
153
|
+
"$type"?: JsonType
|
|
154
|
+
}
|
|
155
|
+
export type JsonPathWhere = { [path: string]: JsonCondition }
|
|
156
|
+
export type CompareRefValue<T> = T | Only<"$not", T>
|
|
157
|
+
export type CompareRefListValue<T> = Only<"$every", T> | Only<"$some", T> | Only<"$none", T>
|
|
158
|
+
|
|
159
|
+
// Module-index search (@custom): $near/$search hand a raw payload to the field's index provider.
|
|
160
|
+
export type VectorSearch = { vector: number[], k?: number, threshold?: number } // @custom(vector, …)
|
|
161
|
+
export type FullTextSearch = string | { query: string, limit?: number } // @custom(fulltext, …)
|
|
162
|
+
export type CustomSearch = Record<string, any> // other providers
|
|
163
|
+
export type CustomSearchValue<P> = Only<"$near", P> | Only<"$search", P>
|
|
164
|
+
|
|
165
|
+
// Aggregate result: only the requested keys; an empty set yields null (except count)
|
|
166
|
+
export type AggregateResult<TModel, T> =
|
|
167
|
+
(T extends { $count: true } ? { count: number } : {}) &
|
|
168
|
+
(T extends { $sum: string } ? { sum: number | null } : {}) &
|
|
169
|
+
(T extends { $avg: string } ? { avg: number | null } : {}) &
|
|
170
|
+
(T extends { $min: infer F } ? { min: (F extends keyof TModel ? TModel[F] : never) | null } : {}) &
|
|
171
|
+
(T extends { $max: infer F } ? { max: (F extends keyof TModel ? TModel[F] : never) | null } : {})
|
|
172
|
+
|
|
173
|
+
// A lazily-executed operation. `await` runs it as a single request;
|
|
174
|
+
// passing it into `db.$transaction([...])` bundles it into one atomic transaction
|
|
175
|
+
declare const __op: unique symbol
|
|
176
|
+
export type Op<T> = PromiseLike<T> & { readonly [__op]: T }
|
|
177
|
+
|
|
178
|
+
// A transport-neutral operation descriptor and the pluggable transport that runs it. The HTTP transport
|
|
179
|
+
// is selected by passing a URL string; marcidb-embedded provides an in-process FFI transport.
|
|
180
|
+
export type MarciOp = { model: string, action: string, query?: any, data?: any, id?: any }
|
|
181
|
+
export type MarciTransport = {
|
|
182
|
+
exec(op: MarciOp): Promise<any>
|
|
183
|
+
batch(ops: MarciOp[]): Promise<any[]>
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ───────────────────────────── query builder ─────────────────────────────
|
|
187
|
+
|
|
188
|
+
// The per-model type bag the codegen emits (`UserTypes`) and `Query<T>` below is parametrised by.
|
|
189
|
+
export type ModelTypes = {
|
|
190
|
+
name: string
|
|
191
|
+
model: any
|
|
192
|
+
id: Record<string, any>
|
|
193
|
+
/** What an empty select returns: id + every scalar field, as a `{ field: true }` shape. */
|
|
194
|
+
scalars: Record<string, true>
|
|
195
|
+
select: Record<string, any>
|
|
196
|
+
query: Record<string, any>
|
|
197
|
+
where: any
|
|
198
|
+
order: Record<string, any>
|
|
199
|
+
insert: any
|
|
200
|
+
update: any
|
|
201
|
+
aggregate: Record<string, any>
|
|
202
|
+
reindex: boolean
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// A sub-query placed as a value in a select shape — a builder, `count()` or `aggregate()` of the relation's
|
|
206
|
+
// model (`posts: db.post.order("id", "desc").limit(5)`). Branded by model name so a builder of the wrong model
|
|
207
|
+
// is a type error; `[__sel]` is the shape it resolves to (see `Resolve`).
|
|
208
|
+
declare const __sub: unique symbol
|
|
209
|
+
declare const __sel: unique symbol
|
|
210
|
+
export type Sub<Name extends string, Shape = any> = { readonly [__sub]: Name, readonly [__sel]: Shape }
|
|
211
|
+
|
|
212
|
+
// A shape without field keys (only `$`-clauses, or nothing) selects id + every scalar — same rule as the engine.
|
|
213
|
+
export type Effective<T extends ModelTypes, S> = [Exclude<keyof S, ServiceKeys>] extends [never] ? T["scalars"] : S
|
|
214
|
+
// Sub-queries inside a shape resolve to the shape they carry, recursively; `$`-clauses are left alone.
|
|
215
|
+
export type Resolve<S> =
|
|
216
|
+
S extends { readonly [__sel]: infer X } ? Resolve<X>
|
|
217
|
+
: S extends object ? { [K in keyof S]: K extends `$${string}` ? S[K] : Resolve<S[K]> }
|
|
218
|
+
: S
|
|
219
|
+
export type Rows<T extends ModelTypes, Sel> = GetResult<T["model"], Resolve<Effective<T, Sel>>>
|
|
220
|
+
// The bare key value, accepted wherever an id object is (`after(42)` = `after({ id: 42 })`, `delete(42)`).
|
|
221
|
+
export type BareId<I> = I extends { id: infer V } ? V : never
|
|
222
|
+
export type IdArg<I> = I | BareId<I>
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* A lazy, immutable query over one model — `db.user` itself is one. Every clause returns a new query, so
|
|
226
|
+
* queries compose (`const active = db.user.where({ active: true })`). `await` runs it as `findMany`; passing
|
|
227
|
+
* it to `$transaction` batches it; placing it in another query's select makes it a sub-select. `Sel` is the
|
|
228
|
+
* projection: id + scalars until `select(...)` sets a shape.
|
|
229
|
+
*/
|
|
230
|
+
export interface Query<T extends ModelTypes, Sel = T["scalars"]> extends PromiseLike<Rows<T, Sel>[]> {
|
|
231
|
+
readonly [__op]: Rows<T, Sel>[]
|
|
232
|
+
readonly [__sub]: T["name"]
|
|
233
|
+
readonly [__sel]: Effective<T, Sel>
|
|
234
|
+
/** Filter (marcidb `$where`). Repeated calls are ANDed. */
|
|
235
|
+
where(where: T["where"]): Query<T, Sel>
|
|
236
|
+
/** Sort by one field: `order("age", "desc")` or `order({ age: "desc" })`. */
|
|
237
|
+
order(field: keyof T["order"] & string, direction?: "asc" | "desc"): Query<T, Sel>
|
|
238
|
+
order(order: T["order"]): Query<T, Sel>
|
|
239
|
+
limit(n: number): Query<T, Sel>
|
|
240
|
+
skip(n: number): Query<T, Sel>
|
|
241
|
+
/** Keyset cursor: rows strictly after this id in the current order (`$cursor`). */
|
|
242
|
+
after(id: IdArg<T["id"]>): Query<T, Sel>
|
|
243
|
+
/**
|
|
244
|
+
* The projection. Values are `true`, a nested shape, or a sub-query of the relation's model
|
|
245
|
+
* (`posts: db.post.limit(5)`, `posts: db.post.where({ published: true }).count()`). No argument, or no
|
|
246
|
+
* field keys, selects id + every scalar. `$`-clauses are accepted here too (the object form).
|
|
247
|
+
*/
|
|
248
|
+
select<S extends T["query"] = T["scalars"]>(shape?: S): Query<T, S>
|
|
249
|
+
/** The first matching row or `null`. */
|
|
250
|
+
first(): Op<Rows<T, Sel> | null>
|
|
251
|
+
/** Row count; inside a select it becomes `{ count }` for the relation. */
|
|
252
|
+
count(): Op<number> & Sub<T["name"], { $count: true }>
|
|
253
|
+
/** @deprecated pass the filter through the chain: `db.user.where(w).count()` (removed in the next minor). */
|
|
254
|
+
count(query: { $where?: T["where"] }): Op<number> & Sub<T["name"], { $count: true }>
|
|
255
|
+
// `NoInfer`: as a select value the contextual type is `Sub<Name, any>`, which would otherwise win the inference of `A`
|
|
256
|
+
aggregate<A extends T["aggregate"]>(query: A): Op<AggregateResult<T["model"], A>> & Sub<T["name"], NoInfer<A>>
|
|
257
|
+
|
|
258
|
+
insert(data: T["insert"]): Op<T["id"]>
|
|
259
|
+
update(id: IdArg<T["id"]>, data: T["update"]): Op<void>
|
|
260
|
+
/** Applies `data` to every row the chain's `where` matches (all rows without one); resolves to the number of rows. */
|
|
261
|
+
updateMany(data: T["update"]): Op<number>
|
|
262
|
+
/** @deprecated pass the filter through the chain: `db.user.where(w).updateMany(data)` (removed in the next minor). */
|
|
263
|
+
updateMany(query: { $where?: T["where"] }, data: T["update"]): Op<number>
|
|
264
|
+
delete(id: IdArg<T["id"]>): Op<void>
|
|
265
|
+
/**
|
|
266
|
+
* Deletes every row the chain's `where` matches (cascades apply, as for `delete`); resolves to the number
|
|
267
|
+
* deleted. Refuses to run without a `where` — write `.where({})` to mean "every row".
|
|
268
|
+
*/
|
|
269
|
+
deleteMany(): Op<number>
|
|
270
|
+
|
|
271
|
+
// ── object form (deprecated): one query object (select shape + $where/$order/$limit/$skip/$cursor)
|
|
272
|
+
/** @deprecated use `.select(query)` — it takes the same object, merged with the chain (removed in the next minor). */
|
|
273
|
+
findMany<Q extends T["query"] = {}>(query?: Q): Op<Rows<T, Q>[]>
|
|
274
|
+
/** @deprecated use `.select(query).first()` (removed in the next minor). */
|
|
275
|
+
findFirst<Q extends T["query"] = {}>(query?: Q): Op<Rows<T, Q> | null>
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** `db.<model>`: the root query, plus `reindex()` for models with a `@custom` (vector / full-text) index. */
|
|
279
|
+
export type Collection<T extends ModelTypes> = Query<T> & (T["reindex"] extends true ? { reindex(): Op<{ ok: boolean, indexed: number }> } : {})
|
|
280
|
+
|
|
281
|
+
// ───────────────────────────── builder runtime ─────────────────────────────
|
|
282
|
+
|
|
283
|
+
/** One field of a model, in slot order: `n` name, `k` key | body | one | many, `m` the relation's target model. */
|
|
284
|
+
export type FieldDesc = { n: string; k: "key" | "body" | "one" | "many"; t?: string | null; m?: string };
|
|
285
|
+
/** Model name → its field descriptors (structs included, as relation targets). */
|
|
286
|
+
export type ModelsMeta = Record<string, readonly FieldDesc[]>;
|
|
287
|
+
|
|
288
|
+
export type QueryLayerOptions = {
|
|
289
|
+
models: ModelsMeta;
|
|
290
|
+
/** Runs one operation (a query, or a write) and resolves its result. */
|
|
291
|
+
run(op: MarciOp): Promise<any>;
|
|
292
|
+
/** Hook for `insert` payloads (the lecodes SDK generates uuid ids here). */
|
|
293
|
+
prepareInsert?(model: string, data: any): any;
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const AGGREGATE_KEYS = ["$count", "$sum", "$avg", "$min", "$max"];
|
|
297
|
+
const and = (a: any, b: any): any => (a ? (b ? { $and: [a, b] } : a) : b);
|
|
298
|
+
|
|
299
|
+
type QueryState = { shape?: Record<string, any>; where?: any; order?: any; limit?: number; skip?: number; cursor?: any };
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* The query builder over a set of models and a `run` function. Returns `op(descriptor)` — a lazy operation
|
|
303
|
+
* (`await` runs it, `$transaction` takes its `__op`) — and `collection(model)`, the immutable `db.<model>`
|
|
304
|
+
* root query. Transport-agnostic: the generated client and the embedded/HTTP transports supply `run`.
|
|
305
|
+
*/
|
|
306
|
+
export function createQueryLayer(options: QueryLayerOptions): { op: (descriptor: MarciOp) => any; collection: (model: string) => any } {
|
|
307
|
+
const { models, run } = options;
|
|
308
|
+
|
|
309
|
+
// Lazily-executed operation: `await` runs it through the transport as a single op,
|
|
310
|
+
// while `$transaction` takes only the `__op` descriptor and sends them as one batch.
|
|
311
|
+
const op = (descriptor: MarciOp): any => ({
|
|
312
|
+
__op: descriptor,
|
|
313
|
+
then: (onFulfilled?: any, onRejected?: any) => run(descriptor).then(onFulfilled, onRejected),
|
|
314
|
+
catch: (onRejected?: any) => run(descriptor).catch(onRejected),
|
|
315
|
+
finally: (onFinally?: any) => run(descriptor).finally(onFinally),
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// A field key is anything that isn't a `$`-clause. A shape without field keys selects id + every scalar
|
|
319
|
+
// (the engine's rule too — the client applies it so the binary decoder and older servers see explicit fields).
|
|
320
|
+
const scalarSelect = (model: string): Record<string, true> => {
|
|
321
|
+
const out: Record<string, true> = {};
|
|
322
|
+
for (const f of models[model] ?? []) if (f.k === "key" || f.k === "body") out[f.n] = true;
|
|
323
|
+
return out;
|
|
324
|
+
};
|
|
325
|
+
const keyField = (model: string): string => (models[model] ?? []).find((f) => f.k === "key")?.n ?? "id";
|
|
326
|
+
// `42` → `{ id: 42 }` (the bare value of a single-field key), an object passes through
|
|
327
|
+
const idObject = (model: string, id: any): any => (id !== null && typeof id === "object" ? id : { [keyField(model)]: id });
|
|
328
|
+
// Resolves sub-queries (values carrying `__select`) and fills empty projections, recursively along relations.
|
|
329
|
+
const resolveShape = (model: string, shape: Record<string, any>): Record<string, any> => {
|
|
330
|
+
const out: Record<string, any> = {};
|
|
331
|
+
let fields = 0;
|
|
332
|
+
for (const k in shape) {
|
|
333
|
+
const v = shape[k];
|
|
334
|
+
if (k.charCodeAt(0) === 36 /* $ */) { out[k] = v; continue; }
|
|
335
|
+
if (v !== undefined && v !== false) fields++;
|
|
336
|
+
if (v !== null && typeof v === "object") {
|
|
337
|
+
if ("__select" in v) { out[k] = v.__select; continue; }
|
|
338
|
+
const desc = (models[model] ?? []).find((f) => f.n === k);
|
|
339
|
+
out[k] = desc && desc.m && !AGGREGATE_KEYS.some((a) => a in v) ? resolveShape(desc.m, v) : v;
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
out[k] = v;
|
|
343
|
+
}
|
|
344
|
+
if (fields === 0) Object.assign(out, scalarSelect(model));
|
|
345
|
+
return out;
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
// `db.<model>` — an immutable builder; each clause returns a new one over the same `run`.
|
|
349
|
+
const collection = (model: string): any => {
|
|
350
|
+
const make = (st: QueryState): any => {
|
|
351
|
+
// The wire query: the (object-form) `query` merged over the chain's state.
|
|
352
|
+
const build = (query?: Record<string, any>): Record<string, any> => {
|
|
353
|
+
const q = resolveShape(model, { ...(st.shape ?? {}), ...(query ?? {}) });
|
|
354
|
+
const where = and(st.where, q.$where);
|
|
355
|
+
if (where) q.$where = where; else delete q.$where;
|
|
356
|
+
if (st.order && !q.$order) q.$order = st.order;
|
|
357
|
+
if (st.limit !== undefined && q.$limit === undefined) q.$limit = st.limit;
|
|
358
|
+
if (st.skip !== undefined && q.$skip === undefined) q.$skip = st.skip;
|
|
359
|
+
if (st.cursor !== undefined && q.$cursor === undefined) q.$cursor = st.cursor;
|
|
360
|
+
return q;
|
|
361
|
+
};
|
|
362
|
+
const whereOnly = (query?: Record<string, any>): Record<string, any> => {
|
|
363
|
+
const w = and(st.where, query && query.$where);
|
|
364
|
+
return w ? { $where: w } : {};
|
|
365
|
+
};
|
|
366
|
+
const findMany = (): MarciOp => ({ model, action: "findMany", query: build() });
|
|
367
|
+
return {
|
|
368
|
+
get __op() { return findMany(); },
|
|
369
|
+
get __select() { return build(); },
|
|
370
|
+
then: (onFulfilled?: any, onRejected?: any) => run(findMany()).then(onFulfilled, onRejected),
|
|
371
|
+
catch: (onRejected?: any) => run(findMany()).catch(onRejected),
|
|
372
|
+
finally: (onFinally?: any) => run(findMany()).finally(onFinally),
|
|
373
|
+
|
|
374
|
+
where: (where: any) => make({ ...st, where: and(st.where, where) }),
|
|
375
|
+
order: (field: any, direction?: "asc" | "desc") => make({ ...st, order: typeof field === "string" ? { [field]: direction ?? "asc" } : field }),
|
|
376
|
+
limit: (n: number) => make({ ...st, limit: n }),
|
|
377
|
+
skip: (n: number) => make({ ...st, skip: n }),
|
|
378
|
+
after: (id: any) => make({ ...st, cursor: idObject(model, id) }),
|
|
379
|
+
select: (shape?: Record<string, any>) => make({ ...st, shape: shape ?? {} }),
|
|
380
|
+
first: () => op({ model, action: "findFirst", query: build() }),
|
|
381
|
+
count: (query?: Record<string, any>) => {
|
|
382
|
+
const q = whereOnly(query);
|
|
383
|
+
return Object.assign(op({ model, action: "count", query: q }), { __select: { $count: true, ...q } });
|
|
384
|
+
},
|
|
385
|
+
aggregate: (query: Record<string, any>) => {
|
|
386
|
+
const q = { ...(query ?? {}), ...whereOnly(query) };
|
|
387
|
+
if (!q.$where) delete q.$where;
|
|
388
|
+
return Object.assign(op({ model, action: "aggregate", query: q }), { __select: q });
|
|
389
|
+
},
|
|
390
|
+
|
|
391
|
+
insert: (data: any) => op({ model, action: "insert", data: options.prepareInsert ? options.prepareInsert(model, data) : data }),
|
|
392
|
+
update: (id: any, data: any) => op({ model, action: "update", id: idObject(model, id), data }),
|
|
393
|
+
// `updateMany(data)`; the deprecated `updateMany(query, data)` form is told apart by arity
|
|
394
|
+
updateMany: (a: any, b?: any) => (b === undefined
|
|
395
|
+
? op({ model, action: "updateMany", query: whereOnly(), data: a })
|
|
396
|
+
: op({ model, action: "updateMany", query: whereOnly(a), data: b })),
|
|
397
|
+
delete: (id: any) => op({ model, action: "delete", id: idObject(model, id) }),
|
|
398
|
+
deleteMany: () => {
|
|
399
|
+
if (st.where === undefined) throw new Error(`marcidb: ${model}.deleteMany() needs a where — use .where({}) to delete every row`);
|
|
400
|
+
return op({ model, action: "deleteMany", query: whereOnly() });
|
|
401
|
+
},
|
|
402
|
+
reindex: () => op({ model, action: "$reindex" }),
|
|
403
|
+
|
|
404
|
+
findMany: (query?: Record<string, any>) => op({ model, action: "findMany", query: build(query) }),
|
|
405
|
+
findFirst: (query?: Record<string, any>) => op({ model, action: "findFirst", query: build(query) }),
|
|
406
|
+
};
|
|
407
|
+
};
|
|
408
|
+
return make({});
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
return { op, collection };
|
|
412
|
+
}
|