@thigasdevelopment/luam 0.1.1

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.
Files changed (39) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +616 -0
  3. package/lua/async.lua +119 -0
  4. package/lua/class.lua +178 -0
  5. package/lua/development-logs-client.lua +13 -0
  6. package/lua/development-logs-server.lua +44 -0
  7. package/lua/dotenv.lua +160 -0
  8. package/lua/env.lua +18 -0
  9. package/lua/math.lua +24 -0
  10. package/lua/string.lua +82 -0
  11. package/lua/table.lua +63 -0
  12. package/lua/threads.lua +274 -0
  13. package/luam.mjs +9910 -0
  14. package/package.json +38 -0
  15. package/template/README.md +120 -0
  16. package/template/config.lua +14 -0
  17. package/template/env +11 -0
  18. package/template/framework/bootstrap-client.luam +9 -0
  19. package/template/framework/bootstrap-server.luam +9 -0
  20. package/template/framework/command.luam +54 -0
  21. package/template/framework/core.luam +58 -0
  22. package/template/framework/event.luam +39 -0
  23. package/template/framework/listener.luam +40 -0
  24. package/template/framework/loader.luam +73 -0
  25. package/template/framework/thread-pool.luam +27 -0
  26. package/template/gitignore +7 -0
  27. package/template/luam.json +11 -0
  28. package/template/src/client/commands/ping-command.luam +8 -0
  29. package/template/src/client/main.luam +5 -0
  30. package/template/src/server/commands/hello-command.luam +9 -0
  31. package/template/src/server/handlers/player-join-listener.luam +9 -0
  32. package/template/src/server/main.luam +11 -0
  33. package/template/src/shared/config.luam +5 -0
  34. package/template/src/shared/framework/command.luam +54 -0
  35. package/template/src/shared/framework/core.luam +58 -0
  36. package/template/src/shared/framework/event.luam +39 -0
  37. package/template/src/shared/framework/listener.luam +40 -0
  38. package/template/src/shared/framework/loader.luam +73 -0
  39. package/template/src/shared/framework/thread-pool.luam +27 -0
package/lua/async.lua ADDED
@@ -0,0 +1,119 @@
1
+ ---@class Async
2
+ ---@field tasks Threads
3
+ ---@field interval number
4
+ ---@field map fun(self: Async, array: table, func: fun(value: any, index: number): any, callback?: fun(elapsed: number): any): number
5
+ ---@field iterate fun(self: Async, from: number, to: number, increment: number, func: fun(i: number): any, callback?: fun(elapsed: number): any): number
6
+ ---@field foreach fun(self: Async, object: table<any, any>, func: fun(value: any, key: any): any, callback?: fun(elapsed: number): any): number
7
+ ---@field getInterval fun(self: Async): number
8
+ ---@field setInterval fun(self: Async, interval: number): boolean
9
+ Async = {
10
+ ---@param interval number
11
+ ---@return Async
12
+ new = function (interval)
13
+ ---@type Async
14
+ local self = setmetatable({ }, { __index = Async });
15
+ self.tasks = Threads.new ('concurrent', 'normal');
16
+
17
+ self.interval = 100;
18
+ self:setInterval (interval);
19
+ return self;
20
+ end,
21
+
22
+ ---@param self Async
23
+ ---@param array table
24
+ ---@param func fun(value: any, index: number): any
25
+ ---@param callback? fun(elapsed: number): any
26
+ ---@return number
27
+ map = function (self, array, func, callback)
28
+ return self.tasks:add (
29
+ function (self)
30
+ local tick = getTickCount ();
31
+ for index, value in ipairs (array) do
32
+ func (value, index);
33
+
34
+ sleep (self.interval);
35
+ end
36
+
37
+ local callbackType = type (callback);
38
+ if (callbackType == 'function') then
39
+ local elapsed = (getTickCount () - tick);
40
+ callback (elapsed);
41
+ end
42
+ end
43
+ );
44
+ end,
45
+
46
+ ---@param self Async
47
+ ---@param from number
48
+ ---@param to number
49
+ ---@param increment number
50
+ ---@param func fun(i: number): any
51
+ ---@param callback? fun(elapsed: number): any
52
+ ---@return number
53
+ iterate = function (self, from, to, increment, func, callback)
54
+ return self.tasks:add (
55
+ function (self)
56
+ local tick = getTickCount ();
57
+ for i = from, to, increment do
58
+ func (i);
59
+
60
+ sleep (self.interval);
61
+ end
62
+
63
+ local callbackType = type (callback);
64
+ if (callbackType == 'function') then
65
+ local elapsed = (getTickCount () - tick);
66
+ callback (elapsed);
67
+ end
68
+ return true;
69
+ end, { }, from, to, increment, func, callback
70
+ );
71
+ end,
72
+
73
+ ---@param self Async
74
+ ---@param object table<any, any>
75
+ ---@param func fun(value: any, key: any): any
76
+ ---@param callback? fun(elapsed: number): any
77
+ ---@return number
78
+ foreach = function (self, object, func, callback)
79
+ return self.tasks:add (
80
+ function (self)
81
+ local tick = getTickCount ();
82
+ for key, value in pairs (object) do
83
+ func (value, key);
84
+
85
+ sleep (self.interval);
86
+ end
87
+
88
+ local callbackType = type (callback);
89
+ if (callbackType == 'function') then
90
+ local elapsed = (getTickCount () - tick);
91
+ callback (elapsed);
92
+ end
93
+ end, { }, object, func, callback
94
+ );
95
+ end,
96
+
97
+ ---@param self Async
98
+ ---@return number
99
+ getInterval = function (self)
100
+ return self.interval;
101
+ end,
102
+
103
+ ---@param self Async
104
+ ---@param interval number
105
+ ---@return boolean
106
+ setInterval = function (self, interval)
107
+ interval = tonumber (interval);
108
+ if (not interval) then
109
+ return false;
110
+ end
111
+
112
+ if (interval < 1) or (interval == self:getInterval ()) then
113
+ return false;
114
+ end
115
+
116
+ self.interval = interval;
117
+ return true;
118
+ end,
119
+ };
package/lua/class.lua ADDED
@@ -0,0 +1,178 @@
1
+ local classes = {}
2
+
3
+ local constructors = {}
4
+
5
+ local BLOCKED_METAMETHODS = {
6
+ ['__call'] = true,
7
+ ['__index'] = true,
8
+ ['__newindex'] = true,
9
+ }
10
+
11
+ local function bindSuper(value, inherited)
12
+ return function(self, ...)
13
+ local previous = rawget(self, 'super')
14
+
15
+ self.super = function(_, ...)
16
+ return inherited(self, ...)
17
+ end
18
+
19
+ local result = value(self, ...)
20
+
21
+ self.super = previous
22
+
23
+ return result
24
+ end
25
+ end
26
+
27
+ local function create(name, struct, options)
28
+ if classes[name] then
29
+ error('Class ' .. tostring(name) .. ' already exists.')
30
+ end
31
+
32
+ local definition = {
33
+ __name = name,
34
+ __super = options.super,
35
+ __metamethods = options.metamethods,
36
+ }
37
+
38
+ for key, value in pairs(struct) do
39
+ local inherited = definition.__super and definition.__super[key]
40
+
41
+ if type(value) == 'function' and type(inherited) == 'function' then
42
+ definition[key] = bindSuper(value, inherited)
43
+ else
44
+ definition[key] = value
45
+ end
46
+ end
47
+
48
+ if definition.__super then
49
+ setmetatable(definition, { __index = definition.__super })
50
+ end
51
+
52
+ classes[name] = definition
53
+
54
+ return definition
55
+ end
56
+
57
+ function class(name)
58
+ local options = { super = nil, metamethods = nil }
59
+
60
+ local modifiers = {
61
+ extends = function(self, super)
62
+ options.super = classes[super]
63
+
64
+ if not options.super then
65
+ error('Class ' .. tostring(name) .. ' extends ' .. tostring(super) .. ', which is not defined.')
66
+ end
67
+
68
+ return self
69
+ end,
70
+
71
+ metamethods = function(self, metamethods)
72
+ options.metamethods = metamethods
73
+
74
+ return self
75
+ end,
76
+ }
77
+
78
+ return setmetatable({}, {
79
+ __index = function(_, key)
80
+ if modifiers[key] then
81
+ return modifiers[key]
82
+ end
83
+
84
+ local definition = classes[name]
85
+
86
+ if not definition then
87
+ return nil
88
+ end
89
+
90
+ return definition[key]
91
+ end,
92
+
93
+ __call = function(_, struct)
94
+ return create(name, struct, options)
95
+ end,
96
+ })
97
+ end
98
+
99
+ local function instanceMetatable(definition)
100
+ local meta = { __index = definition }
101
+
102
+ if not definition.__metamethods then
103
+ return meta
104
+ end
105
+
106
+ for key, value in pairs(definition.__metamethods) do
107
+ if BLOCKED_METAMETHODS[key] then
108
+ error('Cannot override metamethod ' .. tostring(key) .. '.')
109
+ end
110
+
111
+ meta[key] = value
112
+ end
113
+
114
+ return meta
115
+ end
116
+
117
+ local function createConstructor(definition)
118
+ local meta = instanceMetatable(definition)
119
+
120
+ return function(...)
121
+ local instance = setmetatable({}, meta)
122
+
123
+ if type(instance.constructor) == 'function' then
124
+ instance:constructor(...)
125
+ end
126
+
127
+ return instance
128
+ end
129
+ end
130
+
131
+ function new(name)
132
+ local definition = classes[name]
133
+
134
+ if not definition then
135
+ error('Class ' .. tostring(name) .. ' is not defined.')
136
+ end
137
+
138
+ local constructor = constructors[definition]
139
+
140
+ if not constructor then
141
+ constructor = createConstructor(definition)
142
+ constructors[definition] = constructor
143
+ end
144
+
145
+ return constructor
146
+ end
147
+
148
+ function bind(func, self)
149
+ if type(func) ~= 'function' then
150
+ error('Cannot bind a value of type ' .. type(func) .. '.')
151
+ end
152
+
153
+ return function(...)
154
+ return func(self, ...)
155
+ end
156
+ end
157
+
158
+ function enum(names)
159
+ local values = {}
160
+
161
+ if type(names) ~= 'table' then
162
+ return values
163
+ end
164
+
165
+ for index = 1, #names do
166
+ values[names[index]] = index - 1
167
+ end
168
+
169
+ return values
170
+ end
171
+
172
+ function getClasses()
173
+ return classes
174
+ end
175
+
176
+ function getClass(name)
177
+ return classes[name]
178
+ end
@@ -0,0 +1,13 @@
1
+ local luamOriginalOutputDebugString = outputDebugString
2
+ local luamDevelopmentEvent = "luam:development-log"
3
+ local luamMaximumMessageLength = __LUAM_MAX_MESSAGE_LENGTH__
4
+
5
+ function outputDebugString(message, level, red, green, blue)
6
+ local result = luamOriginalOutputDebugString(message, level, red, green, blue)
7
+
8
+ if type(message) == "string" and #message <= luamMaximumMessageLength and (level == nil or type(level) == "number") then
9
+ triggerServerEvent(luamDevelopmentEvent, resourceRoot, message, level or 3)
10
+ end
11
+
12
+ return result
13
+ end
@@ -0,0 +1,44 @@
1
+ local luamDevelopmentEvent = "luam:development-log"
2
+ local luamDevelopmentMarker = "__LUAM_DEV_LOG__"
3
+ local luamMaximumMessageLength = __LUAM_MAX_MESSAGE_LENGTH__
4
+ local luamRateLimit = __LUAM_RATE_LIMIT__
5
+ local luamRateWindow = __LUAM_RATE_WINDOW_MS__
6
+ local luamRateBuckets = {}
7
+
8
+ addEvent(luamDevelopmentEvent, true)
9
+ addEventHandler(luamDevelopmentEvent, resourceRoot, function(message, level)
10
+ if client == nil or source ~= resourceRoot or type(message) ~= "string" or #message > luamMaximumMessageLength then
11
+ return
12
+ end
13
+
14
+ if type(level) ~= "number" or level < 0 or level > 4 or level ~= math.floor(level) then
15
+ return
16
+ end
17
+
18
+ local now = getTickCount()
19
+ local bucket = luamRateBuckets[client]
20
+
21
+ if bucket == nil or now < bucket.started or now - bucket.started >= luamRateWindow then
22
+ bucket = { started = now, count = 0 }
23
+ luamRateBuckets[client] = bucket
24
+ end
25
+
26
+ if bucket.count >= luamRateLimit then
27
+ return
28
+ end
29
+
30
+ bucket.count = bucket.count + 1
31
+
32
+ local record = {
33
+ environment = "client",
34
+ level = level,
35
+ message = message,
36
+ resource = getResourceName(getThisResource())
37
+ }
38
+
39
+ outputDebugString(luamDevelopmentMarker .. toJSON(record, true), 4)
40
+ end)
41
+
42
+ addEventHandler("onPlayerQuit", root, function()
43
+ luamRateBuckets[source] = nil
44
+ end)
package/lua/dotenv.lua ADDED
@@ -0,0 +1,160 @@
1
+ local DEFAULT_FILE = '.env'
2
+
3
+ local KEYWORDS = {
4
+ ['true'] = true,
5
+ ['false'] = false,
6
+ }
7
+
8
+ local ESCAPES = {
9
+ ['n'] = '\n',
10
+ ['r'] = '\r',
11
+ ['t'] = '\t',
12
+ ['"'] = '"',
13
+ ["'"] = "'",
14
+ ['\\'] = '\\',
15
+ }
16
+
17
+ local function trim(value)
18
+ return value:match('^%s*(.-)%s*$') or ''
19
+ end
20
+
21
+ local function unescape(character)
22
+ return ESCAPES[character] or ('\\' .. character)
23
+ end
24
+
25
+ local function cast(raw)
26
+ local quote = raw:sub(1, 1)
27
+
28
+ if quote == '"' or quote == "'" then
29
+ local quoted = raw:match('^' .. quote .. '(.*)' .. quote)
30
+
31
+ if quoted then
32
+ return (quoted:gsub('\\(.)', unescape))
33
+ end
34
+ end
35
+
36
+ local value = trim((raw:gsub('%s+#.*$', '')))
37
+ local keyword = KEYWORDS[value]
38
+
39
+ if keyword ~= nil then
40
+ return keyword
41
+ end
42
+
43
+ return tonumber(value) or value
44
+ end
45
+
46
+ local function read(path)
47
+ local file = fileOpen(path, true)
48
+
49
+ if not file then
50
+ return nil
51
+ end
52
+
53
+ local content = fileRead(file, fileGetSize(file))
54
+
55
+ fileClose(file)
56
+
57
+ return content
58
+ end
59
+
60
+ local function parse(path, content)
61
+ local values = {}
62
+ local number = 0
63
+
64
+ for line in (content .. '\n'):gmatch('(.-)\r?\n') do
65
+ number = number + 1
66
+
67
+ local trimmed = trim(line)
68
+
69
+ if trimmed ~= '' and trimmed:sub(1, 1) ~= '#' then
70
+ local key, raw = trimmed:match('^([%a_][%w_]*)%s*=%s*(.*)$')
71
+
72
+ if not key then
73
+ error('Malformed entry in "' .. path .. '" on line ' .. number .. '. Expected "KEY=value".')
74
+ end
75
+
76
+ values[key] = cast(raw)
77
+ end
78
+ end
79
+
80
+ return values
81
+ end
82
+
83
+ local function seal(path, values)
84
+ return setmetatable({}, {
85
+ __index = function(_, key)
86
+ local value = values[key]
87
+
88
+ if value == nil then
89
+ error('"' .. tostring(key) .. '" is not declared in "' .. path .. '".', 2)
90
+ end
91
+
92
+ return value
93
+ end,
94
+
95
+ __newindex = function(_, key)
96
+ error('The environment is read-only and "' .. tostring(key) .. '" cannot be assigned.', 2)
97
+ end,
98
+
99
+ __metatable = false,
100
+ })
101
+ end
102
+
103
+ if localPlayer ~= nil then
104
+ return
105
+ end
106
+
107
+ Dotenv = {}
108
+ Dotenv.__index = Dotenv
109
+
110
+ function Dotenv.new(path)
111
+ path = path or DEFAULT_FILE
112
+
113
+ local content = read(path)
114
+
115
+ if not content then
116
+ error('Failed to read the environment file "' .. tostring(path) .. '".', 2)
117
+ end
118
+
119
+ local self = setmetatable({}, Dotenv)
120
+
121
+ self.path = path
122
+ self.values = parse(path, content)
123
+
124
+ return self
125
+ end
126
+
127
+ function Dotenv:get(key, default)
128
+ local value = self.values[key]
129
+
130
+ if value == nil then
131
+ return default
132
+ end
133
+
134
+ return value
135
+ end
136
+
137
+ function Dotenv:has(key)
138
+ return self.values[key] ~= nil
139
+ end
140
+
141
+ function Dotenv:all()
142
+ local copy = {}
143
+
144
+ for key, value in pairs(self.values) do
145
+ copy[key] = value
146
+ end
147
+
148
+ return copy
149
+ end
150
+
151
+ function Dotenv:apply()
152
+ if type(process) ~= 'table' then
153
+ process = {}
154
+ end
155
+
156
+ process.env = seal(self.path, self.values)
157
+ env = process.env
158
+
159
+ return process.env
160
+ end
package/lua/env.lua ADDED
@@ -0,0 +1,18 @@
1
+ local ENVIRONMENT_FILE = '.env'
2
+
3
+ if localPlayer ~= nil then
4
+ return
5
+ end
6
+
7
+ if not fileExists(ENVIRONMENT_FILE) then
8
+ if type(process) ~= 'table' then
9
+ process = {}
10
+ end
11
+
12
+ process.env = {}
13
+ env = process.env
14
+
15
+ return
16
+ end
17
+
18
+ Dotenv.new(ENVIRONMENT_FILE):apply()
package/lua/math.lua ADDED
@@ -0,0 +1,24 @@
1
+ function math.clamp(value, minimum, maximum)
2
+ value = tonumber(value)
3
+
4
+ if not value then
5
+ return 0
6
+ end
7
+
8
+ minimum = tonumber(minimum) or value
9
+ maximum = tonumber(maximum) or value
10
+
11
+ if minimum > maximum then
12
+ minimum, maximum = maximum, minimum
13
+ end
14
+
15
+ if value < minimum then
16
+ return minimum
17
+ end
18
+
19
+ if value > maximum then
20
+ return maximum
21
+ end
22
+
23
+ return value
24
+ end
package/lua/string.lua ADDED
@@ -0,0 +1,82 @@
1
+ local function trim(value)
2
+ if type(value) ~= 'string' then
3
+ return tostring(value)
4
+ end
5
+
6
+ return value:match('^%s*(.-)%s*$') or ''
7
+ end
8
+
9
+ local function resolve(context, path)
10
+ local current = context
11
+
12
+ for key in path:gmatch('[^%.]+') do
13
+ if type(current) ~= 'table' then
14
+ return nil
15
+ end
16
+
17
+ current = current[key]
18
+
19
+ if current == nil then
20
+ return nil
21
+ end
22
+ end
23
+
24
+ return current
25
+ end
26
+
27
+ local function parse(raw)
28
+ local key, rest = raw:match('^(.-):(.+)$')
29
+
30
+ if not key then
31
+ return raw, nil
32
+ end
33
+
34
+ return key, trim(rest:match('^([^:]+)'))
35
+ end
36
+
37
+ function string.trim(value)
38
+ return trim(value)
39
+ end
40
+
41
+ function string.startsWith(value, prefix)
42
+ if type(value) ~= 'string' or type(prefix) ~= 'string' then
43
+ return false
44
+ end
45
+
46
+ return value:sub(1, prefix:len()) == prefix
47
+ end
48
+
49
+ function string.endsWith(value, suffix)
50
+ if type(value) ~= 'string' or type(suffix) ~= 'string' then
51
+ return false
52
+ end
53
+
54
+ if suffix == '' then
55
+ return true
56
+ end
57
+
58
+ return value:sub(-suffix:len()) == suffix
59
+ end
60
+
61
+ function string.template(text, context)
62
+ if type(text) ~= 'string' then
63
+ return tostring(text)
64
+ end
65
+
66
+ if type(context) ~= 'table' or next(context) == nil then
67
+ return text
68
+ end
69
+
70
+ return (
71
+ text:gsub('%${(.-)}', function(raw)
72
+ local key, default = parse(trim(raw))
73
+ local value = resolve(context, trim(key))
74
+
75
+ if value == nil then
76
+ return default ~= nil and default or ''
77
+ end
78
+
79
+ return tostring(value)
80
+ end)
81
+ )
82
+ end
package/lua/table.lua ADDED
@@ -0,0 +1,63 @@
1
+ function table.size(value)
2
+ local size = 0
3
+
4
+ if type(value) ~= 'table' then
5
+ return size
6
+ end
7
+
8
+ for _ in pairs(value) do
9
+ size = size + 1
10
+ end
11
+
12
+ return size
13
+ end
14
+
15
+ function table.isEmpty(value)
16
+ if type(value) ~= 'table' then
17
+ return true
18
+ end
19
+
20
+ return next(value) == nil
21
+ end
22
+
23
+ function table.keys(value)
24
+ local keys = {}
25
+
26
+ if type(value) ~= 'table' then
27
+ return keys
28
+ end
29
+
30
+ for key in pairs(value) do
31
+ keys[#keys + 1] = key
32
+ end
33
+
34
+ return keys
35
+ end
36
+
37
+ function table.values(value)
38
+ local values = {}
39
+
40
+ if type(value) ~= 'table' then
41
+ return values
42
+ end
43
+
44
+ for _, item in pairs(value) do
45
+ values[#values + 1] = item
46
+ end
47
+
48
+ return values
49
+ end
50
+
51
+ function table.includes(value, item)
52
+ if type(value) ~= 'table' then
53
+ return false
54
+ end
55
+
56
+ for _, current in pairs(value) do
57
+ if current == item then
58
+ return true
59
+ end
60
+ end
61
+
62
+ return false
63
+ end