diff --git a/CHANGELOG.md b/CHANGELOG.md index 35884668..7db36988 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## 1.4.0 + +- Add ability to set independent middleware per service, but on the server and the client +- Added tutorial video links +- Add short-circuit evaluation to `GetService` and `GetController` functions for better performance when the service/controller exists +- Change Comm module to use service name as namespace instead of nested `__comm__` folder +- Documentation improvements +- Breaking changes to middleware assignment (now within one `Middleware` table instead of two for inbound/outbound) + ## 1.3.0 - Add support for RemoteProperties via Comm library diff --git a/docs/middleware.md b/docs/middleware.md index 24edd670..e4788308 100644 --- a/docs/middleware.md +++ b/docs/middleware.md @@ -8,13 +8,11 @@ Knit's networking layer uses the [Comm](https://sleitnick.github.io/RbxUtil/api/ Middleware can be used to both transform inbound/outbound arguments, and also decide to drop requests/responses. This is useful for many use-cases, such as automatically serializing/deserializing complex data types over the network, or sanitizing incoming data. -Middleware can be added on both the server and client, and affects functions and signals. - -As of right now, middleware is only at a global level across Knit. In the future, it will be possible to have custom middleware per service and controller. +Middleware can be added on both the server and client, and affects functions and signals. Middleware can either be added at the Knit global level, or per service. ## Usage -Middleware is added when Knit is started: `Knit.Start({InboundMiddleware: {...}, OutboundMiddleware: {...}})`. Each "middleware" item in the tables is a function. On the client, this function takes an array table containing all the arguments passed along. On the server, it is nearly the same, except the first argument before the arguments table is the player. +Middleware is added when Knit is started: `Knit.Start({Middleware = {Inbound = {...}, Outbound = {...}}})` _or_ on each service. Each "middleware" item in the tables is a function. On the client, this function takes an array table containing all the arguments passed along. On the server, it is nearly the same, except the first argument before the arguments table is the player. Each function should return a boolean, indicating whether or not to continue to the request/response. If `false`, an optional variadic list of items can be returned, which will be returned back to the caller (essentially a short-circuit, but still returning data). @@ -33,7 +31,7 @@ local function Logger(args: {any}) end Knit.Start({ - InboundMiddleware = {Logger} + Middleware = {Inbound = {Logger}} }) ``` @@ -45,7 +43,7 @@ local function Logger(player: Player, args: {any}) end Knit.Start({ - InboundMiddleware = {Logger} + Middleware = {Inbound = {Logger}} }) ``` @@ -62,7 +60,36 @@ local function DoubleNumbers(args) return true end -Knit.Start({InboundMiddleware = {DoubleNumbers}}) +Knit.Start({Middleware = {Inbound = {DoubleNumbers}}}) +``` + +#### Per-Service Example + +Middleware can also be targeted per-service, which will override the global level middleware for the given service. +```lua +-- Server-side: +local MyService = Knit.CreateService { + Name = "MyService", + Client = {}, + Middleware = { + Inbound = {Logger}, + Outbound = {}, + }, +} +``` + +On the client, things look a little different. Middleware is still per-service, not controller, so the definitions of per-service middleware need to go within `Knit.Start()` on the client: +```lua +-- Client-side: +Knit.Start({ + PerServiceMiddleware = { + -- Mapped by name of the service + MyService = { + Inbound = {Logger}, + Outbound = {}, + }, + }, +}) ``` #### Serialization @@ -113,7 +140,9 @@ local function OutboundClass(args) end Knit.Start({ - InboundMiddleware = {InboundClass}, - OutboundMiddleware = {OutboundClass} + Middleware = { + Inbound = {InboundClass}, + Outbound = {OutboundClass}, + }, }) ``` diff --git a/roblox.toml b/roblox.toml index bf83efe2..6275d970 100644 --- a/roblox.toml +++ b/roblox.toml @@ -1,4 +1,4 @@ -# This file was @generated by generate-roblox-std at 2021-10-25 00:34:59.275362 -04:00 +# This file was @generated by generate-roblox-std at 2021-12-25 08:37:05.585855100 -05:00 [selene] base = "lua51" name = "roblox" @@ -1126,7 +1126,7 @@ type = "any" method = true [[selene.structs.DataModel.GetService.args]] -type = ["ABTestService", "AdService", "AnalyticsService", "AppUpdateService", "AssetCounterService", "AssetDeliveryProxy", "AssetImportService", "AssetManagerService", "AssetService", "AvatarEditorService", "AvatarImportService", "BadgeService", "CoreGui", "StarterGui", "BreakpointManager", "BrowserService", "BulkImportService", "CSGCacheService", "CacheableContentProvider", "MeshContentProvider", "SolidModelContentProvider", "CalloutService", "ChangeHistoryService", "Chat", "ClusterPacketCache", "CollectionService", "CommandService", "ContentProvider", "ContextActionService", "ControllerService", "CookiesService", "CorePackages", "CoreScriptSyncService", "DataStoreService", "Debris", "DebuggerConnectionManager", "DebuggerManager", "DebuggerUIService", "DraftsService", "DraggerService", "EventIngestService", "FlagStandService", "FlyweightService", "CSGDictionaryService", "NonReplicatedCSGDictionaryService", "FriendService", "GamePassService", "GamepadService", "Geometry", "GoogleAnalyticsConfiguration", "GroupService", "GuiService", "GuidRegistryService", "HapticService", "HeightmapImporterService", "Hopper", "HttpRbxApiService", "HttpService", "ILegacyStudioBridge", "LegacyStudioBridge", "IXPService", "IncrementalPatchBuilder", "InsertService", "InternalContainer", "JointsService", "KeyboardService", "KeyframeSequenceProvider", "LanguageService", "Lighting", "LocalStorageService", "AppStorageService", "UserStorageService", "LocalizationService", "LogService", "LoginService", "LuaWebService", "MarketplaceService", "MaterialService", "MemStorageService", "MemoryStoreService", "MessageBusService", "MessagingService", "MouseService", "NetworkClient", "NetworkServer", "NetworkSettings", "NotificationService", "Workspace", "PackageService", "PathfindingService", "PermissionsService", "PhysicsService", "PlayerEmulatorService", "Players", "PluginDebugService", "PluginGuiService", "PluginPolicyService", "PointsService", "PolicyService", "ProcessInstancePhysicsService", "ProximityPromptService", "PublishService", "RbxAnalyticsService", "RemoteDebuggerServer", "RenderSettings", "ReplicatedFirst", "ReplicatedScriptService", "ReplicatedStorage", "RobloxPluginGuiService", "RobloxReplicatedStorage", "RunService", "RuntimeScriptService", "ScriptChangeService", "ScriptContext", "ScriptService", "Selection", "ServerScriptService", "ServerStorage", "SessionService", "SocialService", "SoundService", "SpawnerService", "StarterPack", "StarterPlayer", "Stats", "StopWatchReporter", "Studio", "StudioAssetService", "StudioData", "StudioDeviceEmulatorService", "StudioScriptDebugEventListener", "StudioService", "TaskScheduler", "Teams", "TeleportService", "TemporaryCageMeshProvider", "TestService", "TextChatService", "TextService", "ThirdPartyUserService", "TimerService", "ToastNotificationService", "ToolboxService", "TouchInputService", "TracerService", "TweenService", "UGCValidationService", "UnvalidatedAssetService", "UserInputService", "UserService", "VRService", "VersionControlService", "VirtualInputManager", "VirtualUser", "Visit", "VoiceChatInternal", "VoiceChatService"] +type = ["ABTestService", "AdService", "AnalyticsService", "AnimationClipProvider", "AppUpdateService", "AssetCounterService", "AssetDeliveryProxy", "AssetImportService", "AssetManagerService", "AssetService", "AvatarEditorService", "AvatarImportService", "BadgeService", "CoreGui", "StarterGui", "BreakpointManager", "BrowserService", "BulkImportService", "CSGCacheService", "CacheableContentProvider", "HSRDataContentProvider", "MeshContentProvider", "SolidModelContentProvider", "CalloutService", "ChangeHistoryService", "Chat", "ClusterPacketCache", "CollectionService", "CommandService", "ConfigureServerService", "ContentProvider", "ContextActionService", "ControllerService", "CookiesService", "CorePackages", "CoreScriptSyncService", "DataModelPatchService", "DataStoreService", "Debris", "DebuggerConnectionManager", "DebuggerManager", "DebuggerUIService", "DraftsService", "DraggerService", "EventIngestService", "FlagStandService", "FlyweightService", "CSGDictionaryService", "NonReplicatedCSGDictionaryService", "FriendService", "GamePassService", "GamepadService", "Geometry", "GoogleAnalyticsConfiguration", "GroupService", "GuiService", "GuidRegistryService", "HapticService", "HeightmapImporterService", "Hopper", "HttpRbxApiService", "HttpService", "ILegacyStudioBridge", "LegacyStudioBridge", "IXPService", "IncrementalPatchBuilder", "InsertService", "InternalContainer", "JointsService", "KeyboardService", "KeyframeSequenceProvider", "LanguageService", "Lighting", "LocalStorageService", "AppStorageService", "UserStorageService", "LocalizationService", "LodDataService", "LogService", "LoginService", "LuaWebService", "LuauScriptAnalyzerService", "MarketplaceService", "MaterialService", "MemStorageService", "MemoryStoreService", "MessageBusService", "MessagingService", "MouseService", "NetworkClient", "NetworkServer", "NetworkSettings", "NotificationService", "Workspace", "PackageService", "PackageUIService", "PathfindingService", "PermissionsService", "PhysicsService", "PlayerEmulatorService", "Players", "PluginDebugService", "PluginGuiService", "PluginPolicyService", "PointsService", "PolicyService", "ProcessInstancePhysicsService", "ProximityPromptService", "PublishService", "RbxAnalyticsService", "RemoteDebuggerServer", "RenderSettings", "ReplicatedFirst", "ReplicatedScriptService", "ReplicatedStorage", "RobloxPluginGuiService", "RobloxReplicatedStorage", "RunService", "RuntimeScriptService", "ScriptChangeService", "ScriptContext", "ScriptRegistrationService", "ScriptService", "Selection", "ServerScriptService", "ServerStorage", "SessionService", "SocialService", "SoundService", "SpawnerService", "StarterPack", "StarterPlayer", "Stats", "StopWatchReporter", "Studio", "StudioAssetService", "StudioData", "StudioDeviceEmulatorService", "StudioScriptDebugEventListener", "StudioService", "TaskScheduler", "Teams", "TeleportService", "TemporaryCageMeshProvider", "TemporaryScriptService", "TestService", "TextBoxService", "TextChatService", "TextService", "ThirdPartyUserService", "TimerService", "ToastNotificationService", "ToolboxService", "TouchInputService", "TracerService", "TweenService", "UGCValidationService", "UnvalidatedAssetService", "UserInputService", "UserService", "VRService", "VersionControlService", "VirtualInputManager", "VirtualUser", "Visit", "VoiceChatInternal", "VoiceChatService"] [selene.structs.DataModel.GraphicsQualityChangeRequest] struct = "Event" @@ -4985,6 +4985,9 @@ struct = "EnumItem" [Enum.ConnectionError.DisconnectClientFailure] struct = "EnumItem" +[Enum.ConnectionError.DisconnectClientRequest] +struct = "EnumItem" + [Enum.ConnectionError.DisconnectCloudEditKick] struct = "EnumItem" @@ -5318,6 +5321,9 @@ struct = "EnumItem" [Enum.DebuggerPauseReason.Unknown] struct = "EnumItem" +[Enum.DebuggerStatus.ConnectionClosed] +struct = "EnumItem" + [Enum.DebuggerStatus.ConnectionLost] struct = "EnumItem" @@ -7493,6 +7499,15 @@ struct = "EnumItem" [Enum.Material.WoodPlanks] struct = "EnumItem" +[Enum.MaterialPattern.GetEnumItems] +method = true +args = [] + +[Enum.MaterialPattern.Organic] +struct = "EnumItem" + +[Enum.MaterialPattern.Regular] +struct = "EnumItem" [Enum.MembershipType.BuildersClub] struct = "EnumItem" @@ -7856,6 +7871,36 @@ struct = "EnumItem" [Enum.ParticleEmitterShapeStyle.Volume] struct = "EnumItem" +[Enum.ParticleFlipbookLayout.EightByEight] +struct = "EnumItem" + +[Enum.ParticleFlipbookLayout.FourByFour] +struct = "EnumItem" + +[Enum.ParticleFlipbookLayout.GetEnumItems] +method = true +args = [] + +[Enum.ParticleFlipbookLayout.None] +struct = "EnumItem" + +[Enum.ParticleFlipbookLayout.TwoByTwo] +struct = "EnumItem" +[Enum.ParticleFlipbookMode.GetEnumItems] +method = true +args = [] + +[Enum.ParticleFlipbookMode.Loop] +struct = "EnumItem" + +[Enum.ParticleFlipbookMode.OneShot] +struct = "EnumItem" + +[Enum.ParticleFlipbookMode.PingPong] +struct = "EnumItem" + +[Enum.ParticleFlipbookMode.Random] +struct = "EnumItem" [Enum.ParticleOrientation.FacingCamera] struct = "EnumItem" @@ -7892,6 +7937,9 @@ struct = "EnumItem" [Enum.PathStatus.Success] struct = "EnumItem" +[Enum.PathWaypointAction.Custom] +struct = "EnumItem" + [Enum.PathWaypointAction.GetEnumItems] method = true args = [] @@ -8445,17 +8493,11 @@ struct = "EnumItem" [Enum.RigType.R15] struct = "EnumItem" -[Enum.RigType.R6] -struct = "EnumItem" - [Enum.RigType.Rthro] struct = "EnumItem" [Enum.RigType.RthroNarrow] struct = "EnumItem" - -[Enum.RigType.S15] -struct = "EnumItem" [Enum.RollOffMode.GetEnumItems] method = true args = [] @@ -8855,6 +8897,9 @@ struct = "EnumItem" [Enum.StudioScriptEditorColorCategories.Default] struct = "EnumItem" +[Enum.StudioScriptEditorColorCategories.DocViewCodeBackground] +struct = "EnumItem" + [Enum.StudioScriptEditorColorCategories.Error] struct = "EnumItem" @@ -9086,6 +9131,12 @@ struct = "EnumItem" [Enum.StudioStyleGuideColor.DimmedText] struct = "EnumItem" +[Enum.StudioStyleGuideColor.DocViewCodeBackground] +struct = "EnumItem" + +[Enum.StudioStyleGuideColor.DropShadow] +struct = "EnumItem" + [Enum.StudioStyleGuideColor.Dropdown] struct = "EnumItem" @@ -10178,7 +10229,7 @@ struct = "EnumItem" [[Faces.new.args]] type = "..." [[Instance.new.args]] -type = ["Accoutrement", "Accessory", "Hat", "AdvancedDragger", "AnalyticsService", "Animation", "CurveAnimation", "KeyframeSequence", "AnimationController", "AnimationRigData", "Animator", "Atmosphere", "Attachment", "Bone", "Backpack", "HopperBin", "Tool", "Flag", "WrapLayer", "WrapTarget", "Beam", "BindableEvent", "BindableFunction", "BodyAngularVelocity", "BodyForce", "BodyGyro", "BodyPosition", "BodyThrust", "BodyVelocity", "RocketPropulsion", "Breakpoint", "Camera", "BodyColors", "CharacterMesh", "Pants", "Shirt", "ShirtGraphic", "Skin", "ClickDetector", "Clouds", "Configuration", "AlignOrientation", "AlignPosition", "AngularVelocity", "BallSocketConstraint", "HingeConstraint", "LineForce", "LinearVelocity", "Plane", "RodConstraint", "RopeConstraint", "CylindricalConstraint", "PrismaticConstraint", "SpringConstraint", "Torque", "TorsionSpringConstraint", "UniversalConstraint", "VectorForce", "HumanoidController", "SkateboardController", "VehicleController", "CustomEvent", "CustomEventReceiver", "BlockMesh", "CylinderMesh", "FileMesh", "SpecialMesh", "DataStoreIncrementOptions", "DataStoreOptions", "DataStoreSetOptions", "DebuggerWatch", "Dialog", "DialogChoice", "Dragger", "EulerRotationCurve", "Explosion", "FaceControls", "Decal", "Texture", "Hole", "MotorFeature", "Fire", "FloatCurve", "FlyweightService", "CSGDictionaryService", "NonReplicatedCSGDictionaryService", "Folder", "ForceField", "FunctionalTest", "CanvasGroup", "Frame", "ImageButton", "TextButton", "ImageLabel", "TextLabel", "ScrollingFrame", "TextBox", "VideoFrame", "ViewportFrame", "BillboardGui", "ScreenGui", "GuiMain", "SurfaceGui", "FloorWire", "SelectionBox", "BoxHandleAdornment", "ConeHandleAdornment", "CylinderHandleAdornment", "ImageHandleAdornment", "LineHandleAdornment", "SphereHandleAdornment", "ParabolaAdornment", "SelectionSphere", "ArcHandles", "Handles", "SurfaceSelection", "SelectionPartLasso", "SelectionPointLasso", "HeightmapImporterService", "Highlight", "Humanoid", "HumanoidDescription", "RotateP", "RotateV", "Glue", "ManualGlue", "ManualWeld", "Motor", "Motor6D", "Rotate", "Snap", "VelocityMotor", "Weld", "Keyframe", "KeyframeMarker", "PointLight", "SpotLight", "SurfaceLight", "LocalizationTable", "Script", "LocalScript", "ModuleScript", "MaterialVariant", "MemoryStoreService", "Message", "Hint", "NoCollisionConstraint", "CornerWedgePart", "Part", "FlagStand", "Seat", "SkateboardPlatform", "SpawnLocation", "WedgePart", "MeshPart", "PartOperation", "NegateOperation", "UnionOperation", "TrussPart", "VehicleSeat", "Model", "Actor", "WorldModel", "PartOperationAsset", "ParticleEmitter", "PathfindingLink", "PathfindingModifier", "Player", "PluginAction", "NumberPose", "Pose", "BloomEffect", "BlurEffect", "ColorCorrectionEffect", "DepthOfFieldEffect", "SunRaysEffect", "ProximityPrompt", "ProximityPromptService", "ReflectionMetadata", "ReflectionMetadataCallbacks", "ReflectionMetadataClasses", "ReflectionMetadataEnums", "ReflectionMetadataEvents", "ReflectionMetadataFunctions", "ReflectionMetadataClass", "ReflectionMetadataEnum", "ReflectionMetadataEnumItem", "ReflectionMetadataMember", "ReflectionMetadataProperties", "ReflectionMetadataYieldFunctions", "RemoteEvent", "RemoteFunction", "RenderingTest", "RotationCurve", "Sky", "Smoke", "Sound", "ChorusSoundEffect", "CompressorSoundEffect", "ChannelSelectorSoundEffect", "DistortionSoundEffect", "EchoSoundEffect", "EqualizerSoundEffect", "FlangeSoundEffect", "PitchShiftSoundEffect", "ReverbSoundEffect", "TremoloSoundEffect", "SoundGroup", "Sparkles", "Speaker", "StandalonePluginScripts", "StarterGear", "SurfaceAppearance", "Team", "TeleportOptions", "TerrainRegion", "TestService", "TextChannel", "Trail", "Tween", "UIAspectRatioConstraint", "UISizeConstraint", "UITextSizeConstraint", "UICorner", "UIGradient", "UIGridLayout", "UIListLayout", "UIPageLayout", "UITableLayout", "UIPadding", "UIScale", "UIStroke", "BinaryStringValue", "BoolValue", "BrickColorValue", "CFrameValue", "Color3Value", "DoubleConstrainedValue", "IntConstrainedValue", "IntValue", "NumberValue", "ObjectValue", "RayValue", "StringValue", "Vector3Value", "Vector3Curve", "VirtualInputManager", "VoiceChatService", "WeldConstraint"] +type = ["Accoutrement", "Accessory", "Hat", "AdvancedDragger", "AnalyticsService", "Animation", "CurveAnimation", "KeyframeSequence", "AnimationController", "AnimationRigData", "Animator", "Atmosphere", "Attachment", "Bone", "Backpack", "HopperBin", "Tool", "Flag", "WrapLayer", "WrapTarget", "Beam", "BindableEvent", "BindableFunction", "BodyAngularVelocity", "BodyForce", "BodyGyro", "BodyPosition", "BodyThrust", "BodyVelocity", "RocketPropulsion", "Breakpoint", "Camera", "BodyColors", "CharacterMesh", "Pants", "Shirt", "ShirtGraphic", "Skin", "ClickDetector", "Clouds", "Configuration", "AlignOrientation", "AlignPosition", "AngularVelocity", "BallSocketConstraint", "HingeConstraint", "LineForce", "LinearVelocity", "Plane", "RigidConstraint", "RodConstraint", "RopeConstraint", "CylindricalConstraint", "PrismaticConstraint", "SpringConstraint", "Torque", "TorsionSpringConstraint", "UniversalConstraint", "VectorForce", "HumanoidController", "SkateboardController", "VehicleController", "CustomEvent", "CustomEventReceiver", "BlockMesh", "CylinderMesh", "FileMesh", "SpecialMesh", "DataStoreIncrementOptions", "DataStoreOptions", "DataStoreSetOptions", "DebuggerWatch", "Dialog", "DialogChoice", "Dragger", "EulerRotationCurve", "Explosion", "FaceControls", "Decal", "Texture", "Hole", "MotorFeature", "Fire", "FloatCurve", "FlyweightService", "CSGDictionaryService", "NonReplicatedCSGDictionaryService", "Folder", "ForceField", "FunctionalTest", "CanvasGroup", "Frame", "ImageButton", "TextButton", "ImageLabel", "TextLabel", "ScrollingFrame", "TextBox", "VideoFrame", "ViewportFrame", "BillboardGui", "ScreenGui", "GuiMain", "SurfaceGui", "FloorWire", "SelectionBox", "BoxHandleAdornment", "ConeHandleAdornment", "CylinderHandleAdornment", "ImageHandleAdornment", "LineHandleAdornment", "SphereHandleAdornment", "ParabolaAdornment", "SelectionSphere", "ArcHandles", "Handles", "SurfaceSelection", "SelectionPartLasso", "SelectionPointLasso", "HeightmapImporterService", "HiddenSurfaceRemovalAsset", "Highlight", "Humanoid", "HumanoidDescription", "RotateP", "RotateV", "Glue", "ManualGlue", "ManualWeld", "Motor", "Motor6D", "Rotate", "Snap", "VelocityMotor", "Weld", "Keyframe", "KeyframeMarker", "PointLight", "SpotLight", "SurfaceLight", "LocalizationTable", "Script", "LocalScript", "ModuleScript", "MarkerCurve", "MaterialVariant", "MemoryStoreService", "Message", "Hint", "NoCollisionConstraint", "CornerWedgePart", "Part", "FlagStand", "Seat", "SkateboardPlatform", "SpawnLocation", "WedgePart", "MeshPart", "PartOperation", "NegateOperation", "UnionOperation", "TrussPart", "VehicleSeat", "Model", "Actor", "WorldModel", "PartOperationAsset", "ParticleEmitter", "PathfindingLink", "PathfindingModifier", "Player", "PluginAction", "NumberPose", "Pose", "BloomEffect", "BlurEffect", "ColorCorrectionEffect", "DepthOfFieldEffect", "SunRaysEffect", "ProximityPrompt", "ProximityPromptService", "ReflectionMetadata", "ReflectionMetadataCallbacks", "ReflectionMetadataClasses", "ReflectionMetadataEnums", "ReflectionMetadataEvents", "ReflectionMetadataFunctions", "ReflectionMetadataClass", "ReflectionMetadataEnum", "ReflectionMetadataEnumItem", "ReflectionMetadataMember", "ReflectionMetadataProperties", "ReflectionMetadataYieldFunctions", "RemoteEvent", "RemoteFunction", "RenderingTest", "RotationCurve", "Sky", "Smoke", "Sound", "ChorusSoundEffect", "CompressorSoundEffect", "ChannelSelectorSoundEffect", "DistortionSoundEffect", "EchoSoundEffect", "EqualizerSoundEffect", "FlangeSoundEffect", "PitchShiftSoundEffect", "ReverbSoundEffect", "TremoloSoundEffect", "SoundGroup", "Sparkles", "Speaker", "StandalonePluginScripts", "StarterGear", "SurfaceAppearance", "Team", "TeleportOptions", "TerrainRegion", "TestService", "TextChannel", "Trail", "Tween", "UIAspectRatioConstraint", "UISizeConstraint", "UITextSizeConstraint", "UICorner", "UIGradient", "UIGridLayout", "UIListLayout", "UIPageLayout", "UITableLayout", "UIPadding", "UIScale", "UIStroke", "BinaryStringValue", "BoolValue", "BrickColorValue", "CFrameValue", "Color3Value", "DoubleConstrainedValue", "IntConstrainedValue", "IntValue", "NumberValue", "ObjectValue", "RayValue", "StringValue", "Vector3Value", "Vector3Curve", "VirtualInputManager", "VoiceChannel", "WeldConstraint"] [[NumberRange.new.args]] type = "number" @@ -10200,6 +10251,8 @@ type = "number" [[NumberSequenceKeypoint.new.args]] required = false type = "number" +[OverlapParams.new] +args = [] [[PathWaypoint.new.args]] required = false diff --git a/src/KnitClient.lua b/src/KnitClient.lua index 632359c2..2cd971e1 100644 --- a/src/KnitClient.lua +++ b/src/KnitClient.lua @@ -1,8 +1,41 @@ +--[=[ + @interface Middleware + .Inbound ClientMiddleware? + .Outbound ClientMiddleware? + @within KnitClient +]=] +type Middleware = { + Inbound: ClientMiddleware?, + Outbound: ClientMiddleware?, +} + +--[=[ + @type ClientMiddlewareFn (args: {any}) -> (shouldContinue: boolean, ...: any) + @within KnitClient + + For more info, see [ClientComm](https://sleitnick.github.io/RbxUtil/api/ClientComm/) documentation. +]=] +type ClientMiddlewareFn = (args: {any}) -> (boolean, ...any) + +--[=[ + @type ClientMiddleware {ClientMiddlewareFn} + @within KnitClient + An array of client middleware functions. +]=] +type ClientMiddleware = {ClientMiddlewareFn} + +--[=[ + @type PerServiceMiddleware {[string]: Middleware} + @within KnitClient +]=] +type PerServiceMiddleware = {[string]: Middleware} + --[=[ @interface ControllerDef .Name string .[any] any @within KnitClient + Used to define a controller when creating it in `CreateController`. ]=] type ControllerDef = { Name: string, @@ -29,33 +62,27 @@ type Service = { [any]: any, } ---[=[ - @type ClientMiddlewareFn (args: {any}) -> (shouldContinue: boolean, ...: any) - @within KnitClient - - For more info, see [ClientComm](https://sleitnick.github.io/RbxUtil/api/ClientComm/) documentation. -]=] - --[=[ @interface KnitOptions .ServicePromises boolean? - .InboundMiddleware ClientMiddlewareFn? - .OutboundMiddleware ClientMiddlewareFn? + .Middleware Middleware? + .PerServiceMiddleware PerServiceMiddleware? @within KnitClient - `ServicePromises` defaults to `true` and indicates if service methods use promises. - - `InboundMiddleware` and `OutboundMiddleware` default to `nil`. + - Each service will go through the defined middleware, unless the service + has middleware defined in `PerServiceMiddleware`. ]=] type KnitOptions = { ServicePromises: boolean, - InboundMiddleware: {(...any) -> (boolean, ...any)}?, - OutboundMiddleware: {(...any) -> (boolean, ...any)}?, + Middleware: Middleware?, + PerServiceMiddleware: PerServiceMiddleware? } local defaultOptions: KnitOptions = { ServicePromises = true; - InboundMiddleware = nil; - OutboundMiddleware = nil; + Middleware = nil; + PerServiceMiddleware = {}; } local selectedOptions = nil @@ -99,13 +126,6 @@ local startedComplete = false local onStartedComplete = Instance.new("BindableEvent") -local function BuildService(serviceName: string, folder: Instance): Service - local service = ClientComm.new(folder, selectedOptions.ServicePromises):BuildObject(selectedOptions.InboundMiddleware, selectedOptions.OutboundMiddleware) - services[serviceName] = service - return service -end - - local function DoesControllerExist(controllerName: string): boolean local controller: Controller? = controllers[controllerName] return controller ~= nil @@ -120,6 +140,23 @@ local function GetServicesFolder() end +local function GetMiddlewareForService(serviceName: string) + local knitMiddleware = selectedOptions.Middleware or {} + local serviceMiddleware = selectedOptions.PerServiceMiddleware[serviceName] + return serviceMiddleware or knitMiddleware +end + + +local function BuildService(serviceName: string) + local folder = GetServicesFolder() + local middleware = GetMiddlewareForService(serviceName) + local clientComm = ClientComm.new(folder, selectedOptions.ServicePromises, serviceName) + local service = clientComm:BuildObject(middleware.Inbound, middleware.Outbound) + services[serviceName] = service + return service +end + + --[=[ @param controllerDefinition ControllerDef @return Controller @@ -225,11 +262,13 @@ end ::: ]=] function KnitClient.GetService(serviceName: string): Service + local service = services[serviceName] + if service then + return service + end assert(started, "Cannot call GetService until Knit has been started") assert(type(serviceName) == "string", "ServiceName must be a string; got " .. type(serviceName)) - local folder: Instance? = GetServicesFolder():FindFirstChild(serviceName) - assert(folder ~= nil, "Could not find service \"" .. serviceName .. "\". Check the service name and that the service has client-facing methods/RemoteSignals/RemoteProperties.") - return services[serviceName] or BuildService(serviceName, folder :: Instance) + return BuildService(serviceName) end @@ -240,11 +279,13 @@ end is not found. ]=] function KnitClient.GetController(controllerName: string): Controller + local controller = controllers[controllerName] + if controller then + return controller + end assert(started, "Cannot call GetController until Knit has been started") assert(type(controllerName) == "string", "ControllerName must be a string; got " .. type(controllerName)) - local controller = controllers[controllerName] - assert(controller ~= nil, " Could not find controller \"" .. controllerName .. "\". Check to verify a controller with this name exists.") - return controller + error("Could not find controller \"" .. controllerName .. "\". Check to verify a controller with this name exists.", 2) end @@ -285,6 +326,9 @@ function KnitClient.Start(options: KnitOptions?) end end end + if type(selectedOptions.PerServiceMiddleware) ~= "table" then + selectedOptions.PerServiceMiddleware = {} + end return Promise.new(function(resolve) diff --git a/src/KnitServer.lua b/src/KnitServer.lua index 26d6dc32..f1bc5674 100644 --- a/src/KnitServer.lua +++ b/src/KnitServer.lua @@ -1,14 +1,46 @@ +--[=[ + @interface Middleware + .Inbound ServerMiddleware? + .Outbound ServerMiddleware? + @within KnitServer +]=] +type Middleware = { + Inbound: ServerMiddleware?, + Outbound: ServerMiddleware?, +} + +--[=[ + @type ServerMiddlewareFn (player: Player, args: {any}) -> (shouldContinue: boolean, ...: any) + @within KnitServer + + For more info, see [ServerComm](https://sleitnick.github.io/RbxUtil/api/ServerComm/) documentation. +]=] +type ServerMiddlewareFn = (player: Player, args: {any}) -> (boolean, ...any) + +--[=[ + @type ServerMiddleware {ServerMiddlewareFn} + @within KnitServer + An array of server middleware functions. +]=] +type ServerMiddleware = {ServerMiddlewareFn} + --[=[ @interface ServiceDef .Name string .Client table? + .Middleware Middleware? .[any] any @within KnitServer Used to define a service when creating it in `CreateService`. + + The middleware tables provided will be used instead of the Knit-level + middleware (if any). This allows fine-tuning each service's middleware. + These can also be left out or `nil` to not include middleware. ]=] type ServiceDef = { Name: string, Client: {[any]: any}?, + Middleware: Middleware?, [any]: any, } @@ -38,29 +70,20 @@ type ServiceClient = { [any]: any, } ---[=[ - @type ServerMiddlewareFn (player: Player, args: {any}) -> (shouldContinue: boolean, ...: any) - @within KnitServer - - For more info, see [ServerComm](https://sleitnick.github.io/RbxUtil/api/ServerComm/) documentation. -]=] - --[=[ @interface KnitOptions - .InboundMiddleware ServerMiddlewareFn? - .OutboundMiddleware ServerMiddlewareFn? + .Middleware Middleware? @within KnitServer - - `InboundMiddleware` and `OutboundMiddleware` default to `nil`. + - Middleware will apply to all services _except_ ones that define + their own middleware. ]=] type KnitOptions = { - InboundMiddleware: {(...any) -> (boolean, ...any)}?, - OutboundMiddleware: {(...any) -> (boolean, ...any)}?, + Middleware: Middleware?, } local defaultOptions: KnitOptions = { - InboundMiddleware = nil; - OutboundMiddleware = nil; + Middleware = nil; } local selectedOptions = nil @@ -119,14 +142,6 @@ local startedComplete = false local onStartedComplete = Instance.new("BindableEvent") -local function CreateRepFolder(serviceName: string): Instance - local folder = Instance.new("Folder") - folder.Name = serviceName - folder.Parent = knitRepServiceFolder - return folder -end - - local function DoesServiceExist(serviceName: string): boolean local service: Service? = services[serviceName] return service ~= nil @@ -170,7 +185,7 @@ function KnitServer.CreateService(serviceDef: ServiceDef): Service assert(#serviceDef.Name > 0, "Service.Name must be a non-empty string") assert(not DoesServiceExist(serviceDef.Name), "Service \"" .. serviceDef.Name .. "\" already exists") local service = serviceDef - service.KnitComm = ServerComm.new(CreateRepFolder(serviceDef.Name)) + service.KnitComm = ServerComm.new(knitRepServiceFolder, serviceDef.Name) if type(service.Client) ~= "table" then service.Client = {Server = service} else @@ -315,11 +330,13 @@ end Example of Knit started with options: ```lua Knit.Start({ - InboundMiddleware: { - function(player, args) - print("Player is giving following args to server:", args) - return true - end + Middleware = { + Inbound = { + function(player, args) + print("Player is giving following args to server:", args) + return true + end + } } }):andThen(function() print("Knit started!") @@ -348,15 +365,21 @@ function KnitServer.Start(options: KnitOptions?) return Promise.new(function(resolve) + local knitMiddleware = selectedOptions.Middleware or {} + -- Bind remotes: for _,service in pairs(services) do + local middleware = service.Middleware or {} + local inbound = middleware.Inbound or knitMiddleware.Inbound + local outbound = middleware.Outbound or knitMiddleware.Outbound + service.Middleware = nil for k,v in pairs(service.Client) do if type(v) == "function" then - service.KnitComm:WrapMethod(service.Client, k, selectedOptions.InboundMiddleware, selectedOptions.OutboundMiddleware) + service.KnitComm:WrapMethod(service.Client, k, inbound, outbound) elseif v == SIGNAL_MARKER then - service.Client[k] = service.KnitComm:CreateSignal(k, selectedOptions.InboundMiddleware, selectedOptions.OutboundMiddleware) + service.Client[k] = service.KnitComm:CreateSignal(k, inbound, outbound) elseif type(v) == "table" and v[1] == PROPERTY_MARKER then - service.Client[k] = service.KnitComm:CreateProperty(k, v[2], selectedOptions.InboundMiddleware, selectedOptions.OutboundMiddleware) + service.Client[k] = service.KnitComm:CreateProperty(k, v[2], inbound, outbound) end end end diff --git a/src/wally.toml b/src/wally.toml index fcf60f42..4119c345 100644 --- a/src/wally.toml +++ b/src/wally.toml @@ -1,7 +1,7 @@ [package] name = "sleitnick/knit" description = "Knit is a lightweight game framework" -version = "1.3.0" +version = "1.4.0" license = "MIT" registry = "https://github.com/UpliftGames/wally-index" realm = "shared" diff --git a/test/server/KnitServerTest.server.lua b/test/server/KnitServerTest.server.lua index 4d2a532d..2c69a717 100644 --- a/test/server/KnitServerTest.server.lua +++ b/test/server/KnitServerTest.server.lua @@ -6,6 +6,20 @@ local MyService = Knit.CreateService { TestEvent = Knit.CreateSignal(); TestProperty = Knit.CreateProperty("Hello"); }; + Middleware = { + Inbound = { + function(player, args) + print("MyService Inbound", player, args) + return true + end, + }; + Outbound = { + function(player, args) + print("MyService Outbound", player, args) + return true + end, + }; + }; } function MyService:KnitInit() @@ -20,6 +34,21 @@ function MyService.Client:TestMethod(player, msg) return msg:upper() end -Knit.Start():andThen(function() +Knit.Start({ + Middleware = { + Inbound = { + function(player, ...) + print("INBOUND", player, ...) + return true + end, + }, + Outbound = { + function(player, ...) + print("OUTBOUND", player, ...) + return true + end, + }, + } +}):andThen(function() print("KnitServer started") end):catch(warn)