@defold-typescript/types 0.5.4 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/api-targets.json +1 -1
  2. package/generated/b2d.d.ts +3 -0
  3. package/generated/buffer.d.ts +44 -38
  4. package/generated/builtin-messages.d.ts +1 -1
  5. package/generated/camera.d.ts +3 -0
  6. package/generated/collectionfactory.d.ts +47 -40
  7. package/generated/collectionproxy.d.ts +23 -18
  8. package/generated/crash.d.ts +3 -0
  9. package/generated/factory.d.ts +32 -24
  10. package/generated/go.d.ts +293 -293
  11. package/generated/graphics.d.ts +3 -0
  12. package/generated/gui.d.ts +303 -283
  13. package/generated/http.d.ts +26 -16
  14. package/generated/iac.d.ts +3 -0
  15. package/generated/iap.d.ts +6 -3
  16. package/generated/image.d.ts +30 -26
  17. package/generated/json.d.ts +36 -32
  18. package/generated/kinds/gui-script.d.ts +7 -5
  19. package/generated/kinds/render-script.d.ts +7 -5
  20. package/generated/kinds/script.d.ts +7 -5
  21. package/generated/label.d.ts +16 -9
  22. package/generated/liveupdate.d.ts +29 -26
  23. package/generated/model.d.ts +57 -45
  24. package/generated/msg.d.ts +12 -9
  25. package/generated/particlefx.d.ts +50 -34
  26. package/generated/physics.d.ts +153 -133
  27. package/generated/profiler.d.ts +45 -41
  28. package/generated/push.d.ts +5 -2
  29. package/generated/render.d.ts +410 -349
  30. package/generated/resource.d.ts +619 -572
  31. package/generated/socket.d.ts +49 -33
  32. package/generated/sound.d.ts +83 -72
  33. package/generated/sprite.d.ts +36 -32
  34. package/generated/sys.d.ts +198 -189
  35. package/generated/tilemap.d.ts +43 -39
  36. package/generated/timer.d.ts +42 -36
  37. package/generated/vmath.d.ts +254 -229
  38. package/generated/webview.d.ts +3 -0
  39. package/generated/window.d.ts +23 -17
  40. package/generated/zlib.d.ts +15 -12
  41. package/index.d.ts +3 -1
  42. package/package.json +6 -2
  43. package/scripts/example-store-io.ts +18 -0
  44. package/scripts/fidelity-audit.ts +61 -1
  45. package/scripts/fidelity-baseline.json +10 -10
  46. package/scripts/ref-doc-delta.ts +143 -0
  47. package/scripts/regen.ts +23 -10
  48. package/src/core-types.ts +14 -0
  49. package/src/doc-comment.ts +2 -1
  50. package/src/emit-dts.ts +238 -18
  51. package/src/engine-globals.d.ts +2 -0
  52. package/src/example-store.ts +44 -0
  53. package/src/go-overloads.d.ts +73 -0
  54. package/src/index.ts +5 -0
  55. package/src/lifecycle.ts +157 -16
  56. package/src/message-dispatch.d.ts +21 -0
  57. package/src/message-guard.d.ts +19 -0
  58. package/src/msg-overloads.d.ts +20 -0
  59. package/src/publish-dts.ts +1 -1
package/src/emit-dts.ts CHANGED
@@ -13,6 +13,8 @@ import {
13
13
  htmlToDocText,
14
14
  renderDocComment,
15
15
  } from "./doc-comment";
16
+ import type { TranslationStore } from "./example-store";
17
+ import { hashExampleSource, lookupTranslation } from "./example-store";
16
18
 
17
19
  export interface EmitOptions {
18
20
  mapType?: (defoldType: string) => string;
@@ -21,6 +23,12 @@ export interface EmitOptions {
21
23
  // brands to the same FQN-keyed type its owning module's `const` emits,
22
24
  // instead of widening to `unknown`.
23
25
  knownConstantFqns?: ReadonlySet<string>;
26
+ // Hand-authored TypeScript `@example` translations keyed by element FQN.
27
+ // Defaults to an empty store (every example stays on its Lua fallback,
28
+ // byte-identical output); the build layer (`regen`) passes the loaded
29
+ // `examples/translations.json`. Loading lives in `scripts/example-store-io.ts`
30
+ // so this module stays node-free for downstream consumers.
31
+ translations?: TranslationStore;
24
32
  }
25
33
 
26
34
  export const TS_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
@@ -74,8 +82,11 @@ export const TS_RESERVED_NAMES = new Set([
74
82
  "extends",
75
83
  ]);
76
84
 
77
- // Element names whose `table` slot is a genuinely-arbitrary lua table by design
78
- // the serialization/JSON passthrough functions. Their emitted
85
+ // Element names whose `table` slot is a genuinely-arbitrary lua table by design.
86
+ // Two kinds: the serialization/JSON passthrough functions (Defold-internal — the
87
+ // engine round-trips an arbitrary lua value), and the platform/OS-sourced opaque
88
+ // blobs (external — the shape is set by the host OS or invoking app, not by
89
+ // Defold, so there is no documented field list). Their emitted
79
90
  // `Record<string | number, unknown>` is the faithful "any lua table" type, not a
80
91
  // `recordTables` fidelity loss, so the audit consults this set to avoid counting
81
92
  // them. A new ref-doc function with an opaque table must be added here
@@ -87,6 +98,9 @@ export const ARBITRARY_TABLE_SLOTS = new Set([
87
98
  "sys.load",
88
99
  "sys.serialize",
89
100
  "sys.deserialize",
101
+ "iac.set_listener",
102
+ "push.get_scheduled",
103
+ "push.get_all_scheduled",
90
104
  ]);
91
105
 
92
106
  // Element names whose `table` slot is a prose-only `a table mapping X to Y`
@@ -123,6 +137,157 @@ export const HOMOGENEOUS_ARRAY_SLOTS: ReadonlyMap<string, string | readonly stri
123
137
  ["sound.get_groups", "hash"],
124
138
  ["iap.list", "string"],
125
139
  ["go.delete", ["string", "hash", "url"]],
140
+ // push.register's `notifications` is a prose-only array of push.NOTIFICATION_*
141
+ // bitmask constants (the ref example sums NOTIFICATION_BADGE/SOUND/ALERT). The
142
+ // vendored push_doc.json fixture declares no NOTIFICATION_* constant elements, so
143
+ // no brand exists to reference; `number` is the faithful element token for these
144
+ // numeric constants, mirroring the vmath.vector/buffer.* number entries.
145
+ ["push.register", "number"],
146
+ ]);
147
+
148
+ // A `mapping` curation whose value is itself a single-level mapping, emitted
149
+ // `LuaMap<Kouter, LuaMap<Kinner, Vinner>>` — the nested-row-map shape
150
+ // (`tilemap.get_tiles` → `tiles[row][col]`).
151
+ export type NestedMapping = { key: string; value: string };
152
+
153
+ export type TableSlotCuration =
154
+ | { kind: "mapping"; key: string; value: string | readonly TableField[] | NestedMapping }
155
+ | { kind: "array"; element: string | readonly string[] }
156
+ | { kind: "object"; fields: readonly TableField[] }
157
+ | { kind: "array-object"; fields: readonly TableField[] };
158
+
159
+ const SOCKET_HANDLE_TOKENS = ["client", "master", "unconnected"] as const;
160
+
161
+ export const TABLE_SLOT_CURATIONS: ReadonlyMap<string, TableSlotCuration> = new Map([
162
+ ["collectionfactory.create:return:ids", { kind: "mapping", key: "hash", value: "hash" }],
163
+ // iap.finish and iap.acknowledge take the same Defold IAP transaction object —
164
+ // the table handed to the iap.set_listener callback. The ref-doc fixture
165
+ // describes it in prose only (no field list), so the shape is curated from the
166
+ // Defold iap reference. As a param-side object curation every field emits `?`,
167
+ // which is faithful: original_trans/signature/user_id are platform-specific.
168
+ [
169
+ "iap.finish:param:transaction",
170
+ {
171
+ kind: "object",
172
+ fields: [
173
+ { name: "ident", types: ["string"] },
174
+ { name: "state", types: ["number"] },
175
+ { name: "trans_ident", types: ["string"] },
176
+ { name: "date", types: ["string"] },
177
+ { name: "original_trans", types: ["string"] },
178
+ { name: "receipt", types: ["string"] },
179
+ { name: "signature", types: ["string"] },
180
+ { name: "user_id", types: ["string"] },
181
+ ],
182
+ },
183
+ ],
184
+ [
185
+ "iap.acknowledge:param:transaction",
186
+ {
187
+ kind: "object",
188
+ fields: [
189
+ { name: "ident", types: ["string"] },
190
+ { name: "state", types: ["number"] },
191
+ { name: "trans_ident", types: ["string"] },
192
+ { name: "date", types: ["string"] },
193
+ { name: "original_trans", types: ["string"] },
194
+ { name: "receipt", types: ["string"] },
195
+ { name: "signature", types: ["string"] },
196
+ { name: "user_id", types: ["string"] },
197
+ ],
198
+ },
199
+ ],
200
+ // iap.buy's `options` is documented in the fixture as prose only ("optional
201
+ // parameters as properties"), so the field set is curated from the Defold iap
202
+ // reference: a Facebook-only custom `request_id` and a Google-Play-only
203
+ // subscription-offer `token`. Both are platform-specific, hence param-side
204
+ // optional fields.
205
+ [
206
+ "iap.buy:param:options",
207
+ {
208
+ kind: "object",
209
+ fields: [
210
+ { name: "request_id", types: ["string"] },
211
+ { name: "token", types: ["string"] },
212
+ ],
213
+ },
214
+ ],
215
+ [
216
+ "liveupdate.get_mounts:return:mounts",
217
+ {
218
+ kind: "array-object",
219
+ fields: [
220
+ { name: "name", types: ["string"] },
221
+ { name: "uri", types: ["string"] },
222
+ { name: "priority", types: ["number"] },
223
+ ],
224
+ },
225
+ ],
226
+ [
227
+ "model.get_aabb:return:aabb",
228
+ {
229
+ kind: "object",
230
+ fields: [
231
+ { name: "min", types: ["vector3"] },
232
+ { name: "max", types: ["vector3"] },
233
+ ],
234
+ },
235
+ ],
236
+ [
237
+ "model.get_mesh_aabb:return:aabb",
238
+ {
239
+ kind: "mapping",
240
+ key: "hash",
241
+ value: [
242
+ { name: "min", types: ["vector3"] },
243
+ { name: "max", types: ["vector3"] },
244
+ ],
245
+ },
246
+ ],
247
+ ["physics.raycast:param:groups", { kind: "array", element: "hash" }],
248
+ ["physics.raycast_async:param:groups", { kind: "array", element: "hash" }],
249
+ // push.schedule's `notification_settings` is documented in the fixture as prose
250
+ // only ("Table with notification and platform specific fields"), so the field
251
+ // set is curated from the Defold push reference: an iOS `action`, an iOS
252
+ // `badge_count`, and an Android `priority`. Each is platform-specific, hence
253
+ // param-side optional fields.
254
+ [
255
+ "push.schedule:param:notification_settings",
256
+ {
257
+ kind: "object",
258
+ fields: [
259
+ { name: "action", types: ["string"] },
260
+ { name: "badge_count", types: ["number"] },
261
+ { name: "priority", types: ["number"] },
262
+ ],
263
+ },
264
+ ],
265
+ ["socket.select:param:recvt", { kind: "array", element: SOCKET_HANDLE_TOKENS }],
266
+ ["socket.select:param:sendt", { kind: "array", element: SOCKET_HANDLE_TOKENS }],
267
+ ["socket.select:return:sockets_r", { kind: "array", element: SOCKET_HANDLE_TOKENS }],
268
+ ["socket.select:return:sockets_w", { kind: "array", element: SOCKET_HANDLE_TOKENS }],
269
+ [
270
+ "tilemap.get_tile_info:return:tile_info",
271
+ {
272
+ kind: "object",
273
+ fields: [
274
+ { name: "index", types: ["number"] },
275
+ { name: "h_flip", types: ["boolean"] },
276
+ { name: "v_flip", types: ["boolean"] },
277
+ { name: "rotate_90", types: ["boolean"] },
278
+ ],
279
+ },
280
+ ],
281
+ // tilemap.get_tiles returns a sparse table of rows iterated `tiles[row][col]`,
282
+ // the keys being tile positions offset by tilemap.get_bounds() (not a dense
283
+ // 0..n run). The faithful shape is the nested LuaMap idiom
284
+ // `LuaMap<number, LuaMap<number, number>>` — the keys are plain `number` tile
285
+ // positions, not a branded `Hash`, and a non-string-keyed Lua table is `LuaMap`,
286
+ // never a `Record` (which would imply a dense index object).
287
+ [
288
+ "tilemap.get_tiles:return:tiles",
289
+ { kind: "mapping", key: "number", value: { key: "number", value: "number" } },
290
+ ],
126
291
  ]);
127
292
 
128
293
  /**
@@ -427,6 +592,7 @@ export function emitDeclarations(module: ApiModule, options?: EmitOptions): stri
427
592
 
428
593
  const constantFqns = new Set(module.constants.map((c) => c.name));
429
594
  const knownConstantFqns = options?.knownConstantFqns;
595
+ const translations = options?.translations ?? {};
430
596
  const baseMapType = options?.mapType ?? defaultMapType;
431
597
  const mapType = (token: string): string =>
432
598
  constantFqns.has(token) || knownConstantFqns?.has(token)
@@ -473,6 +639,7 @@ export function emitDeclarations(module: ApiModule, options?: EmitOptions): stri
473
639
  const decl = hasAliases ? "export " : "";
474
640
 
475
641
  const lines: string[] = [];
642
+ for (const docLine of namespaceDocLines(module)) lines.push(docLine);
476
643
  lines.push(`declare namespace ${module.namespace} {`);
477
644
 
478
645
  for (const t of typedefs) {
@@ -502,7 +669,7 @@ export function emitDeclarations(module: ApiModule, options?: EmitOptions): stri
502
669
  for (const fn of functions) {
503
670
  const reserved = TS_RESERVED_NAMES.has(fn.name);
504
671
  const emitName = aliasName(fn.name, aliases);
505
- for (const docLine of functionDocLines(fn.original)) lines.push(docLine);
672
+ for (const docLine of functionDocLines(fn.original, translations)) lines.push(docLine);
506
673
  const line = emitFunction(fn, emitName, mapType, resolver);
507
674
  lines.push(`${INDENT}${reserved ? "" : decl}${line}`);
508
675
  }
@@ -614,18 +781,23 @@ function emitFunction(
614
781
  // fallback applies to non-identifier names, matching `emitParameter`) so the tag
615
782
  // resolves on hover; a single documented return becomes `@returns`. Returns `[]`
616
783
  // for a fully-undocumented function, leaving its emission byte-identical.
617
- function functionDocLines(fn: ApiFunction): string[] {
784
+ function functionDocLines(fn: ApiFunction, translations: TranslationStore): string[] {
618
785
  const params = fn.parameters.map((p, index) => ({
619
786
  name: TS_IDENTIFIER.test(p.name) ? p.name : `arg${index}`,
620
787
  doc: htmlToDocText(p.doc),
621
788
  }));
622
789
  const onlyReturn = fn.returnValues.length === 1 ? fn.returnValues[0] : undefined;
623
- const example = htmlToCodeText(fn.examples ?? "");
790
+ const lua = htmlToCodeText(fn.examples ?? "");
791
+ // A hand-authored TS translation pinned to this exact Lua flips the fence to
792
+ // ```ts; any hash mismatch (or absent translation) keeps the Lua fallback.
793
+ const ts = lua === "" ? null : lookupTranslation(translations, fn.name, hashExampleSource(lua));
794
+ const exampleParts: Pick<DocCommentParts, "example" | "exampleLang"> =
795
+ ts !== null ? { example: ts, exampleLang: "ts" } : lua !== "" ? { example: lua } : {};
624
796
  const parts: DocCommentParts = {
625
797
  summary: htmlToDocText(summaryFor(fn.brief, fn.description)),
626
798
  params,
627
799
  ...(onlyReturn ? { returns: htmlToDocText(onlyReturn.doc) } : {}),
628
- ...(example !== "" ? { example } : {}),
800
+ ...exampleParts,
629
801
  };
630
802
  return indentDocLines(parts, INDENT);
631
803
  }
@@ -639,6 +811,15 @@ function summaryDocLines(brief: string, description: string, indent: string): st
639
811
  return indentDocLines({ summary: htmlToDocText(summaryFor(brief, description)) }, indent);
640
812
  }
641
813
 
814
+ function namespaceDocLines(module: ApiModule): string[] {
815
+ const official = summaryFor(module.brief, module.description).trim();
816
+ const summary =
817
+ official.length > 0
818
+ ? htmlToDocText(official)
819
+ : `(synthesized)\nDefold \`${module.namespace}\` API namespace.`;
820
+ return indentDocLines({ summary }, "");
821
+ }
822
+
642
823
  function indentDocLines(parts: DocCommentParts, indent: string): string[] {
643
824
  return renderDocComment(parts).map((line) => `${indent}${line}`);
644
825
  }
@@ -646,7 +827,7 @@ function indentDocLines(parts: DocCommentParts, indent: string): string[] {
646
827
  // Prefer the full `description`; fall back to the one-line `brief` when prose is
647
828
  // absent. Shared by every documented member kind so the summary source is
648
829
  // consistent across functions, constants, variables, and properties.
649
- function summaryFor(brief: string, description: string): string {
830
+ export function summaryFor(brief: string, description: string): string {
650
831
  return description.trim() !== "" ? description : brief;
651
832
  }
652
833
 
@@ -676,7 +857,7 @@ function emitParameter(
676
857
  const concrete = p.types.filter((t) => t !== "nil");
677
858
  const ts =
678
859
  concrete.length > 0
679
- ? mapSlotUnion(concrete, p.doc, mapType, true, resolver, elementName)
860
+ ? mapSlotUnion(concrete, p.doc, mapType, true, resolver, elementName, "param", p.name)
680
861
  : "unknown";
681
862
  return `${name}${optional ? "?" : ""}: ${ts}`;
682
863
  }
@@ -694,7 +875,7 @@ function emitReturn(
694
875
  // typescript-to-lua erases to `local a, b = fn()`.
695
876
  const slots = returnValues.map((rv) =>
696
877
  rv.types.length > 0
697
- ? mapSlotUnion(rv.types, rv.doc, mapType, false, resolver, elementName)
878
+ ? mapSlotUnion(rv.types, rv.doc, mapType, false, resolver, elementName, "return", rv.name)
698
879
  : "unknown",
699
880
  );
700
881
  return { type: `LuaMultiReturn<[${slots.join(", ")}]>`, trailing: "" };
@@ -703,7 +884,16 @@ function emitReturn(
703
884
  if (!first) return { type: "void", trailing: "" };
704
885
  const ts =
705
886
  first.types.length > 0
706
- ? mapSlotUnion(first.types, first.doc, mapType, false, resolver, elementName)
887
+ ? mapSlotUnion(
888
+ first.types,
889
+ first.doc,
890
+ mapType,
891
+ false,
892
+ resolver,
893
+ elementName,
894
+ "return",
895
+ first.name,
896
+ )
707
897
  : "unknown";
708
898
  return { type: ts, trailing: "" };
709
899
  }
@@ -728,22 +918,38 @@ function mapSlotUnion(
728
918
  optionalFields: boolean,
729
919
  resolver: TableDocResolver,
730
920
  elementName: string,
921
+ slotKind?: "param" | "return",
922
+ slotName?: string,
731
923
  ): string {
732
924
  const mapped: string[] = [];
733
925
  const seen = new Set<string>();
734
926
  for (const token of types) {
735
927
  let ts: string;
736
928
  if (token === "table") {
737
- const mapping = MAPPING_TABLE_SLOTS.get(elementName);
738
- const element = HOMOGENEOUS_ARRAY_SLOTS.get(elementName);
929
+ const curation =
930
+ slotKind !== undefined && slotName !== undefined
931
+ ? TABLE_SLOT_CURATIONS.get(tableSlotKey(elementName, slotKind, slotName))
932
+ : undefined;
933
+ const mapping =
934
+ curation?.kind === "mapping" ? curation : MAPPING_TABLE_SLOTS.get(elementName);
935
+ const element =
936
+ curation?.kind === "array" ? curation.element : HOMOGENEOUS_ARRAY_SLOTS.get(elementName);
739
937
  if (mapping !== undefined) {
740
- ts = `LuaMap<${mapType(mapping.key)}, ${mapType(mapping.value)}>`;
938
+ let value: string;
939
+ if (typeof mapping.value === "string") {
940
+ value = mapType(mapping.value);
941
+ } else if (Array.isArray(mapping.value)) {
942
+ value = inlineTableType(mapping.value, mapType, optionalFields);
943
+ } else {
944
+ const nested = mapping.value as NestedMapping;
945
+ value = `LuaMap<${mapType(nested.key)}, ${mapType(nested.value)}>`;
946
+ }
947
+ ts = `LuaMap<${mapType(mapping.key)}, ${value}>`;
741
948
  } else if (element !== undefined) {
742
- const tokens = typeof element === "string" ? [element] : element;
743
- ts =
744
- tokens.length > 1
745
- ? `(${unionFromTokens(tokens, mapType)})[]`
746
- : `${mapType(tokens[0] as string)}[]`;
949
+ ts = arrayTypeFromTokens(element, mapType);
950
+ } else if (curation?.kind === "object" || curation?.kind === "array-object") {
951
+ const object = inlineTableType(curation.fields, mapType, optionalFields);
952
+ ts = curation.kind === "array-object" ? `${object}[]` : object;
747
953
  } else {
748
954
  const fields = parseTableFields(doc, resolver);
749
955
  if (fields !== null) {
@@ -763,6 +969,20 @@ function mapSlotUnion(
763
969
  return mapped.join(" | ");
764
970
  }
765
971
 
972
+ function tableSlotKey(elementName: string, slotKind: "param" | "return", slotName: string): string {
973
+ return `${elementName}:${slotKind}:${slotName}`;
974
+ }
975
+
976
+ function arrayTypeFromTokens(
977
+ element: string | readonly string[],
978
+ mapType: (t: string) => string,
979
+ ): string {
980
+ const tokens = typeof element === "string" ? [element] : element;
981
+ return tokens.length > 1
982
+ ? `(${unionFromTokens(tokens, mapType)})[]`
983
+ : `${mapType(tokens[0] as string)}[]`;
984
+ }
985
+
766
986
  export function inlineTableType(
767
987
  fields: readonly TableField[],
768
988
  mapType: (t: string) => string,
@@ -1,7 +1,9 @@
1
+ /** @noSelfInFile */
1
2
  import type * as Core from "./core-types";
2
3
 
3
4
  declare global {
4
5
  type Hash = Core.Hash;
6
+ function hash(s: string): Core.Hash;
5
7
  type Opaque<Name extends string> = Core.Opaque<Name>;
6
8
  type Url = Core.Url;
7
9
  type Vector = Core.Vector;
@@ -0,0 +1,44 @@
1
+ // A hand-authored TypeScript translation of one element's ref-doc `@example`,
2
+ // pinned by a hash of the exact source Lua it replaces. A ref-doc re-pin that
3
+ // changes the source Lua flips the hash, so a stale translation stops matching
4
+ // (drift guard) and the emit falls back to the Lua body.
5
+ export interface Translation {
6
+ sourceHash: string;
7
+ ts: string;
8
+ }
9
+
10
+ export type TranslationStore = Record<string, Translation>;
11
+
12
+ const FNV_OFFSET_BASIS = 0xcbf29ce484222325n;
13
+ const FNV_PRIME = 0x100000001b3n;
14
+ const U64_MASK = 0xffffffffffffffffn;
15
+
16
+ // A pure, dependency-free FNV-1a 64-bit hash over the source's UTF-16 code
17
+ // units, returned as zero-padded hex. Deliberately node- and Bun-free: this
18
+ // module is reachable from `index.ts` (via `emit-dts`), so a `node:crypto` or
19
+ // ambient-`Bun` reference here would fail type-checking in every downstream
20
+ // consumer that compiles the shipped `src/` graph.
21
+ //
22
+ // The input is the already-normalized post-`htmlToCodeText` string (per-line
23
+ // trailing whitespace and surrounding blank lines stripped), so the hash is
24
+ // independent of trailing whitespace in the original ref-doc HTML.
25
+ export function hashExampleSource(source: string): string {
26
+ let hash = FNV_OFFSET_BASIS;
27
+ for (let i = 0; i < source.length; i++) {
28
+ hash = ((hash ^ BigInt(source.charCodeAt(i))) * FNV_PRIME) & U64_MASK;
29
+ }
30
+ return hash.toString(16).padStart(16, "0");
31
+ }
32
+
33
+ // Return the stored TypeScript body only when the FQN exists and its pinned
34
+ // `sourceHash` matches the source we are about to emit; any mismatch returns
35
+ // `null` so the caller keeps the Lua fallback.
36
+ export function lookupTranslation(
37
+ store: TranslationStore,
38
+ fqn: string,
39
+ sourceHash: string,
40
+ ): string | null {
41
+ const entry = store[fqn];
42
+ if (!entry || entry.sourceHash !== sourceHash) return null;
43
+ return entry.ts;
44
+ }
@@ -1,6 +1,11 @@
1
1
  /** @noSelfInFile */
2
+
2
3
  import type { Hash, Opaque, Quaternion, Url, Vector3, Vector4 } from "./core-types";
3
4
 
5
+ interface ScriptProperty<TValue> {
6
+ readonly __defoldScriptProperty: TValue;
7
+ }
8
+
4
9
  declare global {
5
10
  namespace go {
6
11
  interface GoPropertyOptions {
@@ -9,6 +14,21 @@ declare global {
9
14
  keys?: Record<string | number, unknown>;
10
15
  }
11
16
 
17
+ /**
18
+ * gets a named property of the specified game object or component
19
+ *
20
+ * @param url - url of the game object or component having the property
21
+ * @param property - id of the property to retrieve
22
+ * @param options - optional options table
23
+ * - index number index into array property (1 based)
24
+ * - key hash name of internal property
25
+ * - keys table array of internal component resources identified by key (e.g. a particle fx emitter, see examples below)
26
+ * @returns the value of the specified property
27
+ * @example
28
+ * ```ts
29
+ * const position = go.get("#sprite", "position");
30
+ * ```
31
+ */
12
32
  function get<K extends keyof go.properties>(
13
33
  url: string | Hash | Url,
14
34
  property: K,
@@ -19,6 +39,21 @@ declare global {
19
39
  property: string | Hash,
20
40
  options?: GoPropertyOptions,
21
41
  ): number | boolean | Hash | Url | Vector3 | Vector4 | Quaternion | Opaque<"resource">;
42
+ /**
43
+ * sets a named property of the specified game object or component, or a material constant
44
+ *
45
+ * @param url - url of the game object or component having the property
46
+ * @param property - id of the property to set
47
+ * @param value - the value to set
48
+ * @param options - optional options table
49
+ * - index integer index into array property (1 based)
50
+ * - key hash name of internal property
51
+ * - keys table array of internal component resources identified by key (e.g. a particle fx emitter, see examples below)
52
+ * @example
53
+ * ```ts
54
+ * go.set("#sprite", "tint", vmath.vector4(1, 0, 0, 1));
55
+ * ```
56
+ */
22
57
  function set<K extends keyof go.properties>(
23
58
  url: string | Hash | Url,
24
59
  property: K,
@@ -31,5 +66,43 @@ declare global {
31
66
  value: number | boolean | Hash | Url | Vector3 | Vector4 | Quaternion | Opaque<"resource">,
32
67
  options?: GoPropertyOptions,
33
68
  ): void;
69
+ /**
70
+ * Registers a Defold editor script property (deprecated escape hatch).
71
+ *
72
+ * @deprecated Don't call `go.property` yourself. Declare the property in
73
+ * `defineScript({ properties })` — that is the only form that types it onto
74
+ * `self`; the transpiler emits the `go.property(...)` registration for you.
75
+ * A direct call still registers, but `self.<name>` stays untyped.
76
+ *
77
+ * @param name - editor property id to register.
78
+ * @param value - default value for the registered property.
79
+ * @returns a phantom descriptor used only for TypeScript self typing.
80
+ * @example
81
+ * ```ts
82
+ * // Declare it as a field — the key is the name, the value is the default:
83
+ * export default defineScript({
84
+ * properties: { speed: 450 },
85
+ * update(self) {
86
+ * self.speed; // number
87
+ * },
88
+ * });
89
+ * // The transpiler emits, at chunk scope: go.property("speed", 450)
90
+ * ```
91
+ */
92
+ function property(name: string, value: number): ScriptProperty<number>;
93
+ /** @deprecated Declare booleans via the `defineScript({ properties })` field. */
94
+ function property(name: string, value: boolean): ScriptProperty<boolean>;
95
+ /** @deprecated Declare hashes via the `defineScript({ properties })` field. */
96
+ function property(name: string, value: Hash): ScriptProperty<Hash>;
97
+ /** @deprecated Declare URLs via the `defineScript({ properties })` field. */
98
+ function property(name: string, value: Url): ScriptProperty<Url>;
99
+ /** @deprecated Declare vector3s via the `defineScript({ properties })` field. */
100
+ function property(name: string, value: Vector3): ScriptProperty<Vector3>;
101
+ /** @deprecated Declare vector4s via the `defineScript({ properties })` field. */
102
+ function property(name: string, value: Vector4): ScriptProperty<Vector4>;
103
+ /** @deprecated Declare quaternions via the `defineScript({ properties })` field. */
104
+ function property(name: string, value: Quaternion): ScriptProperty<Quaternion>;
105
+ /** @deprecated Declare resources via the `defineScript({ properties })` field. */
106
+ function property(name: string, value: Opaque<"resource">): ScriptProperty<Opaque<"resource">>;
34
107
  }
35
108
  }
package/src/index.ts CHANGED
@@ -17,11 +17,16 @@ export {
17
17
  defineRenderScript,
18
18
  defineScript,
19
19
  type GuiScriptHooks,
20
+ type GuiScriptHooksWithProperties,
20
21
  type InputAction,
21
22
  type InputTouch,
22
23
  type RenderScriptHooks,
24
+ type RenderScriptHooksWithProperties,
23
25
  SCRIPT_HOOK_NAMES,
24
26
  type ScriptHookName,
25
27
  type ScriptHooks,
28
+ type ScriptHooksWithProperties,
29
+ type ScriptProperties,
30
+ type ScriptProperty,
26
31
  } from "./lifecycle";
27
32
  export { type WrapOptions, wrapAsAmbientGlobal } from "./publish-dts";