parse-stack-next 5.7.5 → 5.8.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 (97) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +856 -0
  3. data/README.md +15 -4
  4. data/docs/TEST_SERVER.md +2 -2
  5. data/docs/acl_clp_guide.md +7 -0
  6. data/docs/atlas_vector_search_guide.md +190 -14
  7. data/docs/client_sdk_guide.md +11 -0
  8. data/docs/mcp_guide.md +318 -6
  9. data/docs/mongodb_direct_guide.md +27 -0
  10. data/docs/usage_guide.md +38 -0
  11. data/docs/webhooks_guide.md +74 -17
  12. data/lib/parse/acl_scope.rb +159 -41
  13. data/lib/parse/agent/approval_gate.rb +0 -0
  14. data/lib/parse/agent/constraint_translator.rb +42 -15
  15. data/lib/parse/agent/describe.rb +3 -1
  16. data/lib/parse/agent/field_names.rb +53 -0
  17. data/lib/parse/agent/field_policy.rb +74 -0
  18. data/lib/parse/agent/mcp_deployments.rb +426 -0
  19. data/lib/parse/agent/mcp_rack_app.rb +424 -45
  20. data/lib/parse/agent/mcp_server.rb +23 -1
  21. data/lib/parse/agent/mcp_subscriptions.rb +124 -6
  22. data/lib/parse/agent/metadata_registry.rb +67 -8
  23. data/lib/parse/agent/prompt_hardening.rb +9 -3
  24. data/lib/parse/agent/tools.rb +378 -29
  25. data/lib/parse/agent.rb +93 -1
  26. data/lib/parse/api/batch.rb +10 -1
  27. data/lib/parse/api/schema.rb +23 -4
  28. data/lib/parse/api/sessions.rb +6 -2
  29. data/lib/parse/api/users.rb +88 -14
  30. data/lib/parse/atlas_search/protected_paths.rb +236 -0
  31. data/lib/parse/atlas_search.rb +95 -23
  32. data/lib/parse/authorization.rb +54 -1
  33. data/lib/parse/client/batch.rb +231 -35
  34. data/lib/parse/client/body_builder.rb +21 -0
  35. data/lib/parse/client/caching.rb +371 -27
  36. data/lib/parse/client/request.rb +26 -14
  37. data/lib/parse/client/response.rb +49 -6
  38. data/lib/parse/client.rb +201 -38
  39. data/lib/parse/clp_scope.rb +281 -23
  40. data/lib/parse/console.rb +2 -2
  41. data/lib/parse/embeddings/voyage.rb +181 -17
  42. data/lib/parse/graphql/type_generator.rb +3 -0
  43. data/lib/parse/model/acl.rb +119 -21
  44. data/lib/parse/model/associations/belongs_to.rb +25 -3
  45. data/lib/parse/model/associations/collection_proxy.rb +138 -17
  46. data/lib/parse/model/associations/has_many.rb +38 -9
  47. data/lib/parse/model/associations/has_one.rb +3 -1
  48. data/lib/parse/model/associations/pointer_collection_proxy.rb +109 -17
  49. data/lib/parse/model/associations/relation_collection_proxy.rb +134 -28
  50. data/lib/parse/model/bytes.rb +13 -5
  51. data/lib/parse/model/classes/role.rb +72 -0
  52. data/lib/parse/model/classes/session.rb +43 -0
  53. data/lib/parse/model/classes/user.rb +78 -3
  54. data/lib/parse/model/core/actions.rb +269 -67
  55. data/lib/parse/model/core/builder.rb +100 -8
  56. data/lib/parse/model/core/create_lock.rb +27 -2
  57. data/lib/parse/model/core/describe.rb +2 -0
  58. data/lib/parse/model/core/fetching.rb +21 -3
  59. data/lib/parse/model/core/pluralized_aliases.rb +8 -4
  60. data/lib/parse/model/core/properties.rb +488 -39
  61. data/lib/parse/model/core/querying.rb +7 -0
  62. data/lib/parse/model/core/schema.rb +5 -3
  63. data/lib/parse/model/core/search_indexing.rb +63 -0
  64. data/lib/parse/model/core/vector_searchable.rb +35 -6
  65. data/lib/parse/model/file.rb +9 -2
  66. data/lib/parse/model/geopoint.rb +61 -13
  67. data/lib/parse/model/model.rb +160 -9
  68. data/lib/parse/model/object.rb +265 -17
  69. data/lib/parse/model/phone.rb +54 -5
  70. data/lib/parse/model/pointer.rb +40 -6
  71. data/lib/parse/mongodb.rb +170 -60
  72. data/lib/parse/pipeline_security.rb +415 -26
  73. data/lib/parse/query/constraint.rb +30 -0
  74. data/lib/parse/query/constraints.rb +58 -32
  75. data/lib/parse/query/cursor.rb +3 -1
  76. data/lib/parse/query/operation.rb +62 -8
  77. data/lib/parse/query/ordering.rb +34 -6
  78. data/lib/parse/query.rb +1100 -134
  79. data/lib/parse/retrieval/agent_tool.rb +290 -17
  80. data/lib/parse/retrieval/benchmark.rb +149 -0
  81. data/lib/parse/retrieval/profiles.rb +320 -0
  82. data/lib/parse/retrieval/retriever.rb +10 -1
  83. data/lib/parse/retrieval.rb +2 -0
  84. data/lib/parse/schema/search_index_migrator.rb +23 -5
  85. data/lib/parse/schema.rb +74 -18
  86. data/lib/parse/stack/tasks.rb +6 -4
  87. data/lib/parse/stack/version.rb +1 -1
  88. data/lib/parse/stack.rb +72 -14
  89. data/lib/parse/two_factor_auth/user_extension.rb +14 -2
  90. data/lib/parse/two_factor_auth.rb +11 -0
  91. data/lib/parse/vector_search/hybrid.rb +36 -18
  92. data/lib/parse/vector_search/index_definition.rb +237 -0
  93. data/lib/parse/vector_search.rb +46 -17
  94. data/lib/parse/webhooks/payload.rb +93 -6
  95. data/lib/parse/webhooks/replay_protection.rb +58 -20
  96. data/lib/parse/webhooks.rb +412 -40
  97. metadata +8 -1
@@ -34,11 +34,18 @@ module Parse
34
34
  # material against snapshot/MONITOR exposure should set
35
35
  # `PARSE_STACK_LOCK_SECRET` or `Parse.synchronize_create_secret`.
36
36
  module CreateLock
37
+ # Floor for the lock lease, in seconds. When no `ttl:` is given the
38
+ # lease is sized from the client's request budget instead (see
39
+ # {.default_ttl}); this is the minimum it can be.
37
40
  DEFAULT_TTL = 3
38
41
  DEFAULT_WAIT = 2.0
39
42
  DEFAULT_POLL_BASE = 0.05
40
43
  DEFAULT_POLL_JITTER = 0.015
41
- MAX_TTL = 30
44
+ # Ceiling for the lock lease, in seconds. The lease only matters when a
45
+ # holder dies without releasing; a normal exit always releases.
46
+ MAX_TTL = 300
47
+ # Requests the locked block is sized for: the find, then the create.
48
+ LOCKED_REQUESTS = 2
42
49
  MAX_WAIT = 30
43
50
  MAX_PAYLOAD_BYTES = 8_192
44
51
  MAX_DEPTH = 4
@@ -60,7 +67,7 @@ module Parse
60
67
  def synchronize(parse_class:, query_attrs:, options: {}, session_token: nil, master_key: nil, &block)
61
68
  raise ArgumentError, "block required" unless block_given?
62
69
 
63
- ttl = clamp(Integer(options[:ttl] || DEFAULT_TTL), 1, MAX_TTL)
70
+ ttl = options[:ttl] ? clamp(Integer(options[:ttl]), 1, MAX_TTL) : default_ttl
64
71
  wait = clamp(Float(options[:wait] || DEFAULT_WAIT), 0.0, MAX_WAIT)
65
72
  on_degraded = options[:on_degraded] || :warn
66
73
 
@@ -115,6 +122,24 @@ module Parse
115
122
  end
116
123
  end
117
124
 
125
+ # The lease used when no `ttl:` is given. The lock is not renewed
126
+ # while held, so the lease must outlast the block it guards: a find
127
+ # and a create, each of which may run through every retry at the full
128
+ # request timeout. A lease shorter than that expires mid-block, lets a
129
+ # second caller in, and produces the duplicate the lock exists to
130
+ # prevent. The result is clamped to DEFAULT_TTL..MAX_TTL. Pass `ttl:`
131
+ # to override it.
132
+ # @param client [Parse::Client, nil] the client whose timeouts and
133
+ # retry limit size the lease; the default client when nil.
134
+ # @return [Integer] seconds.
135
+ def default_ttl(client = nil)
136
+ client ||= Parse::Client.client
137
+ budget = client.respond_to?(:request_time_budget) ? client.request_time_budget : 0
138
+ clamp((budget * LOCKED_REQUESTS).ceil, DEFAULT_TTL, MAX_TTL)
139
+ rescue StandardError
140
+ DEFAULT_TTL
141
+ end
142
+
118
143
  # Canonical lock key for the given inputs. Public for tests.
119
144
  # @return [String]
120
145
  def canonical_key(parse_class:, query_attrs:, session_token: nil, master_key: nil)
@@ -145,6 +145,8 @@ module Parse
145
145
  unless defined?(Parse::MongoDB) && Parse::MongoDB.respond_to?(:enabled?) && Parse::MongoDB.enabled?
146
146
  return { available: false, reason: :mongodb_not_enabled }
147
147
  end
148
+ # Parse::AtlasSearch is loaded on first use, not with the SDK.
149
+ require_relative "../../atlas_search"
148
150
  indexes = Parse::AtlasSearch::IndexManager.list_indexes(parse_class)
149
151
  {
150
152
  available: true,
@@ -87,7 +87,11 @@ module Parse
87
87
  includes_array = includes.present? ? Array(includes) : nil
88
88
 
89
89
  # Build formatted keys once (reused for query and tracking)
90
- formatted_keys = keys_array&.map { |k| Parse::Query.format_field(k) }
90
+ formatted_keys = if keys_array
91
+ Parse::Query.with_field_aliases(parse_class) do
92
+ keys_array.map { |k| Parse::Query.format_field(k) }
93
+ end
94
+ end
91
95
 
92
96
  # Validate keys against model fields if validation is enabled
93
97
  # Skip validation if warnings are disabled (nothing to report)
@@ -203,6 +207,15 @@ module Parse
203
207
 
204
208
  # Apply attributes from server (only keys in result get updated)
205
209
  apply_attributes!(result, dirty_track: false)
210
+ # The server row is now the source of truth for the ACL. A full
211
+ # fetch whose response has no ACL key is a public (ACL-less) row, so
212
+ # drop any locally stamped default rather than misreport it. Either
213
+ # way the record exists, so the save-time default-ACL resolver must
214
+ # not replace its ACL.
215
+ if !is_partial_fetch && !result.key?("ACL") && !result.key?(:ACL)
216
+ @acl = nil
217
+ end
218
+ @_acl_pristine = false
206
219
  record_authorization_hydration!(result, partial: is_partial_fetch) if
207
220
  respond_to?(:record_authorization_hydration!)
208
221
 
@@ -318,7 +331,9 @@ module Parse
318
331
  def fetch_json(keys: nil, includes: nil)
319
332
  query = {}
320
333
  if keys.present?
321
- keys_array = Array(keys).map { |k| Parse::Query.format_field(k) }
334
+ keys_array = Parse::Query.with_field_aliases(parse_class) do
335
+ Array(keys).map { |k| Parse::Query.format_field(k) }
336
+ end
322
337
  query[:keys] = keys_array.join(",")
323
338
  end
324
339
  if includes.present?
@@ -432,9 +447,12 @@ module Parse
432
447
  # Autofetch if object is a pointer OR was selectively fetched
433
448
  # Skip if autofetch is disabled for this instance
434
449
  needs_fetch = pointer? || has_selective_keys?
450
+ # A pointer never autofetches for its ACL. A partially fetched object
451
+ # does when the ACL was left out, so code that edits it starts from
452
+ # the record's real ACL instead of nil.
435
453
  return unless needs_fetch &&
436
454
  !autofetch_disabled? &&
437
- key != :acl &&
455
+ !(key == :acl && pointer?) &&
438
456
  !Parse::Properties::BASE_KEYS.include?(key) &&
439
457
  respond_to?(:fetch)
440
458
 
@@ -4,12 +4,16 @@
4
4
  module Parse
5
5
  # Global `const_missing` hook that lazily resolves the plural form of a
6
6
  # {Parse::Object} subclass constant to that class. Referencing `Posts`
7
- # when a class `Post` exists installs `Posts` as an alias for `Post` on
8
- # the referencing module and returns it, so query entry points like
7
+ # when a class `Post` exists installs `Posts` as an alias for `Post` and
8
+ # returns it, so query entry points like
9
9
  # `Posts.where(...).count` work without any per-model boilerplate.
10
10
  #
11
- # The hook is prepended onto `Module` so it applies to constant lookups
12
- # in any namespace (top-level and nested). It is tightly guarded: every
11
+ # The hook is prepended onto `Module` so a plural reference resolves from
12
+ # any namespace, but the alias constant is only ever installed in the
13
+ # namespace that defines the singular class (`::Posts` for a top-level
14
+ # `Post`, `Blog::Posts` for `Blog::Post`). A lookup from an unrelated
15
+ # module never adds a constant to that module, and a frozen namespace is
16
+ # skipped rather than raising `FrozenError`. It is tightly guarded: every
13
17
  # path that is not a plural-of-a-Parse-class falls through to `super`,
14
18
  # preserving normal `NameError` and autoloader (Zeitwerk/classic)
15
19
  # behavior. The whole feature is gated on {Parse.pluralized_aliases?} so