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,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type layer: projects a `defineDb({ User: model({...}), … })` literal into marcidb's generic query
|
|
3
|
+
* types (./marci/query.ts — vendored from marcidb, see scripts/sync-marcidb.ts): each model becomes the
|
|
4
|
+
* `ModelTypes` bag marcidb's codegen would have emitted, and `db.<model>` is marcidb's own `Query<T>`.
|
|
5
|
+
* Everything here is type-only; the runtime lives in ./defineDb.ts.
|
|
6
|
+
*
|
|
7
|
+
* Naming: `S` = the schema record (model name → Model), `N` = a model name, `F` = a fields record.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Field, FieldMeta } from "./fields"
|
|
11
|
+
import type {
|
|
12
|
+
CompareNumValue, CompareRefListValue, CompareRefValue, CompareStrValue, CompareValue, CustomSearchValue,
|
|
13
|
+
FullTextSearch, JsonCondition, JsonPathWhere, Op, PrimitiveListUpdate, Query as MarciQuery, RefListUpdate,
|
|
14
|
+
RefListUpdateOrdered, RefUpdate, RefUpdateStruct, Rows, Sub, UpdateNumValue, VectorSearch, WhereValue,
|
|
15
|
+
} from "./marci/query"
|
|
16
|
+
|
|
17
|
+
// ───────────────────────────── schema shapes ─────────────────────────────
|
|
18
|
+
|
|
19
|
+
export type Fields = Record<string, Field<any, any>>
|
|
20
|
+
export type Model<F extends Fields = Fields> = { readonly fields: F, readonly __model: true }
|
|
21
|
+
export type Schema = Record<string, Model<any>>
|
|
22
|
+
|
|
23
|
+
export type FieldsOf<M> = M extends Model<infer F> ? F : never
|
|
24
|
+
type TsOf<Fld> = Fld extends Field<infer T, any> ? T : never
|
|
25
|
+
type StructFieldsOf<Fld> = TsOf<Fld> extends Fields ? TsOf<Fld> : never
|
|
26
|
+
type MetaOf<Fld> = Fld extends Field<any, infer M> ? M : never
|
|
27
|
+
type ModelName<S, Fld> = MetaOf<Fld>["model"] & keyof S & string
|
|
28
|
+
|
|
29
|
+
type Nullable<M extends FieldMeta, T> = M["optional"] extends true ? T | null : T
|
|
30
|
+
type Arr<M extends FieldMeta, T> = M["array"] extends true ? T[] : T
|
|
31
|
+
type IsNum<M extends FieldMeta> = M["scalar"] extends "int" | "float" | "date" ? true : false
|
|
32
|
+
|
|
33
|
+
// ───────────────────────────── key ─────────────────────────────
|
|
34
|
+
|
|
35
|
+
/** Explicit `.id()` fields, or a field literally named `id`. */
|
|
36
|
+
type IdKeys<F extends Fields> = { [K in keyof F]: MetaOf<F[K]>["isId"] extends true ? K : K extends "id" ? K : never }[keyof F]
|
|
37
|
+
type IdValue<S, Fld> = MetaOf<Fld>["kind"] extends "one" ? Id<S, ModelName<S, Fld>> : TsOf<Fld>
|
|
38
|
+
|
|
39
|
+
/** The id object of a model: `{ id: number }` implicitly, or the declared key fields. */
|
|
40
|
+
export type Id<S, N extends keyof S> =
|
|
41
|
+
[IdKeys<FieldsOf<S[N]>>] extends [never] ? { id: number } : { [K in IdKeys<FieldsOf<S[N]>>]: IdValue<S, FieldsOf<S[N]>[K]> }
|
|
42
|
+
/** What `update`/`delete` accept: the id object, or the bare value when the key is a single scalar. */
|
|
43
|
+
export type IdArg<S, N extends keyof S> = Id<S, N> | (Id<S, N> extends { id: infer V } ? V : never)
|
|
44
|
+
|
|
45
|
+
// ───────────────────────────── row ─────────────────────────────
|
|
46
|
+
|
|
47
|
+
type FieldRow<S, Fld, M extends FieldMeta = MetaOf<Fld>> =
|
|
48
|
+
M["kind"] extends "one" ? Nullable<M, Row<S, ModelName<S, Fld>>>
|
|
49
|
+
: M["kind"] extends "many" | "list" ? Row<S, ModelName<S, Fld>>[]
|
|
50
|
+
: M["kind"] extends "struct" ? Nullable<M, StructRow<S, StructFieldsOf<Fld>>>
|
|
51
|
+
: Nullable<M, Arr<M, TsOf<Fld>>>
|
|
52
|
+
|
|
53
|
+
/** Full row type of a model (relations included) — the `TModel` marcidb's `GetResult` selects from. */
|
|
54
|
+
export type Row<S, N extends keyof S> = Id<S, N> & { [K in Exclude<keyof FieldsOf<S[N]>, IdKeys<FieldsOf<S[N]>>>]: FieldRow<S, FieldsOf<S[N]>[K]> }
|
|
55
|
+
type StructRow<S, F extends Fields> = { [K in keyof F]: FieldRow<S, F[K]> }
|
|
56
|
+
|
|
57
|
+
type ScalarKeys<F extends Fields> = { [K in keyof F]: MetaOf<F[K]>["kind"] extends "scalar" | "enum" | "json" ? K : never }[keyof F]
|
|
58
|
+
type NumKeys<F extends Fields> = { [K in keyof F]: IsNum<MetaOf<F[K]>> extends true ? (MetaOf<F[K]>["array"] extends true ? never : K) : never }[keyof F]
|
|
59
|
+
/** What an empty select returns: id + every scalar field. */
|
|
60
|
+
export type Scalars<S, N extends keyof S> = Pick<Row<S, N>, (ScalarKeys<FieldsOf<S[N]>> | keyof Id<S, N>) & keyof Row<S, N>>
|
|
61
|
+
/** The same, as the `{ field: true }` select shape a query starts with. */
|
|
62
|
+
export type ScalarSelect<S, N extends keyof S> = { [K in (ScalarKeys<FieldsOf<S[N]>> | keyof Id<S, N>) & keyof Row<S, N>]: true }
|
|
63
|
+
|
|
64
|
+
// ───────────────────────────── insert ─────────────────────────────
|
|
65
|
+
|
|
66
|
+
type FieldInsert<S, Fld, M extends FieldMeta = MetaOf<Fld>> =
|
|
67
|
+
M["kind"] extends "one" ? Nullable<M, Id<S, ModelName<S, Fld>>>
|
|
68
|
+
: M["kind"] extends "many" | "list" ? Id<S, ModelName<S, Fld>>[]
|
|
69
|
+
: M["kind"] extends "struct" ? Nullable<M, StructInsert<S, StructFieldsOf<Fld>>>
|
|
70
|
+
: Nullable<M, Arr<M, TsOf<Fld>>>
|
|
71
|
+
|
|
72
|
+
type RequiredKeys<F extends Fields> = {
|
|
73
|
+
[K in keyof F]: K extends IdKeys<F> ? never
|
|
74
|
+
: MetaOf<F[K]>["optional"] extends true ? never
|
|
75
|
+
: MetaOf<F[K]>["hasDefault"] extends true ? never
|
|
76
|
+
: MetaOf<F[K]>["kind"] extends "many" | "list" ? never
|
|
77
|
+
: K
|
|
78
|
+
}[keyof F]
|
|
79
|
+
type Simplify<T> = { [K in keyof T]: T[K] } & {}
|
|
80
|
+
type InsertOf<S, F extends Fields> = Simplify<
|
|
81
|
+
{ [K in RequiredKeys<F>]: FieldInsert<S, F[K]> } &
|
|
82
|
+
{ [K in Exclude<keyof F, RequiredKeys<F>>]?: FieldInsert<S, F[K]> }
|
|
83
|
+
>
|
|
84
|
+
export type Insert<S, N extends keyof S> = InsertOf<S, FieldsOf<S[N]>>
|
|
85
|
+
type StructInsert<S, F extends Fields> = InsertOf<S, F>
|
|
86
|
+
|
|
87
|
+
// ───────────────────────────── update ─────────────────────────────
|
|
88
|
+
|
|
89
|
+
type FieldUpdate<S, Fld, M extends FieldMeta = MetaOf<Fld>> =
|
|
90
|
+
M["kind"] extends "one" ? Nullable<M, RefUpdate<Id<S, ModelName<S, Fld>>>> // `{ $connect: { id } }` / null — marcidb's update grammar (bare ids are for insert)
|
|
91
|
+
: M["kind"] extends "many" ? RefListUpdate<Id<S, ModelName<S, Fld>>>
|
|
92
|
+
: M["kind"] extends "list" ? RefListUpdateOrdered<Id<S, ModelName<S, Fld>>>
|
|
93
|
+
: M["kind"] extends "struct" ? RefUpdateStruct<StructInsert<S, StructFieldsOf<Fld>>, UpdateOf<S, StructFieldsOf<Fld>>> | null
|
|
94
|
+
: M["array"] extends true ? TsOf<Fld>[] | PrimitiveListUpdate<TsOf<Fld>>
|
|
95
|
+
: Nullable<M, TsOf<Fld> | (IsNum<M> extends true ? UpdateNumValue : never)>
|
|
96
|
+
|
|
97
|
+
type UpdateOf<S, F extends Fields> = { [K in Exclude<keyof F, IdKeys<F>>]?: FieldUpdate<S, F[K]> }
|
|
98
|
+
export type Update<S, N extends keyof S> = UpdateOf<S, FieldsOf<S[N]>>
|
|
99
|
+
|
|
100
|
+
// ───────────────────────────── select / where / order / query ─────────────────────────────
|
|
101
|
+
|
|
102
|
+
// A relation also accepts a sub-query built from the related model's collection (`posts: db.post.limit(5)`,
|
|
103
|
+
// `db.post.count()`), branded `Sub<"Post">` so a builder of the wrong model is a type error.
|
|
104
|
+
type FieldSelect<S, Fld, M extends FieldMeta = MetaOf<Fld>> =
|
|
105
|
+
M["kind"] extends "one" ? Select<S, ModelName<S, Fld>> | boolean | Sub<ModelName<S, Fld>>
|
|
106
|
+
: M["kind"] extends "many" | "list" ? QueryObject<S, ModelName<S, Fld>> | AggregateQuery<S, ModelName<S, Fld>> | boolean | Sub<ModelName<S, Fld>>
|
|
107
|
+
: M["kind"] extends "struct" ? StructSelect<S, StructFieldsOf<Fld>> | boolean
|
|
108
|
+
: boolean
|
|
109
|
+
|
|
110
|
+
export type Select<S, N extends keyof S> = { [K in keyof Row<S, N>]?: K extends keyof FieldsOf<S[N]> ? FieldSelect<S, FieldsOf<S[N]>[K]> : boolean }
|
|
111
|
+
type StructSelect<S, F extends Fields> = { [K in keyof F]?: FieldSelect<S, F[K]> }
|
|
112
|
+
|
|
113
|
+
type ScalarWhere<T, M extends FieldMeta> =
|
|
114
|
+
| Nullable<M, CompareValue<T>>
|
|
115
|
+
| (IsNum<M> extends true ? CompareNumValue<T> : M["scalar"] extends "string" ? CompareStrValue<T> : never)
|
|
116
|
+
|
|
117
|
+
type FieldWhere<S, Fld, M extends FieldMeta = MetaOf<Fld>> =
|
|
118
|
+
M["kind"] extends "one" ? CompareRefValue<Where<S, ModelName<S, Fld>> | null>
|
|
119
|
+
: M["kind"] extends "many" | "list" ? CompareRefListValue<Where<S, ModelName<S, Fld>>>
|
|
120
|
+
: M["kind"] extends "struct" ? CompareRefValue<WhereValue<StructWhereFields<S, StructFieldsOf<Fld>>> | null>
|
|
121
|
+
: M["kind"] extends "json" ? JsonPathWhere | JsonCondition
|
|
122
|
+
: M["array"] extends true ? CompareValue<TsOf<Fld>>
|
|
123
|
+
: ScalarWhere<TsOf<Fld>, M> | CustomSearchValue<FullTextSearch> | CustomSearchValue<VectorSearch>
|
|
124
|
+
|
|
125
|
+
type WhereFieldsOf<S, F extends Fields> = { [K in keyof F]?: FieldWhere<S, F[K]> }
|
|
126
|
+
type IdWhere<S, N extends keyof S> = { [K in keyof Id<S, N> as K extends keyof FieldsOf<S[N]> ? never : K]?: CompareValue<Id<S, N>[K]> | CompareNumValue<number> }
|
|
127
|
+
export type Where<S, N extends keyof S> = WhereValue<WhereFieldsOf<S, FieldsOf<S[N]>> & IdWhere<S, N>>
|
|
128
|
+
type StructWhereFields<S, F extends Fields> = WhereFieldsOf<S, F>
|
|
129
|
+
|
|
130
|
+
export type Order<S, N extends keyof S> = { [K in (ScalarKeys<FieldsOf<S[N]>> | keyof Id<S, N>) & keyof Row<S, N>]?: "asc" | "desc" }
|
|
131
|
+
|
|
132
|
+
/** The query object — marcidb's wire form: a select shape plus the `$`-clauses. */
|
|
133
|
+
export type QueryObject<S, N extends keyof S> = Select<S, N> & {
|
|
134
|
+
$where?: Where<S, N>
|
|
135
|
+
$order?: Order<S, N>
|
|
136
|
+
$limit?: number
|
|
137
|
+
$skip?: number
|
|
138
|
+
$cursor?: Id<S, N>
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export type AggregateQuery<S, N extends keyof S> = {
|
|
142
|
+
$where?: Where<S, N>
|
|
143
|
+
$count?: true
|
|
144
|
+
$sum?: NumKeys<FieldsOf<S[N]>> & string
|
|
145
|
+
$avg?: NumKeys<FieldsOf<S[N]>> & string
|
|
146
|
+
$min?: (ScalarKeys<FieldsOf<S[N]>> | keyof Id<S, N>) & string
|
|
147
|
+
$max?: (ScalarKeys<FieldsOf<S[N]>> | keyof Id<S, N>) & string
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ───────────────────────────── query / db ─────────────────────────────
|
|
151
|
+
|
|
152
|
+
/** The per-model type bag marcidb's generic `Query<T>` is parametrised by (what its codegen emits as `UserTypes`). */
|
|
153
|
+
export type Types<S, N extends keyof S> = {
|
|
154
|
+
name: N & string
|
|
155
|
+
model: Row<S, N>
|
|
156
|
+
id: Id<S, N>
|
|
157
|
+
scalars: ScalarSelect<S, N>
|
|
158
|
+
select: Select<S, N>
|
|
159
|
+
query: QueryObject<S, N>
|
|
160
|
+
where: Where<S, N>
|
|
161
|
+
order: Order<S, N>
|
|
162
|
+
insert: Insert<S, N>
|
|
163
|
+
update: Update<S, N>
|
|
164
|
+
aggregate: AggregateQuery<S, N>
|
|
165
|
+
reindex: false
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* `db.<model>` — marcidb's lazy, immutable query over one model: clauses (`where/order/limit/skip/after`)
|
|
170
|
+
* return a new query, `select(shape)` sets the projection, `await` runs it, `first/count/aggregate` terminate,
|
|
171
|
+
* `updateMany(data)`/`deleteMany()` write through the chain's `where`; a query placed in another query's
|
|
172
|
+
* select is a sub-query. `Sel` is the projection: id + scalars until `select(...)` sets a shape.
|
|
173
|
+
*/
|
|
174
|
+
export type Query<S, N extends keyof S, Sel = ScalarSelect<S, N>> = MarciQuery<Types<S, N>, Sel>
|
|
175
|
+
/** The rows a query with projection `Q` resolves to (no field keys = id + scalars; sub-queries resolved). */
|
|
176
|
+
export type Result<S, N extends keyof S, Q> = Rows<Types<S, N>, Q>
|
|
177
|
+
/** `db.<model>` — the root query of a model. */
|
|
178
|
+
export type Collection<S, N extends keyof S> = Query<S, N>
|
|
179
|
+
|
|
180
|
+
export type Db<S extends Schema> = {
|
|
181
|
+
[N in keyof S & string as Uncapitalize<N>]: Query<S, N>
|
|
182
|
+
} & {
|
|
183
|
+
/** Atomic batch: all ops commit or none. `ref("0.id")` feeds a generated id into a later op. */
|
|
184
|
+
$transaction<P extends readonly Op<any>[]>(ops: [...P]): Promise<{ [K in keyof P]: P[K] extends Op<infer T> ? T : never }>
|
|
185
|
+
/** The `.marci` schema text this db syncs with. */
|
|
186
|
+
readonly $schema: string
|
|
187
|
+
/** @internal */ readonly $models: Record<string, Model>
|
|
188
|
+
/** @internal which models the auth runtime binds to (`Session` always exists; `User` when declared). */
|
|
189
|
+
readonly $auth: AuthBinding
|
|
190
|
+
}
|
|
191
|
+
/** @internal */
|
|
192
|
+
export type AuthBinding = { user: string | null, session: string }
|
|
193
|
+
|
|
194
|
+
// ───────────────────────────── relation-name validation ─────────────────────────────
|
|
195
|
+
|
|
196
|
+
type RelKinds = "one" | "many" | "list"
|
|
197
|
+
type CheckField<S, Fld> =
|
|
198
|
+
MetaOf<Fld>["kind"] extends RelKinds
|
|
199
|
+
? MetaOf<Fld>["model"] extends keyof S ? Fld : `Unknown model "${MetaOf<Fld>["model"]}" — not a key of defineDb({...})`
|
|
200
|
+
: Fld
|
|
201
|
+
/** Intersected with `S` in `defineDb`'s parameter: an unknown relation target fails to type-check on that field. */
|
|
202
|
+
export type ValidateRefs<S extends Schema> = { [N in keyof S]: { readonly fields: { [K in keyof FieldsOf<S[N]>]: CheckField<S, FieldsOf<S[N]>[K]> } } }
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `throw new ApiError(404, "Not found")` inside an endpoint → the client's promise rejects with the
|
|
3
|
+
* same status + message. Any other throw is a 500 (message hidden, logged to the server console).
|
|
4
|
+
*/
|
|
5
|
+
export class ApiError extends Error {
|
|
6
|
+
readonly status: number
|
|
7
|
+
constructor(status: number, message = "") {
|
|
8
|
+
super(message)
|
|
9
|
+
this.name = "ApiError"
|
|
10
|
+
this.status = status
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sdk/server` — the HOST side of the app backend, imported normally by the runner
|
|
3
|
+
* (packages/lecodes-runner), the backend and tests. Nothing here is bundled into project code; the
|
|
4
|
+
* bundle carries its own SDK copy and meets this module only through the `globalThis.__lecodes*`
|
|
5
|
+
* seams (see ./runtime.ts).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
loadServerModules, createInvoker, createJoinGuard, setChannelPublisher, setRequestProvider,
|
|
10
|
+
} from "./runtime"
|
|
11
|
+
export type { LoadedServer, InvokeResult, InvokerOptions, ServerRegistration } from "./runtime"
|
|
12
|
+
export type { RequestContext } from "./context"
|
|
13
|
+
export { setDbTransport } from "./db/defineDb"
|
|
14
|
+
export { marciHttpTransport, marciRequest, encodeMarciId, MarciHttpError } from "./db/httpTransport"
|
|
15
|
+
export type { MarciHttpOptions } from "./db/httpTransport"
|
|
16
|
+
export type { MarciOp, MarciTransport } from "./db/marci/query"
|
|
17
|
+
export type { ServerManifest, ServerExport } from "../compile/serverSplit"
|
|
18
|
+
export { validateArgs, ValidationError } from "./validate"
|
|
19
|
+
export type { ParamSchema, Schema as ParamSchemaNode } from "./validate"
|
|
20
|
+
export { createAuthHost, signNonce, verifyNonce, sha256 } from "./auth/host"
|
|
21
|
+
export type { AuthHost, AuthHostConfig, AuthMail } from "./auth/host"
|
|
22
|
+
export type { AuthUser, AuthState, AuthOps, OAuthProfile, OAuthCredential } from "./auth/types"
|
|
23
|
+
export type { AuthProvider, AppConfig } from "./auth/appConfig"
|
|
24
|
+
|
|
25
|
+
import type { LoadedServer } from "./runtime"
|
|
26
|
+
import type { Field, FieldDef } from "./db/fields"
|
|
27
|
+
import type { Model } from "./db/types"
|
|
28
|
+
|
|
29
|
+
/** JSON view of one field for the platform (Data tab, docs): `FieldDef` with nested struct fields flattened
|
|
30
|
+
* to defs and functions/undefineds dropped. */
|
|
31
|
+
export type FieldDescription = Omit<FieldDef, "fields" | "default"> & { fields?: Record<string, FieldDescription>, default?: unknown }
|
|
32
|
+
|
|
33
|
+
/** What the runner reports to the platform after loading a bundle: schema text + models + the
|
|
34
|
+
* endpoint/channel lists (with the manifest's param schemas). */
|
|
35
|
+
export type ServerDescription = {
|
|
36
|
+
/** `.marci` schema text ("" when the project defines no db). */
|
|
37
|
+
marci: string
|
|
38
|
+
models: Record<string, { fields: Record<string, FieldDescription> }>
|
|
39
|
+
endpoints: { id: string, params: unknown[] | null }[]
|
|
40
|
+
channels: string[]
|
|
41
|
+
/** Which models the auth runtime binds to (`null` = the project has no db) and the app.json providers. */
|
|
42
|
+
auth: { user: string | null, session: string | null, providers: string[] }
|
|
43
|
+
/** `app.json` name (when the compile embedded one). */
|
|
44
|
+
appName?: string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const describeField = (f: Field<any, any>): FieldDescription => {
|
|
48
|
+
const def = f.def
|
|
49
|
+
const out: FieldDescription = { ...def, fields: undefined, default: undefined }
|
|
50
|
+
delete out.fields
|
|
51
|
+
delete out.default
|
|
52
|
+
if (def.hasDefault) out.default = def.default === undefined ? null : def.default
|
|
53
|
+
if (def.fields) out.fields = Object.fromEntries(Object.entries(def.fields).map(([k, v]) => [k, describeField(v)]))
|
|
54
|
+
return JSON.parse(JSON.stringify(out))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const describeServer = (server: LoadedServer): ServerDescription => {
|
|
58
|
+
const db = server.dbs[0]
|
|
59
|
+
const models: ServerDescription["models"] = {}
|
|
60
|
+
if (db) {
|
|
61
|
+
for (const [name, m] of Object.entries(db.$models as Record<string, Model>)) {
|
|
62
|
+
models[name] = { fields: Object.fromEntries(Object.entries(m.fields as Record<string, Field<any, any>>).map(([k, f]) => [k, describeField(f)])) }
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const params = (server.manifest.params ?? {}) as Record<string, unknown[]>
|
|
66
|
+
return {
|
|
67
|
+
marci: db?.$schema ?? "",
|
|
68
|
+
models,
|
|
69
|
+
endpoints: [...server.endpoints.keys()].map(id => ({ id, params: params[id] ?? null })),
|
|
70
|
+
channels: [...server.channels.keys()],
|
|
71
|
+
auth: { user: db?.$auth.user ?? null, session: db?.$auth.session ?? null, providers: server.manifest.app?.auth?.providers ?? [] },
|
|
72
|
+
appName: server.manifest.app?.name,
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The SERVER inject module — the globals available in `*.server.ts` files (docs/backend-plan.md §4).
|
|
3
|
+
* chisel injects these into the server bundle exactly like ./inject.ts does for the client bundle;
|
|
4
|
+
* nothing here may reach the client bundle. Keep this the ONLY file that exports server globals.
|
|
5
|
+
*
|
|
6
|
+
* Globals: t, model, defineDb, ref (schema + queries), ApiError, channel, request, auth.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export { t, model, defineDb, ref } from "./db"
|
|
10
|
+
export { ApiError } from "./errors"
|
|
11
|
+
export { channel } from "./channel"
|
|
12
|
+
export { request } from "./context"
|
|
13
|
+
export { auth } from "./auth/global"
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host side of a compiled server bundle — used by the runner (packages/lecodes-runner) and by tests.
|
|
3
|
+
* Imported normally (Node/Bun), NOT through chisel: the bundle carries its own SDK copy and talks
|
|
4
|
+
* to this module only through globals (`__lecodesServer`, `__lecodesRequest`, `__lecodesPublish`).
|
|
5
|
+
*
|
|
6
|
+
* evaluate the bundle (it sets globalThis.__lecodesServer)
|
|
7
|
+
* const server = loadServerModules() // classify exports, name channels
|
|
8
|
+
* const invoke = createInvoker(server) // validate → run in request context → envelope
|
|
9
|
+
* await invoke("notes.server.ts#listNotes", [20], { headers, ip })
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { AsyncLocalStorage } from "node:async_hooks"
|
|
13
|
+
import type { ServerManifest } from "../compile/serverSplit"
|
|
14
|
+
import type { AuthHost } from "./auth/host"
|
|
15
|
+
import type { AuthState } from "./auth/types"
|
|
16
|
+
import { setChannelPublisher, type Channel } from "./channel"
|
|
17
|
+
import { setRequestProvider, type RequestContext } from "./context"
|
|
18
|
+
import type { Db } from "./db/types"
|
|
19
|
+
import { takeRegisteredDbs } from "./db/defineDb"
|
|
20
|
+
import { validateArgs, ValidationError, type ParamSchema } from "./validate"
|
|
21
|
+
|
|
22
|
+
export type ServerRegistration = { modules: Record<string, Record<string, unknown>>, manifest: ServerManifest }
|
|
23
|
+
|
|
24
|
+
export type LoadedServer = {
|
|
25
|
+
endpoints: Map<string, (...args: any[]) => unknown>
|
|
26
|
+
channels: Map<string, Channel<any>>
|
|
27
|
+
/** Every `defineDb` handle — exported or not (the bundle registers them on globalThis; normally exactly one). */
|
|
28
|
+
dbs: Db<any>[]
|
|
29
|
+
manifest: ServerManifest
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const isDb = (v: unknown): v is Db<any> => typeof v === "object" && v !== null && typeof (v as Db<any>).$schema === "string"
|
|
33
|
+
const isChannel = (v: unknown): v is Channel<any> => typeof v === "object" && v !== null && (v as Channel<any>).__channel === true
|
|
34
|
+
|
|
35
|
+
/** Read the registration a server bundle left on `globalThis.__lecodesServer` (or a given one). */
|
|
36
|
+
export const loadServerModules = (
|
|
37
|
+
reg: ServerRegistration | undefined = (globalThis as any).__lecodesServer,
|
|
38
|
+
): LoadedServer => {
|
|
39
|
+
if (!reg) throw new Error("loadServerModules: no server bundle registered (globalThis.__lecodesServer)")
|
|
40
|
+
const out: LoadedServer = { endpoints: new Map(), channels: new Map(), dbs: [], manifest: reg.manifest }
|
|
41
|
+
for (const [mod, exports] of Object.entries(reg.modules)) {
|
|
42
|
+
for (const [name, value] of Object.entries(exports)) {
|
|
43
|
+
const id = `${mod}#${name}`
|
|
44
|
+
if (typeof value === "function") out.endpoints.set(id, value as (...args: any[]) => unknown)
|
|
45
|
+
else if (isChannel(value)) { value.__id = id; out.channels.set(id, value) }
|
|
46
|
+
else if (isDb(value)) out.dbs.push(value)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Non-exported handles (`const db = defineDb(...)`): the bundle's SDK copy registered them on globalThis.
|
|
50
|
+
for (const db of takeRegisteredDbs()) if (!out.dbs.includes(db)) out.dbs.push(db)
|
|
51
|
+
return out
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The wire envelope. `session` = a token the client must store from now on (new guest session / rotation). */
|
|
55
|
+
export type InvokeResult =
|
|
56
|
+
| { ok: true, result: unknown, session?: string }
|
|
57
|
+
| { ok: false, status: number, message: string, session?: string }
|
|
58
|
+
|
|
59
|
+
export type InvokerOptions = {
|
|
60
|
+
/** Validate args against the manifest's param schemas (default true; endpoints without a schema pass). */
|
|
61
|
+
validate?: boolean
|
|
62
|
+
/** Where unexpected (non-ApiError) failures are reported. Default: console.error. */
|
|
63
|
+
onError?: (id: string, error: unknown) => void
|
|
64
|
+
/** The auth host (./auth/host.ts): resolves the bearer token into `ctx.auth` before each call. Absent = no sessions. */
|
|
65
|
+
auth?: AuthHost | null
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// The bundle's ApiError is a different class object than ours (its own SDK copy) — duck-type it.
|
|
69
|
+
const apiStatus = (e: unknown): number | null =>
|
|
70
|
+
e && typeof e === "object" && (e as { name?: string }).name === "ApiError" && typeof (e as { status?: unknown }).status === "number"
|
|
71
|
+
? (e as { status: number }).status : null
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Build the invoker: `invoke(id, args, ctx)` runs endpoint `id` inside an AsyncLocalStorage-scoped
|
|
75
|
+
* request context (what the `request`/`auth` globals read) and returns the wire envelope.
|
|
76
|
+
*/
|
|
77
|
+
// One request store for endpoints AND channel guards (a second store would steal the provider).
|
|
78
|
+
const als = new AsyncLocalStorage<RequestContext>()
|
|
79
|
+
const installProvider = () => setRequestProvider(() => als.getStore())
|
|
80
|
+
|
|
81
|
+
// A token minted/rotated during the call rides on the envelope (the client transport stores it).
|
|
82
|
+
const withSession = (r: InvokeResult, rc: RequestContext): InvokeResult => {
|
|
83
|
+
const issued = (rc.auth as AuthState | undefined)?.issuedToken
|
|
84
|
+
return issued ? { ...r, session: issued } : r
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const createInvoker = (server: LoadedServer, opts: InvokerOptions = {}) => {
|
|
88
|
+
installProvider()
|
|
89
|
+
const validate = opts.validate ?? true
|
|
90
|
+
const onError = opts.onError ?? ((id, e) => console.error(`[server] ${id}:`, e))
|
|
91
|
+
const params = server.manifest.params as Record<string, ParamSchema[]> | undefined
|
|
92
|
+
const auth = opts.auth ?? null
|
|
93
|
+
|
|
94
|
+
return async (id: string, args: unknown[], ctx: Omit<RequestContext, "id"> = { headers: {} }): Promise<InvokeResult> => {
|
|
95
|
+
const fn = server.endpoints.get(id)
|
|
96
|
+
if (!fn) return { ok: false, status: 404, message: `Unknown endpoint ${id}` }
|
|
97
|
+
if (!Array.isArray(args)) return { ok: false, status: 400, message: "args must be an array" }
|
|
98
|
+
const rc: RequestContext = { ...ctx, id }
|
|
99
|
+
try {
|
|
100
|
+
if (validate && params?.[id]) validateArgs(params[id], args)
|
|
101
|
+
if (auth) await auth.resolve(rc)
|
|
102
|
+
const result = await als.run(rc, () => fn(...args))
|
|
103
|
+
return withSession({ ok: true, result: result === undefined ? null : result }, rc)
|
|
104
|
+
} catch (e) {
|
|
105
|
+
const status = apiStatus(e)
|
|
106
|
+
if (status !== null) return withSession({ ok: false, status, message: (e as Error).message }, rc)
|
|
107
|
+
if (e instanceof ValidationError) return { ok: false, status: 400, message: e.message }
|
|
108
|
+
onError(id, e)
|
|
109
|
+
return withSession({ ok: false, status: 500, message: "Internal error" }, rc)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Run a channel's `onJoin` guard for a subscription (same request scoping as endpoints). */
|
|
115
|
+
export const createJoinGuard = (server: LoadedServer, opts: Pick<InvokerOptions, "auth"> = {}) => {
|
|
116
|
+
installProvider()
|
|
117
|
+
const auth = opts.auth ?? null
|
|
118
|
+
return async (channelId: string, topic: string, ctx: Omit<RequestContext, "id"> = { headers: {} }): Promise<InvokeResult> => {
|
|
119
|
+
const ch = server.channels.get(channelId)
|
|
120
|
+
if (!ch) return { ok: false, status: 404, message: `Unknown channel ${channelId}` }
|
|
121
|
+
const rc: RequestContext = { ...ctx, id: channelId }
|
|
122
|
+
try {
|
|
123
|
+
if (auth) await auth.resolve(rc)
|
|
124
|
+
await als.run(rc, () => ch.options.onJoin?.(topic))
|
|
125
|
+
return withSession({ ok: true, result: null }, rc)
|
|
126
|
+
} catch (e) {
|
|
127
|
+
const status = apiStatus(e)
|
|
128
|
+
return withSession({ ok: false, status: status ?? 500, message: status !== null ? (e as Error).message : "Internal error" }, rc)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export { setChannelPublisher, setRequestProvider }
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime validation of endpoint arguments against the compact schemas the compiler derives from the
|
|
3
|
+
* TypeScript parameter types (src/compile/serverTypes.ts). Dependency-free; the schema is JSON so it
|
|
4
|
+
* travels inside the server bundle's manifest.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type Schema =
|
|
8
|
+
| { t: "any" }
|
|
9
|
+
| { t: "string" } | { t: "number" } | { t: "boolean" } | { t: "null" } | { t: "undefined" }
|
|
10
|
+
| { t: "lit", v: string | number | boolean }
|
|
11
|
+
| { t: "array", of: Schema }
|
|
12
|
+
| { t: "tuple", items: Schema[], rest?: Schema }
|
|
13
|
+
| { t: "object", props: Record<string, Schema>, optional?: string[], index?: Schema }
|
|
14
|
+
| { t: "union", of: Schema[] }
|
|
15
|
+
|
|
16
|
+
/** One endpoint parameter: its schema and whether it may be omitted (`?`, default value, rest). */
|
|
17
|
+
export type ParamSchema = { name: string, schema: Schema, optional?: boolean, rest?: boolean }
|
|
18
|
+
|
|
19
|
+
export class ValidationError extends Error {
|
|
20
|
+
readonly status = 400
|
|
21
|
+
constructor(message: string) { super(message); this.name = "ValidationError" }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const typeName = (v: unknown) => v === null ? "null" : Array.isArray(v) ? "array" : typeof v
|
|
25
|
+
|
|
26
|
+
/** Returns the first problem as a message, or null when `value` matches. */
|
|
27
|
+
export const check = (schema: Schema, value: unknown, path: string): string | null => {
|
|
28
|
+
switch (schema.t) {
|
|
29
|
+
case "any": return null
|
|
30
|
+
case "string": case "number": case "boolean":
|
|
31
|
+
return typeof value === schema.t ? null : `${path}: expected ${schema.t}, got ${typeName(value)}`
|
|
32
|
+
case "null": return value === null ? null : `${path}: expected null, got ${typeName(value)}`
|
|
33
|
+
case "undefined": return value === undefined ? null : `${path}: expected undefined, got ${typeName(value)}`
|
|
34
|
+
case "lit": return value === schema.v ? null : `${path}: expected ${JSON.stringify(schema.v)}, got ${JSON.stringify(value)}`
|
|
35
|
+
case "array":
|
|
36
|
+
if (!Array.isArray(value)) return `${path}: expected array, got ${typeName(value)}`
|
|
37
|
+
for (let i = 0; i < value.length; i++) { const e = check(schema.of, value[i], `${path}[${i}]`); if (e) return e }
|
|
38
|
+
return null
|
|
39
|
+
case "tuple": {
|
|
40
|
+
if (!Array.isArray(value)) return `${path}: expected tuple, got ${typeName(value)}`
|
|
41
|
+
if (value.length < schema.items.length || (!schema.rest && value.length > schema.items.length))
|
|
42
|
+
return `${path}: expected ${schema.items.length} items, got ${value.length}`
|
|
43
|
+
for (let i = 0; i < value.length; i++) {
|
|
44
|
+
const s = i < schema.items.length ? schema.items[i] : schema.rest!
|
|
45
|
+
const e = check(s, value[i], `${path}[${i}]`); if (e) return e
|
|
46
|
+
}
|
|
47
|
+
return null
|
|
48
|
+
}
|
|
49
|
+
case "object": {
|
|
50
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return `${path}: expected object, got ${typeName(value)}`
|
|
51
|
+
const obj = value as Record<string, unknown>
|
|
52
|
+
const optional = new Set(schema.optional ?? [])
|
|
53
|
+
for (const [k, s] of Object.entries(schema.props)) {
|
|
54
|
+
if (!(k in obj) || obj[k] === undefined) { if (optional.has(k)) continue; return `${path}.${k}: required` }
|
|
55
|
+
const e = check(s, obj[k], `${path}.${k}`); if (e) return e
|
|
56
|
+
}
|
|
57
|
+
for (const k of Object.keys(obj)) {
|
|
58
|
+
if (k in schema.props) continue
|
|
59
|
+
if (schema.index) { const e = check(schema.index, obj[k], `${path}.${k}`); if (e) return e }
|
|
60
|
+
// unknown keys without an index signature are ignored (structural typing)
|
|
61
|
+
}
|
|
62
|
+
return null
|
|
63
|
+
}
|
|
64
|
+
case "union": {
|
|
65
|
+
const errors: string[] = []
|
|
66
|
+
for (const s of schema.of) { const e = check(s, value, path); if (!e) return null; errors.push(e) }
|
|
67
|
+
return `${path}: no union member matched (${errors.join("; ")})`
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Validate a positional argument list. Extra arguments are ignored (JS call semantics). */
|
|
73
|
+
export const validateArgs = (params: ParamSchema[], args: unknown[]): void => {
|
|
74
|
+
for (let i = 0; i < params.length; i++) {
|
|
75
|
+
const p = params[i]
|
|
76
|
+
if (p.rest) {
|
|
77
|
+
const rest = args.slice(i)
|
|
78
|
+
const e = check(p.schema, rest, p.name)
|
|
79
|
+
if (e) throw new ValidationError(e)
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
const v = args[i]
|
|
83
|
+
if (v === undefined) { if (p.optional) continue; throw new ValidationError(`${p.name}: required`) }
|
|
84
|
+
const e = check(p.schema, v, p.name)
|
|
85
|
+
if (e) throw new ValidationError(e)
|
|
86
|
+
}
|
|
87
|
+
}
|