hackmud-script-manager 0.20.4-9f460e8 → 0.20.4-a7d8309

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/bin/hsm.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { Cache } from "@samual/lib/Cache"
2
+ import { AutoMap } from "@samual/lib/AutoMap"
3
3
  import { assert } from "@samual/lib/assert"
4
4
  import { countHackmudCharacters } from "@samual/lib/countHackmudCharacters"
5
5
  import { writeFilePersistent } from "@samual/lib/writeFilePersistent"
@@ -13,11 +13,10 @@ import { syncMacros } from "../syncMacros.js"
13
13
  import "@samual/lib/readDirectoryWithStats"
14
14
  import "path/posix"
15
15
  import "@samual/lib/copyFilePersistent"
16
- const version = "0.20.4-9f460e8",
17
- formatOption = name => colourN(`-${1 == name.length ? "" : "-"}${name}`),
16
+ const formatOption = name => colourN(`-${1 == name.length ? "" : "-"}${name}`),
18
17
  options = new Map(),
19
18
  commands = [],
20
- userColours = new Cache(user => {
19
+ userColours = new AutoMap(user => {
21
20
  let hash = 0
22
21
  for (const char of user) hash += (hash >> 1) + hash + "xi1_8ratvsw9hlbgm02y5zpdcn7uekof463qj".indexOf(char) + 1
23
22
  return [colourJ, colourK, colourM, colourW, colourL, colourB][hash % 6](user)
@@ -59,11 +58,11 @@ const pushModule = import("../push.js"),
59
58
  process.version.startsWith("v21.") &&
60
59
  console.warn(
61
60
  colourF(
62
- "Warning: Support for Node.js 21 will be dropped in the next minor version of HSM\n You should update your version of Node.js\n https://nodejs.org/en/download/package-manager"
61
+ `Warning: Support for Node.js 21 will be dropped in the next minor version of HSM\n Your current version of Node.js is ${chalk.bold(process.version)}\n You should update your version of Node.js\n https://nodejs.org/en/download/package-manager\n`
63
62
  )
64
63
  )
65
64
  if ("v" == commands[0] || "version" == commands[0] || popOption("version", "v")?.value) {
66
- console.log(version)
65
+ console.log("0.20.4-a7d8309")
67
66
  process.exit()
68
67
  }
69
68
  if (popOption("help", "h")?.value) {
@@ -201,18 +200,27 @@ switch (commands[0]) {
201
200
  )
202
201
  } else infos.length || logError("Could not find any scripts to push")
203
202
  } else {
204
- const typeDeclarationPathOption = popOption(
203
+ const dtsPathOption = popOption(
204
+ "dts-path",
205
205
  "type-declaration-path",
206
206
  "type-declaration",
207
207
  "dts",
208
208
  "gen-types"
209
209
  )
210
+ dtsPathOption &&
211
+ "emit-dts" != dtsPathOption.name &&
212
+ "gen-dts" != dtsPathOption.name &&
213
+ console.warn(
214
+ colourF(
215
+ `Warning: ${colourN(dtsPathOption.name)} is being deprecated and will be removed in the\n next minor release of HSM\n You should switch to using its alias ${colourN("--dts-path")}\n`
216
+ )
217
+ )
210
218
  complainAboutUnrecognisedOptions()
211
219
  const { watch } = await watchModule
212
220
  watch(sourcePath, hackmudPath, {
213
221
  scripts,
214
222
  onPush: info => logInfo(info, hackmudPath),
215
- typeDeclarationPath: typeDeclarationPathOption?.value.toString(),
223
+ typeDeclarationPath: dtsPathOption?.value.toString(),
216
224
  minify: noMinifyOption && !noMinifyOption.value,
217
225
  mangleNames: mangleNamesOption?.value,
218
226
  onReady: () => log("Watching"),
@@ -252,7 +260,15 @@ switch (commands[0]) {
252
260
  case "gen-type-declaration":
253
261
  case "gen-dts":
254
262
  case "gen-types":
263
+ case "emit-dts":
255
264
  {
265
+ "emit-dts" != commands[0] &&
266
+ "gen-dts" != commands[0] &&
267
+ console.warn(
268
+ colourF(
269
+ `Warning: ${colourC("hsm")} ${colourL(commands[0])} is being deprecated and will be removed\n in the next minor release of HSM\n You should switch to using its alias ${colourC("hsm")} ${colourL("emit-dts")}\n`
270
+ )
271
+ )
256
272
  const hackmudPath = getHackmudPath(),
257
273
  target = commands[1]
258
274
  if (!target) {
@@ -266,7 +282,7 @@ switch (commands[0]) {
266
282
  typeDeclaration = await generateTypeDeclaration(sourcePath, hackmudPath)
267
283
  let typeDeclarationPath = resolve(outputPath)
268
284
  await writeFile(typeDeclarationPath, typeDeclaration).catch(error => {
269
- assert(error instanceof Error, "src/bin/hsm.ts:343:35")
285
+ assert(error instanceof Error, "src/bin/hsm.ts:361:35")
270
286
  if ("EISDIR" != error.code) throw error
271
287
  typeDeclarationPath = resolve(typeDeclarationPath, "player.d.ts")
272
288
  return writeFile(typeDeclarationPath, typeDeclaration)
@@ -275,7 +291,6 @@ switch (commands[0]) {
275
291
  }
276
292
  break
277
293
  case "help":
278
- case "h":
279
294
  logHelp()
280
295
  break
281
296
  default:
@@ -287,21 +302,20 @@ function logHelp() {
287
302
  const pushCommandDescription = "Push scripts from a directory to hackmud user's scripts directories",
288
303
  forceQuineCheatsOptionDescription = `Force quine cheats on. Use ${colourN("--force-quine-cheats")}=${colourV("false")} to force off`,
289
304
  hackmudPathOption = `${colourN("--hackmud-path")}=${colourB("<path>")}\n Override hackmud path`
290
- console.log(colourN("Version") + colourS(": ") + colourV(version))
291
305
  switch (commands[0]) {
292
306
  case "dev":
293
307
  case "watch":
294
308
  case "push":
295
309
  console.log(
296
310
  colourS(
297
- `\n${colourJ("push" == commands[0] ? pushCommandDescription : "Watch a directory and push a script when modified")}\n\n${colourA("Usage:")}\n${colourC("hsm")} ${colourL(commands[0])} ${colourB('<directory> ["<script user>.<script name>"...]')}\n\n${colourA("Arguments:")}\n${colourB("<directory>")}\n The source directory containing your scripts\n${colourB("<script user>")}\n A user to push script(s) to. Can be set to wild card (${colourV("*")}) which will try\n and discover users to push to\n${colourB("<script name>")}\n Name of a script to push. Can be set to wild card (${colourV("*")}) to find all scripts\n\n${colourA("Options:")}\n${colourN("--no-minify")}\n Skip minification to produce a "readable" script\n${colourN("--mangle-names")}\n Reduce character count further but lose function names in error call stacks\n${colourN("--force-quine-cheats")}\n ${forceQuineCheatsOptionDescription}\n${hackmudPathOption}\n${"push" == commands[0] ? "" : `${colourN("--type-declaration-path")}=${colourB("<path>")}\n Path to generate a type declaration file for the scripts\n`}\n${colourA("Examples:")}\n${colourC("hsm")} ${colourL(commands[0])} ${colourV("src")}\n Pushes all scripts found in ${colourV("src")} folder to all users\n${colourC("hsm")} ${colourL(commands[0])} ${colourV("src")} ${colourC("foo")}${colourV(".")}${colourL("bar")}\n Pushes a script named ${colourL("bar")} found in ${colourV("src")} folder to user ${userColours.get("foo")}\n${colourC("hsm")} ${colourL(commands[0])} ${colourV("src")} ${colourC("foo")}${colourV(".")}${colourL("bar")} ${colourC("baz")}${colourV(".")}${colourL("qux")}\n Multiple can be specified\n${colourC("hsm")} ${colourL(commands[0])} ${colourV("src")} ${colourC("foo")}${colourV(".")}${colourL("*")}\n Pushes all scripts found in ${colourV("src")} folder to user ${userColours.get("foo")}\n${colourC("hsm")} ${colourL(commands[0])} ${colourV("src")} ${colourC("*")}${colourV(".")}${colourL("foo")}\n Pushes all scripts named ${colourL("foo")} found in ${colourV("src")} folder to all user\n${colourC("hsm")} ${colourL(commands[0])} ${colourV("src")} ${colourC("*")}${colourV(".")}${colourL("*")}\n Pushes all scripts found in ${colourV("src")} folder to all users`
311
+ `${colourJ("push" == commands[0] ? pushCommandDescription : "Watch a directory and push a script when modified")}\n\n${colourA("Usage:")}\n${colourC("hsm")} ${colourL(commands[0])} ${colourB('<directory> ["<script user>.<script name>"...]')}\n\n${colourA("Arguments:")}\n${colourB("<directory>")}\n The source directory containing your scripts\n${colourB("<script user>")}\n A user to push script(s) to. Can be set to wild card (${colourV("*")}) which will try\n and discover users to push to\n${colourB("<script name>")}\n Name of a script to push. Can be set to wild card (${colourV("*")}) to find all scripts\n\n${colourA("Options:")}\n${colourN("--no-minify")}\n Skip minification to produce a "readable" script\n${colourN("--mangle-names")}\n Reduce character count further but lose function names in error call stacks\n${colourN("--force-quine-cheats")}\n ${forceQuineCheatsOptionDescription}\n${hackmudPathOption}\n${"push" == commands[0] ? "" : `${colourN("--dts-path")}=${colourB("<path>")}\n Path to generate a type declaration (.d.ts) file for the scripts\n`}\n${colourA("Examples:")}\n${colourC("hsm")} ${colourL(commands[0])} ${colourV("src")}\n Pushes all scripts found in ${colourV("src")} folder to all users\n${colourC("hsm")} ${colourL(commands[0])} ${colourV("src")} ${colourC("foo")}${colourV(".")}${colourL("bar")}\n Pushes a script named ${colourL("bar")} found in ${colourV("src")} folder to user ${userColours.get("foo")}\n${colourC("hsm")} ${colourL(commands[0])} ${colourV("src")} ${colourC("foo")}${colourV(".")}${colourL("bar")} ${colourC("baz")}${colourV(".")}${colourL("qux")}\n Multiple can be specified\n${colourC("hsm")} ${colourL(commands[0])} ${colourV("src")} ${colourC("foo")}${colourV(".")}${colourL("*")}\n Pushes all scripts found in ${colourV("src")} folder to user ${userColours.get("foo")}\n${colourC("hsm")} ${colourL(commands[0])} ${colourV("src")} ${colourC("*")}${colourV(".")}${colourL("foo")}\n Pushes all scripts named ${colourL("foo")} found in ${colourV("src")} folder to all user\n${colourC("hsm")} ${colourL(commands[0])} ${colourV("src")} ${colourC("*")}${colourV(".")}${colourL("*")}\n Pushes all scripts found in ${colourV("src")} folder to all users`
298
312
  )
299
313
  )
300
314
  break
301
315
  case "pull":
302
316
  console.log(
303
317
  colourS(
304
- `\n${colourJ("Pull a script a from a hackmud user's script directory")}\n\n${colourA("Usage:")}\n${colourC("hsm")} ${colourL(commands[0])} ${colourB("<script user>")}${colourV(".")}${colourB("<script name>")}\n\n${colourA("Options:")}\n${hackmudPathOption}`
318
+ `${colourJ("Pull a script a from a hackmud user's script directory")}\n\n${colourA("Usage:")}\n${colourC("hsm")} ${colourL(commands[0])} ${colourB("<script user>")}${colourV(".")}${colourB("<script name>")}\n\n${colourA("Options:")}\n${hackmudPathOption}`
305
319
  )
306
320
  )
307
321
  break
@@ -309,7 +323,7 @@ function logHelp() {
309
323
  case "golf":
310
324
  console.log(
311
325
  colourS(
312
- `\n${colourJ("Minify a script file on the spot")}\n\n${colourA("Usage:")}\n${colourC("hsm")} ${colourL(commands[0])} ${colourB("<target> [output path]")}\n\n${colourA("Options:")}\n${colourN("--no-minify")}\n Skip minification to produce a "readable" script\n${colourN("--mangle-names")}\n Reduce character count further but lose function names in error call stacks\n${colourN("--force-quine-cheats")}\n ${forceQuineCheatsOptionDescription}\n${colourN("--watch")}\n Watch for changes`
326
+ `${colourJ("Minify a script file on the spot")}\n\n${colourA("Usage:")}\n${colourC("hsm")} ${colourL(commands[0])} ${colourB("<target> [output path]")}\n\n${colourA("Options:")}\n${colourN("--no-minify")}\n Skip minification to produce a "readable" script\n${colourN("--mangle-names")}\n Reduce character count further but lose function names in error call stacks\n${colourN("--force-quine-cheats")}\n ${forceQuineCheatsOptionDescription}\n${colourN("--watch")}\n Watch for changes`
313
327
  )
314
328
  )
315
329
  break
@@ -317,6 +331,14 @@ function logHelp() {
317
331
  case "gen-type-declaration":
318
332
  case "gen-dts":
319
333
  case "gen-types":
334
+ case "emit-dts":
335
+ "emit-dts" != commands[0] &&
336
+ "gen-dts" != commands[0] &&
337
+ console.warn(
338
+ colourF(
339
+ `Warning: ${colourC("hsm")} ${colourL(commands[0])} is being deprecated and will be removed\n in the next minor release of HSM\n You should switch to using its alias ${colourC("hsm")} ${colourL("emit-dts")}\n`
340
+ )
341
+ )
320
342
  console.log(
321
343
  colourS(
322
344
  `${colourJ("Generate a type declaration file for a directory of scripts")}\n\n${colourA("Usage:")}\n${colourC("hsm")} ${colourL(commands[0])} ${colourB("<directory> [output path]")}\n\n${colourA("Options:")}\n${hackmudPathOption}`
@@ -326,14 +348,14 @@ function logHelp() {
326
348
  case "sync-macros":
327
349
  console.log(
328
350
  colourS(
329
- `\n${colourJ("Sync macros across all hackmud users")}\n\n${colourA("Options:")}\n${hackmudPathOption}`
351
+ `${colourJ("Sync macros across all hackmud users")}\n\n${colourA("Options:")}\n${hackmudPathOption}`
330
352
  )
331
353
  )
332
354
  break
333
355
  default:
334
356
  console.log(
335
357
  colourS(
336
- `\n${colourJ("Hackmud Script Manager")}\n\n${colourA("Commands:")}\n${colourL("push")}\n ${pushCommandDescription}\n${colourL("dev")}\n Watch a directory and push a script when modified\n${colourL("golf")}\n Minify a script file on the spot\n${colourL("gen-dts")}\n Generate a type declaration file for a directory of scripts\n${colourL("sync-macros")}\n Sync macros across all hackmud users\n${colourL("pull")}\n Pull a script a from a hackmud user's script directory`
358
+ `${colourJ("Hackmud Script Manager")}\n${colourN("Version") + colourS(": ") + colourV("0.20.4-a7d8309")}\n\n${colourA("Commands:")}\n${colourL("push")}\n ${pushCommandDescription}\n${colourL("dev")}\n Watch a directory and push a script when modified\n${colourL("minify")}\n Minify a script file on the spot\n${colourL("emit-dts")}\n Generate a type declaration file for a directory of scripts\n${colourL("sync-macros")}\n Sync macros across all hackmud users\n${colourL("pull")}\n Pull a script a from a hackmud user's script directory\n\n${colourA("Options:")}\n${colourN("--help")}\n Can be used on any command e.g. ${colourC("hsm")} ${colourL("push")} ${colourN("--help")} to show helpful information`
337
359
  )
338
360
  )
339
361
  }
@@ -351,7 +373,7 @@ function logError(message) {
351
373
  process.exitCode = 1
352
374
  }
353
375
  function getHackmudPath() {
354
- const hackmudPathOption = popOption("hackmud-path")
376
+ const hackmudPathOption = popOption("hackmud-path")?.value
355
377
  if (null != hackmudPathOption && "string" != typeof hackmudPathOption) {
356
378
  logError(`Option ${colourN("--hackmud-path")} must be a string, got ${colourV(hackmudPathOption)}\n`)
357
379
  logHelp()
package/env.d.ts CHANGED
@@ -1,9 +1,8 @@
1
1
  type Replace<A, B> = Omit<A, keyof B> & B
2
- type ScriptSuccess<T = object> = { ok: true } & T
2
+ type ScriptSuccess<T = unknown> = { ok: true } & T
3
3
  type ScriptFailure = { ok: false, msg?: string }
4
- type ScriptResponse<T = object> = ScriptSuccess<T> | ScriptFailure
4
+ type ScriptResponse<T = unknown> = ScriptSuccess<T> | ScriptFailure
5
5
  type ErrorScripts = Record<string, () => ScriptFailure>
6
- type Scriptor<TArgs extends any[] = any[]> = { name: string, call: (...args: TArgs) => unknown }
7
6
 
8
7
  type Subscripts = Record<string, Record<string, (...args: any) => any>> & {
9
8
  accts: ErrorScripts
@@ -28,8 +27,7 @@ interface PlayerLowsec {}
28
27
  interface PlayerNullsec {}
29
28
 
30
29
  type UpgradeRarityString = "`0noob`" | "`1kiddie`" | "`2h4x0r`" | "`3h4rdc0r3`" | "`4|_|b3|2`" | "`531337`"
31
- type UpgradeRarityNumber = 0 | 1 | 2 | 3 | 4 | 5;
32
- type UpgradeRarity = UpgradeRarityString | UpgradeRarityNumber;
30
+ type UpgradeRarityNumber = 0 | 1 | 2 | 3 | 4 | 5
33
31
 
34
32
  type UpgradeBase = {
35
33
  name: string
@@ -45,10 +43,8 @@ type UpgradeBase = {
45
43
 
46
44
  type Upgrade = UpgradeBase & Record<string, null | boolean | number | string>
47
45
 
48
- type CliUpgrade = Omit<UpgradeBase, `rarity`> & {
49
- [x: string]: null | boolean | number | string
50
- rarity: UpgradeRarityString
51
- }
46
+ type CliUpgrade = Omit<UpgradeBase, `rarity`> &
47
+ { [k: string]: null | boolean | number | string, rarity: UpgradeRarityString }
52
48
 
53
49
  type UsersTopItem<R> = { rank: R, name: string, last_activity: string, balance: string }
54
50
  type CorpsTopItem<R> = { rank: R, name: string, worth: string }
@@ -126,21 +122,11 @@ type Fullsec = Subscripts & PlayerFullsec & {
126
122
  }
127
123
 
128
124
  escrow: {
129
- /** **FULLSEC** */ charge: (args: {
130
- cost: number | string
131
- is_unlim?: boolean
132
- }) => null | ScriptFailure
133
-
125
+ /** **FULLSEC** */ charge: (args: { cost: number | string, is_unlim?: boolean }) => null | ScriptFailure
134
126
  confirm: never
135
127
  }
136
128
 
137
- gui: {
138
- chats: never
139
- quiet: never
140
- size: never
141
- vfx: never
142
- vol: never
143
- }
129
+ gui: { chats: never, quiet: never, size: never, vfx: never, vol: never }
144
130
 
145
131
  market: {
146
132
  /** **FULLSEC** */ browse: {
@@ -459,7 +445,10 @@ type Highsec = Fullsec & PlayerHighsec & {
459
445
  /** **HIGHSEC**
460
446
  * @returns GC balance as number if `is_script` is true (default).
461
447
  * @returns GC balance as string if `is_script` is false. */
462
- balance: ((args?: { is_script?: true }) => number) & ((args: { is_script: false }) => string)
448
+ balance: {
449
+ (args?: { is_script?: true }): number
450
+ (args: { is_script: false }): string
451
+ }
463
452
 
464
453
  /** **HIGHSEC**
465
454
  * @returns Transaction history according to filter.
@@ -716,95 +705,145 @@ type Nullsec = Lowsec & PlayerNullsec & {
716
705
  }
717
706
  }
718
707
 
719
- type MongoTypeString = "minKey" | "double" | "string" | "object" | "array" | "binData" | "undefined" | "objectId" |
720
- "bool" | "date" | "null" | "regex" | "dbPointer" | "javascript" | "symbol" | "int" | "timestamp" | "long" | "decimal" | "maxKey";
721
- type MongoTypeNumber = -1 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 17 | 18 | 19 | 127;
708
+ // database
709
+ type MongoPrimitive = null | boolean | number | Date | string
710
+ type MongoValue = MongoPrimitive | MongoValue[] | MongoObject
711
+ type MongoObject = { [k: string]: MongoValue, [k: `$${string}`]: never }
712
+ type MongoQueryValue = MongoPrimitive | MongoQueryValue[] | MongoQueryObject
713
+
714
+ type MongoQueryObject =
715
+ { [k: string]: MongoQueryValue, [k: `$${string}`]: MongoValue, $type?: keyof MongoTypeStringsToTypes | (string & {}) }
716
+
717
+ type MongoTypeStringsToTypes = {
718
+ double: number
719
+ string: string
720
+ object: MongoObject
721
+ array: MongoValue[]
722
+ objectId: ObjectId
723
+ bool: boolean
724
+ date: Date
725
+ null: null
726
+ int: number
727
+ long: number
728
+ }
729
+
730
+ type MongoTypeString = keyof MongoTypeStringsToTypes
731
+ type MongoTypeNumber = -1 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 17 | 18 | 19 | 127
732
+ type MongoId = Exclude<MongoPrimitive, null> | MongoObject
733
+ type MongoQueryId = Exclude<MongoPrimitive, null> | MongoQueryObject
734
+ type MongoDocument = MongoObject & { _id?: MongoId }
735
+
736
+ type MongoQueryType<TQuery extends MongoQueryObject> = {
737
+ -readonly [K in keyof TQuery]:
738
+ TQuery[K] extends MongoPrimitive ?
739
+ TQuery[K]
740
+ : TQuery[K] extends { $type: infer TType } ?
741
+ TType extends keyof MongoTypeStringsToTypes ? MongoTypeStringsToTypes[TType] : unknown
742
+ : TQuery[K] extends { $in: (infer TIn)[] } ?
743
+ TIn
744
+ : keyof TQuery[K] extends `$${string}` ?
745
+ unknown
746
+ : TQuery[K] extends { [k: string]: any } ?
747
+ MongoQueryType<TQuery[K]>
748
+ : never
749
+ }
722
750
 
723
- type MongoValue = string | number | boolean | Date | MongoValue[] | { [key: string]: MongoValue } | null
751
+ type MongoCommandValue = MongoPrimitive | MongoCommandValue[] | { [k: string]: MongoCommandValue }
752
+ type MongoArraySelectors<T extends MongoValue[] = MongoValue[]> = { $all: T, $elemMatch: T, $size: number }
724
753
 
725
- type MongoCommandValue = string | number | boolean | Date | MongoCommandValue[] | { [key: string]: MongoCommandValue } |
726
- null | undefined
754
+ type MongoComparisonSelectors<T extends MongoValue = MongoValue> =
755
+ { $eq: T, $gt: T, $gte: T, $in: T[], $lt: T, $lte: T, $ne: T, $nin: T[] }
727
756
 
728
- /**
729
- * Currently unused
730
- */
731
- type MongoLogicalSelectors<T extends MongoValue = MongoValue> = {
732
- $not: T | MongoComparisonSelectors<T> | MongoLogicalSelectors<T>
733
- $nor: T[]
734
- $or: T[]
735
- $and: T[]
736
- }
757
+ type MongoElementSelectors = { $exists: boolean, $type: MongoTypeNumber | MongoTypeString }
737
758
 
738
- type MongoArraySelectors<T extends Array<MongoValue> = Array<MongoValue>> = {
739
- $all: T
740
- $elemMatch: T
741
- $size: number
742
- }
759
+ type MongoQuerySelector<T extends MongoValue> = Partial<
760
+ T extends []
761
+ ? MongoArraySelectors<T> & MongoElementSelectors & MongoComparisonSelectors<T>
762
+ : MongoElementSelectors & MongoComparisonSelectors<T>
763
+ >
743
764
 
744
- type MongoComparisonSelectors<T extends MongoValue = MongoValue> = {
745
- $eq: T
746
- $gt: T
747
- $gte: T
748
- $in: T[]
749
- $lt: T
750
- $lte: T
751
- $ne: T
752
- $nin: T[]
753
- }
765
+ type MongoQuery<T extends MongoObject> = { [K in keyof T]?: T[K] | MongoQuerySelector<T[K]> } & { _id?: MongoId }
754
766
 
755
- type MongoElementSelectors = {
756
- $exists: boolean
757
- $type: MongoTypeNumber | MongoTypeString
758
- }
767
+ type MongoUpdateOperators<T extends MongoObject> = Partial<{
768
+ /* Universal operators */
769
+ $set: Partial<Record<string, MongoCommandValue> & T>
770
+ $setOnInsert: Partial<Record<string, MongoCommandValue> & T>
771
+ $unset: Partial<Record<string, ""> & T>
759
772
 
760
- type MongoQuerySelector<T extends MongoValue = MongoValue> = Partial<T extends MongoValue[] ?
761
- (MongoArraySelectors<T> & MongoElementSelectors & MongoComparisonSelectors<T>) :
762
- (MongoElementSelectors & MongoComparisonSelectors<T>)>
773
+ $rename: Partial<Record<string, string> & { [key in keyof T]: string }>
763
774
 
764
- type Query = { [key: string]: MongoValue | Query } & { _id?: Id, $in?: MongoValue[] }
765
- type Projection = Record<string, boolean | 0 | 1>
775
+ /* Date & number operators */
776
+ $inc: Record<string, number> &
777
+ { [K in keyof T as T[K] extends number | Date ? K : never]?: T[K] extends number ? number : Date }
766
778
 
767
- type MongoCommand = MongoCommandValue & Partial<
768
- { $set: Record<string, MongoCommandValue>, $push: Record<string, MongoCommandValue>, $unset: Record<string, ""> }
769
- >
779
+ $mul: Record<string, number> & { [K in keyof T as T[K] extends number ? K : never]?: number }
780
+ $min: Record<string, number> & { [K in keyof T as T[K] extends number ? K : never]?: number }
781
+ $max: Record<string, number> & { [K in keyof T as T[K] extends number ? K : never]?: number }
782
+
783
+ /* Array operators */
784
+ $pop: Record<string, -1 | 1> & { [K in keyof T as T[K] extends [] ? K : never]?: -1 | 1 }
785
+
786
+ $push: Record<string, MongoCommandValue> & {
787
+ [K in keyof T as T[K] extends [] ? K : never]?: (T[K] extends (infer U)[] ? U : never)
788
+ | MongoUpdateArrayOperatorModifiers<T[K]>
789
+ }
790
+
791
+ $addToSet: Partial<Record<string, MongoCommandValue> & {
792
+ [K in keyof T as T[K] extends [] ? K : never]: (T[K] extends (infer U)[] ? U : never)
793
+ | MongoUpdateArrayOperatorUniversalModifiers<T[K]>
794
+ }>
795
+
796
+ $pull: Partial<Record<string, MongoCommandValue> & {
797
+ [K in keyof T as T[K] extends [] ? K : never]: (T[K] extends (infer U)[] ? U : never)
798
+ | MongoQuerySelector<T[K]>
799
+ }>
800
+
801
+ $pullAll: Record<string, MongoCommandValue> & { [K in keyof T as T[K] extends [] ? K : never]?: T[K] }
802
+ }>
803
+
804
+ type MongoUpdateArrayOperatorUniversalModifiers<T> = { $each?: T extends [] ? T : T[] }
805
+
806
+ type MongoUpdateArrayOperatorModifiers<T> = MongoUpdateArrayOperatorUniversalModifiers<T> &
807
+ { $position?: number, $slice?: number, $sort?: 1 | -1 }
808
+
809
+ type MongoUpdateCommand<Schema extends MongoObject> = MongoUpdateOperators<Schema>
770
810
 
771
- type Id = string | number | boolean | Date | Record<string, MongoValue>
772
- type MongoDocument = { [key: string]: MongoValue, _id: Id }
773
811
  type SortOrder = { [key: string]: 1 | -1 | SortOrder }
774
812
 
775
- type Cursor = {
776
- /** Returns the first document that satisfies the query. */ first: () => MongoDocument | null
777
- /** Returns an array of documents that satisfy the query. */ array: () => MongoDocument[]
813
+ type Cursor<T> = {
814
+ /** Returns the first document that satisfies the query. */ first: () => T | null
815
+ /** Returns an array of documents that satisfy the query. */ array: () => T[]
778
816
  /** Returns the number of documents that match the query. */ count: () => number
779
817
 
780
818
  /** Returns the first document that satisfies the query. Also makes cursor unusable. */
781
- first_and_close: () => MongoDocument
819
+ first_and_close: () => T
782
820
 
783
821
  /** Returns an array of documents that satisfy the query. Also makes cursor unusable. */
784
- array_and_close: () => MongoDocument[]
822
+ array_and_close: () => T[]
785
823
 
786
824
  /** Returns the number of documents that match the query. Also makes cursor unusable. */
787
825
  count_and_close: () => number
788
826
 
789
827
  /** Run `callback` on each document that satisfied the query. */
790
- each: (callback: (document: MongoDocument) => void) => null
828
+ each: (callback: (document: T) => void) => null
791
829
 
792
830
  /** Returns a new cursor with documents sorted as specified.
793
831
  * A value of 1 sorts the property ascending, and -1 descending.
794
832
  * @param order The way the documents are to be sorted. */
795
- sort: (order?: SortOrder) => Cursor
833
+ sort: (order?: SortOrder) => Cursor<T>
796
834
 
797
835
  /** Returns a new cursor without the first number of documents.
798
836
  * @param count Number of documents to skip. */
799
- skip: (count: number) => Cursor
837
+ skip: (count: number) => Cursor<T>
800
838
 
801
839
  /** Returns a new cursor limited to a number of documents as specified.
802
840
  * @param count Number of documents. */
803
- limit: (count: number) => Cursor
841
+ limit: (count: number) => Cursor<T>
804
842
 
805
- /** @param key The key of the documents. */ distinct: ((key: string) => MongoValue[]) & ((key: "_id") => Id[])
843
+ /** @param key The key of the documents. */ distinct: { (key: string): MongoValue[], (key: "_id"): MongoId[] }
806
844
  /** Make cursor unusable. */ close: () => null
807
845
  NumberLong: (number: number) => number
846
+ // TODO what actually is the type here?
808
847
  ObjectId: () => any
809
848
  }
810
849
 
@@ -815,7 +854,9 @@ type CliContext = {
815
854
  /** The number of rows in the caller’s terminal. */ rows: number
816
855
 
817
856
  /** The name of the script that directly called this script, or null if called on the command line or as a
818
- * scriptor. */ calling_script: null
857
+ * scriptor. */
858
+ calling_script: null
859
+
819
860
  is_scriptor?: undefined
820
861
  is_brain?: undefined
821
862
  }
@@ -826,245 +867,212 @@ type SubscriptContext = Replace<CliContext, {
826
867
  calling_script: string
827
868
  }>
828
869
 
829
- type ScriptorContext =
830
- Replace<CliContext, { /** Whether the script is being run as a scriptor. */ is_scriptor: true }>
831
-
832
- type BrainContext =
833
- Replace<CliContext, { /** Whether the script is being run via a bot brain. */ is_brain: true }>
834
-
835
- type Context = CliContext | SubscriptContext | ScriptorContext | BrainContext
836
-
837
- /** Subscript space that can call FULLSEC scripts. */ declare const $fs: Fullsec
838
-
839
- /** Subscript space that can call HIGHSEC and above scripts. Makes your script HIGHSEC (overrides FULLSEC). */
840
- declare const $hs: Highsec
841
-
842
- /** Subscript space that can call MIDSEC and above scripts. Makes your script MIDSEC (overrides higher security levels).
843
- */
844
- declare const $ms: Midsec
845
-
846
- /** Subscript space that can call LOWSEC and above scripts. Makes your script LOWSEC (overrides higher security levels).
847
- */
848
- declare const $ls: Lowsec
849
-
850
- /** Subscript space that can call any script. Makes your script NULLSEC (overrides higher security levels). */
851
- declare const $ns: Nullsec
870
+ type ScriptorContext = Replace<CliContext, { /** Whether the script is being run as a scriptor. */ is_scriptor: true }>
871
+ type BrainContext = Replace<CliContext, { /** Whether the script is being run via a bot brain. */ is_brain: true }>
872
+
873
+ // _id is always returned unless _id: false is passed
874
+ // when anyField: true is given, other fields (except _id) are omitted
875
+
876
+ type MongoProject<TDocument, TProjection> =
877
+ true extends (1 extends TProjection[keyof TProjection] ? true : TProjection[keyof TProjection]) ?
878
+ (TProjection extends { _id: false | 0 } ? {} : { _id: TDocument extends { _id: infer TId } ? TId : MongoId }) &
879
+ {
880
+ [K in
881
+ keyof TDocument as K extends keyof TProjection ? TProjection[K] extends true | 1 ? K : never : never
882
+ ]: TDocument[K]
883
+ } &
884
+ {
885
+ -readonly [K in
886
+ keyof TProjection as TProjection[K] extends true | 1 ? K extends keyof TDocument ? never : K : never
887
+ ]?: MongoValue
888
+ }
889
+ : { [k: string]: MongoValue } & { [K in keyof TDocument as K extends keyof TProjection ? never : K]: TDocument[K] }
852
890
 
853
- /** Subscript space that can call FULLSEC scripts. */ declare const $4s: typeof $fs
891
+ type DeepFreeze<T> = { readonly [P in keyof T]: DeepFreeze<T[P]> }
854
892
 
855
- /** Subscript space that can call HIGHSEC and above scripts. Makes your script HIGHSEC (overrides FULLSEC). */
856
- declare const $3s: typeof $hs
893
+ declare global {
894
+ type Scriptor<TArgs extends any[] = any[]> = { name: string, call: (...args: TArgs) => unknown }
895
+ type Context = CliContext | SubscriptContext | ScriptorContext | BrainContext
896
+ type ObjectId = { $oid: string }
857
897
 
858
- /** Subscript space that can call MIDSEC and above scripts. Makes your script MIDSEC (overrides higher security levels).
859
- */
860
- declare const $2s: typeof $ms
898
+ /** Subscript space that can call FULLSEC scripts. */ const $fs: Fullsec
861
899
 
862
- /** Subscript space that can call LOWSEC and above scripts. Makes your script LOWSEC (overrides higher security levels).
863
- */
864
- declare const $1s: typeof $ls
900
+ /** Subscript space that can call HIGHSEC and above scripts. Makes your script HIGHSEC (overrides FULLSEC). */
901
+ const $hs: Highsec
865
902
 
866
- /** Subscript space that can call any script. Makes your script NULLSEC (overrides higher security levels). */
867
- declare const $0s: typeof $ns
903
+ /** Subscript space that can call MIDSEC and above scripts. Makes your script MIDSEC (overrides higher security levels).
904
+ */
905
+ const $ms: Midsec
868
906
 
869
- /** Subscript space that can call any script. Uses seclevel provided in comment before script (defaults to NULLSEC)
870
- * @example
871
- * // @ seclevel MIDSEC
872
- * // remove the space betwen "@" and "s", there's only a space because otherwise vscode breaks
873
- * export function script() {
874
- * $s.foo.bar() // will be converted to #ms.foo.bar()
875
- * } */
876
- declare const $s: Nullsec
907
+ /** Subscript space that can call LOWSEC and above scripts. Makes your script LOWSEC (overrides higher security levels).
908
+ */
909
+ const $ls: Lowsec
877
910
 
878
- type ObjectId = { $oid: string }
911
+ /** Subscript space that can call any script. Makes your script NULLSEC (overrides higher security levels). */
912
+ const $ns: Nullsec
879
913
 
880
- declare const $db: {
881
- /** Insert a document or documents into a collection.
882
- * @param documents A document or array of documents to insert into the collection. */
883
- i: (documents: object | object[]) => {
884
- ok: 1
885
- n: number
886
- opTime: { ts: "Undefined Conversion", t: number }
887
- electionId: "Undefined Conversion"
888
- operationTime: "Undefined Conversion"
889
- $clusterTime: {
890
- clusterTime: "Undefined Conversion"
891
- signature: { hash: "Undefined Conversion", keyId: "Undefined Conversion" }
892
- }
893
- }
914
+ /** Subscript space that can call FULLSEC scripts. */ const $4s: typeof $fs
894
915
 
895
- /** Remove documents from a collection.
896
- * @param query Specifies deletion criteria using query operators. */
897
- r: (query: Query) => {
898
- ok: 0 | 1
899
- n: number
900
- opTime: { ts: "Undefined Conversion", t: number }
901
- electionId: "Undefined Conversion"
902
- operationTime: "Undefined Conversion"
903
- $clusterTime: {
904
- clusterTime: "Undefined Conversion"
905
- signature: { hash: "Undefined Conversion", keyId: "Undefined Conversion" }
906
- }
907
- }
916
+ /** Subscript space that can call HIGHSEC and above scripts. Makes your script HIGHSEC (overrides FULLSEC). */
917
+ const $3s: typeof $hs
908
918
 
909
- /** Find documents in a collection or view and returns a cursor to the selected documents.
910
- * @param query Specifies deletion criteria using query operators.
911
- * @param projection Specifies the fields to return in the documents that match the query filter. */
912
- f: (query?: Query, projection?: Projection) => Cursor
913
-
914
- /** Update an existing documents in a collection.
915
- * @param query Specifies deletion criteria using query operators.
916
- * @param command The modifications to apply.
917
- * {@link https://docs.mongodb.com/manual/reference/method/db.collection.update/#parameters} */
918
- u: (query: Query | Query[], command: MongoCommand) => {
919
- ok: 0 | 1
920
- nModified: number
921
- n: number
922
- opTime: { ts: "Undefined Conversion", t: number }
923
- electionId: "Undefined Conversion"
924
- operationTime: "Undefined Conversion"
925
- $clusterTime: {
926
- clusterTime: "Undefined Conversion"
927
- signature: { hash: "Undefined Conversion", keyId: "Undefined Conversion" }
928
- }
929
- }
930
-
931
- /** Updates one document within the collection based on the filter.
932
- * @param query Specifies deletion criteria using query operators.
933
- * @param command The modifications to apply.
934
- * {@link https://docs.mongodb.com/manual/reference/method/db.collection.update/#parameters} */
935
- u1: (query: Query | Query[], command: MongoCommand) => {
936
- ok: 0 | 1
937
- nModified: number
938
- n: number
939
- opTime: {
940
- ts: "Undefined Conversion"
941
- t: number
942
- }
943
- electionId: "Undefined Conversion"
944
- operationTime: "Undefined Conversion"
945
- $clusterTime: {
946
- clusterTime: "Undefined Conversion"
947
- signature: {
948
- hash: "Undefined Conversion"
949
- keyId: "Undefined Conversion"
950
- }
951
- }
952
- }
919
+ /** Subscript space that can call MIDSEC and above scripts. Makes your script MIDSEC (overrides higher security levels).
920
+ */
921
+ const $2s: typeof $ms
953
922
 
954
- /** Update or insert or insert document.
955
- * Same as Update, but if no documents match the query, one document will be inserted based on the properties in
956
- * both the query and the command.
957
- * The `$setOnInsert` operator is useful to set defaults.
958
- * @param query Specifies deletion criteria using query operators.
959
- * @param command The modifications to apply.
960
- * {@link https://docs.mongodb.com/manual/reference/method/db.collection.update/#parameters} */
961
- us: (query: Query | Query[], command: MongoCommand) => {
962
- ok: 0 | 1
963
- nModified: number
964
- n: number
965
- opTime: { ts: "Undefined Conversion", t: number }
966
- electionId: "Undefined Conversion"
967
- operationTime: "Undefined Conversion"
968
- $clusterTime: {
969
- clusterTime: "Undefined Conversion"
970
- signature: { hash: "Undefined Conversion", keyId: "Undefined Conversion" }
971
- }
923
+ /** Subscript space that can call LOWSEC and above scripts. Makes your script LOWSEC (overrides higher security levels).
924
+ */
925
+ const $1s: typeof $ls
926
+
927
+ /** Subscript space that can call any script. Makes your script NULLSEC (overrides higher security levels). */
928
+ const $0s: typeof $ns
929
+
930
+ /** Subscript space that can call any script. Uses seclevel provided in comment before script (defaults to NULLSEC)
931
+ * @example
932
+ * // @​seclevel MIDSEC
933
+ * // note, do NOT copy paste the above line because there is a zero-width space inserted between "@" and "s"
934
+ * export function script() {
935
+ * $s.foo.bar() // will be converted to #ms.foo.bar()
936
+ * } */
937
+ const $s: Nullsec
938
+
939
+ const $db: {
940
+ /** Insert a document or documents into a collection.
941
+ * @param documents A document or array of documents to insert into the collection. */
942
+ i: <T extends MongoDocument>(documents: (T & { _id?: MongoId }) | (T & { _id?: MongoId })[]) =>
943
+ { n: number, opTime: { t: number }, ok: 0 | 1 }[]
944
+
945
+ /** Remove documents from a collection.
946
+ * @param query Specifies deletion criteria using query operators. */
947
+ r: <T extends MongoDocument>(query: MongoQuery<T>) => { n: number, opTime: { t: number }, ok: 0 | 1 }[]
948
+
949
+ /** Find documents in a collection or view and returns a cursor to the selected documents.
950
+ * @param query Specifies deletion criteria using query operators.
951
+ * @param projection Specifies the fields to return in the documents that match the query filter. */
952
+ f: <
953
+ const TQuery extends MongoQueryObject & { _id?: MongoQueryId },
954
+ const TProjection extends { [k: string]: boolean | 0 | 1 } = {}
955
+ >(query: TQuery, projection?: TProjection) => Cursor<MongoProject<MongoQueryType<TQuery>, TProjection>>
956
+
957
+ /** Update existing documents in a collection.
958
+ * @param query Specifies deletion criteria using query operators.
959
+ * @param command The modifications to apply.
960
+ * {@link https://docs.mongodb.com/manual/reference/method/db.collection.update/#parameters} */
961
+ u: <T extends MongoDocument>(query: MongoQuery<T> | MongoQuery<T>[], command: MongoUpdateCommand<T>) =>
962
+ { n: number, opTime: { t: number }, ok: 0 | 1, nModified: number }[]
963
+
964
+ /** Updates one document within the collection based on the filter.
965
+ * @param query Specifies deletion criteria using query operators.
966
+ * @param command The modifications to apply.
967
+ * {@link https://docs.mongodb.com/manual/reference/method/db.collection.update/#parameters} */
968
+ u1: <T extends MongoDocument>(query: MongoQuery<T> | MongoQuery<T>[], command: MongoUpdateCommand<T>) =>
969
+ { n: number, ok: 0 | 1, opTime: { t: number }, nModified: number }[]
970
+
971
+ /** Update or insert document.
972
+ * Same as Update, but if no documents match the query, one document will be inserted based on the properties in
973
+ * both the query and the command.
974
+ * The `$setOnInsert` operator is useful to set defaults.
975
+ * @param query Specifies deletion criteria using query operators.
976
+ * @param command The modifications to apply.
977
+ * {@link https://docs.mongodb.com/manual/reference/method/db.collection.update/#parameters} */
978
+ us: <T extends MongoDocument>(query: MongoQuery<T> | MongoQuery<T>[], command: MongoUpdateCommand<T>) =>
979
+ { n: number, ok: 0 | 1, opTime: { t: number }, nModified: number }[]
980
+
981
+ ObjectId: () => ObjectId
972
982
  }
973
983
 
974
- ObjectId: () => ObjectId
984
+ /** Debug Log.
985
+ *
986
+ * If `$D()` is called in a script you own, the `return` value of the top level script is suppressed and instead an
987
+ * array of every `$D()`’d entry is printed.
988
+ * This lets you use `$D()` like `console.log()`.
989
+ *
990
+ * `$D()` in scripts not owned by you are not shown but the `return` value always is.
991
+ *
992
+ * `$D()` returns the first argument so `$D("Hello, World!") evaluates to `"Hello, World!"` as if the `$D` text wasn't
993
+ * there.
994
+ *
995
+ * `$D()`’d items are returned even if the script times out or errors. */
996
+ function $D<T>(args: T): T
997
+
998
+ /** Function Multi-Call Lock.
999
+ *
1000
+ * This is used by escrow to ensure that it is only used once in script execution.
1001
+ *
1002
+ * The first time (per-script) `$FMCL` is encountered, it returns `undefined`, every other time it `return`s `true`.
1003
+ *
1004
+ * @example
1005
+ * if ($FMCL)
1006
+ * return { ok: false, msg: "This script can only be used once per script execution." }
1007
+ *
1008
+ * // all code here will only run once */
1009
+ const $FMCL: undefined | true
1010
+
1011
+ /** Per-script mutable "global" persistent object that is discarded at the end of top level script execution.
1012
+ *
1013
+ * `$G` persists between script calls until the end of the main script run making it useful for caching db entries when
1014
+ * your script is a subscript.
1015
+ * @example
1016
+ * if (!$G.dbCache)
1017
+ * $G.dbCache = $db.f({ whatever: true }).first() */
1018
+ const $G: Record<string | symbol, any>
1019
+
1020
+ /** This contains a JS timestamp (not Date) set immediately before your code begins running.
1021
+ * @example
1022
+ * $D(Date.now() - _START) // milliseconds left of run time
1023
+ */
1024
+ const _START: number
1025
+
1026
+ /** This contains a JS timestamp (not Date) set immediately before your code begins running.
1027
+ * @example
1028
+ * $D(Date.now() - _ST) // milliseconds left of run time */
1029
+ const _ST: typeof _START
1030
+
1031
+ /** JavaScript timestamp for the end of the script run (`_START + _TIMEOUT`). */ const _END: number
1032
+
1033
+ /** The number of milliseconds a script can run for. Normally `5000` though it has been known to change. */
1034
+ const _TIMEOUT: number
1035
+
1036
+ /** The number of milliseconds a script can run for. Normally `5000` though it has been known to change. */
1037
+ const _TO: typeof _TIMEOUT
1038
+
1039
+ /** The source code of this script as a string. */ const _SOURCE: string
1040
+ /** A unix timestamp of the date this script was built. */ const _BUILD_DATE: number
1041
+
1042
+ /** The user this script has been uploaded to.
1043
+ *
1044
+ * Shorter alternative to `context.this_script.split(".")[0].
1045
+ *
1046
+ * In rare cases where it's not known at build time, it's `"UNKNOWN"`. */
1047
+ const _SCRIPT_USER: string
1048
+
1049
+ /** The name of this script excluding the user and `.`.
1050
+ *
1051
+ * e.g. in the script `foo.bar`, `_SCRIPT_NAME` is `bar`.
1052
+ *
1053
+ * Shorter alternative to `context.this_script.split(".")[1].
1054
+ *
1055
+ * In rare cases where it's not known at build time, it's `"UNKNOWN"`. */
1056
+ const _SCRIPT_NAME: string
1057
+
1058
+ /** The full name of this script equivilent to `context.this_script` but should use less characters.
1059
+ *
1060
+ * In rare cases where it's not known at build time, it's `"UNKNOWN"`. */
1061
+ const _FULL_SCRIPT_NAME: string
1062
+
1063
+ /** The seclevel of this script as a number.
1064
+ *
1065
+ * In rare cases where it's not known at build time, it's `-1`. */
1066
+ const _SECLEVEL: -1 | 0 | 1 | 2 | 3 | 4
1067
+
1068
+ /** Recursively
1069
+ * [`Object.freeze()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze)
1070
+ * an object and its properties' objects and its properties' objects and so on.
1071
+ *
1072
+ * [Official Hackmud Wiki](https://wiki.hackmud.com/scripting/extensions/deep_freeze) */
1073
+ const DEEP_FREEZE: <T>(value: T) => DeepFreeze<T>
1074
+
1075
+ const _RUN_ID: string
975
1076
  }
976
1077
 
977
- /** Debug Log.
978
- *
979
- * If `$D()` is called in a script you own, the `return` value of the top level script is suppressed and instead an
980
- * array of every `$D()`’d entry is printed.
981
- * This lets you use `$D()` like `console.log()`.
982
- *
983
- * `$D()` in scripts not owned by you are not shown but the `return` value always is.
984
- *
985
- * `$D()` returns the first argument so `$D("Hello, World!") evaluates to `"Hello, World!"` as if the `$D` text wasn't
986
- * there.
987
- *
988
- * `$D()`’d items are returned even if the script times out or errors. */
989
- declare function $D<T>(args: T): T
990
-
991
- /** Function Multi-Call Lock.
992
- *
993
- * This is used by escrow to ensure that it is only used once in script execution.
994
- *
995
- * The first time (per-script) `$FMCL` is encountered, it returns `undefined`, every other time it `return`s `true`.
996
- *
997
- * @example
998
- * if ($FMCL)
999
- * return { ok: false, msg: "This script can only be used once per script execution." }
1000
- *
1001
- * // all code here will only run once */
1002
- declare const $FMCL: undefined | true
1003
-
1004
- /** Per-script mutable "global" persistent object that is discarded at the end of top level script execution.
1005
- *
1006
- * `$G` persists between script calls until the end of the main script run making it useful for caching db entries when
1007
- * your script is a subscript.
1008
- * @example
1009
- * if (!$G.dbCache)
1010
- * $G.dbCache = $db.f({ whatever: true }).first() */
1011
- declare const $G: Record<string | symbol, any>
1012
-
1013
- /** This contains a JS timestamp (not Date) set immediately before your code begins running.
1014
- * @example
1015
- * $D(Date.now() - _START) // milliseconds left of run time
1016
- */
1017
- declare const _START: number
1018
-
1019
- /** This contains a JS timestamp (not Date) set immediately before your code begins running.
1020
- * @example
1021
- * $D(Date.now() - _ST) // milliseconds left of run time */
1022
- declare const _ST: typeof _START
1023
-
1024
- /** JavaScript timestamp for the end of the script run (`_START + _TIMEOUT`). */ declare const _END: number
1025
-
1026
- /** The number of milliseconds a script can run for. Normally `5000` though it has been known to change. */
1027
- declare const _TIMEOUT: number
1028
-
1029
- /** The number of milliseconds a script can run for. Normally `5000` though it has been known to change. */
1030
- declare const _TO: typeof _TIMEOUT
1031
-
1032
- /** The source code of this script as a string. */ declare const _SOURCE: string
1033
- /** A unix timestamp of the date this script was built. */ declare const _BUILD_DATE: number
1034
-
1035
- /** The user this script has been uploaded to.
1036
- *
1037
- * Shorter alternative to `context.this_script.split(".")[0].
1038
- *
1039
- * In rare cases where it's not known at build time, it's `"UNKNOWN"`. */
1040
- declare const _SCRIPT_USER: string
1041
-
1042
- /** The name of this script excluding the user and `.`.
1043
- *
1044
- * e.g. in the script `foo.bar`, `_SCRIPT_NAME` is `bar`.
1045
- *
1046
- * Shorter alternative to `context.this_script.split(".")[1].
1047
- *
1048
- * In rare cases where it's not known at build time, it's `"UNKNOWN"`. */
1049
- declare const _SCRIPT_NAME: string
1050
-
1051
- /** The full name of this script equivilent to `context.this_script` but should use less characters.
1052
- *
1053
- * In rare cases where it's not known at build time, it's `"UNKNOWN"`. */
1054
- declare const _FULL_SCRIPT_NAME: string
1055
-
1056
- /** The seclevel of this script as a number.
1057
- *
1058
- * In rare cases where it's not known at build time, it's `-1`. */
1059
- declare const _SECLEVEL: -1 | 0 | 1 | 2 | 3 | 4
1060
-
1061
- type DeepFreeze<T> = { readonly [P in keyof T]: DeepFreeze<T[P]> }
1062
-
1063
- /** Recursively
1064
- * [`Object.freeze()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze)
1065
- * an object and its properties' objects and its properties' objects and so on.
1066
- *
1067
- * [Official Hackmud Wiki](https://wiki.hackmud.com/scripting/extensions/deep_freeze) */
1068
- declare const DEEP_FREEZE: <T>(value: T) => DeepFreeze<T>
1069
-
1070
- declare const _RUN_ID: string
1078
+ export {}
package/index.js CHANGED
@@ -46,7 +46,7 @@ import "import-meta-resolve"
46
46
  import "./processScript/transform.js"
47
47
  import "@samual/lib/clearObject"
48
48
  import "@samual/lib/copyFilePersistent"
49
- import "@samual/lib/Cache"
49
+ import "@samual/lib/AutoMap"
50
50
  import "@samual/lib/writeFilePersistent"
51
51
  import "fs/promises"
52
52
  import "chokidar"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hackmud-script-manager",
3
- "version": "0.20.4-9f460e8",
3
+ "version": "0.20.4-a7d8309",
4
4
  "description": "Script manager for game hackmud, with minification, TypeScript support, and player script type definition generation.",
5
5
  "keywords": [
6
6
  "api",
@@ -31,50 +31,50 @@
31
31
  "url": "https://github.com/samualtnorman/hackmud-script-manager.git"
32
32
  },
33
33
  "dependencies": {
34
- "@babel/generator": "^7.24.4",
35
- "@babel/parser": "^7.24.4",
36
- "@babel/plugin-proposal-decorators": "^7.24.1",
37
- "@babel/plugin-proposal-destructuring-private": "^7.24.1",
38
- "@babel/plugin-proposal-do-expressions": "^7.24.1",
39
- "@babel/plugin-proposal-explicit-resource-management": "^7.24.1",
40
- "@babel/plugin-proposal-function-bind": "^7.24.1",
41
- "@babel/plugin-proposal-function-sent": "^7.24.1",
42
- "@babel/plugin-proposal-partial-application": "^7.24.1",
43
- "@babel/plugin-proposal-pipeline-operator": "^7.24.1",
44
- "@babel/plugin-proposal-record-and-tuple": "^7.24.1",
45
- "@babel/plugin-proposal-throw-expressions": "^7.24.1",
46
- "@babel/plugin-transform-class-properties": "^7.24.1",
47
- "@babel/plugin-transform-class-static-block": "^7.24.4",
48
- "@babel/plugin-transform-exponentiation-operator": "^7.24.1",
49
- "@babel/plugin-transform-json-strings": "^7.24.1",
50
- "@babel/plugin-transform-logical-assignment-operators": "^7.24.1",
51
- "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.1",
52
- "@babel/plugin-transform-numeric-separator": "^7.24.1",
53
- "@babel/plugin-transform-object-rest-spread": "^7.24.1",
54
- "@babel/plugin-transform-optional-catch-binding": "^7.24.1",
55
- "@babel/plugin-transform-optional-chaining": "^7.24.1",
56
- "@babel/plugin-transform-private-property-in-object": "^7.24.1",
57
- "@babel/plugin-transform-typescript": "^7.24.4",
58
- "@babel/plugin-transform-unicode-sets-regex": "^7.24.1",
59
- "@babel/traverse": "^7.24.1",
60
- "@babel/types": "^7.24.0",
34
+ "@babel/generator": "^7.26.2",
35
+ "@babel/parser": "^7.26.2",
36
+ "@babel/plugin-proposal-decorators": "^7.25.9",
37
+ "@babel/plugin-proposal-destructuring-private": "^7.26.0",
38
+ "@babel/plugin-proposal-do-expressions": "^7.25.9",
39
+ "@babel/plugin-proposal-explicit-resource-management": "^7.25.9",
40
+ "@babel/plugin-proposal-function-bind": "^7.25.9",
41
+ "@babel/plugin-proposal-function-sent": "^7.25.9",
42
+ "@babel/plugin-proposal-partial-application": "^7.25.9",
43
+ "@babel/plugin-proposal-pipeline-operator": "^7.25.9",
44
+ "@babel/plugin-proposal-record-and-tuple": "^7.25.9",
45
+ "@babel/plugin-proposal-throw-expressions": "^7.25.9",
46
+ "@babel/plugin-transform-class-properties": "^7.25.9",
47
+ "@babel/plugin-transform-class-static-block": "^7.26.0",
48
+ "@babel/plugin-transform-exponentiation-operator": "^7.25.9",
49
+ "@babel/plugin-transform-json-strings": "^7.25.9",
50
+ "@babel/plugin-transform-logical-assignment-operators": "^7.25.9",
51
+ "@babel/plugin-transform-nullish-coalescing-operator": "^7.25.9",
52
+ "@babel/plugin-transform-numeric-separator": "^7.25.9",
53
+ "@babel/plugin-transform-object-rest-spread": "^7.25.9",
54
+ "@babel/plugin-transform-optional-catch-binding": "^7.25.9",
55
+ "@babel/plugin-transform-optional-chaining": "^7.25.9",
56
+ "@babel/plugin-transform-private-property-in-object": "^7.25.9",
57
+ "@babel/plugin-transform-typescript": "^7.25.9",
58
+ "@babel/plugin-transform-unicode-sets-regex": "^7.25.9",
59
+ "@babel/traverse": "^7.25.9",
60
+ "@babel/types": "^7.26.0",
61
61
  "@bloomberg/record-tuple-polyfill": "^0.0.4",
62
62
  "@rollup/plugin-babel": "^6.0.4",
63
- "@rollup/plugin-commonjs": "^25.0.7",
63
+ "@rollup/plugin-commonjs": "^28.0.1",
64
64
  "@rollup/plugin-json": "^6.1.0",
65
- "@rollup/plugin-node-resolve": "^15.2.3",
66
- "@samual/lib": "0.11.0",
67
- "acorn": "^8.11.3",
65
+ "@rollup/plugin-node-resolve": "^15.3.0",
66
+ "@samual/lib": "^0.13.0",
67
+ "acorn": "^8.14.0",
68
68
  "chalk": "^5.3.0",
69
- "chokidar": "^3.6.0",
70
- "import-meta-resolve": "^4.0.0",
71
- "prettier": "^3.2.5",
69
+ "chokidar": "^4.0.1",
70
+ "import-meta-resolve": "^4.1.0",
71
+ "prettier": "^3.3.3",
72
72
  "proxy-polyfill": "^0.3.2",
73
- "rollup": "^4.16.4",
74
- "terser": "^5.30.4"
73
+ "rollup": "^4.27.4",
74
+ "terser": "^5.36.0"
75
75
  },
76
76
  "peerDependencies": {
77
- "typescript": "5.4.5"
77
+ "typescript": "^5.4.5"
78
78
  },
79
79
  "type": "module",
80
80
  "exports": {
@@ -162,21 +162,25 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
162
162
  if (program.scope.hasGlobal("_SECLEVEL"))
163
163
  for (const referencePath of getReferencePathsToGlobal("_SECLEVEL", program))
164
164
  referencePath.replaceWith(t.numericLiteral(seclevel))
165
- let needGetPrototypeOf = !1
165
+ let needGetPrototypeOf = !1,
166
+ needHasOwn = !1
166
167
  if (program.scope.hasGlobal("Object"))
167
168
  for (const referencePath of getReferencePathsToGlobal("Object", program))
168
169
  if ("MemberExpression" == referencePath.parent.type && !referencePath.parent.computed) {
169
- assert("Identifier" == referencePath.parent.property.type, "src/processScript/transform.ts:241:64")
170
+ assert("Identifier" == referencePath.parent.property.type, "src/processScript/transform.ts:242:64")
170
171
  if ("getPrototypeOf" == referencePath.parent.property.name) {
171
172
  referencePath.parentPath.replaceWith(t.identifier(`_${uniqueId}_GET_PROTOTYPE_OF_`))
172
173
  needGetPrototypeOf = !0
174
+ } else if ("hasOwn" == referencePath.parent.property.name) {
175
+ referencePath.parentPath.replaceWith(t.identifier(`_${uniqueId}_HAS_OWN_`))
176
+ needHasOwn = !0
173
177
  }
174
178
  }
175
179
  const consoleMethodsReferenced = new Set()
176
180
  if (program.scope.hasGlobal("console"))
177
181
  for (const referencePath of getReferencePathsToGlobal("console", program))
178
182
  if ("MemberExpression" == referencePath.parent.type && !referencePath.parent.computed) {
179
- assert("Identifier" == referencePath.parent.property.type, "src/processScript/transform.ts:256:64")
183
+ assert("Identifier" == referencePath.parent.property.type, "src/processScript/transform.ts:260:64")
180
184
  referencePath.parentPath.replaceWith(
181
185
  t.identifier(`_${uniqueId}_CONSOLE_METHOD_${referencePath.parent.property.name}_`)
182
186
  )
@@ -184,13 +188,13 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
184
188
  }
185
189
  const lastStatement = program.node.body.at(-1)
186
190
  let exportDefaultName
187
- assert(lastStatement, "src/processScript/transform.ts:270:27 program is empty")
191
+ assert(lastStatement, "src/processScript/transform.ts:274:27 program is empty")
188
192
  if ("ExportNamedDeclaration" == lastStatement.type) {
189
193
  program.node.body.pop()
190
194
  for (const specifier of lastStatement.specifiers) {
191
195
  assert(
192
196
  "ExportSpecifier" == specifier.type,
193
- `src/processScript/transform.ts:276:51 ${specifier.type} is currently unsupported`
197
+ `src/processScript/transform.ts:280:51 ${specifier.type} is currently unsupported`
194
198
  )
195
199
  if (
196
200
  "default" !=
@@ -292,11 +296,11 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
292
296
  let hoistedGlobalBlockFunctions = 0
293
297
  for (const [globalBlockIndex, globalBlockStatement] of [...globalBlock.body.entries()].reverse())
294
298
  if ("VariableDeclaration" == globalBlockStatement.type) {
295
- assert(1 == globalBlockStatement.declarations.length, "src/processScript/transform.ts:390:59")
299
+ assert(1 == globalBlockStatement.declarations.length, "src/processScript/transform.ts:394:59")
296
300
  const declarator = globalBlockStatement.declarations[0]
297
301
  assert(
298
302
  "Identifier" == declarator.id.type,
299
- `src/processScript/transform.ts:394:51 declarator.id.type was "${declarator.id.type}"`
303
+ `src/processScript/transform.ts:398:51 declarator.id.type was "${declarator.id.type}"`
300
304
  )
301
305
  program.scope.crawl()
302
306
  if (program.scope.hasGlobal(declarator.id.name)) {
@@ -311,9 +315,9 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
311
315
  Object.keys(program.scope.globals).some(global => globalBlockVariables.has(global))
312
316
  ) {
313
317
  const binding = program.scope.getBinding(declarator.id.name)
314
- assert(binding, "src/processScript/transform.ts:413:23")
318
+ assert(binding, "src/processScript/transform.ts:417:23")
315
319
  for (const referencePath of binding.referencePaths) {
316
- assert("Identifier" == referencePath.node.type, "src/processScript/transform.ts:416:56")
320
+ assert("Identifier" == referencePath.node.type, "src/processScript/transform.ts:420:56")
317
321
  referencePath.replaceWith(
318
322
  t.memberExpression(
319
323
  t.identifier(`_${uniqueId}_G_`),
@@ -361,16 +365,16 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
361
365
  } else globalBlockVariables.add(declarator.id.name)
362
366
  } else if ("ClassDeclaration" == globalBlockStatement.type) {
363
367
  program.scope.crawl()
364
- assert(globalBlockStatement.id, "src/processScript/transform.ts:473:37")
368
+ assert(globalBlockStatement.id, "src/processScript/transform.ts:477:37")
365
369
  if (program.scope.hasGlobal(globalBlockStatement.id.name)) {
366
370
  globalBlock.body.splice(globalBlockIndex, 1)
367
371
  const [globalBlockPath] = program.unshiftContainer("body", globalBlock),
368
372
  [globalBlockStatementPath] = program.unshiftContainer("body", globalBlockStatement)
369
373
  program.scope.crawl()
370
374
  const binding = program.scope.getBinding(globalBlockStatement.id.name)
371
- assert(binding, "src/processScript/transform.ts:485:22")
375
+ assert(binding, "src/processScript/transform.ts:489:22")
372
376
  for (const referencePath of binding.referencePaths) {
373
- assert("Identifier" == referencePath.node.type, "src/processScript/transform.ts:488:55")
377
+ assert("Identifier" == referencePath.node.type, "src/processScript/transform.ts:492:55")
374
378
  referencePath.replaceWith(
375
379
  t.memberExpression(t.identifier(`_${uniqueId}_G_`), t.identifier(referencePath.node.name))
376
380
  )
@@ -448,6 +452,31 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
448
452
  )
449
453
  ])
450
454
  )
455
+ needHasOwn &&
456
+ mainFunction.body.body.unshift(
457
+ t.variableDeclaration("let", [
458
+ t.variableDeclarator(
459
+ t.identifier(`_${uniqueId}_HAS_OWN_`),
460
+ t.callExpression(
461
+ t.memberExpression(
462
+ t.memberExpression(
463
+ t.identifier(
464
+ globalFunctionsUnder7Characters.find(name => !program.scope.hasOwnBinding(name))
465
+ ),
466
+ t.identifier("call")
467
+ ),
468
+ t.identifier("bind")
469
+ ),
470
+ [
471
+ t.memberExpression(
472
+ t.memberExpression(t.identifier("Object"), t.identifier("prototype")),
473
+ t.identifier("hasOwnProperty")
474
+ )
475
+ ]
476
+ )
477
+ )
478
+ ])
479
+ )
451
480
  consoleMethodsReferenced.size &&
452
481
  mainFunction.body.body.unshift(
453
482
  t.variableDeclaration(
@@ -537,7 +566,7 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
537
566
  }
538
567
  },
539
568
  ClassBody({ node: classBody, scope, parent }) {
540
- assert(t.isClass(parent), "src/processScript/transform.ts:658:30")
569
+ assert(t.isClass(parent), "src/processScript/transform.ts:687:30")
541
570
  let thisIsReferenced = !1
542
571
  for (const classMethod of classBody.body) {
543
572
  if ("ClassMethod" != classMethod.type) continue
@@ -637,23 +666,23 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
637
666
  }
638
667
  function processFakeSubscriptObject(fakeSubscriptObjectName, seclevel) {
639
668
  for (const referencePath of getReferencePathsToGlobal(fakeSubscriptObjectName, program)) {
640
- assert("MemberExpression" == referencePath.parent.type, "src/processScript/transform.ts:764:60")
669
+ assert("MemberExpression" == referencePath.parent.type, "src/processScript/transform.ts:793:60")
641
670
  assert("Identifier" == referencePath.parent.property.type)
642
671
  assert(
643
672
  "MemberExpression" == referencePath.parentPath.parentPath?.node.type,
644
- "src/processScript/transform.ts:766:81"
673
+ "src/processScript/transform.ts:795:81"
645
674
  )
646
675
  assert(
647
676
  "Identifier" == referencePath.parentPath.parentPath.node.property.type,
648
- "src/processScript/transform.ts:767:83"
677
+ "src/processScript/transform.ts:796:83"
649
678
  )
650
679
  assert(
651
680
  /^[_a-z][\d_a-z]{0,24}$/.test(referencePath.parent.property.name),
652
- `src/processScript/transform.ts:771:8 invalid user "${referencePath.parent.property.name}" in subscript`
681
+ `src/processScript/transform.ts:800:8 invalid user "${referencePath.parent.property.name}" in subscript`
653
682
  )
654
683
  assert(
655
684
  /^[_a-z][\d_a-z]{0,24}$/.test(referencePath.parentPath.parentPath.node.property.name),
656
- `src/processScript/transform.ts:776:8 invalid script name "${referencePath.parentPath.parentPath.node.property.name}" in subscript`
685
+ `src/processScript/transform.ts:805:8 invalid script name "${referencePath.parentPath.parentPath.node.property.name}" in subscript`
657
686
  )
658
687
  if ("CallExpression" == referencePath.parentPath.parentPath.parentPath?.type)
659
688
  referencePath.parentPath.parentPath.replaceWith(
package/push.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Cache } from "@samual/lib/Cache"
1
+ import { AutoMap } from "@samual/lib/AutoMap"
2
2
  import { ensure, assert } from "@samual/lib/assert"
3
3
  import { countHackmudCharacters } from "@samual/lib/countHackmudCharacters"
4
4
  import { readDirectoryWithStats } from "@samual/lib/readDirectoryWithStats"
@@ -84,8 +84,8 @@ async function push(
84
84
  return new NoUsersError(
85
85
  "Could not find any users. Either provide the names of your users or log into a user in hackmud"
86
86
  )
87
- const usersToScriptsToPush = new Cache(_user => new Map()),
88
- scriptNamesToUsers = new Cache(_scriptName => new Set())
87
+ const usersToScriptsToPush = new AutoMap(_user => new Map()),
88
+ scriptNamesToUsers = new AutoMap(_scriptName => new Set())
89
89
  for (const script of scripts) {
90
90
  const [user, scriptName] = script.split(".")
91
91
  assert(user, "src/push.ts:105:16")
@@ -121,7 +121,7 @@ async function push(
121
121
  for (const user of users)
122
122
  if (!usersToScriptsToPush.get(user).has(scriptName))
123
123
  return new NoScriptsError(`Could not find script ${user}.${scriptName} to push`)
124
- const pathsToUsers = new Cache(_path => new Set())
124
+ const pathsToUsers = new AutoMap(_path => new Set())
125
125
  for (const [user, scriptsToPush] of usersToScriptsToPush)
126
126
  for (const path of scriptsToPush.values()) pathsToUsers.get(path).add(user)
127
127
  const allInfo = []
package/watch.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Cache } from "@samual/lib/Cache"
1
+ import { AutoMap } from "@samual/lib/AutoMap"
2
2
  import { assert } from "@samual/lib/assert"
3
3
  import { countHackmudCharacters } from "@samual/lib/countHackmudCharacters"
4
4
  import { readDirectoryWithStats } from "@samual/lib/readDirectoryWithStats"
@@ -60,7 +60,7 @@ async function watch(
60
60
  ) {
61
61
  if (!scripts.length) throw Error("scripts option was an empty array")
62
62
  if (!(await stat(sourceDirectory)).isDirectory()) throw Error("Target folder must be a folder")
63
- const scriptNamesToUsers = new Cache(_scriptName => new Set()),
63
+ const scriptNamesToUsers = new AutoMap(_scriptName => new Set()),
64
64
  wildScriptUsers = new Set(),
65
65
  wildUserScripts = new Set()
66
66
  let pushEverything = !1
@@ -92,7 +92,7 @@ async function watch(
92
92
  )
93
93
  )
94
94
  return
95
- const scriptNamesToUsersToSkip = new Cache(_scriptName => [])
95
+ const scriptNamesToUsersToSkip = new AutoMap(_scriptName => [])
96
96
  await Promise.all(
97
97
  (await readDirectoryWithStats(sourceDirectory)).map(async ({ stats, name, path }) => {
98
98
  if (stats.isDirectory())